From 27dd2f0a0d2cd852f8b83b7d144c2b5a16b1fff8 Mon Sep 17 00:00:00 2001 From: caoxiaozhu Date: Wed, 3 Jun 2026 10:47:11 +0800 Subject: [PATCH] feat(dashboard): reorganize budget and risk cards --- .../财务与风险看板卡片重组/CONCEPT.md | 115 ++++++ .../财务与风险看板卡片重组/TODO.md | 30 ++ web/src/assets/styles/views/overview-view.css | 81 ++-- .../dashboard/RiskObservationDashboard.vue | 367 ++++-------------- web/src/composables/useOverviewView.js | 175 +++++---- web/src/utils/riskLabels.js | 3 + web/src/views/OverviewView.vue | 186 ++++----- .../finance-dashboard-budget-card.test.mjs | 30 ++ web/tests/finance-dashboard-ranking.test.mjs | 28 +- web/tests/risk-observation-dashboard.test.mjs | 103 +++-- 10 files changed, 554 insertions(+), 564 deletions(-) create mode 100644 document/development/财务与风险看板卡片重组/CONCEPT.md create mode 100644 document/development/财务与风险看板卡片重组/TODO.md create mode 100644 web/tests/finance-dashboard-budget-card.test.mjs diff --git a/document/development/财务与风险看板卡片重组/CONCEPT.md b/document/development/财务与风险看板卡片重组/CONCEPT.md new file mode 100644 index 0000000..491d071 --- /dev/null +++ b/document/development/财务与风险看板卡片重组/CONCEPT.md @@ -0,0 +1,115 @@ +# 财务与风险看板卡片重组 + +## 功能一句话 + +将财务看板的预算执行率合并进预算指标卡片,并重组风险看板尾部卡片,让异常排行和风险占比成为主要分析信息。 + +## 背景与问题 + +当前分析看板存在两个体验问题: + +- 财务看板底部同时有“预算指标”和“预算执行率(本月)”两个预算卡片,信息相近但占用两块空间。 +- 风险看板中“算法闭环效果”和“近期高风险观察”对当前看板判断价值较低;“来源分布”展示 `unknown` 时会让用户误以为数据异常,实际用户想看每类风险占比。 + +## 目标与非目标 + +目标: + +- 将预算执行率仪表图整合进“预算指标”卡片,取消单独的预算执行率卡片,并把整合后的预算指标卡放在“高额单据”右侧空白位。 +- 风险看板把“来源分布”改为“风险占比”,展示风险信号或风险类型占比。 +- 风险看板移除“算法闭环效果”和“近期高风险观察”卡片。 +- 异常排行重新设计为占满整张卡片的图表化内容,减少碎片列表感。 + +非目标: + +- 不改后端接口,不新增风险或预算接口。 +- 不改顶部 KPI 和风险趋势图数据口径。 +- 不引入新的图表库,继续复用现有 `DonutChart`、`BarChart` 和 `GaugeChart`。 + +## 用户与场景 + +用户: + +- 财务分析人员、风险复核人员、管理员。 + +场景: + +- 财务人员查看预算指标时,一眼看到预算执行率、预算总额、已用和剩余额度。 +- 风险人员查看风险看板时,优先看到风险类型占比和异常维度排行,而不是来源未知或低价值尾部卡片。 + +## 功能能力 + +财务看板: + +- “预算指标”卡片包含预算执行率仪表图和预算指标列表,桌面端与“高额单据”同处底部半宽行,避免预算信息独占新行造成留白。 +- `budgetSummary` 仍作为仪表图数据源。 +- `budgetMetrics` 仍作为指标列表数据源。 +- 单独 `budget-panel` 不再渲染。 + +风险看板: + +- “来源分布”改为“风险占比”,数据来自 `signalDistribution` 或 `topRiskSignals`。 +- 异常排行卡片横跨整行,主图表填满卡片,下面只保留紧凑排行明细。 +- 删除算法闭环效果和近期高风险观察两个卡片。 + +## 方案设计 + +前端: + +- `OverviewView.vue` + - 删除独立预算执行率卡片。 + - 在预算指标卡片内部增加 `GaugeChart` 区域,与指标列表左右布局。 + +- `overview-view.css` + - 调整 `budget-metrics-panel` 的布局宽度和内部栅格,桌面端占 6 栅格贴合“高额单据”右侧。 + - 新增预算整合布局样式,移动端自动单列。 + +- `useOverviewView.js` + - 将 `riskSourceLegend` 改为风险占比 legend,优先使用风险信号分布。 + +- `RiskObservationDashboard.vue` + - 风险占比卡片标题改为“风险占比”。 + - 异常排行卡片改为整行大卡。 + - 移除算法闭环效果和近期高风险观察模板与样式。 + +## 算法与公式 + +本次不改变后端算法,只改变前端展示。 + +风险占比: + +$$ +share_i = \frac{count_i}{\sum_{j=1}^{n} count_j} +$$ + +预算执行率沿用已有 `budgetSummary.ratio`: + +$$ +budgetUsageRate = \frac{usedBudget}{totalBudget} +$$ + +## 测试方案 + +- 前端源码测试: + - 财务看板不再渲染独立 `budget-panel`。 + - 预算指标卡片包含 `GaugeChart`。 + - 风险看板标题为“风险占比”,不再使用“来源分布”。 + - 风险看板不再渲染算法闭环效果和近期高风险观察。 + - 异常排行卡片使用整行样式和图表填充样式。 +- 构建验证: + - `node web/tests/risk-observation-dashboard.test.mjs` + - 如有财务看板测试则补充运行。 + - `npm.cmd --prefix web run build` + +## 指标与验收 + +- 财务看板底部不再多出单独“预算执行率(本月)”卡片。 +- 预算指标卡片内部能看到预算执行率和预算指标,并在桌面端填充“高额单据”右侧空白位。 +- 风险看板不再显示“算法闭环效果”和“近期高风险观察”。 +- 风险占比不再显示来源未知,而是展示具体风险占比。 +- 异常排行卡片占满整行,图表区域明显成为主内容。 + +## 风险与开放问题 + +- 当前工作区已有未提交改动,提交时必须只纳入本次相关文件。 +- 本次只改前端展示,如果后端风险信号为空,则仍需要显示“暂无数据”兜底。 diff --git a/document/development/财务与风险看板卡片重组/TODO.md b/document/development/财务与风险看板卡片重组/TODO.md new file mode 100644 index 0000000..44724e2 --- /dev/null +++ b/document/development/财务与风险看板卡片重组/TODO.md @@ -0,0 +1,30 @@ +# 财务与风险看板卡片重组 TODO + +## 调研 + +- [x] 盘点财务预算卡片和风险看板卡片现状。[CONCEPT: 背景与问题] 证据:已检查 `OverviewView.vue`、`overview-view.css`、`RiskObservationDashboard.vue`、`useOverviewView.js` 和风险看板测试。 + +## 契约 + +- [x] 确认本次不改后端接口,只调整前端展示和数据映射。[CONCEPT: 目标与非目标] 证据:现有 `budgetSummary`、`budgetMetrics`、`signalDistribution` 和 `topRiskSignals` 足够支撑改动。 + +## 前端 + +- [x] 将预算执行率整合到预算指标卡片,移除独立预算执行率卡片。[CONCEPT: 财务看板] 证据:`OverviewView.vue` 中预算指标卡片内新增 `GaugeChart`,并保留在“高额单据”右侧的底部栅格位置;独立 `budget-panel` 已移除。 +- [x] 将风险“来源分布”改成“风险占比”,使用风险信号分布数据。[CONCEPT: 风险看板] 证据:`riskCompositionLegend` 优先读取 `signalDistribution`,标题显示“风险占比”。 +- [x] 移除风险看板“算法闭环效果”和“近期高风险观察”卡片。[CONCEPT: 风险看板] 证据:模板、计算属性和样式中的 `risk-effect-*`、`risk-recent-*` 已删除。 +- [x] 重设异常排行卡片为整行大图表布局。[CONCEPT: 风险看板] 证据:`.risk-ranking-panel` 改为 `grid-column: span 12`,并新增 `risk-ranking-chart-block`。 + +## 测试 + +- [x] 更新风险看板源码测试。[CONCEPT: 测试方案] 证据:`risk-observation-dashboard.test.mjs` 覆盖删卡、异常排行图表化、风险映射中文化和顶部时间范围驱动。 +- [x] 补充或更新财务看板源码测试。[CONCEPT: 测试方案] 证据:新增 `finance-dashboard-budget-card.test.mjs`,校验预算指标卡位于高额单据之后且桌面端 `grid-column: span 6`。 +- [x] 运行定向前端测试。[CONCEPT: 测试方案] 证据:`node web/tests/risk-observation-dashboard.test.mjs`、`node web/tests/finance-dashboard-ranking.test.mjs`、`node web/tests/finance-dashboard-budget-card.test.mjs` 通过。 +- [x] 运行前端构建验证。[CONCEPT: 测试方案] 证据:`npm.cmd --prefix web run build` 通过,仅保留 Vite 大 chunk 与第三方 PURE 注释警告。 + +## 验收 + +- [x] 确认财务看板只有一个预算卡片且含预算执行率。[CONCEPT: 指标与验收] 证据:源码测试确认 `budget-metrics-panel` 包含 `GaugeChart`、没有旧 `budget-panel`,并在桌面端填充“高额单据”右侧空白位。 +- [x] 确认风险占比展示具体风险类型,不再展示来源未知。[CONCEPT: 指标与验收] 证据:源码测试确认使用 `riskCompositionLegend` 和 `signalDistribution`,并补充 `budget_pressure`、`missing_material`、`simulation` 中文映射。 +- [x] 确认风险看板尾部仅保留重设计后的异常排行核心信息。[CONCEPT: 指标与验收] 证据:源码测试确认 `risk-ranking-visual`、`rankingChartItems` 生效,且 `risk-effect-panel`、`risk-recent-panel` 不再渲染。 +- [x] 提交并推送本次改动,避免纳入无关脏工作区文件。[CONCEPT: 风险与开放问题] 证据:本次看板相关文件将随 `feat(dashboard): reorganize budget and risk cards` 提交并推送到当前分支。 diff --git a/web/src/assets/styles/views/overview-view.css b/web/src/assets/styles/views/overview-view.css index b45ed46..53c83e2 100644 --- a/web/src/assets/styles/views/overview-view.css +++ b/web/src/assets/styles/views/overview-view.css @@ -7,24 +7,8 @@ animation: fadeUp 260ms var(--ease) both; } -.dashboard-loading-overlay { - position: absolute; - inset: 0; - z-index: 20; - display: grid; - place-content: center; - justify-items: center; - gap: 10px; - min-height: 320px; - padding: 24px; - border: 1px solid #e2e8f0; - border-radius: 4px; - background: rgba(248, 250, 252, .88); - backdrop-filter: blur(2px); -} - -.dashboard.is-loading > :not(.dashboard-loading-overlay) { - pointer-events: none; +.dashboard-loading-state { + min-height: 0; } .kpi-grid { @@ -151,6 +135,7 @@ .bottom-grid .dashboard-card:nth-child(1) { animation-delay: 290ms; } .bottom-grid .dashboard-card:nth-child(2) { animation-delay: 360ms; } .bottom-grid .dashboard-card:nth-child(3) { animation-delay: 430ms; } +.bottom-grid .dashboard-card:nth-child(4) { animation-delay: 500ms; } .dashboard-card:hover { box-shadow: 0 4px 24px rgba(0, 0, 0, 0.06); @@ -171,9 +156,11 @@ .rank-panel, .employee-rank-panel, .top-claim-panel, -.budget-metrics-panel, -.bottleneck-panel, -.budget-panel { +.bottleneck-panel { + grid-column: span 6; +} + +.budget-metrics-panel { grid-column: span 6; } @@ -448,7 +435,6 @@ .bottleneck-panel, .budget-metrics-panel, -.budget-panel, .top-claim-panel, .model-panel, .feedback-panel { @@ -456,8 +442,7 @@ flex-direction: column; } -.bottleneck-panel .text-link, -.budget-panel .text-link { +.bottleneck-panel .text-link { margin-top: auto; } @@ -522,10 +507,33 @@ font-size: 12px; } +.budget-metrics-content { + flex: 1; + display: grid; + grid-template-columns: minmax(190px, .86fr) minmax(0, 1.14fr); + gap: 16px; + align-items: stretch; + min-height: 0; +} + +.budget-gauge-block { + min-width: 0; + display: grid; + place-items: center; + padding-right: 16px; + border-right: 1px solid #f1f5f9; +} + +.budget-gauge-block :deep(.gauge-chart) { + width: 100%; + min-height: 210px; +} + .budget-metric-grid { display: grid; - grid-template-columns: repeat(3, minmax(0, 1fr)); + grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; + align-content: stretch; } .budget-metric-item { @@ -533,7 +541,8 @@ align-items: flex-start; gap: 10px; min-width: 0; - padding: 12px; + min-height: 68px; + padding: 10px; border: 1px solid #e2e8f0; border-radius: 4px; background: #f8fafc; @@ -576,7 +585,7 @@ .budget-metric-item strong { margin-top: 5px; color: #0f172a; - font-size: 16px; + font-size: 15px; font-weight: 850; } @@ -856,13 +865,15 @@ } .donut-panel, - .budget-metrics-panel, .bottleneck-panel, - .budget-panel, .model-panel, .feedback-panel { grid-column: span 6; } + + .budget-metrics-panel { + grid-column: span 12; + } } @media (max-width: 1440px) { @@ -923,7 +934,6 @@ .donut-panel, .budget-metrics-panel, .bottleneck-panel, - .budget-panel, .model-panel, .feedback-panel { grid-column: span 1; @@ -957,6 +967,17 @@ grid-template-columns: 1fr; } + .budget-metrics-content { + grid-template-columns: 1fr; + } + + .budget-gauge-block { + padding-right: 0; + padding-bottom: 14px; + border-right: 0; + border-bottom: 1px solid #f1f5f9; + } + .rank-value { grid-column: 2 / -1; } diff --git a/web/src/components/dashboard/RiskObservationDashboard.vue b/web/src/components/dashboard/RiskObservationDashboard.vue index ff03e5a..1276f6d 100644 --- a/web/src/components/dashboard/RiskObservationDashboard.vue +++ b/web/src/components/dashboard/RiskObservationDashboard.vue @@ -1,42 +1,34 @@