Files
group_xinghuo_jinrong/docs/frontend/FRONTEND-HANDOFF.md
T

224 lines
10 KiB
Markdown
Raw Normal View History

# JinRong 前端交接手册
## 1. 项目定位
当前前端采用「静奢智能」视觉方向:暖白与米灰背景、墨蓝品牌色、浅金点缀,收益语义使用朱砂红(正值)与翡翠绿(负值)。视觉层已经与业务数据层分离,后续 agent 可以在不改 API 契约的前提下扩展页面。
## 2. 启动与验证
从仓库根目录执行:
```bash
cd web
npm install
npm run dev
npm run build
npm run test
npm run lint
```
后端需要单独启动:
```bash
uvicorn app.main:app --reload
```
Vite 开发服务器默认使用 `http://127.0.0.1:5173`,并将 `/api` 代理到 `http://127.0.0.1:8000`。路由使用 `HashRouter`,页面 URL 形如 `http://127.0.0.1:5173/#/app/customer/home`。
**环境自检(2026-09-11):** 后端 `GET /api/ready`(Redis + 关键路由)· 前端顶栏 `DevReadyBanner`(开发态提示,不替代生产探针)。
**测试基线:** 全仓 `python -m pytest` → **825 passed**, 1 skipped · `cd web && npm run test` → **22** Vitest。
## 3. 依赖方向
```text
pages → hooks/api/utils
pages → components/dashboard
components/dashboard → components/ui
layouts/AppLayout → components/layout
components/ui → styles/tokens.css
```
边界规则:
- `components/ui` 只能处理展示和可访问性,禁止导入 `api`、`hooks`、`authStore` 或角色业务类型。
- `components/layout` 只接收导航和身份上下文,不发起 API 请求。
- `components/dashboard` 只接收页面已经计算好的值、数组、ReactNode 和表格配置,不负责请求或聚合。
- hooks 是 API loading、error、refresh 状态的所有者。
- 页面负责业务文案、hook 调用、路由 Link、表格列和业务字段映射。
## 4. 设计令牌
令牌位于 `web/src/styles/tokens.css`,优先使用 Tailwind utility,不要在页面新增品牌色或重复 spacing 值。
| Token | Value | 用途 |
| --- | --- | --- |
| `jr-bg` | `#F6F5F1` | 页面背景 |
| `jr-surface` | `#FBFAF7` | 次级表面 |
| `jr-surface-strong` | `#FFFFFF` | 卡片和主表面 |
| `jr-ink` | `#132B3A` | 品牌墨蓝、主按钮 |
| `jr-text` | `#17212B` | 正文 |
| `jr-muted` | `#71808A` | 次级文字 |
| `jr-border` | `#E5E2DB` | 边框和分隔线 |
| `jr-positive` | `#178A68` | 负收益、正常状态 |
| `jr-negative` | `#C9564A` | 正收益、风险状态 |
| `jr-gold` | `#C9A66B` | eyebrow、焦点、强调 |
| `jr-info` | `#3F718A` | 信息提示 |
金融产品已有语义不能反转:正值 P&L 显示朱砂红,负值显示翡翠绿,零值使用次级灰。这是现有产品约定,不要按通用股票配色改成相反语义。
## 5. 可复用组件
### 基础 UI:`src/components/ui`
```tsx
<Surface>
<MetricCard label="总市值" value={<AmountText value={value} />} />
</Surface>
<PageHeader eyebrow="WEALTH DESK" title="我的资产" action={<Button>刷新</Button>} />
<Badge tone="positive">运行正常</Badge>
<EmptyState title="暂无持仓" image="/assets/illustrations/empty-data.svg" />
```
组件职责和 props:
- `Button`: `variant` 为 `primary | secondary | ghost | quiet`,`size` 为 `sm | md | lg`,支持 `loading` 和标准 button props。
- `Surface`: `variant` 为 `default | subtle | dark`,`padding` 为 `none | sm | md | lg`,支持 `as` 和 HTML attributes。
- `Badge`: `tone` 为 `neutral | info | positive | negative | gold | ink`。
- `MetricCard`: `label`、`value`、可选 `detail`、`trend`、`emphasis`;`value` 接收 ReactNode,不会格式化或改写金额。
- `PageHeader`: `eyebrow`、`title`、`description`、`action`。
- `SectionHeading`: `eyebrow`、`title`、`description`、`action`。
- `EmptyState`: 可选 `image`、`description`、`action`。
这些组件不拥有数据获取;用 slots/ReactNode 接入业务内容。
### Dashboard:`src/components/dashboard`
```tsx
<ChartCard title="资产结构">...</ChartCard>
<DataSection title="持仓明细">
<Table columns={columns} dataSource={rows} />
</DataSection>
```
- `DashboardHero`: 保留主指标、掩码、眼睛按钮和三项响应式指标;主值由页面提供。
- `QuickActionBar`: 接收 `{ label, to }[]`,使用真正的 React Router `Link`,不能改为只有 `onClick` 的 button。
- `ChartCard`: `{ eyebrow?, title, description?, children }`,只包裹图表 surface,不了解图表库内部。
- `DataSection`: `{ title, description?, action?, children }`,只在表格内容区提供 `overflow-x-auto`。
- `DashboardLayout<T>`: 接收页面传入的 hero、quick actions、chart data、`ColumnsType<T>`、row key 和空文案;保留 AntD Table 的排序、分页和行操作行为。
## 6. 新增角色 Dashboard 的流程
1. 在 `src/api` 增加或复用 API 函数,保持 URL、method、参数、Authorization 行为不变。
2. 在 `src/hooks` 保持 loading、error、refresh 和请求编排;不要把请求写进视觉组件。
3. 需要聚合或格式化时放在 `src/utils`,并为纯函数补测试。
4. 页面调用 hook,组合 `PageHeader`、`DashboardHero`、`ChartCard`、`DataSection` 和 `EmptyState`。
5. 保留 `ApiErrorResult`、无假数据行为和已有 route Link。
6. 菜单只在 `src/routes/menus.tsx` 增加,路由只在 `src/App.tsx` 增加;不要在页面复制导航模型。
## 7. 状态矩阵与 Ant Design 边界
```text
Loading → Skeleton,应用壳层和页面层级仍可见
Success → 共享 Surface + 现有 Table/Charts
Empty → EmptyState 或 AntD Empty,绝不填充假数字
Error → ApiErrorResult,显示 error code、message 和 trace ID
```
Ant Design 继续负责:
- Table 的排序、分页、row key 和行渲染;
- Alert、Result、Empty、Skeleton 和 message;
- `@ant-design/charts` 图表内部、tooltip、legend 和延迟挂载。
Tailwind 负责:
- 页面布局、间距、响应式网格、排版、Surface 层级和轻量 hover/focus 动效;
- 令牌来自 `tokens.css`,不要在页面硬编码新的品牌色。
禁止:
- 全局 `!important` 覆盖 AntD 行为;
- 将 API 请求或角色判断移入 `components/ui`;
- API 失败时添加 demo/fallback 数字;
- 把真实 `Link` 换成 click-only button;
- 为了视觉改动 `AuthState`、`jinrong.auth`、接口字段或路由。
## 8. 浏览器验收清单
启动 FastAPI 与 Vite 后,使用四个 demo 账号检查:
- 客户 → `/app/customer/home`
- 理财师 → `/app/advisor/home`
- 分析员 → `/app/analyst/home`
- 风控 → `/app/risk/home`
每个角色都检查:
- Logo、角色上下文、当前菜单高亮;
- 侧栏折叠/展开;
- actor ID copy;
- 退出后回到登录页;
- 至少一个共享平台 route。
业务回归:
- 客户显示总市值、累计盈亏、昨日收益和持仓数;眼睛按钮只切换 `****`,不改变表格行数据;图表、分页、排序、详情和助手链接可用。
- 理财师显示客户数、总 AUM 和最大客户 fallback;客户市值降序、客户与 Chat 链接可用。
- 分析员显示产品数、上涨/下跌/平盘;按日变化绝对值排序,行情/问数/Chat 链接可用。
- 风控显示待审数量和 API disclaimer;台账 **`GET /alerts` 的 `stats.pending`** 与「已处置」聚合筛选(`status=handled`);适当性/AML/模拟页为结构化结果(非裸 JSON);无预警时显示 Empty,不出现假图表或假表格;有预警时显示图表、处置和助手链接。
- **四角色 Chat(含客户 SSE)**:…侧栏每条可删除(调用 close API);侧栏**仅展示 active**;**清空历史会话** 调用 `POST /api/chat/sessions/close-all`(消息仍留库,仅关闭续聊)。
- API 错误态显示 code、message、trace ID 和重试;loading 态保留 shell 与 skeleton。
- 检查正收益红色、负收益绿色、零值灰色。
视口矩阵:`1280px`、`1024px`、`768px`、`320px`。
- body 不出现横向滚动条;
- 只有表格区域发生局部横向滚动;
- 登录列在窄屏堆叠;
- action rail 自动换行;
- Hero 指标变成可读的响应式列;
- 键盘 focus ring 可见。
## 9. 常见坑
- 不要把 API 调用迁移到 visual component。
- 不要在 API error 时补默认金额或默认条数。
- 不要在页面里新增硬编码颜色或随意 spacing。
- 不要删除共享组件而不检查 downstream agent 的引用。
- 不要修改 `AuthState`、`jinrong.auth` localStorage key 或登录默认落点。
- 对话续聊用 `sessionStorage` 键 `jinrong.chat.activeSession.{agentType}`(与登录 key 无关);勿在流式 POST 省略 `session_id`。
- 不要把表格的 sorting、pagination、row action 逻辑重写成页面外的副本。
- 修改后至少运行:
```bash
cd web
npm run build
npm run test
npm run lint
```
当前已知 lint warning 主要来自既有 hooks 的 `set-state-in-effect` 和 React Fast Refresh 对“组件文件同时导出常量”的提示;它们不是编译错误。Vite 可能提示主 chunk 超过 500 kB,属于当前依赖包体积提示。
## 10. 看板地图与加载(2026-09-10)
**有可视化,入口是角色 home,不是单独 BI 产品:**
| 角色 | 路由 | 图表 |
| --- | --- | --- |
| 客户 | `#/app/customer/home` | 资产结构饼图 · 持仓盈亏柱图 · 表 |
| 理财师 | `#/app/advisor/home` | 名下客户 AUM 分布 |
| 分析 | `#/app/analyst/home` | 市场/产品概览 |
| 风控 | `#/app/risk/home` | 预警类型分布 |
| 问数 | `#/app/analytics/query` | MetricCard(`/api/analyst/dashboard`)· **抽样溯源 / 转人工**(N-03/N-07)· 无钻取(D-12 未做) |
**为何每次进页都转圈:**
- **Redis 不缓存**平台 REST(持仓/净值/预警);只服务 Chat 窗口、问数 D-06、限流、L3。
- 每个页面 **mount 时 `loading=true` 全量重拉**;离开路由 state 丢弃(无 React Query)。
- `fetchNavMap` 对**每个 product_id 单独请求** `/nav`,持仓/行情页请求次数多。
**明日优化方向(见 `TODO.md` §2026-09-11):** 批量净值 API · 客户端 stale-while-revalidate · 可选 Dashboard 短 TTL 缓存(后端)。
**客户「产品趋势」:** 客户助手 Chat 仅 **C-05 最新净值**;走势/预测 reject。统计类趋势走 **问数工作台** + customer **`self` 域**(`数据分析Agent-合并说明.md` §9.6)。**分析对话 URL 已重定向问数**。