feat: 新增风险图谱算法与系统仪表盘及操作反馈体系

后端新增风险图谱算法模块、风险观察与反馈服务、规则 DSL
校验器和可解释性引擎,完善系统仪表盘和财务仪表盘统计,
优化 agent 运行和编排执行链路,清理旧开发文档,前端新增
系统趋势、负载热力图等多种仪表盘图表组件,完善操作反馈
对话框和工作台日期选择器,优化报销创建和审批详情交互,
补充单元测试覆盖。
This commit is contained in:
caoxiaozhu
2026-05-30 15:46:51 +08:00
parent 4c59941ec6
commit 7989f3a159
314 changed files with 30073 additions and 20626 deletions

View File

@@ -1,8 +1,8 @@
<template>
<section class="dashboard">
<section class="dashboard" :class="`dashboard-${activeDashboard}`">
<div class="kpi-grid">
<article
v-for="metric in kpiMetrics"
v-for="metric in activeKpiMetrics"
:key="metric.label"
class="kpi-card panel"
:style="{ '--accent': metric.accent, '--delay': `${metric.delay}ms` }"
@@ -22,122 +22,297 @@
</article>
</div>
<div class="content-grid top-grid">
<article class="panel dashboard-card trend-panel">
<div class="card-head">
<h3>报销申请与审批趋势 <i class="mdi mdi-information-outline"></i></h3>
<EnterpriseSelect
v-model="activeTrendRange"
class="card-select"
:options="trendRanges"
aria-label="趋势时间范围"
size="small"
<template v-if="activeDashboard === 'finance'">
<div class="content-grid top-grid">
<article class="panel dashboard-card trend-panel">
<div class="card-head">
<h3>报销申请与审批趋势 <i class="mdi mdi-information-outline"></i></h3>
<EnterpriseSelect
v-model="activeTrendRange"
class="card-select"
:options="trendRanges"
aria-label="趋势时间范围"
size="small"
/>
</div>
<TrendChart
:labels="activeTrend.labels"
:applications="activeTrend.applications"
:approved="activeTrend.approved"
:avg-hours="activeTrend.avgHours"
/>
</div>
</article>
<TrendChart
:labels="activeTrend.labels"
:applications="activeTrend.applications"
:approved="activeTrend.approved"
:avg-hours="activeTrend.avgHours"
/>
</article>
<article class="panel dashboard-card donut-panel">
<div class="card-head">
<h3>费用结构 <i class="mdi mdi-information-outline"></i></h3>
</div>
<DonutChart :items="spendLegend" :center-value="spendCenterValue" center-label="费用总额" />
<p class="panel-note">* 百分比按当前时间范围内的费用金额计算</p>
</article>
<article class="panel dashboard-card donut-panel">
<div class="card-head">
<h3>费用结构 <i class="mdi mdi-information-outline"></i></h3>
</div>
<DonutChart :items="spendLegend" center-value="¥361.6K" center-label="待处理金额" />
<p class="panel-note">* 百分比为占待处理金额比例</p>
</article>
<article class="panel dashboard-card donut-panel">
<div class="card-head">
<h3>风险异常分布 <i class="mdi mdi-information-outline"></i></h3>
</div>
<DonutChart :items="riskLegend" :center-value="`${riskTotal}`" center-label="异常预警单" />
<p class="panel-note">* 30 天数据</p>
</article>
</div>
<article class="panel dashboard-card donut-panel">
<div class="card-head">
<h3>风险异常分布 <i class="mdi mdi-information-outline"></i></h3>
</div>
<DonutChart :items="riskLegend" :center-value="`${riskTotal}`" center-label="异常预警单" />
<p class="panel-note">* 30 天数据</p>
</article>
</div>
<div class="content-grid bottom-grid">
<article class="panel dashboard-card rank-panel">
<div class="card-head">
<h3>部门报销排行待处理金额<i class="mdi mdi-information-outline"></i></h3>
<EnterpriseSelect
v-model="activeDepartmentRange"
class="card-select"
:options="departmentRangeOptions"
aria-label="部门排行时间范围"
size="small"
/>
</div>
<div class="content-grid bottom-grid">
<article class="panel dashboard-card rank-panel">
<div class="card-head">
<h3>部门报销排行待处理金额<i class="mdi mdi-information-outline"></i></h3>
<EnterpriseSelect
v-model="activeDepartmentRange"
class="card-select"
:options="departmentRangeOptions"
aria-label="部门排行时间范围"
size="small"
/>
</div>
<BarChart :items="rankedDepartments" />
</article>
<BarChart :items="rankedDepartments" />
</article>
<article class="panel dashboard-card bottleneck-panel">
<div class="card-head">
<h3>审批瓶颈平均处理时长 <i class="mdi mdi-information-outline"></i></h3>
</div>
<article class="panel dashboard-card bottleneck-panel">
<div class="card-head">
<h3>审批瓶颈平均处理时长 <i class="mdi mdi-information-outline"></i></h3>
</div>
<div class="bottleneck-list">
<div
v-for="(item, index) in bottlenecks"
:key="item.name"
class="bottleneck-row"
:style="{ '--delay': `${index * 70}ms` }"
>
<div class="reviewer">
<div class="reviewer-avatar">{{ item.avatar }}</div>
<div>
<strong>{{ item.name }}</strong>
<span>{{ item.role }}</span>
<div class="bottleneck-list">
<div
v-for="(item, index) in bottlenecks"
:key="item.name"
class="bottleneck-row"
:style="{ '--delay': `${index * 70}ms` }"
>
<div class="reviewer">
<div class="reviewer-avatar">{{ item.avatar }}</div>
<div>
<strong>{{ item.name }}</strong>
<span>{{ item.role }}</span>
</div>
</div>
<div class="reviewer-stats">
<strong>{{ item.duration }}</strong>
<span class="status-tag" :class="item.tone">{{ item.status }}</span>
</div>
</div>
<div class="reviewer-stats">
<strong>{{ item.duration }}</strong>
<span class="status-tag" :class="item.tone">{{ item.status }}</span>
</div>
<button type="button" class="text-link">查看全部 <i class="mdi mdi-chevron-right"></i></button>
</article>
<article class="panel dashboard-card budget-panel">
<div class="card-head">
<h3>预算执行率本月<i class="mdi mdi-information-outline"></i></h3>
</div>
<GaugeChart
:ratio="budgetSummary.ratio"
:total="budgetSummary.total"
:used="budgetSummary.used"
:left="budgetSummary.left"
/>
<button type="button" class="text-link">查看详情 <i class="mdi mdi-chevron-right"></i></button>
</article>
</div>
</template>
<RiskObservationDashboard
v-else-if="activeDashboard === 'risk'"
:dashboard="riskDashboard"
:loading="riskDashboardLoading"
:error="riskDashboardError"
:level-legend="riskLevelLegend"
:source-legend="riskSourceLegend"
:signal-ranking="riskSignalRanking"
:daily-rows="riskDailyTrendRows"
:window-options="riskWindowOptions"
:active-window-days="activeRiskWindowDays"
@update:window-days="setRiskWindowDays"
/>
<template v-else>
<div class="system-observability-grid">
<article class="panel dashboard-card system-agent-ratio-panel">
<div class="card-head">
<h3>智能体调用占比 <i class="mdi mdi-information-outline"></i></h3>
</div>
<p class="card-subtitle">按天查看几个核心智能体的调用构成比例变化比单纯总量更容易定位偏移</p>
<SystemAgentRatioBar
:labels="systemAgentDailyRatio.labels"
:agents="systemAgentDailyRatio.agents"
:series="systemAgentDailyRatio.series"
/>
</article>
<article class="panel dashboard-card system-token-pie-panel">
<div class="card-head">
<h3>用户 Token 消耗占比 <i class="mdi mdi-information-outline"></i></h3>
</div>
<p class="card-subtitle">左侧看每日 Token 消耗波动右侧看高消耗用户便于排查重复问答或异常长上下文</p>
<div class="system-token-panel-grid">
<SystemTokenDailyWaveChart
:labels="systemTokenDailyWave.labels"
:input-tokens="systemTokenDailyWave.inputTokens"
:output-tokens="systemTokenDailyWave.outputTokens"
:total-tokens="systemTokenDailyWave.totalTokens"
/>
<SystemUserTokenPie :items="systemUserTokenUsage" />
</div>
</article>
<article class="panel dashboard-card system-accuracy-panel">
<div class="card-head">
<h3>正确 / 错误对比 <i class="mdi mdi-information-outline"></i></h3>
</div>
<p class="card-subtitle">按智能体对比正确与错误次数错误柱越靠前越需要优先追踪日志</p>
<SystemAccuracyCompareBar
:categories="systemAccuracyComparison.categories"
:correct="systemAccuracyComparison.correct"
:wrong="systemAccuracyComparison.wrong"
/>
</article>
<article class="panel dashboard-card system-tool-detail-panel">
<div class="card-head">
<h3>工具调用明细 <i class="mdi mdi-information-outline"></i></h3>
</div>
<div class="system-tool-table">
<div
v-for="item in systemToolDetailItems"
:key="item.name"
class="system-tool-row"
>
<div class="system-tool-row-head">
<strong>{{ item.name }}</strong>
<span>{{ item.callLabel }}</span>
</div>
<div class="system-tool-meter" aria-hidden="true">
<i :style="{ width: item.width, background: item.color }"></i>
</div>
<div class="system-tool-row-meta">
<span>成功率 {{ item.successRate }}%</span>
<span>平均 {{ item.avgLatency }}</span>
<span>{{ item.tokenLabel }}</span>
</div>
</div>
</div>
</div>
</article>
<button type="button" class="text-link">查看全部 <i class="mdi mdi-chevron-right"></i></button>
</article>
<aside class="system-side-stack">
<article class="panel dashboard-card system-side-card system-login-wave-panel">
<div class="card-head">
<h3>用户在线波动 <i class="mdi mdi-information-outline"></i></h3>
</div>
<p class="card-subtitle">登录人数与互动次数的时段波动</p>
<article class="panel dashboard-card budget-panel">
<div class="card-head">
<h3>预算执行率本月<i class="mdi mdi-information-outline"></i></h3>
</div>
<SystemLoginWaveChart
compact
:labels="systemLoginWave.labels"
:login-users="systemLoginWave.loginUsers"
:interactions="systemLoginWave.interactions"
/>
</article>
<GaugeChart
:ratio="budgetSummary.ratio"
:total="budgetSummary.total"
:used="budgetSummary.used"
:left="budgetSummary.left"
/>
<article class="panel dashboard-card system-side-card system-duration-panel">
<div class="card-head">
<h3>用户使用时长 <i class="mdi mdi-information-outline"></i></h3>
</div>
<button type="button" class="text-link">查看详情 <i class="mdi mdi-chevron-right"></i></button>
</article>
</div>
<div class="duration-summary">
<div>
<strong>{{ systemUsageDurationSummary.average }}</strong>
<span>平均使用时长</span>
</div>
<em>{{ systemUsageDurationSummary.trend }}</em>
</div>
<div class="duration-meta">
<span>中位数 {{ systemUsageDurationSummary.median }}</span>
<span>峰值 {{ systemUsageDurationSummary.peak }}</span>
</div>
<div class="duration-bars">
<div
v-for="item in systemUsageDurationRows"
:key="item.label"
class="duration-bar-row"
>
<span>{{ item.label }}</span>
<i><b :style="{ width: item.width, background: item.color }"></b></i>
<strong>{{ item.value }} </strong>
</div>
</div>
</article>
<article class="panel dashboard-card feedback-panel system-feedback-panel">
<div class="card-head">
<h3>用户反馈概览 <i class="mdi mdi-information-outline"></i></h3>
</div>
<div class="feedback-list">
<div
v-for="item in systemFeedbackSummary"
:key="item.label"
class="feedback-row"
:class="item.tone"
>
<span class="feedback-icon"><i :class="item.icon"></i></span>
<div>
<strong>{{ item.value }}</strong>
<span>{{ item.label }}</span>
</div>
</div>
</div>
<p class="panel-note">* 反馈用于衡量智能体回答和工具执行体验</p>
</article>
</aside>
</div>
</template>
</section>
</template>
<script setup>
import { computed } from 'vue'
import TrendChart from '../components/charts/TrendChart.vue'
import DonutChart from '../components/charts/DonutChart.vue'
import BarChart from '../components/charts/BarChart.vue'
import GaugeChart from '../components/charts/GaugeChart.vue'
import SystemAccuracyCompareBar from '../components/charts/SystemAccuracyCompareBar.vue'
import SystemAgentRatioBar from '../components/charts/SystemAgentRatioBar.vue'
import SystemLoginWaveChart from '../components/charts/SystemLoginWaveChart.vue'
import SystemTokenDailyWaveChart from '../components/charts/SystemTokenDailyWaveChart.vue'
import SystemUserTokenPie from '../components/charts/SystemUserTokenPie.vue'
import RiskObservationDashboard from '../components/dashboard/RiskObservationDashboard.vue'
import EnterpriseSelect from '../components/shared/EnterpriseSelect.vue'
import { useOverviewView } from '../composables/useOverviewView.js'
defineProps({
filteredRequests: { type: Array, required: true }
const props = defineProps({
filteredRequests: { type: Array, required: true },
dashboard: { type: String, default: 'finance' },
activeRange: { type: String, default: '近10日' },
customRange: {
type: Object,
default: () => ({ start: '', end: '' })
}
})
const {
activeDepartmentRange,
activeRiskWindowDays,
activeTrend,
activeTrendRange,
bottlenecks,
@@ -145,11 +320,45 @@ const {
departmentRangeOptions,
kpiMetrics,
rankedDepartments,
riskDashboard,
riskDashboardError,
riskDashboardLoading,
riskDailyTrendRows,
riskLegend,
riskKpiMetrics,
riskLevelLegend,
riskSignalRanking,
riskSourceLegend,
riskTotal,
riskWindowOptions,
setRiskWindowDays,
spendCenterValue,
spendLegend,
systemAccuracyComparison,
systemAgentDailyRatio,
systemFeedbackSummary,
systemKpiMetrics,
systemLoginWave,
systemTokenDailyWave,
systemToolDetailItems,
systemUsageDurationRows,
systemUsageDurationSummary,
systemUserTokenUsage,
trendRanges
} = useOverviewView()
} = useOverviewView(props)
const activeDashboard = computed(() => {
if (props.dashboard === 'system') return 'system'
if (props.dashboard === 'risk') return 'risk'
return 'finance'
})
const activeKpiMetrics = computed(() => (
activeDashboard.value === 'system'
? systemKpiMetrics.value
: activeDashboard.value === 'risk'
? riskKpiMetrics.value
: kpiMetrics.value
))
</script>
<style scoped src="../assets/styles/views/overview-view.css"></style>