feat(dashboard): polish risk and digital employee boards

This commit is contained in:
caoxiaozhu
2026-06-03 09:41:32 +08:00
parent 15006a05a7
commit 0d6327a990
11 changed files with 716 additions and 100 deletions

View File

@@ -6,6 +6,10 @@ import {
fetchSystemDashboard
} from '../services/analytics.js'
import { fetchRiskObservationDashboard } from '../services/riskObservations.js'
import {
formatRiskSignalLabel,
formatRiskSourceLabel
} from '../utils/riskLabels.js'
import {
buildDigitalEmployeeCategoryRows,
buildDigitalEmployeeDailyRows,
@@ -75,6 +79,13 @@ const emptyFinanceBudgetMetrics = [
]
export function useOverviewView(options = {}) {
const activeDashboardKey = computed(() => {
const dashboard = String(options.dashboard || '').trim()
if (dashboard === 'system') return 'system'
if (dashboard === 'risk') return 'risk'
if (dashboard === 'digitalEmployee') return 'digitalEmployee'
return 'finance'
})
const activeTrendRange = ref(trendRanges[0])
const activeDepartmentRange = ref(departmentRangeOptions[0])
const riskWindowOptions = [
@@ -86,18 +97,22 @@ export function useOverviewView(options = {}) {
const financeDashboardPayload = ref(null)
const financeDashboardLoading = ref(false)
const financeDashboardError = ref(null)
const financeDashboardLoaded = computed(() => Boolean(financeDashboardPayload.value))
const systemDashboardPayload = ref(null)
const systemDashboardLoading = ref(false)
const systemDashboardError = ref(null)
const systemDashboardLoaded = computed(() => Boolean(systemDashboardPayload.value))
const riskDashboardPayload = ref(null)
const riskDashboardLoading = ref(false)
const riskDashboardError = ref(null)
const riskDashboardLoaded = computed(() => Boolean(riskDashboardPayload.value))
const riskDashboardLastUpdatedAt = ref('')
let riskDashboardRefreshTimer = 0
let riskDashboardRequestSeq = 0
const digitalEmployeeDashboardPayload = ref(null)
const digitalEmployeeDashboardLoading = ref(false)
const digitalEmployeeDashboardError = ref(null)
const digitalEmployeeDashboardLoaded = computed(() => Boolean(digitalEmployeeDashboardPayload.value))
const formatCompact = (value) => {
if (value >= 1_000_000) return `¥${(value / 1_000_000).toFixed(1)}M`
@@ -251,12 +266,29 @@ export function useOverviewView(options = {}) {
activeRiskWindowDays.value = matched ? days : 30
}
onMounted(() => {
const loadActiveDashboard = () => {
if (activeDashboardKey.value === 'system') {
void loadSystemDashboard()
stopRiskDashboardRealtimeRefresh()
return
}
if (activeDashboardKey.value === 'risk') {
void loadRiskDashboard()
startRiskDashboardRealtimeRefresh()
return
}
if (activeDashboardKey.value === 'digitalEmployee') {
void loadDigitalEmployeeDashboard()
stopRiskDashboardRealtimeRefresh()
return
}
void loadFinanceDashboard()
void loadSystemDashboard()
void loadRiskDashboard()
void loadDigitalEmployeeDashboard()
startRiskDashboardRealtimeRefresh()
stopRiskDashboardRealtimeRefresh()
}
onMounted(() => {
loadActiveDashboard()
})
onBeforeUnmount(() => {
@@ -272,12 +304,20 @@ export function useOverviewView(options = {}) {
activeDepartmentRange.value
],
() => {
void loadFinanceDashboard()
if (activeDashboardKey.value === 'finance') {
void loadFinanceDashboard()
}
}
)
watch(activeRiskWindowDays, () => {
void loadRiskDashboard()
if (activeDashboardKey.value === 'risk') {
void loadRiskDashboard()
}
})
watch(activeDashboardKey, () => {
loadActiveDashboard()
})
const systemDashboardTotals = computed(() => (
@@ -695,7 +735,8 @@ export function useOverviewView(options = {}) {
financial_risk_graph: 'var(--theme-primary)',
rule_center: '#0f766e',
unknown: '#94a3b8'
}
},
formatRiskSourceLabel
))
const riskSignalRanking = computed(() => {
const rows = Array.isArray(riskDashboard.value.topRiskSignals)
@@ -739,7 +780,7 @@ export function useOverviewView(options = {}) {
const digitalEmployeeTaskRanking = computed(() => buildDigitalEmployeeTaskRanking(digitalEmployeeDashboard.value))
const digitalEmployeeCategoryRows = computed(() => buildDigitalEmployeeCategoryRows(digitalEmployeeDashboard.value))
function buildRiskDistributionLegend(distribution, labels, colors) {
function buildRiskDistributionLegend(distribution, labels, colors, formatter = formatRiskSignalName) {
const entries = Object.entries(distribution || {})
.filter(([, value]) => Number(value || 0) > 0)
@@ -755,7 +796,7 @@ export function useOverviewView(options = {}) {
}
return entries.map(([key, value]) => ({
name: labels[key] || formatRiskSignalName(key),
name: labels[key] || formatter(key),
value: Number(value || 0),
display: `${Number(value || 0)}`,
color: colors[key] || 'var(--theme-primary)'
@@ -763,16 +804,7 @@ export function useOverviewView(options = {}) {
}
function formatRiskSignalName(value) {
const text = String(value || '').trim()
const labels = {
duplicate_invoice: '重复发票',
split_billing: '拆分报销',
frequent_small_claims: '高频小额',
location_mismatch: '地点不一致',
amount_outlier: '金额异常',
preapproval_absent: '缺少事前申请'
}
return labels[text] || text.replace(/_/g, ' ') || '未知风险'
return formatRiskSignalLabel(value)
}
function isMissingDimension(value) {
@@ -800,12 +832,14 @@ export function useOverviewView(options = {}) {
digitalEmployeeCategoryRows,
digitalEmployeeDashboard,
digitalEmployeeDashboardError,
digitalEmployeeDashboardLoaded,
digitalEmployeeDashboardLoading,
digitalEmployeeDailyRows,
digitalEmployeeKpiMetrics,
digitalEmployeeTaskRanking,
exceptionMix,
financeDashboardError,
financeDashboardLoaded,
financeDashboardLoading,
formatCompact,
formatCurrency,
@@ -818,6 +852,7 @@ export function useOverviewView(options = {}) {
rankedEmployees,
riskDashboard,
riskDashboardError,
riskDashboardLoaded,
riskDashboardLastUpdatedAt,
riskDashboardLoading,
riskDailyTrendRows,
@@ -835,6 +870,7 @@ export function useOverviewView(options = {}) {
spendTotal,
systemDashboardTotals,
systemDashboardError,
systemDashboardLoaded,
systemDashboardLoading,
systemAgentDailyRatio,
systemLoginWave,