Files
group_xinghuo_jinrong/docs/superpowers/specs/2026-09-09-frontend-wealth-dashboard-design.md
T
zhanghongyu_0626 ad930c26b1 feat(frontend): Implement initial P0 scaffolding and enhance dashboard features
- Established the P0 scaffolding for the frontend, including login, layout, and routing for four roles.
- Integrated the Customer Wealth Dashboard, Advisor Clients Dashboard, Analyst Market Dashboard, and Risk Alerts Dashboard.
- Updated the API client to support fetching customer and product data, enhancing the overall functionality of the dashboard.
- Added error handling components to improve user experience during data fetching.
- Enhanced charting capabilities using Ant Design Charts for better data visualization.

This update lays the groundwork for further development of the frontend application, ensuring a robust structure for future features and integrations.
2026-09-09 15:50:33 +08:00

21 KiB
Raw Blame History

前端 · 四角色「收益/洞察」登录首页(桌面端)

日期:2026-09-09
状态:需求已定稿 · 待实现
动机:参考移动端「我的基金 / 固收+ / 板块信号」——登录第一眼看到资产与涨跌,而非占位 Statistic。
关联:2026-09-09-frontend-p0-design.md §8 · 接口契约 v0.1 · web/ · 场景 C-01/C-04/C-05 · R-01


0. 用户痒点(为什么要做)

痒点 现在(占位工作台) 目标(本需求)
一眼看资产 「演示指标 A=128」 登录即 总市值(真持仓汇总)
一眼看涨跌 无 昨日收益估算 + 累计盈亏(红涨绿跌)
一眼看结构 无 饼图/柱图(哪只占得多、哪只赚/亏)
马上行动 只有文字链接 表内 Chat / 详情 / 处置 按钮
角色各不同 四角色同一套假指标 客户看持仓、顾问看客户、分析看市场、风控看预警

参考图映射(移动端 → 桌面,非 1:1 像素复刻):

参考 桌面落地
「我的基金」总览卡 DashboardHero:大数字 + 3 副指标
产品列表卡片 Table(桌面信息密度更高,可排序)
固收+ 对比柱图 客户:各产品 pnl_pct 柱图;分析员:类型平均涨跌
板块潜力信号列表 风控:待审预警表 + 类型分布

1. 目标与原则

原则 说明
痒点优先 登录 defaultRoute = 各角色 Dashboard,不是 generic /app/home
桌面适配 最小设计宽 1280px(与 P0 spec §8.3 一致);lg+ 双列图表
真数据 平台读 API 真接;禁止 Mock 整页 REST
诚实边界 无历史净值序列 → 不画 K 线/分时;页眉 Alert 标明静态模拟
角色分化 四 Demo 账号各一套叙事(用户拍板 2026-09-09)

2. 方案(已选 B)

各角色 独立 Dashboard 页,共用 DashboardLayout(Hero + ChartsRow + DetailTable + QuickActions)。


3. 共用布局壳

