feat(selection): 重构选股结果筛选与状态入口
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
{"file":".trellis/spec/frontend/index.md","reason":"检查前端改动是否仍遵守 feature/shared 边界、路由和同源 API 约束。"}
|
||||
{"file":".trellis/spec/frontend/component-guidelines.md","reason":"检查 Select、DatePicker、抽屉入口、语义控件和 Tailwind 组件组合。"}
|
||||
{"file":".trellis/spec/frontend/state-management.md","reason":"检查日期、URL search、抽屉局部状态与服务器结果的所有权。"}
|
||||
{"file":".trellis/spec/frontend/type-safety.md","reason":"检查 Base UI/DayPicker props、日期转换和 SelectionResults 类型边界。"}
|
||||
{"file":".trellis/spec/frontend/quality-guidelines.md","reason":"执行最终格式、lint、类型、测试、构建和审查门禁。"}
|
||||
@@ -0,0 +1,93 @@
|
||||
# 选股结果页筛选与状态入口技术设计
|
||||
|
||||
## 1. Boundaries
|
||||
|
||||
本任务只修改 `zhixing-web` 前端:
|
||||
|
||||
- `shared/ui` 提供基于 `@base-ui/react` 的通用 `Select`、`Popover`、`Calendar`、`DatePicker` 组件。
|
||||
- `features/selection` 负责选项、URL search 更新、目标日期字符串转换、结果状态和抽屉入口布局。
|
||||
- `SelectionResultsPage` 继续拥有服务器查询、执行/重执行状态和抽屉开关;`SelectionResultsWorkbench` 负责信号列表顶部的入口和筛选区。
|
||||
- `ExecutionStatusDrawer` 负责执行摘要和未完成评估的按需展示,不请求数据、不修改 URL。
|
||||
|
||||
不改变后端 API、React Query query key、路由路径、`SelectionResults` 类型契约和策略执行流程。
|
||||
|
||||
## 2. Component Shape
|
||||
|
||||
```text
|
||||
SelectionResultsPage
|
||||
├─ PageLayout
|
||||
│ ├─ actions: ExecutionToolbar
|
||||
│ │ ├─ DatePicker
|
||||
│ │ ├─ Select (strategy)
|
||||
│ │ └─ Button (execute / rerun)
|
||||
│ └─ children: result states
|
||||
│ └─ SelectionResultsWorkbench
|
||||
│ ├─ signal-list header
|
||||
│ │ ├─ Input (search)
|
||||
│ │ ├─ Select (category)
|
||||
│ │ └─ Button (execution status)
|
||||
│ ├─ SignalTable / SignalRecordList
|
||||
│ └─ Pagination
|
||||
└─ ExecutionStatusDrawer
|
||||
├─ execution summary metrics
|
||||
└─ IncompleteEvaluationTable
|
||||
```
|
||||
|
||||
移除页面级 `ResultMetrics`,避免 `PageLayout.metrics` 形成中间常驻区域。执行状态按钮移动到 `SelectionResultsWorkbench` 的列表 header,页面通过 props 传入 `onOpenExecutionStatus`、`drawerOpen` 和 `triggerRef`,不把开关状态放入 Zustand。
|
||||
|
||||
## 3. Shared UI Design
|
||||
|
||||
### Select
|
||||
|
||||
`shared/ui/select.tsx` 对 `@base-ui/react/select` 的 Root、Trigger、Value、Portal、Positioner、Popup、List、Item、ItemIndicator 和滚动箭头做样式封装。对外保留 Base UI props、`className` 和 `data-slot`,提供可组合的 `SelectGroup`、`SelectLabel`、`SelectSeparator` 等导出。Popup 使用 portal 和 positioner,确保列表不受页面 bounded workspace 的 `overflow` 影响;`data-highlighted`、`data-selected`、`data-disabled` 和 open/closed 状态提供明确反馈。
|
||||
|
||||
### Popover / Calendar / DatePicker
|
||||
|
||||
`DatePicker` 按官方 shadcn/ui Base UI Date Picker 的组合方式实现:`Popover` 负责定位和关闭行为,`Calendar` 负责单日选择。`Calendar` 基于 `react-day-picker`,使用项目 `Button`、`cn` 和现有主题 token;`DatePicker` 对外以通用 `Date | undefined` 传值,业务 feature 在边界处把它转换为 `YYYY-MM-DD`,避免 shared 层理解交易日概念。
|
||||
|
||||
补充 `date-fns` 与 `react-day-picker` 为显式前端依赖。日期转换使用本地年月日构造/格式化,避免直接解析 ISO 日期造成时区前移或后移。
|
||||
|
||||
## 4. Data Flow and State
|
||||
|
||||
```text
|
||||
DatePicker<Date>
|
||||
└─ SelectionResultsPage: Date ↔ YYYY-MM-DD
|
||||
├─ useSelectionResults(strategy, targetTradeDate, resultQuery)
|
||||
└─ useTriggerSelectionRun({ target_trade_date })
|
||||
|
||||
SelectionResultsPage
|
||||
├─ resultQuery ← route search (page/pageSize/search/category)
|
||||
├─ SelectionResultsWorkbench
|
||||
│ ├─ Select(category) → navigate(search: { category, page: 1 })
|
||||
│ └─ status button → setExecutionStatusDrawerOpen(true)
|
||||
└─ ExecutionStatusDrawer(result)
|
||||
├─ result summary metrics
|
||||
└─ result.failures
|
||||
```
|
||||
|
||||
目标日期继续是页面局部状态;搜索、分类和分页继续是 TanStack Router search 状态。Select 不直接操作 URL,feature handler 负责转换和分页重置。抽屉只接收当前 `displayedResult`,切换日期或开始执行时关闭并清理已有局部执行状态。
|
||||
|
||||
## 5. Layout and Accessibility
|
||||
|
||||
- `PageLayout.metrics` 置空后,`PageLayout` 的 bounded workspace 只承载执行控件和结果区;结果列表继续沿用现有高度链和内部滚动边界。
|
||||
- Workbench header 在桌面端把搜索、分类和结果数量/执行状态按钮放在同一行,在移动端自然换行;所有主要按钮保留至少 `44px` 的移动触控高度。
|
||||
- 执行状态按钮使用真实 `Button`,提供 `aria-controls`、`aria-expanded`、`aria-haspopup="dialog"` 和包含状态/失败数的可访问名称。抽屉继续由 Dialog 管理焦点、Escape 和遮罩。
|
||||
- Select Trigger 使用真实 button;DatePicker Trigger 使用 Button,并提供 label/placeholder 和 `aria-label`。日历日期按钮使用 `react-day-picker` 的语义和键盘导航。
|
||||
|
||||
## 6. Compatibility and Rollback
|
||||
|
||||
- API 和路由协议不变,旧结果数据可直接驱动新布局;若新 shared primitive 样式有问题,可只回退控件接线而保留抽屉数据流。
|
||||
- 日期依赖只影响前端依赖和 bundle;若日期选择器有兼容问题,保留 `DatePicker` 对外接口并回退其内部实现,不改变页面与 API 的字符串边界。
|
||||
- 抽屉摘要与入口位置的回滚点分别位于 `execution-status-drawer.tsx` 和 `selection-results-workbench.tsx`,不会影响 query 或策略执行。
|
||||
|
||||
## 7. Verification Strategy
|
||||
|
||||
- 页面测试:无常驻 metrics、DatePicker 选择日期、strategy/category Select 更新、状态入口出现、抽屉摘要/失败表格/空状态、关闭焦点恢复。
|
||||
- shared UI 测试或页面行为测试:Select 的可见选项和键盘语义、DatePicker 的按钮和选中日期可见性。
|
||||
- 质量门禁:`pnpm format:check`、`pnpm lint`、`pnpm typecheck`、`pnpm test`、`pnpm build`。
|
||||
- 浏览器 mock:检查 1440×900、390×844 下筛选区域、信号列表、DatePicker/Select popup 和右侧抽屉不产生页面级水平溢出。
|
||||
|
||||
## 8. References
|
||||
|
||||
- shadcn/ui Base Select: https://ui.shadcn.com/docs/components/base/select
|
||||
- shadcn/ui Base Date Picker: https://ui.shadcn.com/docs/components/base/date-picker
|
||||
@@ -0,0 +1,5 @@
|
||||
{"file":".trellis/spec/frontend/index.md","reason":"前端 feature/shared 边界、现有质量入口和 API/路由约束。"}
|
||||
{"file":".trellis/spec/frontend/component-guidelines.md","reason":"Select、DatePicker、Drawer、组合组件、Tailwind token 和可访问性交互规范。"}
|
||||
{"file":".trellis/spec/frontend/state-management.md","reason":"保留 URL search、页面局部日期/抽屉状态和 TanStack Query 的状态边界。"}
|
||||
{"file":".trellis/spec/frontend/type-safety.md","reason":"shared primitive props、日期值转换和现有 SelectionResults 类型的严格 TypeScript 约束。"}
|
||||
{"file":".trellis/spec/frontend/quality-guidelines.md","reason":"页面行为测试、可访问性审查和格式/lint/typecheck/test/build 质量门禁。"}
|
||||
@@ -0,0 +1,75 @@
|
||||
# 选股结果页筛选与状态入口执行计划
|
||||
|
||||
## Scope
|
||||
|
||||
只修改 `zhixing-web` 前端和本任务规划/验证产物;不修改后端接口、数据库、策略计算或现有路由协议。
|
||||
|
||||
## Ordered Checklist
|
||||
|
||||
### 1. 开发前上下文
|
||||
|
||||
- [x] 运行 `task.py start` 将任务切换为 `in_progress`。
|
||||
- [x] 读取 `prd.md`、`design.md`、前端规格及 shared 复用指南。
|
||||
- [x] 确认工作区没有用户现有改动,记录本任务实际改动文件。
|
||||
|
||||
### 2. Shared UI 组件
|
||||
|
||||
- [x] 新增 `shared/ui/select.tsx`,封装 Base UI Select 的触发器、列表、选项、滚动和状态样式。
|
||||
- [x] 新增 `shared/ui/popover.tsx`,封装 Base UI Popover 的 trigger、portal、positioner、popup 和关闭语义。
|
||||
- [x] 新增 `shared/ui/calendar.tsx`,基于 `react-day-picker` 和现有 Button/cn/token 提供单日选择基础能力。
|
||||
- [x] 新增 `shared/ui/date-picker.tsx`,组合 Popover + Calendar,支持受控 `Date | undefined`、禁用态、placeholder、label 语义和窄屏布局。
|
||||
- [x] 按实际依赖更新 `package.json` / `pnpm-lock.yaml`,只加入 Date Picker 所需的 `date-fns` / `react-day-picker`。
|
||||
|
||||
### 3. 选股页面接线
|
||||
|
||||
- [x] 将 `ExecutionToolbar` 的目标日期 Input 替换为 DatePicker,保持 `YYYY-MM-DD` 字符串边界和现有执行状态清理逻辑。
|
||||
- [x] 将策略原生 select 替换为 shared Select;将 `SelectionResultsWorkbench` 的分类原生 select 替换为 shared Select,保留 URL 更新和分页归一化。
|
||||
- [x] 从 `PageLayout` 移除 `metrics={resultMetrics}` 和页面级常驻 `ResultMetrics`。
|
||||
- [x] 将执行摘要指标放入 `ExecutionStatusDrawer`,并把执行状态按钮接到 `SelectionResultsWorkbench` 信号列表顶部;失败和无信号结果保留按需入口。
|
||||
- [x] 保留既有失败表格、空状态、主从信号选择、移动展开、分页、重执行确认和查询状态分支。
|
||||
|
||||
### 4. 测试与验证
|
||||
|
||||
- [x] 更新页面测试,覆盖 DatePicker 选中/清空日期、两个 Select 的可见值/回调、无常驻指标区、顶部状态按钮、抽屉摘要和焦点恢复。
|
||||
- [x] 通过页面行为覆盖 shared UI 的关键交互(Select 键盘/选择、DatePicker 弹层/选择),避免复制 shared primitive 实现逻辑新增重复测试。
|
||||
- [ ] 运行 `pnpm format:check`、`pnpm lint`、`pnpm typecheck`、`pnpm test`、`pnpm build`;其中完整格式检查仍被未改动的既有 `DESIGN.md` 阻断,其余门禁通过。
|
||||
- [x] 使用浏览器 mock 验证桌面/移动视口、Select/DatePicker popup、抽屉、Escape/焦点恢复和页面级水平溢出。
|
||||
- [x] 执行 `trellis-check`,修复质量门禁或规格偏差后重新验证。
|
||||
|
||||
## Validation Commands
|
||||
|
||||
在 `zhixing-web/` 下运行:
|
||||
|
||||
```bash
|
||||
pnpm format:check
|
||||
pnpm lint
|
||||
pnpm typecheck
|
||||
pnpm test
|
||||
pnpm build
|
||||
```
|
||||
|
||||
## Verification Record
|
||||
|
||||
- `pnpm lint`:通过。
|
||||
- `pnpm typecheck`:通过。
|
||||
- `pnpm test`:通过,4 个测试文件、26 项测试。
|
||||
- `pnpm build`:通过;Vite 仅提示现有单 chunk 超过 500 kB。
|
||||
- 变更文件 `prettier --check`:通过;完整 `pnpm format:check` 仅因未改动的 `zhixing-web/DESIGN.md` 失败。
|
||||
- `git diff --check`:通过。
|
||||
- 浏览器 mock:桌面与 390×844 移动视口均验证;Select 点击/键盘选择、DatePicker 弹层与中文无障碍 labels、抽屉 Escape/焦点恢复、页面级水平溢出均通过。
|
||||
- `trellis-check`:通过,并修复 DatePicker 清空后日期被服务器结果回填的边界问题。
|
||||
|
||||
## Risky Files and Rollback Points
|
||||
|
||||
- `src/shared/ui/select.tsx`、`popover.tsx`、`calendar.tsx`、`date-picker.tsx`:新 shared primitive 和 Base UI/DayPicker API 兼容点;可独立回退并保留页面旧控件。
|
||||
- `package.json`、`pnpm-lock.yaml`:日期依赖变更;若安装/构建不兼容,回退依赖和四个 shared primitive。
|
||||
- `src/features/selection/pages/selection-results-page.tsx`:移除 metrics、接入日期选择器和抽屉状态;回退点不影响 API。
|
||||
- `src/features/selection/components/selection-results-workbench.tsx`:顶部筛选/状态入口和 Select 接线;回退时保留结果表、分页和详情面板。
|
||||
- `src/features/selection/components/execution-status-drawer.tsx`:执行摘要迁移;失败表格和 Dialog 生命周期应保持可回滚。
|
||||
|
||||
## Review Gates Before Start
|
||||
|
||||
- [ ] `prd.md` 已通过需求收敛:目标、范围、验收标准明确且无阻塞问题。
|
||||
- [ ] `design.md` 已确认 shared/feature 边界、日期字符串转换、抽屉数据流和回滚点。
|
||||
- [ ] `implement.jsonl` 与 `check.jsonl` 已填入真实前端规格上下文。
|
||||
- [ ] 已向用户展示最终规划摘要,并获得对该摘要的明确实施批准。
|
||||
@@ -0,0 +1,56 @@
|
||||
# 迭代选股结果页筛选与状态入口
|
||||
|
||||
## Goal
|
||||
|
||||
让选股结果页把空间集中给筛选和信号结果:移除常驻的执行状态总览,将执行摘要改为信号列表顶部的按需入口,并通过右侧抽屉查看完整信息。同时把选股页使用的原生 `select` 和日期输入升级为项目 `shared/ui` 中基于 shadcn/ui Base UI 组合的可访问组件。
|
||||
|
||||
## Confirmed Facts
|
||||
|
||||
- [SelectionResultsPage](zhixing-web/src/features/selection/pages/selection-results-page.tsx) 当前通过 `PageLayout.metrics` 渲染常驻的 `ResultMetrics`,展示目标股票数、实际参与数、命中股票数、信号条数、数据覆盖率和执行状态(约第 126–136、305–355 行)。
|
||||
- 当前目标交易日控件仍是 `Input type="date"`,策略控件和信号分类筛选仍是原生 `<select>`(约第 247–274 行,以及 [SelectionResultsWorkbench](zhixing-web/src/features/selection/components/selection-results-workbench.tsx) 约第 95–120 行)。
|
||||
- 项目已有基于 `@base-ui/react` 的本地 `Button`、`Dialog`、`Input` 等封装,但尚无可复用的 `Select`、`Popover`、`Calendar` 或 `DatePicker` shared primitive。
|
||||
- `SelectionResultsWorkbench` 已经拥有 URL 驱动的搜索词、分类、分页状态;`SelectionResults` 已包含执行摘要和 `failures`,本轮不需要改变 API、query key 或后端契约。
|
||||
- 上一轮已经实现右侧 `ExecutionStatusDrawer`,当前抽屉仍由页面状态控制并使用执行状态入口作为焦点恢复目标;本轮沿用这条数据流,只调整入口位置并补充摘要呈现。
|
||||
|
||||
## Requirements
|
||||
|
||||
### R1. 筛选与执行控件
|
||||
|
||||
- 目标交易日使用 `shared/ui/date-picker`,以单日选择器呈现;业务层继续以 `YYYY-MM-DD` 字符串调用现有查询和执行接口。
|
||||
- 策略选择和信号分类筛选使用 `shared/ui/select`,不再在选股页面直接渲染原生 `<select>`。
|
||||
- `Select`、`DatePicker` 必须支持键盘操作、焦点可见、Escape/外部点击关闭、禁用态和窄屏不溢出;控件的业务选项和路由更新仍由 `selection` feature 持有。
|
||||
- 保留现有目标交易日、策略、执行/重执行、搜索词、分类筛选和分页行为;不引入全局 UI 状态。
|
||||
|
||||
### R2. 移除常驻执行状态总览
|
||||
|
||||
- 选股结果页不再通过 `PageLayout.metrics` 渲染常驻执行状态总览,主内容区域不再为这组指标预留高度。
|
||||
- 当存在可展示结果时,信号列表顶部提供一个明确的“执行状态”按钮,展示当前状态和未完成评估数量,并作为右侧抽屉的触发器。
|
||||
- 抽屉继续支持关闭按钮、遮罩、Escape 和关闭后焦点返回;移动端不能产生页面级水平溢出。
|
||||
- 执行摘要(目标股票数、实际参与数、命中股票数、信号条数、数据覆盖率、状态)和未完成评估表格迁移到抽屉内;无失败记录时保留可读空状态。
|
||||
|
||||
### R3. Shared UI 沉淀
|
||||
|
||||
- 在 `zhixing-web/src/shared/ui/` 新增可复用的 `Select`、`Popover`、`Calendar` 和 `DatePicker` 组合组件,底层复用现有 `@base-ui/react`、`Button`、`cn` 以及日期选择依赖。
|
||||
- shared UI 只负责通用交互、语义、样式和原生属性扩展,不读取 `selection` API、路由或业务类型。
|
||||
- 新组件遵循项目现有的具名导出、小写文件名、Tailwind token、`cn` 合并和严格 TypeScript 约束。
|
||||
|
||||
## Out of Scope
|
||||
|
||||
- 不修改后端接口、数据库、策略计算、`SelectionResults` 字段含义或 query key。
|
||||
- 不改变 `/selection` URL 结构和现有搜索词、分类、分页参数的语义;目标交易日仍保持当前页面局部状态边界。
|
||||
- 不建设独立 npm/package 级 UI 库,不把 selection 业务组件迁移到 `shared/ui`,也不在本轮扩展全局表单校验或日期范围选择。
|
||||
- 不重做已有结果表、移动端信号卡片、详情面板和执行抽屉的业务数据逻辑。
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- [x] `SelectionResultsPage` 不再给 `PageLayout.metrics` 传入常驻执行状态总览;桌面和移动页面均能把剩余空间用于结果内容。
|
||||
- [x] 目标交易日使用可访问的 `DatePicker`;选中日期后仍以 `YYYY-MM-DD` 触发当前结果查询/执行逻辑,清空和禁用行为不破坏既有状态机。
|
||||
- [x] 策略和信号分类使用 shared `Select`;键盘打开、上下选择、Enter 确认和 Escape 关闭均可用,分类改变仍更新既有 URL search 并重置分页到第 1 页。
|
||||
- [x] 有结果时,信号列表顶部可见执行状态按钮;点击后右侧抽屉展示摘要指标和未完成评估表格,关闭后焦点回到该按钮;失败和无信号结果也保留按需入口。
|
||||
- [x] 无失败记录时抽屉仍可打开并显示“暂无未完成评估”;主页面不再出现独立的常驻指标区或内联失败明细卡片。
|
||||
- [x] shared UI 组件不依赖 `features/selection`,没有直接业务分支;组件和页面测试覆盖关键可见交互。
|
||||
- [ ] `pnpm format:check`、`pnpm lint`、`pnpm typecheck`、`pnpm test` 和 `pnpm build` 通过;lint/typecheck/test/build 与变更文件格式检查通过,完整格式检查仍被未改动的 `DESIGN.md` 阻断;浏览器验证已覆盖桌面/移动视口、Select、DatePicker、抽屉和页面级水平溢出。
|
||||
|
||||
## Open Questions
|
||||
|
||||
无。按用户要求将原总览信息整体移入右侧抽屉,保留现有后端数据流和 URL 筛选边界。
|
||||
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"id": "selection-results-filter-state-ui",
|
||||
"name": "selection-results-filter-state-ui",
|
||||
"title": "迭代选股结果页筛选与状态入口",
|
||||
"description": "",
|
||||
"status": "in_progress",
|
||||
"dev_type": null,
|
||||
"scope": null,
|
||||
"package": null,
|
||||
"priority": "P2",
|
||||
"creator": "yuxuanhui",
|
||||
"assignee": "yuxuanhui",
|
||||
"createdAt": "2026-08-10",
|
||||
"completedAt": null,
|
||||
"branch": null,
|
||||
"base_branch": "main",
|
||||
"worktree_path": null,
|
||||
"commit": null,
|
||||
"pr_url": null,
|
||||
"subtasks": [],
|
||||
"children": [],
|
||||
"parent": null,
|
||||
"relatedFiles": [],
|
||||
"notes": "",
|
||||
"meta": {}
|
||||
}
|
||||
Reference in New Issue
Block a user