From e33b56797aef1455a01312ef74d5b2f9c570a9a5 Mon Sep 17 00:00:00 2001 From: wuzilong <704541015@qq.com> Date: Tue, 8 Sep 2026 22:07:29 +0800 Subject: [PATCH] docs: add BX date column dual-caret sort design. Co-authored-by: Cursor --- .../2026-09-08-bx-date-column-sort-design.md | 138 ++++++++++++++++++ 1 file changed, 138 insertions(+) create mode 100644 docs/superpowers/specs/2026-09-08-bx-date-column-sort-design.md diff --git a/docs/superpowers/specs/2026-09-08-bx-date-column-sort-design.md b/docs/superpowers/specs/2026-09-08-bx-date-column-sort-design.md new file mode 100644 index 000000000..b6010bed1 --- /dev/null +++ b/docs/superpowers/specs/2026-09-08-bx-date-column-sort-design.md @@ -0,0 +1,138 @@ +# 报销域日期列头双三角排序 + +**日期**:2026-09-08 +**状态**:待审阅 +**对齐**:`docs/superpowers/specs/2026-09-08-crm-date-column-sort-design.md`(交互与参数一致) +**Demo**:`docs/crm-date-column-sort-demo.html`(样式 2 · 双三角) + +--- + +## 1. 背景与目标 + +CRM 客户 / 报价 / 合同已落地日期列双三角整库排序。报销管理下多份列表已有列头漏斗(`ColumnHeaderFilter` + `datetime`),但尚不能按日期列升/降序。 + +### 目标 + +1. 有日期列的报销相关列表:日期列头双三角,整库 `asc` / `desc`,取消后恢复现网默认序。 +2. 与 CRM 同一套交互与查询参数:`sortField` / `sortOrder`。 +3. 前端优先在现有 `ColumnHeaderFilter` 上扩展(报销页已直接使用该组件),避免整页改迁 `CrmColFilterHeader`。 +4. 后端复用 `resolve_datetime_order_by` + 各 list 白名单。 + +### 非目标 + +- 费用项目管理、工作流配置(备用金/报销单)、发票夹管理(员工汇总) +- 文本 / 枚举 / 数字列排序 +- 弹窗内嵌表明细日期(如费用明细 `occurDate`、冲抵记录子表)除非该列已是主列表带漏斗的 `datetime` +- 排序状态 URL / localStorage 持久化 + +--- + +## 2. 已确认产品决策 + +| # | 决策 | +|---|------| +| D1 | 范围:仅「有日期列」的页面;费用项目 / 工作流 / 发票夹管理跳过 | +| D2 | 交互与 CRM 相同:双三角;同向再点取消;仅 `datetime`;空值升序前、降序后 | +| D3 | 架构:扩展 `ColumnHeaderFilter` + 页面 `useColumnSort`(方案 1);不用逐列手写三角 | +| D4 | 参数:`sortField`(列 prop)+ `sortOrder`(`asc`\|`desc`);非法回退默认序 | + +--- + +## 3. 本批页面与日期列 + +| 菜单 | 前端路径 | 日期列 prop | +|------|----------|-------------| +| 备用金申请 | `views/bx/advance/apply/index.vue` | `applyTime` | +| 备用金审批 | `views/bx/advance/approve/index.vue` | `applyTime` | +| 备用金发放 | `views/bx/advance/issue/index.vue` | `applyTime`、`issueTime` | +| 报销申请 | `views/bx/reimburse/apply/index.vue` | `applyTime` | +| 报销审批 | `views/bx/reimburse/approve/index.vue` | `applyTime` | +| 支付发放冲抵 | `views/bx/payment/index.vue` | `applyTime`、`payTime` | +| 我的发票夹 | `views/bx/invoice-wallet/mine/index.vue` | `createTime`(发票表 + 流水表,各一套 filter/sort) | +| 我的申请单 | `views/bx/view/mine/index.vue` | `applyTime` | +| 待我审批 | `views/bx/view/todo/index.vue` | `applyTime` | +| 全部申请单(总经理) | `views/bx/view/all/index.vue` | `applyTime` | + +**跳过**:`expense-item`、`workflow/advance`、`workflow/reimburse`、`invoice-wallet/admin`。 + +--- + +## 4. 产品与交互 + +与 CRM 设计 §4 一致: + +- 列头顺序:标题 → 双三角(仅 datetime) → 漏斗。 +- 同一时刻仅一列排序;藏列若为当前排序列则清除并排序重查。 +- 过滤与排序 AND;导出与列表同一套 sort 参数(若该页有导出且走同一 query builder)。 + +「申请时间」展示字段 `applyTime` 在后端多映射 `create_time`:排序白名单须 `applyTime` → `create_time`(或实体等价列),禁止按不存在的 `apply_time` 列动态拼接。 + +--- + +## 5. 架构 + +### 5.1 前端 · `ColumnHeaderFilter` + +在 `column-header-filter/index.vue` 的漏斗按钮旁: + +- `inject('crmColumnSort')`(与 CRM 同 key,避免双套 inject;可选再兼容 `columnSort`)。 +- `showSort = Boolean(sortApi && type === 'datetime')`。 +- 样式复用已有 `.col-header-sort-pair` / `.col-header-sort-btn`。 + +`CrmColFilterHeader` 已具备三角,行为不变。未 provide 排序的页面(含跳过页)不显示三角。 + +### 5.2 前端 · 各列表页 + +```ts +const { setSort, clearSort, isSortActive, sortField, sortOrder, toQueryParams: toSortQueryParams, watchVisibleColumns: watchSortVisibleColumns } = + useColumnSort({ onChange: () => { /* page=1; reload */ } }) + +provide('crmColumnSort', { setSort, clearSort, isSortActive, sortField, sortOrder }) +// list/export params: ...toSortQueryParams() +// watchSortVisibleColumns(visibleFilterProps) +``` + +我的发票夹:发票 Tab / 流水 Tab 可各用一套 `useColumnSort`,inject key 用默认 + 可选第二 key,或切换 Tab 时共用一套(实现时选改动更小者;须保证当前可见表的 datetime 列能驱动正确 list API)。 + +### 5.3 后端 + +复用 `backend/app/services/crm/list_sort.py` 的 `resolve_datetime_order_by`(路径名含 crm,本批不强制搬家;若后续统一可迁到 `app/services/list_sort.py`,非本批必做)。 + +各 list(及共用路径的 export)增加可选 `sortField` / `sortOrder`,白名单示例: + +| 域 | prop → Column | +|----|----------------| +| 备用金申请/审批/发放主列表 | `applyTime` → `BxAdvanceApply.create_time`;发放另加 `issueTime` → 实际发放时间列(以 model 为准,如 `issue_time` / `pay_time`) | +| 报销申请/审批 | `applyTime` → `BxReimburseApply.create_time` | +| 支付发放冲抵 | `applyTime` → 单据创建/申请时间列;`payTime` → 处理时间列(以现网字段为准) | +| 视图 mine/todo/all | 按 biz 分支映射到 advance/reimburse 的 `create_time`,或统一视图查询的申请时间表达式 | +| 我的发票夹 | `createTime` → 发票/流水表的 `create_time` | + +非法 `sortField` → 现网默认(一般为 `create_time DESC`)。 + +--- + +## 6. 测试要点 + +1. 报销申请:`applyTime` 升/降/取消;与漏斗日期段 AND;换页保持序。 +2. 备用金发放:`issueTime` 仅在已发放可见列时可用;藏列清排序。 +3. 支付:`payTime` 在已处理 Tab。 +4. 我的申请单 / 待我审批 / 全部:备用金与报销单混合列表排序正确。 +5. 我的发票夹:上传时间/流水时间排序打整库。 +6. 费用项目、工作流、发票夹管理:无双三角。 +7. 抓包非法 `sortField` → 默认序、不 500。 + +--- + +## 7. 风险与后续 + +- **风险**:视图页(mine/todo/all)若 SQL 为 union/多表,白名单须落到真实可排序表达式,避免只改前端。 +- **风险**:`applyTime` 与 DB `create_time` 映射遗漏导致排序无效或报错——实现时对照 schema `validation_alias="create_time"`。 +- **后续**:发票夹管理若补日期列再接线;可将 `list_sort` 抽到中性路径。 + +--- + +## 8. 文档 + +- 本设计:`docs/superpowers/specs/2026-09-08-bx-date-column-sort-design.md` +- CRM 对照:`docs/superpowers/specs/2026-09-08-crm-date-column-sort-design.md`