┌─ PageShell ─────────────────────────────────────────────────────────┐
│ Alert:数据截至 {as_of} · Core 模拟静态净值 · 非实时交易行情         │
├─ DashboardHero(浅蓝渐变 #e6f4ff→#fff,仅此区允许渐变 · 见 P0 §8.1 例外)│
│  主指标(Typography.Title level={2} + tabular-nums)                 │
│  副指标 ×3(Statistic compact)                                      │
│  [可选] 眼睛图标:掩码金额(localStorage,仅前端 display)            │
├─ QuickActions(4 个 Button/Link,角色不同)──────────────────────────┤
├─ ChartsRow ─────────────────────────────────────────────────────────┤
│  Col lg=8  结构图(Pie/Donut)   Col lg=16  对比图(Column/Bar)     │
├─ DetailTable(Card 包裹 Table size=small)───────────────────────────┤
│  默认排序 · 分页 · 行操作                                            │
└─────────────────────────────────────────────────────────────────────┘
项 规格
图表库 @ant-design/charts(新增依赖;仅 Dashboard + 后续 market 可复用)
涨跌色 正 Typography.Text type="danger" · 负 type="success" · 零 secondary
金额格式 Intl.NumberFormat('zh-CN', { minimumFractionDigits: 2 }) + 后缀「元」
百分比 带符号 +1.23% / -0.50%
Loading Hero/Chart/Table 分块 Skeleton;整页不白屏
空状态 Empty + 插画 empty-data.svg + 文案「暂无持仓/暂无预警」
错误 ApiErrorResult + 刷新按钮;不静默降级假数据
刷新 Toolbar「刷新」重新拉 API(不 auto-poll)
金额掩码 Hero 区眼睛图标切换 localStorage['dashboard:maskAmount'];掩码时显示 ****(仅 display,不影响 API)
表格默认排序 客户表:market_value 降序 · 理财师表:客户持仓市值降序 · 分析员表:|daily_chg_pct| 降序 · 风控表:created_at 降序
图表空数据 饼图 0 项 → 隐藏饼图 Col,对比图 Col 拉满 lg=24

3.1 QuickActions 路由映射(定稿)

角色 按钮 1 按钮 2 按钮 3 按钮 4
客户 交易明细 → #/app/customer/trades(P0 占位页) 产品行情 → #/app/market 客户助手 → #/app/customer/chat 数据分析 → #/app/analytics/query
理财师 名下客户 → #/app/advisor/customers 顾问助手 → #/app/advisor/chat 产品行情 → #/app/market 数据分析 → #/app/analytics/query
分析员 问数工作台 → #/app/analytics/query 分析 Chat → #/app/analytics/chat 产品行情 → #/app/market —(3 按钮)
风控 预警台账 → #/app/risk/alerts 风控助手 → #/app/risk/chat 产品行情 → #/app/market 数据分析 → #/app/analytics/query

3.2 图表配置(@ant-design/charts)

图 类型 数据键 配色
结构图 Pie(内半径 0.6 甜甜圈) label + value AntD 默认色板;≤5 项全显,>5 项 Top5 +「其他」
对比图 Column(横向 Bar) label + value 值 >0 #cf1322 · <0 #389e0d · =0 #8c8c8c
Tooltip 金额图带「元」后缀 · 百分比图带 % — —
高度 固定 height={280} — —

4. API 与字段契约(实现必读)

4.1 通用

  • 请求头:Authorization: Bearer {accessToken}
  • 响应:{ code, data, trace_id } 或 risk alerts 直出 { items, total, disclaimer }
  • 前端封装:web/src/api/client.ts 已有 apiFetch

4.2 持仓 GET /api/customers/{customer_id}/holdings

{
  "data": {
    "items": [{
      "product_id": "PROD-110022",
      "product_name": "...",
      "product_type": "...",
      "min_risk_code": "R3",
      "market_value": 82400.0,
      "pnl_pct": 3.0,
      "cost_amount": 80000.0,
      "qty": 80000.0,
      "as_of": "2026-09-04"
    }],
    "total": 3,
    "truncated": false
  }
}

字段名是 pnl_pct(不是 profit_pct) · 种子 as_of 统一 2026-09-04

4.3 净值 GET /api/products/{product_id}/nav

{
  "data": {
    "nav": 1.03,
    "daily_chg_pct": 0.35,
    "nav_date": "2026-09-04"
  }
}

合并逻辑抽 mergeHoldingsWithNav(holdings, navMap)(Vitest 覆盖)。

4.4 产品列表 GET /api/products?limit=50

分析员/行情页共用;再 N×nav 或等 v0.2 nav-snapshot。

4.5 预警 GET /api/risk/alerts?status=pending_review&page_size=100

{
  "items": [{ "alert_id", "alert_type", "customer_id", "risk_score", "status", "created_at", ... }],
  "total": 12,
  "disclaimer": "本预警由系统自动生成..."
}

Dashboard 页脚或 Alert 旁展示 disclaimer(风控域,区别于 Chat G-08)。

4.6 鉴权与 actor 映射

角色 持仓/客户 API 的 {customer_id} 理财师 API 的 {advisor_id}
客户 authStore.actorId(CUST-9527) —
理财师 名下各客户 ID(roster 返回) authStore.actorId(STAFF-10086)
分析员 任意(全量只读) 任意
风控 任意(全量只读) —

403 时走 ApiErrorResult;客户 token 查他人 holdings → AUTH_403_NOT_OWNER。

4.7 理财师 roster enrichment

GET /api/advisors/{id}/customers 仅返回 customer_id + display_name。Dashboard 表需 风险等级 与 持仓市值:

1. GET /api/advisors/{advisor_id}/customers
2. Promise.all(items.map(c =>
     Promise.all([
       GET /api/customers/{c.customer_id}        → risk_code
       GET /api/customers/{c.customer_id}/holdings → Σ market_value
     ])
   ))

抽 aggregateAdvisorRoster(rows)(Vitest);Demo ≤12 客户,N+1 可接受。

4.8 风控预警 · 数据前置(重要)

Core reset 后 risk_alert 默认为空(预警由 simulate/trade 或风控引擎产生,不在 Core 种子内)。

场景 行为
仅跑 reset.ps1 + prepare_risk_demo.sql 风控 Dashboard 展示 Empty + 文案「暂无待审预警」+ 链到 #/app/risk/alerts;通过 AC-5a
走查前按《演示SOP-风控模块》§4 执行 A-1/A-3/A-5 产生 pending_review 预警;Dashboard 有图有表;通过 AC-5b

前端 不得 Mock 预警条数;Empty 与有数据两种态均须 UI 就绪。


5. 聚合公式(客户 · 纯函数 · 单测)

指标 公式 说明
totalValue Σ market_value Hero 主数字
totalPnlAmount Σ market_value * pnl_pct / 100 累计盈亏(元)
yesterdayPnl Σ market_value * daily_chg_pct / 100 估算;缺 nav 的项跳过
holdingCount items.length 副指标
asOf max(holding.as_of, nav.nav_date) Alert 文案

5.1 Demo 验收锚点 · 客户 CUST-9527(reset 后 · 可写 Vitest)

逐行明细(04-seed-holdings.sql + 06-seed-nav.sql):

product_id market_value pnl_pct daily_chg_pct 行盈亏(元) 行昨日(元)
PROD-005827 47,500.00 -5.00 -0.80 -2,375.00 -380.00
PROD-110022 82,400.00 3.00 0.15 2,472.00 123.60
PROD-000001 20,040.00 0.20 0.01 40.08 2.00
合计 149,940.00 — — 137.08 -254.40
Hero 指标 期望值
持仓只数 3
总市值 149,940.00
累计盈亏 +137.08(展示 +137.08 元)
昨日收益 -254.40(展示 -254.40 元)
as_of 2026-09-04

旧稿「约 +1,034 元」为计算笔误,以上表为准。

5.2 Demo 验收锚点 · 理财师 STAFF-10086

Hero 指标 期望值(reset 后)
名下客户数 12
客户持仓总市值 1,654,850.00
最大单客户 CUST-DEMO-C · 615,000.00
Top5 客户(市值) DEMO-C 615k · 3001 363.5k · 9527 149.94k · 1003 152.7k · DEMO-A 71.57k

5.3 Demo 验收锚点 · 分析员 STAFF-20001

Hero 指标 期望值(14 产品有 nav)
产品总数 14
上涨 / 下跌 / 平盘 10 / 4 / 0(按 daily_chg_pct 符号)
|daily_chg_pct| Top1 PROD-XYZ999 -2.00%
类型数 bond 3 · mixed 2 · stock 4 · index 2 · money 1 · wealth_mgmt 1 · private_fund 1

5.4 Demo 验收锚点 · 风控 STAFF-30001

场景 Hero
reset 后无预警 待审 0 · Empty 态
SOP A-1~A-5 走查后 待审 ≥5(含 suitability / large_amount / aml 等);disclaimer 固定文案可见

6. 分角色需求

6.1 客户(CUST-9527)

路由: #/app/customer/home(登录 defaultRoute,不变)

用户故事:

  • 作为客户,登录后 3 秒内 看到总市值与涨跌,不用点「持仓」菜单。
  • 作为客户,我能看出 哪只产品拖后腿(柱图 + 表排序)。
  • 作为客户,我能 一键进客户助手 Chat 问「为什么亏了」。
区块 内容
Hero 主指标 总资产(元)
Hero 副指标 昨日收益 · 累计盈亏 · 持仓只数
结构图 持仓市值占比(按 product_name,Top5 + 其他)
对比图 各产品 pnl_pct 横向柱图
表列 产品名 · 市值 · 昨日涨跌% · 累计盈亏% · 风险等级 · 操作
表操作 「详情」→ #/app/customer/holdings(或 Drawer 产品详情 P1)· 「问助手」→ Chat
QuickActions 交易明细 · 产品行情 · 客户助手 · 数据分析

场景 ID: C-01 持仓 · C-04 阈值(P1 阈值行高亮)· C-05 净值


6.2 理财师(STAFF-10086)

路由: #/app/advisor/home(新增);侧栏「首页」指向此;「名下客户」仍 #/app/advisor/customers 全量表

用户故事:

  • 作为理财师,登录后看到 名下有几位客户、谁资产最大,便于安排跟进。
  • 作为理财师,我能点进 顾问 Chat 或客户详情(P1)。
区块 内容
Hero 名下客户数 · 客户持仓总市值(Top 客户汇总)· 待跟进占位(P1 接 L2)
结构图 客户市值占比(Top5 + 其他)
对比图 Top5 客户市值柱图
表 客户 ID · 姓名 · 风险等级 · 持仓市值 · 操作(查看/Chat)
QuickActions 名下客户 · 顾问助手 · 产品行情 · 数据分析

API: GET /api/advisors/{id}/customers + 每客户 holdings(Demo ≤10 客户,前端 Promise.all)

v0.2 候选: GET /api/advisors/{id}/portfolio-summary 避免 N+1


6.3 分析员(STAFF-20001)

路由: #/app/analyst/home 或登录 default 仍 #/app/analytics/query —— 拍板:defaultRoute 改为 #/app/analyst/home,问数仍进 analytics/query

用户故事:

  • 作为分析员,登录后看到 全市场产品涨跌概况,而不是空占位。
  • 作为分析员,我能一键进 问数工作台 与 分析 Chat。
区块 内容
Hero 产品总数 · 上涨只数 · 下跌只数 · 平盘只数
结构图 product_type 数量分布
对比图 各 product_type 平均 daily_chg_pct
表 产品行情 Top10(按 |daily_chg_pct| 排序)
QuickActions 问数工作台 · 分析 Chat · 产品行情

API: GET /api/products + nav 合并(与 /app/market 共享 hook)

场景 ID: D-01 读数入口


6.4 风控(STAFF-30001)

路由: #/app/risk/home(新增);登录 defaultRoute 从 #/app/risk/alerts 改为 #/app/risk/home;完整台账仍 /app/risk/alerts

用户故事:

  • 作为风控专员,登录后 待审预警数量 一眼可见(参考「板块信号」信息密度)。
  • 作为风控专员,我能看 预警类型结构 并点进处置或 Chat。
区块 内容
Hero 待审总数 · 今日新增(created_at 当天)· 涉及客户数(去重)
结构图 alert_type 分布(aml/large_amount/…)
对比图 各 alert_type 平均 risk_score
表 预警 ID · 类型 · 客户 · 分数 · 状态 · 创建时间 · 操作
表操作 「处置」→ alerts 详情/抽屉(P1)· 「风控助手」→ Chat
Alert 展示 API disclaimer
QuickActions 预警台账 · 风控助手 · 产品行情 · 数据分析

API: GET /api/risk/alerts?status=pending_review&page_size=100

场景 ID: R-01 预警 · A-6 对话查预警


7. 路由与信息架构(定稿)

角色 defaultRoute(改) Dashboard 路由 组件
客户 #/app/customer/home 同左 CustomerWealthDashboard
理财师 #/app/advisor/home 同左 AdvisorClientsDashboard
分析员 #/app/analyst/home 同左 AnalystMarketDashboard
风控 #/app/risk/home 同左 RiskAlertsDashboard

侧栏「工作台 / 首页」 各角色指向上表;删除 generic #/app/home 或保留 redirect 到角色 home。

demoAccounts.ts: 同步更新四个 defaultRoute。

7.1 侧栏与路由文件变更清单

文件 变更
web/src/config/demoAccounts.ts 理财师 → #/app/advisor/home · 分析员 → #/app/analyst/home · 风控 → #/app/risk/home
web/src/routes/menus.tsx 理财师增「首页」→ /app/advisor/home · 风控增「首页」→ /app/risk/home · 分析员增「首页」→ /app/analyst/home(问数仍保留)
web/src/App.tsx 新增四 Dashboard 路由;customer/home 改挂 CustomerWealthDashboard;/app/home redirect 到角色 defaultRoute
删除/弃用 RoleHomePage.tsx 假 Statistic(实现后删除或仅测试保留)

7.2 数据加载 Hook 契约

// useHoldingsDashboard(customerId)
// 1. GET holdings → 2. 对 items 去重 product_id → Promise.all nav
// 返回 { hero, pieData, barData, tableRows, asOf, loading, error, refresh }

// useMarketSnapshot() — 与 /app/market 共用
// GET products?limit=50 → N×nav → mergeProductNavRows

// useAlertsDashboard()
// GET /api/risk/alerts?status=pending_review&page_size=100
// 注意:响应无 ok() 外壳,直读 items/total/disclaimer

// useAdvisorRosterDashboard(advisorId)
// roster + 并行 enrichment(§4.7)

并发上限:nav 请求 ≤20 并发(p-limit 或手写 batch);失败单项不影响整页,表行 daily_chg_pct 显示 -。


8. 组件与文件

web/src/
  api/customers.ts | products.ts | risk.ts | advisors.ts
  utils/
    formatMoney.ts | formatPct.ts | pnlColor.ts
    mergeHoldingsWithNav.ts | aggregateHoldingsHero.ts
    mergeProductNavRows.ts          # 分析员/行情共用
    aggregateAdvisorRoster.ts       # 理财师 Hero+表
  hooks/
    useHoldingsDashboard.ts | useAlertsDashboard.ts | useMarketSnapshot.ts
  components/dashboard/
    DashboardLayout.tsx | DashboardHero.tsx | DashboardCharts.tsx
    PnlText.tsx | AmountText.tsx        # 掩码 + 红涨绿跌
    QuickActionBar.tsx
  pages/dashboard/
    CustomerWealthDashboard.tsx
    AdvisorClientsDashboard.tsx
    AnalystMarketDashboard.tsx
    RiskAlertsDashboard.tsx

9. 与 P0 主 spec 的关系

文档 关系
2026-09-09-frontend-p0-design.md 壳/路由/Chat/market 仍有效;本 spec 替换「RoleHomePage 假指标」与部分占位首页
实现顺序 本 spec §10 优先于 P0 §6 的 customer 只读页(Dashboard 已含持仓摘要)
共享复用 mergeProductNavRows、market 页、alerts 全页 共用 api/hook

10. 非目标(P0)

  • K 线 / 分时 / 自选股 / WebSocket 行情
  • 「追加购买」等交易 CTA(simulate 不做)
  • 理财师后端聚合 API(Demo N+1 可接受)
  • 完整 ChatPanel(Dashboard 只放入口按钮;Chat 仍单独路由)
  • 移动端优先布局

11. 验收标准(可测)

# 条件 通过标准
AC-1 四 Demo 登录 各进 角色 Dashboard,非 Placeholder
AC-2 客户 Hero 总市值 149940.00 · 累计盈亏 +137.08 · 昨日 -254.40(CUST-9527,reset 后)
AC-3 涨跌色 正红负绿;pnl_pct=0 灰色
AC-4 图表 每 Dashboard ≥1 张图有数据(风控 reset 后 Empty 除外)
AC-5a 风控 Empty reset 后无预警:Empty + disclaimer 区域占位,不报错
AC-5b 风控有数据 SOP 走查后:待审数 = API total;页眉/页脚见 disclaimer
AC-6 合规文案 客户页 Alert 静态净值;风控页展示 API disclaimer
AC-7 刷新 点刷新重新请求,trace_id 可变
AC-8 后端挂 ApiErrorResult,无假数字
AC-9 构建 npm run build + Vitest 纯函数绿
AC-10 理财师 Hero 客户数 12 · 总市值 1654850.00(STAFF-10086)
AC-11 分析员 Hero 产品 14 · 涨 10 跌 4

12. 测试清单(Vitest)

  • aggregateHoldingsHero:CUST-9527 锚点(§5.1 表)
  • mergeHoldingsWithNav:缺 nav、部分缺 nav
  • mergeProductNavRows:products + nav;14 产品涨跌统计
  • aggregateAdvisorRoster:STAFF-10086 客户数与总市值
  • formatPct / pnlColor 边界 0、null

13. 实现顺序

  1. 依赖 @ant-design/charts + api/utils 纯函数 + Vitest
  2. DashboardLayout + CustomerWealthDashboard(跑通全链)
  3. RiskAlertsDashboard
  4. AnalystMarketDashboard(与 market 共用 hook)
  5. AdvisorClientsDashboard
  6. 路由 + demoAccounts defaultRoute + 删 RoleHomePage 假指标
  7. P0 其余:ChatPanel、完整 alerts/holdings 页

14. 联调前置(FLOW §0)

步骤 命令/脚本 Dashboard 影响
① Core 灌库 scripts/core/reset.ps1 或 FLOW 脚本化 DROP+灌 客户/理财师/分析员 有数据
② 风评刷新 scripts/demo/prepare_risk_demo.sql 不影响 Dashboard 数字
③ 后端启动 uvicorn app.main:app --reload API 可达
④ 前端 cd web && npm run dev Vite proxy → :8000
⑤ 风控演示(可选) 《演示SOP-风控模块》§4 A-1~A-5 风控 Dashboard 有预警

登录: 四 Demo 卡片一键登录(web/src/config/demoAccounts.ts)· Bearer 自动写入 authStore。


15. 修订记录

日期 说明
2026-09-09 首版:四角色桌面收益/洞察首页
2026-09-09 需求完善 v1:痒点表 · API 字段 pnl_pct · 聚合公式与 Demo 锚点 · 交互/空错态 · 路由定稿 · AC/测试 · 与 P0 关系
2026-09-09 需求完善 v2:修正累计盈亏锚点 137.08 · 四角色全量锚点 · QuickActions/图表/Hook · 风控 Empty 前置 · 侧栏变更清单 · 联调步骤