docs: add implementation plan for mp product category tree style.
Break scheme A restyle into helper tests, list markup, and scoped CSS tasks. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
1 parent
9b405a9227
commit
4529a18a8d
1 file changed
+565
@@ -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<string, boolean> = {}
|
||||||
|
): Record<string, boolean> {
|
||||||
|
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<string, boolean>
|
||||||
|
): 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
|
||||||
|
<MpBusinessHeader title="产品分类" back-label="返回产品列表" @back="goBack" />
|
||||||
|
<div class="mp-cat-header-tools">
|
||||||
|
<button
|
||||||
|
v-if="flatNodes.length"
|
||||||
|
class="mp-cat-header-btn"
|
||||||
|
type="button"
|
||||||
|
@click="toggleExpandAll"
|
||||||
|
>
|
||||||
|
{{ expandAllLabel }}
|
||||||
|
</button>
|
||||||
|
<button class="mp-cat-header-btn" type="button" @click="openAdd()">新增</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="loading && !tree.length" class="mp-product-loading">
|
||||||
|
<van-loading vertical>正在加载分类</van-loading>
|
||||||
|
</div>
|
||||||
|
<van-empty v-else-if="!flatNodes.length" description="暂无分类,点击右上角新增">
|
||||||
|
<van-button round type="primary" @click="openAdd()">新增分类</van-button>
|
||||||
|
</van-empty>
|
||||||
|
<div v-else class="mp-cat-tree">
|
||||||
|
<div
|
||||||
|
v-for="item in flatNodes"
|
||||||
|
:key="item.node.id"
|
||||||
|
class="mp-cat-row"
|
||||||
|
:class="{
|
||||||
|
'is-l2': item.depth === 1,
|
||||||
|
'is-l3': item.depth >= 2
|
||||||
|
}"
|
||||||
|
:style="{ paddingLeft: `${12 + item.depth * 16}px` }"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="mp-cat-row__toggle"
|
||||||
|
type="button"
|
||||||
|
:class="{ 'is-leaf': !item.hasChildren, 'is-open': item.expanded }"
|
||||||
|
:aria-label="item.expanded ? '折叠' : '展开'"
|
||||||
|
:disabled="!item.hasChildren"
|
||||||
|
@click="toggleExpand(item.node.id)"
|
||||||
|
>
|
||||||
|
<span v-if="item.hasChildren">▶</span>
|
||||||
|
</button>
|
||||||
|
<div class="mp-cat-row__main" @click="openNodeActions(item.node)">
|
||||||
|
<div class="mp-cat-row__name">{{ item.node.categoryName || '—' }}</div>
|
||||||
|
<div class="mp-cat-row__meta">
|
||||||
|
<span
|
||||||
|
class="status-tag"
|
||||||
|
:class="`status-tag--${statusTone(item.node.status)}`"
|
||||||
|
>
|
||||||
|
{{ dictStore.getLabel('sys_normal_disable', item.node.status) || '—' }}
|
||||||
|
</span>
|
||||||
|
<span>排序 {{ item.node.orderNum ?? 0 }}</span>
|
||||||
|
<span>{{ categoryChildMetaLabel(item.node) }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
class="mp-cat-row__add"
|
||||||
|
type="button"
|
||||||
|
aria-label="新增子级"
|
||||||
|
@click="openAdd(item.node)"
|
||||||
|
>
|
||||||
|
+
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="mp-cat-row__more"
|
||||||
|
type="button"
|
||||||
|
aria-label="操作"
|
||||||
|
@click="openNodeActions(item.node)"
|
||||||
|
>
|
||||||
|
⋯
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
保留其后的 `van-action-sheet`、表单 popup 等不变。
|
||||||
|
|
||||||
|
- [ ] **Step 2: 接入工具函数与全部展开逻辑**
|
||||||
|
|
||||||
|
在 `<script setup>` 中:
|
||||||
|
|
||||||
|
1. 删除本地 `collectExpandIds` 函数。
|
||||||
|
2. 增加 import:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import {
|
||||||
|
areAllExpandableExpanded,
|
||||||
|
categoryChildMetaLabel,
|
||||||
|
collectExpandIds
|
||||||
|
} from '@/utils/mp-crm-product-category-tree'
|
||||||
|
```
|
||||||
|
|
||||||
|
3. 增加 computed 与 toggle:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const expandAllLabel = computed(() =>
|
||||||
|
areAllExpandableExpanded(tree.value, expandedIds.value) ? '全部折叠' : '全部展开'
|
||||||
|
)
|
||||||
|
|
||||||
|
function toggleExpandAll() {
|
||||||
|
if (areAllExpandableExpanded(tree.value, expandedIds.value)) {
|
||||||
|
expandedIds.value = {}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
expandedIds.value = collectExpandIds(tree.value)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
4. `loadTree` 内继续使用导入的 `collectExpandIds`(替换原本地函数调用,行为不变)。
|
||||||
|
|
||||||
|
- [ ] **Step 3: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add web-admin/src/views/m/crm/product/categories.vue
|
||||||
|
git commit -m "$(cat <<'EOF'
|
||||||
|
feat(web-admin): wire category tree list markup for scheme A.
|
||||||
|
|
||||||
|
Add expand-all control, row meta, and inline add-child on the mp category page.
|
||||||
|
EOF
|
||||||
|
)"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 3: Scoped 样式对齐 Demo A 一体树表
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `web-admin/src/views/m/crm/product/categories.vue`(`<style scoped>`)
|
||||||
|
|
||||||
|
- [ ] **Step 1: 替换样式块**
|
||||||
|
|
||||||
|
将 `<style scoped lang="scss">` 整段替换为:
|
||||||
|
|
||||||
|
```scss
|
||||||
|
.mp-product-page {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mp-cat-header-tools {
|
||||||
|
position: absolute;
|
||||||
|
top: calc(8px + env(safe-area-inset-top, 0px));
|
||||||
|
right: 12px;
|
||||||
|
z-index: 60;
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mp-cat-header-btn {
|
||||||
|
height: 32px;
|
||||||
|
padding: 0 10px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #fff;
|
||||||
|
cursor: pointer;
|
||||||
|
background: rgb(255 255 255 / 15%);
|
||||||
|
border: 1px solid rgb(255 255 255 / 75%);
|
||||||
|
border-radius: 8px;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mp-cat-header-btn:active {
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mp-cat-tree {
|
||||||
|
margin: 12px 10px 24px;
|
||||||
|
overflow: hidden;
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid #dcebe4;
|
||||||
|
border-radius: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mp-cat-row {
|
||||||
|
display: flex;
|
||||||
|
gap: 6px;
|
||||||
|
align-items: center;
|
||||||
|
min-height: 54px;
|
||||||
|
padding: 10px 10px 10px 0;
|
||||||
|
background: #fff;
|
||||||
|
border-bottom: 1px solid #edf5f1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mp-cat-row:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mp-cat-row.is-l2 {
|
||||||
|
background: #fbfdff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mp-cat-row.is-l3 {
|
||||||
|
background: #fafcfb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mp-cat-row:active {
|
||||||
|
background: #f7fbf9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mp-cat-row__toggle {
|
||||||
|
flex-shrink: 0;
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
padding: 0;
|
||||||
|
font-size: 11px;
|
||||||
|
line-height: 28px;
|
||||||
|
color: #0a9f6d;
|
||||||
|
text-align: center;
|
||||||
|
cursor: pointer;
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
transition: transform 0.15s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mp-cat-row__toggle.is-open {
|
||||||
|
transform: rotate(90deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mp-cat-row__toggle.is-leaf {
|
||||||
|
visibility: hidden;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mp-cat-row__main {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mp-cat-row__name {
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #1f2a26;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mp-cat-row__meta {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 6px;
|
||||||
|
align-items: center;
|
||||||
|
margin-top: 3px;
|
||||||
|
font-size: 11px;
|
||||||
|
color: #6b7b74;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mp-cat-row__add,
|
||||||
|
.mp-cat-row__more {
|
||||||
|
flex-shrink: 0;
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
font-size: 18px;
|
||||||
|
line-height: 1;
|
||||||
|
color: #087f57;
|
||||||
|
cursor: pointer;
|
||||||
|
background: #e9f9f2;
|
||||||
|
border: none;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mp-cat-row__add {
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-tag {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
min-height: 20px;
|
||||||
|
padding: 1px 7px;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 600;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
border-radius: 999px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-tag--success {
|
||||||
|
color: #087f57;
|
||||||
|
background: #e9f9f2;
|
||||||
|
border-color: #b7ebce;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-tag--danger {
|
||||||
|
color: #b91c1c;
|
||||||
|
background: #fef2f2;
|
||||||
|
border-color: #fecaca;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
删除已无用的 `.mp-cat-header-add`、`.mp-cat-list` 旧规则(已被上面替换)。
|
||||||
|
|
||||||
|
- [ ] **Step 2: 本地目视对照**
|
||||||
|
|
||||||
|
Run(若已在跑开发服可跳过 install):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd web-admin && pnpm dev
|
||||||
|
```
|
||||||
|
|
||||||
|
打开 webview 产品分类页(或路由 `/#/m/crm/product/categories`),对照 Demo A:
|
||||||
|
|
||||||
|
1. 一体树表(非整行散卡)
|
||||||
|
2. 缩进 + 箭头旋转
|
||||||
|
3. 行内状态/排序/子类数、`+`、`⋯`
|
||||||
|
4. 「全部展开/全部折叠」文案切换
|
||||||
|
|
||||||
|
Expected: 与 Demo A 一致,表单/删除流程无回归。
|
||||||
|
|
||||||
|
- [ ] **Step 3: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add web-admin/src/views/m/crm/product/categories.vue
|
||||||
|
git commit -m "$(cat <<'EOF'
|
||||||
|
style(web-admin): restyle mp product category list as unified tree.
|
||||||
|
|
||||||
|
Match scheme A indent-expand demo with sanqiang tokens.
|
||||||
|
EOF
|
||||||
|
)"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 4: Spec 状态回写 + 图谱更新
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `docs/superpowers/specs/2026-10-18-mp-crm-product-category-tree-style-design.md`
|
||||||
|
- Run: `./scripts/update-graph.sh`(或 `graphify update .` 于仓库根)
|
||||||
|
|
||||||
|
- [ ] **Step 1: 更新 Spec 状态**
|
||||||
|
|
||||||
|
将文件头 `状态:待审阅` 改为 `状态:已实现`。
|
||||||
|
|
||||||
|
- [ ] **Step 2: 再跑单测**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd web-admin && node --import tsx --test src/utils/mp-crm-product-category-tree.test.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: PASS
|
||||||
|
|
||||||
|
- [ ] **Step 3: 更新知识图谱**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./scripts/update-graph.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
若脚本不可用,则在仓库根执行:`graphify update .`
|
||||||
|
|
||||||
|
- [ ] **Step 4: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add docs/superpowers/specs/2026-10-18-mp-crm-product-category-tree-style-design.md graphify-out backend/graphify-out web-admin/graphify-out 2>/dev/null
|
||||||
|
git commit -m "$(cat <<'EOF'
|
||||||
|
docs: mark product category tree style design as implemented.
|
||||||
|
|
||||||
|
Refresh graphify after the mp category list style change.
|
||||||
|
EOF
|
||||||
|
)"
|
||||||
|
```
|
||||||
|
|
||||||
|
(若图谱无变更,仅提交 spec 状态即可。)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Spec coverage checklist
|
||||||
|
|
||||||
|
| Spec 要求 | Task |
|
||||||
|
|-----------|------|
|
||||||
|
| 一体树表 + 缩进展开 | Task 2 + 3 |
|
||||||
|
| 行元信息:状态 · 排序 · 子类数 | Task 1 + 2 |
|
||||||
|
| 行内 `+` 新增子级 | Task 2 |
|
||||||
|
| Header「全部展开/折叠」 | Task 1 + 2 |
|
||||||
|
| Token 三墙色、行高触控 | Task 3 |
|
||||||
|
| 不改 API / 表单 / 删除权限 | 全任务未触碰这些逻辑 |
|
||||||
|
| 验收清单 1–6 | Task 2–3 目视 + Task 1 单测覆盖展开辅助逻辑 |
|
||||||
|
|
||||||
|
## Placeholder / 一致性自检
|
||||||
|
|
||||||
|
- 无 TBD/TODO;工具函数名在 Task 1–2 一致(`collectExpandIds` / `areAllExpandableExpanded` / `categoryChildMetaLabel`)。
|
||||||
|
- 缩进公式与现页一致:`12 + depth * 16`。
|
||||||
|
- Header 用绝对定位双按钮组,兼容现有无右侧 slot 的 `MpBusinessHeader`。
|
||||||
Reference in new issue
Block a user