Files
zhixing-system/.trellis/tasks/08-09-responsive-frontend-design/design.md
T
2026-08-09 23:00:07 +08:00

229 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 响应式前端交互体系技术设计
## 1. 设计目标
本任务先交付可交互 HTML 原型和可执行的目标架构设计,不直接重写生产页面。原型回答两个问题:
1. 暖白、炭黑、白色浮层和克制圆角能否形成适合量化研究工作台的高密度组件语言;
2. 同一套页面语义能否通过一个 layout 模块在桌面、平板和移动端形成不同但一致的交互布局。
生产实现必须等原型方案被选定后另行执行;原型代码不直接提升为生产组件。
## 2. 当前约束与证据
- `src/features/home/components/home-shell.tsx` 同时承担品牌、导航、搜索、用户入口和内容容器,并要求页面传入 `activeSection`。它位于 `home` feature,却被 `selection` feature 反向使用,说明应用壳 seam 放置不当。
- 当前窄屏只是把侧栏从 256px 缩到 80px,没有移动端主导航、底部安全区或触控密度策略。
- `SignalTable` 以 `min-width: 720px` + `overflow-x-auto` 处理窄屏,没有分页,也没有移动端信息优先级。
- Button/Input 默认 32px 高度适合桌面紧凑密度,但移动触控需单独的最小命中区域。
- 前端规范要求业务留在 feature、跨 feature 能力进入 shared/app,路由只负责映射和组合,URL 状态由 TanStack Router 承担。
- TanStack Router 官方资料确认:code-based routing 可用只有 `id`、没有 `path` 的 pathless layout route 包裹子路由,并通过 `<Outlet />` 共享应用壳;`validateSearch` 可为分页和原型 variant 提供类型化 URL 状态。
## 3. Target Architecture
```text
src/
├── app/
│ └── layout/
│ ├── app-layout.tsx # 应用品牌、响应式主导航、工具区、Outlet
│ ├── navigation.ts # 应用级导航模型与可用状态
│ └── page-layout.tsx # 页头、操作区、内容宽度与纵向节奏
├── routes/
│ └── route-tree.tsx # pathless workspace route + child routes
├── features/
│ └── selection/
│ └── components/
│ └── signal-results.tsx # 业务列定义与移动卡片呈现
└── shared/
└── ui/
└── pagination.tsx # 无业务语义的分页 primitive
```
### 3.1 Layout seam
外部 seam 位于 pathless workspace route 与所有业务 child route 之间。路由树只挂载一次 `AppLayout`,页面不再 import 应用壳,也不再传 `activeSection`。
```tsx
const workspaceRoute = createRoute({
getParentRoute: () => rootRoute,
id: "workspace",
component: AppLayout,
});
const indexRoute = createRoute({
getParentRoute: () => workspaceRoute,
path: "/",
component: HomePage,
});
```
`AppLayout` 的 interface 对页面保持极小:页面只作为 `<Outlet />` 内容进入。当前导航项由 Router Link 的 active 状态得出,不再由页面手工声明。
### 3.2 Page layout interface
页面仅通过稳定的页面语义描述页头,不知道断点、侧栏宽度和安全区:
```tsx
interface PageLayoutProps {
mode?: "document" | "bounded-workspace";
actions?: React.ReactNode;
metrics?: React.ReactNode;
children: React.ReactNode;
}
```
`PageLayout` 内部集中处理:
- 视口 gutter、最大内容宽度与内容密度;
- 操作区和指标区在窄屏的换行/堆叠;
- 页面区块的紧凑纵向节奏;
- sticky header 预留和移动底部安全区;
- `bounded-workspace` 模式下的连续高度链与唯一滚动区域;
- 跳过导航链接与语义化 `<main>`。
页面不再通过 `PageLayout` 渲染独立的大标题、眉题和说明区。当前页面名称与路由上下文由 `AppLayout` 顶栏提供;`PageLayout` 的首个内容区直接承载页面操作、指标和业务数据,避免在紧凑工作台中重复表达同一上下文。
删除该模块会使上述规则重新散落到每个页面,因此它提供真实 Depth 与 Locality,而不是 className 转发层。
`document` 是普通详情页、表单页的默认模式,由应用内容区自然滚动。`bounded-workspace` 只用于表格、树表和主从研究台:`AppLayout → main → PageLayout → business panel` 连续使用确定高度、`min-height: 0` 与 Flex 剩余空间;操作区、指标、筛选栏和分页不可收缩,纵向滚动权收敛到表格数据区或详情正文。实现不得依赖 `calc(100vh - Npx)` 或 JavaScript 测量固定区域高度。
移动断点下不强制延续桌面有界模式。主从区域转为纵向单列时,`PageLayout` 恢复自然高度和页面滚动,避免记录卡、分页与底部导航之间形成嵌套滚动。
### 3.3 Navigation model
`navigation.ts` 保存应用级、只读导航模型:`id`、`label`、`to`、`icon`、`availability`。它不读取 feature 数据,也不保存当前激活项。
- Desktop(≥1024px):232px 固定侧栏;品牌、四个主入口、底部低频入口。
- Tablet(768–1023px):64px 图标 rail;标签通过 tooltip/可访问名称提供。
- Mobile(<768px):顶部只保留当前页面标题和低频操作;底部 4 项主导航,其他入口进入“更多”sheet。
- 底部导航高度 60px,并使用 `padding-bottom: env(safe-area-inset-bottom)`;页面内容 padding 同步包含该占位。
- 响应式切换以 CSS media/container rules 为主,不在 React render 中读取 `window.innerWidth`,避免双树渲染和水合差异。
## 4. Visual and Interaction Tokens
### 4.1 视觉层级
- Canvas:`#f5f1ec`,作为应用背景。
- Surface:`#ffffff`,用于卡片、表格和浮层。
- Ink:`#111111`,作为主文本和主操作颜色。
- Muted Ink:`#626260`;Hairline:`#d3cec6`。
- 不使用阴影建立常规层级;只允许 dialog、popover、prototype switcher 使用必要浮层阴影。
- `DESIGN.md` 的 Fin Orange 不进入通用主操作,因为本产品没有 Fin 语义;涨跌、成功、警告等颜色只承担数据或语义状态。
- 字体使用 Inter/system fallback;桌面工作台不采用营销页 40–72px display 字号。
### 4.2 密度
| Token | Desktop | Touch/Mobile | Usage |
| -------------- | ------: | ---------------: | -------------------------- |
| control-height | 32px | 44px | input、select、常规 button |
| icon-target | 32px | 44px | 仅图标按钮 |
| page-gutter | 24px | 16px | 主内容边距 |
| card-padding | 16px | 14px | 默认卡片 |
| table-row | 44px | 不适用 | 桌面表格行 |
| bottom-nav | 不适用 | 60px + safe area | 移动主导航 |
触控尺寸由 layout/touch capability token 控制,不要求每个页面选择 `mobile` variant。hover 样式仅在支持 hover 的设备启用;所有交互同时提供 focus-visible 和 pressed 状态。
### 4.3 组件覆盖
原型展示并验证以下状态:
- Button:primary、secondary、ghost、destructive、disabled、loading;
- Form:input、search、select、date、focus、filled、disabled;
- Badge/Status:neutral、success、warning、error;
- Card:基础、metric、empty、error;
- Overlay:确认 dialog、移动“更多”sheet、toast;
- Data:筛选工具条、桌面表格、移动记录卡、分页;
- Feedback:skeleton、空结果、请求失败、执行中、成功提示。
## 5. Responsive Data and Pagination
### 5.1 同一数据,两种呈现
`selection` feature 持有字段优先级和业务格式化:
- Desktop:列式 table,优先显示股票、子信号、收盘价、关键详情;面板消费页面剩余高度,筛选栏与分页不可收缩,表头在数据滚动区内吸顶。
- Mobile:每条信号变为紧凑记录卡;首行显示股票与价格,第二行显示信号 badge,关键详情默认折叠后按需展开。
两种呈现消费同一页数据和同一分页状态,不复制请求或业务判断。共享层不尝试理解 `SelectionSignal`。
### 5.2 Pagination interface
```tsx
interface PaginationProps {
page: number;
pageSize: number;
total: number;
pageSizeOptions?: readonly number[];
onPageChange: (page: number) => void;
onPageSizeChange: (pageSize: number) => void;
}
```
分页 primitive 负责总数、当前范围、页码、上一页/下一页、每页数量、禁用和 aria 文案;feature 负责数据切片或 query 参数。
首版生产迁移可对当前已加载 `signals` 使用客户端分页,`page`/`pageSize` 使用 selection route 的 validated search params,使链接可复现。若真实数据规模要求服务端分页,保持 Pagination interface 不变,只替换 feature 内数据 adapter。
## 6. HTML Prototype
### 6.1 Artifact shape
- 文件:`zhixing-web/prototypes/responsive-workbench.html`。
- 单文件 HTML/CSS/JS,无后端、无 CDN、无持久化,双击即可运行。
- 通过 `?variant=a|b|c` 分享方案;底部浮动切换器支持按钮和左右方向键。
- 增加 Desktop/Mobile 预览切换,同时保留真实 media query,便于无需开发者工具评审。
- 使用内存 mock 数据演示搜索、筛选、分页、page size、dialog、sheet、toast 和折叠详情。
### 6.2 Three structural variants
1. **A — 紧凑工作台**:完整侧栏 + 轻量工具栏 + 分区卡片;默认推荐,迁移成本最低。
2. **B — 数据账本**:窄图标 rail + 表格主画布 + 吸顶筛选/操作条;信息密度最高,但首次理解成本更高。
3. **C — 主从研究台**:导航侧栏 + 结果列表/详情双栏;适合研究钻取,但在简单页面上占用更多结构。
三者必须改变信息层级与主操作位置,不能只改变颜色。移动端都遵循已确认的底部导航,但分别比较页头、筛选和记录详情的收纳方式。
### 6.3 Selected direction
用户选定 A+C 组合:保留 A 的完整工作台侧栏、顶栏和紧凑指标区,引入 C 的结果列表/研究详情双栏。原型默认打开该组合;A、B 仅保留为对比参考。
内容区不再渲染页面大标题、眉题或说明文案。操作按钮以紧凑 action row 呈现,其后立即进入指标与数据区域;移动端沿用同一信息顺序。
业务页面不展示用于设计评审的“状态语义 / 加载反馈”样例区。状态组件仍可在独立组件预览弹窗中评审;实际页面只在对应请求状态发生时就地呈现 loading、empty 或 error 反馈。
## 7. State and Data Flow
```text
URL route/search
├─ active route ──> AppLayout navigation presentation
├─ page/pageSize ─> selection feature pagination state
└─ variant ───────> prototype-only variant renderer
Query data ──> feature view model ──> current page slice
├─ desktop table
└─ mobile record cards
```
- 响应式视口不是业务状态,不进入 Zustand。
- 服务器结果继续由 TanStack Query 管理,不复制进 layout 或 UI store。
- 移动“更多”sheet、dialog、展开行属于局部瞬时状态。
- 原型 state 全部保存在内存或 URL,不写 localStorage。
## 8. Compatibility, Rollout, and Rollback
- 本任务只新增原型和规划文档,不改变现有路由、API 或生产 bundle。
- 原型选型后,生产迁移按“pathless layout → 首页 → selection 页面 → shared pagination”的顺序进行,每步保持路由 URL 不变。
- `HomeShell` 在所有页面迁移完成前保留;最终一次性删除,避免两套壳长期并存。
- 若新 layout 出现回归,可将 child routes 的 parent 恢复为 root route,并恢复页面外层 `HomeShell`,不涉及数据回滚。
## 9. Risks and Deferred Items
- `DESIGN.md` 源自营销站分析,若直接套用会造成工作台留白和字号过大;原型明确以数据密度重新校准。
- 当前 signals API 返回完整列表,客户端分页只改善渲染与浏览,不降低传输量;服务端分页在数据规模证明确有需要后单独设计。
- 生产 dark mode、表格排序/列配置、虚拟滚动、离线支持不在本任务范围。
- 原型为 throwaway code;选中方案必须按生产质量重新实现并补测试,不能直接复制粘贴上线。
## 10. Research References
- [TanStack Router routing concepts](https://github.com/tanstack/router/blob/main/docs/router/routing/routing-concepts.md)
- [TanStack Router code-based routing](https://github.com/tanstack/router/blob/main/docs/router/routing/code-based-routing.md)
- [TanStack Router search params](https://github.com/tanstack/router/blob/main/docs/router/guide/search-params.md)