Make dynamic column-facet values respect other active filters (Excel-style), with backend facet context helpers, list-page wiring, slip print/sidebar fixes, and refreshed graphify graphs. Co-authored-by: Cursor <cursoragent@cursor.com>
10 KiB
列头漏斗 Excel 级联可选值 — 分批 Implementation Plan
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: 列头动态可选值按「当前列表上下文条件」去重统计(排除本列自身条件),对齐 Excel 级联筛选;前端一次铺管道,后端按模块分批点亮。
Architecture: useColumnFacetOptions 打开漏斗时附带 getContextParams()(固定条件 + 顶部筛选区 + 列头,并剥离本列 query 键);各模块 column-facet-values 复用列表 filter 构建,在过滤结果集上 group_by。未接入模块保持全集行为。
Batch 1 范围(已确认): 运输计划、运输归档、危化品证、合同完善(plan-contract)。
Tech Stack: Vue 3 composable、FastAPI、SQLAlchemy、build_plan_column_filters / 各模块 list filters、column_facet helper
Spec: docs/superpowers/specs/2026-10-11-column-facet-context-cascade-design.md
文件职责(总览)
| 文件 | 职责 |
|---|---|
web-admin/src/composables/useColumnFacetOptions.ts |
context 附带、本列键剥离、调用 fetchFacet |
web-admin/src/composables/columnFacetContext.ts(新建,建议) |
纯函数:按 ColumnFilterDef + prop 从 params 去掉本列键 |
web-admin/src/api/pl/index.ts 等 |
facet API 入参允许附加过滤字段 |
backend/app/api/pl/plan.py |
计划 facet 吃列表同款 params,忽略本 field |
backend/app/services/pl/plan_list_filters.py |
复用,必要时抽「忽略某字段」钩子 |
backend/app/services/column_facet.py |
可选:在已有 query 上做 facet |
各列表 index.vue |
传入 getContextParams(固定条件 + toQueryParams()) |
Batch 0:公共管道
Task 0.1: 本列 query 键剥离纯函数
Files:
-
Create:
web-admin/src/composables/columnFacetContext.ts -
Create:
web-admin/src/composables/columnFacetContext.spec.ts(或项目既有单测位置) -
Step 1: 实现
stripSelfColumnParams(params, prop, defs): Record<string, …>- 覆盖 enum / text / datetime / number / loadCustomers 别名
-
Step 2: 单测:承运商只剥
carrierName/carrierNameIn;装货客户剥loadCustomers*与loadCustomer* -
Step 3: Commit:
feat(web-admin): add column facet context strip helper(待用户要求再提交)
Task 0.2: 扩展 useColumnFacetOptions
Files:
-
Modify:
web-admin/src/composables/useColumnFacetOptions.ts -
Step 1: 增加
getContextParams?: () => Record<string, unknown> -
Step 2:
onFilterOpen合并 context →stripSelfColumnParams→ 传入fetchFacet -
Step 3: 无
getContextParams时行为与现网一致 -
Step 4: Commit:
feat(web-admin): pass list context into column facet fetch(待用户要求再提交)
Task 0.3: 计划 facet API 类型放宽
Files:
-
Modify:
web-admin/src/api/pl/index.ts(fetchPlanColumnFacetValues) -
Step 1: data 类型改为
field+ 可选过滤字段索引签名(或显式与列表 query 对齐的可选字段) -
Step 2: Commit:
chore(web-admin): allow context filters on plan facet API(待用户要求再提交)
Batch 1:运输计划族(优先交付)
Task 1.1: 后端计划 facet 吃上下文
Files:
-
Modify:
backend/app/api/pl/plan.py(PlanColumnFacetBody、plan_column_facet_values) -
Modify:
backend/app/services/pl/plan_list_filters.py(若需 ignore_field) -
Test:
backend/tests/test_pl_plan_column_facet_context.py(新建) -
Step 1: Body 增加与列表一致的可选过滤字段(或
extra: dict+ 与_filters_from_params同源解析;优先复用现有 param 解析,避免双份字段表) -
Step 2: 构建 filters 时忽略当前
field对应键(carrierName→忽略carrier_name/carrier_name_in等) -
Step 3: 各 field 分支在已过滤的 plan 查询上 group_by(改写现有仅租户过滤的 query)
-
Step 4: 单测:剥离 helper(carrier / loadCustomer 别名);打开 carrier 时不受自身 In 限制
-
Step 5: Commit:
feat(backend): contextual filters for plan column-facet-values(待用户要求再提交)
Task 1.2: 运输归档页接入 context
Files:
-
Modify:
web-admin/src/views/pl/plan-archive/index.vue -
Step 1:
getContextParams= 固定条件(如transportStatus=arranged)+ 顶部筛选区(与列表同源)+toQueryParams() -
Step 2:
fetchFacet展开 payload 全部字段发给接口 -
Step 3: 手动验收:承运商 → 装货客户;装货客户 → 承运商;有顶部筛选时可选值同步收窄
-
Step 4: Commit:
feat(web-admin): contextual facets on plan-archive(待用户要求再提交)
Task 1.3: 运输计划页 + 危化品证页接入
Files:
-
Modify:
web-admin/src/views/pl/plan/index.vue -
Modify:
web-admin/src/views/pl/plan-cert/index.vue -
Step 1: 同上:固定条件 + 顶部筛选区 + 列头;与各页
handleSearch/ 列表 query 对齐(含 Tab、followStatus、危化相关) -
Step 2: 手动抽查两页级联
-
Step 3: Commit:
feat(web-admin): contextual facets on plan and plan-cert(待用户要求再提交)
Task 1.4: 合同完善页(plan-contract)接入
Files:
-
Modify:
web-admin/src/views/pl/plan-contract/index.vue -
Modify:
web-admin/src/api/pl/index.ts(若合同完善走独立 facet API,一并放宽类型) -
Step 1:
getContextParams= 缺口页固定条件(contractGapOnly/gapType等)+ 顶部筛选区 +toQueryParams();离散列改为动态 facet -
Step 2:
fetchFacet带全量 context;后端计划 facet 支持contractGapOnly/gapType -
Step 3: 手动验收:先筛装/送货客户或产品,再开另一环动态列,可选值收窄且缺口条件不丢
-
Step 4: Commit:
feat(web-admin): contextual facets on plan-contract(待用户要求再提交)
Task 1.5: Batch 1 验收清单
- 归档:先筛承运商,装/送货客户可选值仅为该承运商下出现过的名称(需手动)
- 归档:先筛装货客户,承运商可选值收窄;再开装货客户不受自身勾选锁死(需手动)
- 归档/计划:顶部筛选有值时,打开任意动态列可选值受其约束(需手动)
- 计划列表 / 危化证:同上逻辑 + 各自固定条件(需手动)
- 合同完善:缺口固定条件 + 列头级联均生效(需手动)
- 单测:
test_pl_plan_column_facet_context+test_pl_plan_list_filters通过 - 未改页面(如 CRM 客户)仍为全集,无报错(需抽查)
Batch 2:PL 其它
Task 2.x(每个资源一组,可并行)
资源顺序建议: freight 未结算 → carrier → vehicle / self-vehicle → personnel → transport-address
对每个资源:
- 后端对应
column-facet-values复用该列表 filters + ignore field(facet_context.strip_simple_facet_self_params) - 前端页
getContextParams+ API 类型放宽 - 抽查 2 列级联(需手动)
- Commit:
feat: contextual facets on pl-<resource>(待用户要求再提交)
已接入:UnsettledPlanPanel、carrier、vehicle、self-vehicle、personnel、transport-address。
Batch 3:CRM
Task 3.x
顺序建议: customer / pool → clue → opportunity → quote → contract 族 → price-letter → supplier-hazard → product
- 各
POST /web/crm/.../column-facet-values吃 list 同款 params(facet_context.clear_facet_field_kwargs/facet_filter_kwargs_from_body+ 各build_*_column_filters) - 各列表
getContextParams(customer/pool、product、clue、opportunity、quote、contract/change/template/approval、price-letter、supplier-hazard) - 单测:
tests/test_crm_facet_context.py - Commit 按子域拆分(勿一个巨大 commit;待用户要求再提交)
Batch 4:地磅 / 报销 / 系统
Task 4.x
顺序建议(可按投诉插队): db/plans、db/weighing、db/inventory → bx/* → admin users/tenants/roles… → workflow/leave/operlog
- 同 Batch 2 模式(后端
extra="allow"+strip_simple_facet_self_params/ 列表 filter 复用;前端getContextParams+ APIRecord<string, unknown>) - 地磅:
db/plans、db/weighing、db/inventory - 报销:
bx/reimburse/apply|approve、bx/payment、bx/advance/apply|approve|issue、bx/invoice-wallet/mine - 系统:
system/user|tenant|role|post|dict|leave|operlog、workflow/task-pending|task-completed|my-applications - 全站「动态 facet 已接入 context」清单见下文「接入清单」
- 抽查级联(需手动)
- Commit(待用户要求再提交)
接入清单(动态 facet + getContextParams)
| 域 | 页面 |
|---|---|
| PL | plan、plan-archive、plan-cert、plan-contract、freight/UnsettledPlanPanel、carrier、vehicle、self-vehicle、personnel、transport-address |
| CRM | customer、customer/pool、product、clue、opportunity、quote、contract 族、price-letter、supplier-hazard |
| DB | plans、weighing、inventory |
| BX | reimburse/apply|approve、payment、advance/apply|approve|issue、invoice-wallet/mine |
| System | user、tenant、role、post、dict、leave、operlog、workflow pending/completed/mine |
规范收尾(可与 Batch 1 同迭代)
Task N: 文档 / OpenSpec
Files:
-
Optional:
openspec/changes/zkxw-engineering-standards/specs/ui-conventions/spec.md -
Optional:
docs/web-admin-ui-pattern-index.md或 facet 相关说明一小段 -
SHALL:动态列头可选值请求须携带当前列表上下文,并排除本列条件
-
Commit:
docs: excel-like contextual column facet convention
执行原则
- 先 Batch 0+1 上线,再扩其它域。
- 每批可独立合并、独立验收;未点亮后端的页保持旧行为。
- 禁止在未复用列表 filter 的情况下手写第二套条件(易漂移)。
- 实现时若
PlanColumnFacetBody字段爆炸:优先「body 收 list 同款 dict / 与 request 合并」方案,与plan.py现有_merge_query/_filters_from_params对齐。