feat: 扩展风险规则体系、审批动态路由与预算中心列表化改造
- 新增 25+ 条风险规则(预算/报销/申请/通用类),完善风险规则模拟与反馈发布机制 - 引入费用审批动态路由、平台风险分级、预审与风险阶段管理 - 预算中心列表化改造,优化票据夹仪表盘与数字员工工作看板 - 新增 Hermes 风险线索收集器、Agent 链路追踪中心 - 扩展数字员工能力库(18 个领域 Skill)与交通费用自动预估 - 完善报销申请快速预览、权限控制与前端测试覆盖
This commit is contained in:
@@ -1,18 +1,24 @@
|
||||
import { computed, onMounted, ref, watch } from 'vue'
|
||||
|
||||
import { fetchFinanceDashboard, fetchSystemDashboard } from '../services/analytics.js'
|
||||
import {
|
||||
fetchDigitalEmployeeDashboard,
|
||||
fetchFinanceDashboard,
|
||||
fetchSystemDashboard
|
||||
} from '../services/analytics.js'
|
||||
import { fetchRiskObservationDashboard } from '../services/riskObservations.js'
|
||||
import {
|
||||
buildDigitalEmployeeCategoryRows,
|
||||
buildDigitalEmployeeDailyRows,
|
||||
buildDigitalEmployeeKpiMetrics,
|
||||
buildDigitalEmployeeTaskRanking,
|
||||
emptyDigitalEmployeeDashboard
|
||||
} from '../views/scripts/overviewDigitalEmployeeDashboardModel.js'
|
||||
|
||||
import {
|
||||
metricBlueprints,
|
||||
systemMetricBlueprints,
|
||||
trendRanges,
|
||||
trendSeries,
|
||||
spendByCategory as fallbackSpendByCategory,
|
||||
exceptionMix as fallbackExceptionMix,
|
||||
departmentRangeOptions,
|
||||
bottlenecks as fallbackBottlenecks,
|
||||
budgetSummary as fallbackBudgetSummary,
|
||||
systemDashboardTotals as fallbackSystemDashboardTotals,
|
||||
systemAgentDailyRatio as fallbackSystemAgentDailyRatio,
|
||||
systemLoginWave as fallbackSystemLoginWave,
|
||||
@@ -30,6 +36,33 @@ import {
|
||||
systemToolDetailRows as fallbackSystemToolDetailRows
|
||||
} from '../data/metrics.js'
|
||||
|
||||
const emptyFinanceTotals = {
|
||||
pendingCount: 0,
|
||||
pendingAmount: 0,
|
||||
avgSla: 0,
|
||||
autoPassRate: 0,
|
||||
riskCount: 0,
|
||||
slaRate: 0
|
||||
}
|
||||
|
||||
const emptyFinanceTrend = {
|
||||
labels: [],
|
||||
applications: [],
|
||||
approved: [],
|
||||
avgHours: []
|
||||
}
|
||||
|
||||
const emptyFinanceDonut = [
|
||||
{ name: '暂无数据', value: 0, color: '#cbd5e1' }
|
||||
]
|
||||
|
||||
const emptyFinanceBudgetSummary = {
|
||||
ratio: 0,
|
||||
total: '¥0',
|
||||
used: '¥0',
|
||||
left: '¥0'
|
||||
}
|
||||
|
||||
export function useOverviewView(options = {}) {
|
||||
const activeTrendRange = ref(trendRanges[0])
|
||||
const activeDepartmentRange = ref(departmentRangeOptions[0])
|
||||
@@ -48,23 +81,9 @@ export function useOverviewView(options = {}) {
|
||||
const riskDashboardPayload = ref(null)
|
||||
const riskDashboardLoading = ref(false)
|
||||
const riskDashboardError = ref(null)
|
||||
|
||||
const demoTotals = {
|
||||
pendingCount: 128,
|
||||
pendingAmount: 361600,
|
||||
avgSla: 6.8,
|
||||
autoPassRate: 78,
|
||||
riskCount: 14,
|
||||
slaRate: 96
|
||||
}
|
||||
|
||||
const demoDepartments = [
|
||||
{ name: '销售部', amount: 182000, color: 'var(--theme-primary)' },
|
||||
{ name: '研发中心', amount: 146000, color: 'var(--chart-blue)' },
|
||||
{ name: '市场部', amount: 96000, color: 'var(--chart-amber)' },
|
||||
{ name: '运营部', amount: 68600, color: 'var(--chart-purple)' },
|
||||
{ name: '行政部', amount: 48300, color: 'var(--chart-blue)' }
|
||||
]
|
||||
const digitalEmployeeDashboardPayload = ref(null)
|
||||
const digitalEmployeeDashboardLoading = ref(false)
|
||||
const digitalEmployeeDashboardError = ref(null)
|
||||
|
||||
const formatCompact = (value) => {
|
||||
if (value >= 1_000_000) return `¥${(value / 1_000_000).toFixed(1)}M`
|
||||
@@ -163,6 +182,23 @@ export function useOverviewView(options = {}) {
|
||||
}
|
||||
}
|
||||
|
||||
const loadDigitalEmployeeDashboard = async () => {
|
||||
digitalEmployeeDashboardLoading.value = true
|
||||
digitalEmployeeDashboardError.value = null
|
||||
|
||||
try {
|
||||
digitalEmployeeDashboardPayload.value = await fetchDigitalEmployeeDashboard({
|
||||
days: 7,
|
||||
limit: 300
|
||||
})
|
||||
} catch (error) {
|
||||
digitalEmployeeDashboardPayload.value = null
|
||||
digitalEmployeeDashboardError.value = error
|
||||
} finally {
|
||||
digitalEmployeeDashboardLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const setRiskWindowDays = (value) => {
|
||||
const days = Number(value || 30)
|
||||
const matched = riskWindowOptions.some((item) => Number(item.value) === days)
|
||||
@@ -173,6 +209,7 @@ export function useOverviewView(options = {}) {
|
||||
void loadFinanceDashboard()
|
||||
void loadSystemDashboard()
|
||||
void loadRiskDashboard()
|
||||
void loadDigitalEmployeeDashboard()
|
||||
})
|
||||
|
||||
watch(
|
||||
@@ -224,14 +261,15 @@ export function useOverviewView(options = {}) {
|
||||
windowDays: activeRiskWindowDays.value,
|
||||
totalObservations: 0,
|
||||
pendingCount: 0,
|
||||
riskClueCount: 0,
|
||||
highOrAboveCount: 0,
|
||||
confirmedCount: 0,
|
||||
falsePositiveCount: 0,
|
||||
feedbackSampleCount: 0,
|
||||
totalAmount: 0,
|
||||
averageScore: 0,
|
||||
confirmationRate: 0,
|
||||
falsePositiveRate: 0,
|
||||
candidateRuleCount: 0,
|
||||
levelDistribution: {},
|
||||
statusDistribution: {},
|
||||
signalDistribution: {},
|
||||
@@ -252,29 +290,32 @@ export function useOverviewView(options = {}) {
|
||||
recentHighObservations: []
|
||||
}
|
||||
))
|
||||
const digitalEmployeeDashboard = computed(() => (
|
||||
digitalEmployeeDashboardPayload.value || emptyDigitalEmployeeDashboard
|
||||
))
|
||||
const financeDashboardTotals = computed(() => (
|
||||
financeDashboardPayload.value?.totals || demoTotals
|
||||
financeDashboardPayload.value?.totals || emptyFinanceTotals
|
||||
))
|
||||
const financeMetricMeta = computed(() => (
|
||||
financeDashboardPayload.value?.metricMeta || {}
|
||||
))
|
||||
const financeTrend = computed(() => (
|
||||
financeDashboardPayload.value?.trend || trendSeries[activeTrendRange.value]
|
||||
financeDashboardPayload.value?.trend || emptyFinanceTrend
|
||||
))
|
||||
const financeSpendByCategory = computed(() => (
|
||||
financeDashboardPayload.value?.spendByCategory || fallbackSpendByCategory
|
||||
financeDashboardPayload.value?.spendByCategory || emptyFinanceDonut
|
||||
))
|
||||
const financeExceptionMix = computed(() => (
|
||||
financeDashboardPayload.value?.exceptionMix || fallbackExceptionMix
|
||||
financeDashboardPayload.value?.exceptionMix || emptyFinanceDonut
|
||||
))
|
||||
const financeDepartmentRanking = computed(() => (
|
||||
financeDashboardPayload.value?.departmentRanking || demoDepartments
|
||||
financeDashboardPayload.value?.departmentRanking || []
|
||||
))
|
||||
const financeBottlenecks = computed(() => (
|
||||
financeDashboardPayload.value?.bottlenecks || fallbackBottlenecks
|
||||
financeDashboardPayload.value?.bottlenecks || []
|
||||
))
|
||||
const financeBudgetSummary = computed(() => (
|
||||
financeDashboardPayload.value?.budgetSummary || fallbackBudgetSummary
|
||||
financeDashboardPayload.value?.budgetSummary || emptyFinanceBudgetSummary
|
||||
))
|
||||
|
||||
const resolveSystemMetricMeta = (metric) => {
|
||||
@@ -327,8 +368,8 @@ export function useOverviewView(options = {}) {
|
||||
|
||||
if (!financeDashboardPayload.value || !meta) {
|
||||
return {
|
||||
changeText: metric.change,
|
||||
delta: metric.delta,
|
||||
changeText: financeDashboardLoading.value ? '加载中' : '实时',
|
||||
delta: financeDashboardError.value ? '真实数据加载失败' : '等待真实数据',
|
||||
trend: metric.trend
|
||||
}
|
||||
}
|
||||
@@ -432,6 +473,10 @@ export function useOverviewView(options = {}) {
|
||||
}))
|
||||
})
|
||||
|
||||
const digitalEmployeeKpiMetrics = computed(() => (
|
||||
buildDigitalEmployeeKpiMetrics(digitalEmployeeDashboard.value, formatNumberCompact)
|
||||
))
|
||||
|
||||
const activeTrend = computed(() => financeTrend.value)
|
||||
const spendTotal = computed(() => financeSpendByCategory.value.reduce((sum, item) => sum + Number(item.value || 0), 0))
|
||||
const riskTotal = computed(() => financeExceptionMix.value.reduce((sum, item) => sum + Number(item.value || 0), 0))
|
||||
@@ -585,6 +630,9 @@ export function useOverviewView(options = {}) {
|
||||
highWidth: `${Math.max((item.highOrAbove / maxValue) * 100, item.highOrAbove ? 4 : 0)}%`
|
||||
}))
|
||||
})
|
||||
const digitalEmployeeDailyRows = computed(() => buildDigitalEmployeeDailyRows(digitalEmployeeDashboard.value))
|
||||
const digitalEmployeeTaskRanking = computed(() => buildDigitalEmployeeTaskRanking(digitalEmployeeDashboard.value))
|
||||
const digitalEmployeeCategoryRows = computed(() => buildDigitalEmployeeCategoryRows(digitalEmployeeDashboard.value))
|
||||
|
||||
function buildRiskDistributionLegend(distribution, labels, colors) {
|
||||
const entries = Object.entries(distribution || {})
|
||||
@@ -635,6 +683,13 @@ export function useOverviewView(options = {}) {
|
||||
bottlenecks,
|
||||
budgetSummary,
|
||||
departmentRangeOptions,
|
||||
digitalEmployeeCategoryRows,
|
||||
digitalEmployeeDashboard,
|
||||
digitalEmployeeDashboardError,
|
||||
digitalEmployeeDashboardLoading,
|
||||
digitalEmployeeDailyRows,
|
||||
digitalEmployeeKpiMetrics,
|
||||
digitalEmployeeTaskRanking,
|
||||
exceptionMix,
|
||||
financeDashboardError,
|
||||
financeDashboardLoading,
|
||||
@@ -688,7 +743,6 @@ export function useOverviewView(options = {}) {
|
||||
systemToolRankings,
|
||||
systemToolTotal,
|
||||
systemTrendSeries,
|
||||
trendRanges,
|
||||
trendSeries
|
||||
trendRanges
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user