diff --git a/docs/superpowers/plans/2026-10-18-mp-crm-product-category-tree-style.md b/docs/superpowers/plans/2026-10-18-mp-crm-product-category-tree-style.md new file mode 100644 index 000000000..c85b22c16 --- /dev/null +++ b/docs/superpowers/plans/2026-10-18-mp-crm-product-category-tree-style.md @@ -0,0 +1,565 @@ +# 产品分类列表样式(方案 A 缩进展开树)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:** 将移动端产品分类列表改为 Demo 方案 A:一体缩进展开树表,补齐行元信息、行内 `+` 与「全部展开/折叠」。 + +**Architecture:** 抽出纯函数工具 `mp-crm-product-category-tree.ts`(展开集合、是否全展开、子类文案)供页面与单测复用;`categories.vue` 改模板为整卡一体树表并调整 Header 双按钮与 scoped 样式。不改后端契约与表单弹窗字段。 + +**Tech Stack:** Vue 3 + Vant(WebView)+ TypeScript;node:test 单测;三墙 Token。 + +**Spec:** `docs/superpowers/specs/2026-10-18-mp-crm-product-category-tree-style-design.md` + +**Demo:** `bak/mobile-ui/shanxia-mp-crm-product-category-tree-compare-demo.html`(方案 A) + +--- + +## File map + +| File | Responsibility | +|------|----------------| +| `web-admin/src/utils/mp-crm-product-category-tree.ts` | 展开 ID 收集、是否全展开、子节点数与行元信息文案 | +| `web-admin/src/utils/mp-crm-product-category-tree.test.ts` | 上述纯函数单测 | +| `web-admin/src/views/m/crm/product/categories.vue` | 列表 DOM、Header 工具、调用工具函数、scoped 样式 | + +--- + +### Task 1: 单测驱动 — 树展开与行元信息纯函数 + +**Files:** +- Create: `web-admin/src/utils/mp-crm-product-category-tree.ts` +- Create: `web-admin/src/utils/mp-crm-product-category-tree.test.ts` + +- [ ] **Step 1: 写失败测试** + +创建 `web-admin/src/utils/mp-crm-product-category-tree.test.ts`: + +```ts +import assert from 'node:assert/strict' +import { describe, it } from 'node:test' +import { + collectExpandIds, + areAllExpandableExpanded, + categoryChildCount, + categoryChildMetaLabel +} from './mp-crm-product-category-tree' + +type Node = { + id: string + children?: Node[] +} + +const sample: Node[] = [ + { + id: '1', + children: [ + { id: '11', children: [{ id: '111' }] }, + { id: '12' } + ] + }, + { id: '2' } +] + +describe('collectExpandIds', () => { + it('marks every node that has children', () => { + assert.deepEqual(collectExpandIds(sample), { '1': true, '11': true }) + }) +}) + +describe('areAllExpandableExpanded', () => { + it('returns true when all expandable ids are true', () => { + assert.equal(areAllExpandableExpanded(sample, { '1': true, '11': true }), true) + }) + + it('returns false when any expandable id is missing or false', () => { + assert.equal(areAllExpandableExpanded(sample, { '1': true }), false) + assert.equal(areAllExpandableExpanded(sample, { '1': true, '11': false }), false) + }) + + it('returns true for empty tree', () => { + assert.equal(areAllExpandableExpanded([], {}), true) + }) +}) + +describe('categoryChildCount / categoryChildMetaLabel', () => { + it('counts direct children only', () => { + assert.equal(categoryChildCount(sample[0]), 2) + assert.equal(categoryChildCount(sample[1]), 0) + }) + + it('labels leaf vs parent', () => { + assert.equal(categoryChildMetaLabel(sample[0]), '2 个子类') + assert.equal(categoryChildMetaLabel(sample[1]), '末级') + }) +}) +``` + +- [ ] **Step 2: 跑测确认失败** + +Run: + +```bash +cd web-admin && node --import tsx --test src/utils/mp-crm-product-category-tree.test.ts +``` + +Expected: FAIL(模块不存在或导出缺失) + +- [ ] **Step 3: 最小实现** + +创建 `web-admin/src/utils/mp-crm-product-category-tree.ts`: + +```ts +export type CategoryTreeNodeLike = { + id: string + children?: CategoryTreeNodeLike[] +} + +export function categoryChildCount(node: CategoryTreeNodeLike | null | undefined): number { + const children = Array.isArray(node?.children) ? node!.children! : [] + return children.length +} + +export function categoryChildMetaLabel(node: CategoryTreeNodeLike | null | undefined): string { + const n = categoryChildCount(node) + return n > 0 ? `${n} 个子类` : '末级' +} + +export function collectExpandIds( + nodes: CategoryTreeNodeLike[], + acc: Record = {} +): Record { + for (const node of nodes) { + if (categoryChildCount(node) > 0) { + acc[node.id] = true + collectExpandIds(node.children || [], acc) + } + } + return acc +} + +export function areAllExpandableExpanded( + nodes: CategoryTreeNodeLike[], + expandedIds: Record +): boolean { + const required = collectExpandIds(nodes, {}) + const ids = Object.keys(required) + if (!ids.length) return true + return ids.every((id) => !!expandedIds[id]) +} +``` + +- [ ] **Step 4: 跑测确认通过** + +Run: + +```bash +cd web-admin && node --import tsx --test src/utils/mp-crm-product-category-tree.test.ts +``` + +Expected: PASS(全部用例通过) + +- [ ] **Step 5: Commit** + +```bash +git add web-admin/src/utils/mp-crm-product-category-tree.ts web-admin/src/utils/mp-crm-product-category-tree.test.ts +git commit -m "$(cat <<'EOF' +feat(web-admin): add product category tree expand helpers. + +Centralize expand-id collection and child meta labels for the mp category list. +EOF +)" +``` + +--- + +### Task 2: 页面模板 — 一体树表 + Header 双按钮 + 行元信息与 `+` + +**Files:** +- Modify: `web-admin/src/views/m/crm/product/categories.vue` + +- [ ] **Step 1: 替换 Header 与列表模板** + +将模板中 Header 与列表区域(约第 3–50 行)改为: + +```vue + +
+ + +
+ +
+ 正在加载分类 +
+ + 新增分类 + +
+
+ +
+
{{ item.node.categoryName || '—' }}
+
+ + {{ dictStore.getLabel('sys_normal_disable', item.node.status) || '—' }} + + 排序 {{ item.node.orderNum ?? 0 }} + {{ categoryChildMetaLabel(item.node) }} +
+
+ + +
+
+``` + +保留其后的 `van-action-sheet`、表单 popup 等不变。 + +- [ ] **Step 2: 接入工具函数与全部展开逻辑** + +在 `