task-282(验收反馈): 类目改回旧版树形表格(缩进+[+-]折叠标记+名称/层级路径/排序/来源/说明/操作列,懒加载与加载更多保留)
This commit is contained in:
@@ -8,10 +8,8 @@ import { createProductCategory, deleteProductCategory, updateProductCategory } f
|
|||||||
import {
|
import {
|
||||||
appendCategoryChildren,
|
appendCategoryChildren,
|
||||||
isCategoryLoadMoreNode,
|
isCategoryLoadMoreNode,
|
||||||
isCategoryPlaceholderNode,
|
|
||||||
makeCategoryLoadMoreNode,
|
makeCategoryLoadMoreNode,
|
||||||
PRODUCT_CATEGORY_DEFAULT_PAGE_SIZE,
|
PRODUCT_CATEGORY_DEFAULT_PAGE_SIZE,
|
||||||
productCategoryNodeMeta,
|
|
||||||
productCategorySourceLabel,
|
productCategorySourceLabel,
|
||||||
type ProductCategoryNode,
|
type ProductCategoryNode,
|
||||||
} from './product-category-model.ts'
|
} from './product-category-model.ts'
|
||||||
@@ -45,7 +43,7 @@ function stateKeyOf(parentId: number | null): string {
|
|||||||
function flatten(nodes: ProductCategoryNode[]): ProductCategoryNode[] {
|
function flatten(nodes: ProductCategoryNode[]): ProductCategoryNode[] {
|
||||||
const out: ProductCategoryNode[] = []
|
const out: ProductCategoryNode[] = []
|
||||||
for (const node of nodes || []) {
|
for (const node of nodes || []) {
|
||||||
if (!isCategoryLoadMoreNode(node) && !isCategoryPlaceholderNode(node)) {
|
if (!isCategoryLoadMoreNode(node)) {
|
||||||
out.push(node)
|
out.push(node)
|
||||||
out.push(...flatten(node.children))
|
out.push(...flatten(node.children))
|
||||||
}
|
}
|
||||||
@@ -74,12 +72,49 @@ async function loadTree(): Promise<void> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 节点展开时按需加载子级第一页(childCount>0 且未加载过真实子级才拉取;占位子级不算已加载)。 */
|
/** 树形表格(对齐旧版 panel-product-categories):客户端展开状态 + 按展开扁平化行集。 */
|
||||||
async function onNodeExpand(node: ProductCategoryNode): Promise<void> {
|
interface TreeRow {
|
||||||
if (isCategoryLoadMoreNode(node) || isCategoryPlaceholderNode(node) || node.childCount <= 0) return
|
node: ProductCategoryNode
|
||||||
const hasRealChildren = node.children.some((child) => !isCategoryPlaceholderNode(child) && !isCategoryLoadMoreNode(child))
|
depth: number
|
||||||
if (hasRealChildren) return
|
}
|
||||||
|
|
||||||
|
const expandedIds = ref<Set<number>>(new Set())
|
||||||
|
|
||||||
|
function collectRows(nodes: ProductCategoryNode[], depth: number, out: TreeRow[]): void {
|
||||||
|
for (const node of nodes || []) {
|
||||||
|
out.push({ node, depth })
|
||||||
|
if (!isCategoryLoadMoreNode(node) && expandedIds.value.has(node.id)) {
|
||||||
|
collectRows(node.children, depth + 1, out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const treeRows = computed<TreeRow[]>(() => {
|
||||||
|
const out: TreeRow[] = []
|
||||||
|
collectRows(tree.value, 0, out)
|
||||||
|
return out
|
||||||
|
})
|
||||||
|
|
||||||
|
/** 展开/折叠:未加载过子级的先拉取第一页(懒加载),加载更多行点击翻页。 */
|
||||||
|
async function toggleExpand(node: ProductCategoryNode): Promise<void> {
|
||||||
|
if (isCategoryLoadMoreNode(node)) {
|
||||||
|
void loadMoreChildren(node)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (node.childCount <= 0) return
|
||||||
|
if (expandedIds.value.has(node.id)) {
|
||||||
|
const next = new Set(expandedIds.value)
|
||||||
|
next.delete(node.id)
|
||||||
|
expandedIds.value = next
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const hasRealChildren = node.children.some((child) => !isCategoryLoadMoreNode(child))
|
||||||
|
if (!hasRealChildren) {
|
||||||
await loadChildrenPage(node, 1, false)
|
await loadChildrenPage(node, 1, false)
|
||||||
|
}
|
||||||
|
const next = new Set(expandedIds.value)
|
||||||
|
next.add(node.id)
|
||||||
|
expandedIds.value = next
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 加载某父级一页子级(append=false 重置;append=true 追加并重建「加载更多」节点)。 */
|
/** 加载某父级一页子级(append=false 重置;append=true 追加并重建「加载更多」节点)。 */
|
||||||
@@ -279,45 +314,71 @@ onMounted(loadTree)
|
|||||||
</el-card>
|
</el-card>
|
||||||
|
|
||||||
<el-card v-if="!searching" shadow="never">
|
<el-card v-if="!searching" shadow="never">
|
||||||
<el-tree
|
<el-table v-loading="loading" :data="treeRows" row-key="node.id" :class="{ 'tree-table': true }">
|
||||||
v-loading="loading"
|
<el-table-column label="类目名称" min-width="320">
|
||||||
:data="tree"
|
<template #default="{ row }">
|
||||||
node-key="id"
|
<template v-if="isCategoryLoadMoreNode(row.node)">
|
||||||
:props="{ label: 'name', children: 'children' }"
|
<div class="tree-name-cell">
|
||||||
:expand-on-click-node="false"
|
<span class="tree-indent" :style="{ '--indent': `${(row.depth + 1) * 24}px` }"></span>
|
||||||
:indent="28"
|
<el-button link size="small" type="primary" :loading="moreLoading(row.node)" @click="loadMoreChildren(row.node)">加载更多</el-button>
|
||||||
highlight-current
|
<span class="dim">已加载 {{ row.node.loadedCount }} / {{ row.node.totalCount }}</span>
|
||||||
@node-click="onNodeClick"
|
</div>
|
||||||
@node-expand="onNodeExpand"
|
|
||||||
>
|
|
||||||
<template #default="{ data }">
|
|
||||||
<template v-if="isCategoryLoadMoreNode(data)">
|
|
||||||
<span class="node-more" @click.stop="loadMoreChildren(data)">
|
|
||||||
<el-button link size="small" :loading="moreLoading(data)">加载更多</el-button>
|
|
||||||
<span class="dim">已加载 {{ data.loadedCount }} / {{ data.totalCount }}</span>
|
|
||||||
</span>
|
|
||||||
</template>
|
</template>
|
||||||
<span v-else-if="isCategoryPlaceholderNode(data)" class="node-placeholder" @click.stop>加载子级中…</span>
|
<div v-else class="tree-name-cell" @click="onNodeClick(row.node as ProductCategoryNode)">
|
||||||
<span v-else class="node-row">
|
<span class="tree-indent" :style="{ '--indent': `${row.depth * 24}px` }"></span>
|
||||||
<span class="node-main">
|
<button
|
||||||
<span class="node-name">{{ data.name }}</span>
|
type="button"
|
||||||
<el-tag size="small" effect="plain" :type="data.isBuiltin ? 'info' : 'success'">{{ productCategorySourceLabel(data) }}</el-tag>
|
class="tree-node-mark"
|
||||||
<span class="node-meta">{{ productCategoryNodeMeta(data) }}</span>
|
:class="{ 'is-leaf': (row.node as ProductCategoryNode).childCount <= 0 }"
|
||||||
<span v-if="data.description" class="node-desc" :title="data.description">{{ data.description }}</span>
|
:disabled="(row.node as ProductCategoryNode).childCount <= 0"
|
||||||
</span>
|
:title="expandedIds.has((row.node as ProductCategoryNode).id) ? '折叠' : '展开'"
|
||||||
<span class="node-ops" @click.stop>
|
@click.stop="toggleExpand(row.node as ProductCategoryNode)"
|
||||||
<el-button link size="small" type="primary" @click="openEdit(data as ProductCategoryNode)">编辑</el-button>
|
>
|
||||||
|
{{ (row.node as ProductCategoryNode).childCount > 0 ? (expandedIds.has((row.node as ProductCategoryNode).id) ? '−' : '+') : '' }}
|
||||||
|
</button>
|
||||||
|
<strong class="node-name">{{ (row.node as ProductCategoryNode).name }}</strong>
|
||||||
|
<el-tag size="small" effect="plain" :type="(row.node as ProductCategoryNode).isBuiltin ? 'info' : 'success'">
|
||||||
|
{{ productCategorySourceLabel(row.node as ProductCategoryNode) }}
|
||||||
|
</el-tag>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="层级路径" min-width="220">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<span class="dim" :title="(row.node as ProductCategoryNode).path">{{ (row.node as ProductCategoryNode).path || (row.node as ProductCategoryNode).name }}</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="排序" min-width="80">
|
||||||
|
<template #default="{ row }">{{ (row.node as ProductCategoryNode).sortOrder ?? 0 }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="来源" min-width="90">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-tag size="small" effect="plain" :type="(row.node as ProductCategoryNode).isBuiltin ? 'info' : 'success'">
|
||||||
|
{{ productCategorySourceLabel(row.node as ProductCategoryNode) }}
|
||||||
|
</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="说明" min-width="180">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<span v-if="(row.node as ProductCategoryNode).description" class="node-desc" :title="(row.node as ProductCategoryNode).description">{{ (row.node as ProductCategoryNode).description }}</span>
|
||||||
|
<span v-else class="dim">—</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="操作" min-width="140" fixed="right">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<template v-if="!isCategoryLoadMoreNode(row.node)">
|
||||||
|
<el-button link size="small" type="primary" @click="openEdit(row.node as ProductCategoryNode)">编辑</el-button>
|
||||||
<el-button
|
<el-button
|
||||||
link
|
link
|
||||||
size="small"
|
size="small"
|
||||||
type="danger"
|
type="danger"
|
||||||
:disabled="!canDelete(data as ProductCategoryNode)"
|
:disabled="!canDelete(row.node as ProductCategoryNode)"
|
||||||
@click="removeCategory(data as ProductCategoryNode)"
|
@click="removeCategory(row.node as ProductCategoryNode)"
|
||||||
>删除</el-button>
|
>删除</el-button>
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</template>
|
</template>
|
||||||
</el-tree>
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
<div class="tree-actions">
|
<div class="tree-actions">
|
||||||
<span class="dim">{{ selected ? `已选:${selected.path || selected.name}` : '未选中节点' }}</span>
|
<span class="dim">{{ selected ? `已选:${selected.path || selected.name}` : '未选中节点' }}</span>
|
||||||
<span class="btn-group">
|
<span class="btn-group">
|
||||||
@@ -395,22 +456,30 @@ onMounted(loadTree)
|
|||||||
.btn-group { display: inline-flex; gap: 6px; }
|
.btn-group { display: inline-flex; gap: 6px; }
|
||||||
.search-head { display: flex; justify-content: space-between; margin-bottom: 12px; }
|
.search-head { display: flex; justify-content: space-between; margin-bottom: 12px; }
|
||||||
.load-more-row { text-align: center; margin-top: 12px; }
|
.load-more-row { text-align: center; margin-top: 12px; }
|
||||||
/* 类目树节点:加大间距不再紧凑,父子级缩进对齐旧版树形表格。 */
|
/* 树形表格(对齐旧版 panel-product-categories):缩进 + [+/−] 折叠标记 + 粗体名称。 */
|
||||||
.node-row { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 7px 8px; width: 100%; min-width: 0; }
|
.tree-name-cell { display: flex; align-items: center; gap: 8px; min-height: 24px; }
|
||||||
.node-main { display: flex; align-items: center; gap: 8px; min-width: 0; overflow: hidden; }
|
.tree-indent { flex: 0 0 auto; width: var(--indent, 0px); }
|
||||||
|
.tree-node-mark {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
border: 0;
|
||||||
|
border-radius: 5px;
|
||||||
|
background: #eef0fe;
|
||||||
|
color: #6366f1;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 700;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 0;
|
||||||
|
line-height: 1;
|
||||||
|
flex: none;
|
||||||
|
transition: background 0.12s ease, color 0.12s ease;
|
||||||
|
}
|
||||||
|
.tree-node-mark:not(.is-leaf):hover { background: #6366f1; color: #fff; }
|
||||||
|
.tree-node-mark.is-leaf { background: transparent; cursor: default; }
|
||||||
.node-name { font-weight: 600; color: var(--el-text-color-primary); white-space: nowrap; }
|
.node-name { font-weight: 600; color: var(--el-text-color-primary); white-space: nowrap; }
|
||||||
.node-meta { color: var(--admin-muted); font-size: 12px; white-space: nowrap; }
|
.node-desc { color: var(--el-text-color-secondary); font-size: 12px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 320px; display: inline-block; vertical-align: middle; }
|
||||||
.node-desc { color: var(--el-text-color-secondary); font-size: 12px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
.tree-table .el-table__row { cursor: pointer; }
|
||||||
.node-ops { flex: none; }
|
|
||||||
.node-placeholder { display: inline-block; padding: 7px 8px; color: var(--el-text-color-placeholder); font-size: 12px; }
|
|
||||||
.node-more { display: inline-flex; align-items: center; gap: 8px; padding: 4px 8px; }
|
|
||||||
/* 展开/折叠箭头放大加色(对齐旧版 +/- 折叠标记的可见度)。 */
|
|
||||||
:deep(.el-tree-node__expand-icon) {
|
|
||||||
font-size: 18px;
|
|
||||||
color: var(--admin-primary);
|
|
||||||
}
|
|
||||||
:deep(.el-tree-node__content) {
|
|
||||||
height: auto;
|
|
||||||
min-height: 34px;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -77,10 +77,6 @@ export function toProductCategoryNode(raw: unknown): ProductCategoryNode | null
|
|||||||
.map((child) => toProductCategoryNode(child))
|
.map((child) => toProductCategoryNode(child))
|
||||||
.filter((child): child is ProductCategoryNode => child !== null)
|
.filter((child): child is ProductCategoryNode => child !== null)
|
||||||
}
|
}
|
||||||
// 有子级但未返回 children 数据:注入占位子级,让 el-tree 显示展开箭头(懒加载入口)。
|
|
||||||
if (node.children.length === 0 && childCount > 0) {
|
|
||||||
node.children = [makeCategoryPlaceholderNode(id)]
|
|
||||||
}
|
|
||||||
return node
|
return node
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -134,32 +130,6 @@ export function isCategoryLoadMoreNode(node: ProductCategoryNode | null | undefi
|
|||||||
return !!node && (node as Partial<CategoryLoadMoreNode>).isLoadMore === true
|
return !!node && (node as Partial<CategoryLoadMoreNode>).isLoadMore === true
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 合成"占位"节点:childCount>0 但子级未加载时占位,保证 el-tree 显示展开箭头(懒加载入口)。 */
|
|
||||||
export interface CategoryPlaceholderNode extends ProductCategoryNode {
|
|
||||||
isPlaceholder: true
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isCategoryPlaceholderNode(node: ProductCategoryNode | null | undefined): node is CategoryPlaceholderNode {
|
|
||||||
return !!node && (node as Partial<CategoryPlaceholderNode>).isPlaceholder === true
|
|
||||||
}
|
|
||||||
|
|
||||||
export function makeCategoryPlaceholderNode(parentId: number | null): CategoryPlaceholderNode {
|
|
||||||
return {
|
|
||||||
id: -(parentId ?? 0) - 1_000_000,
|
|
||||||
parentId,
|
|
||||||
name: '…',
|
|
||||||
categoryKey: '',
|
|
||||||
sortOrder: null,
|
|
||||||
description: '',
|
|
||||||
isBuiltin: false,
|
|
||||||
childCount: 0,
|
|
||||||
level: null,
|
|
||||||
path: '',
|
|
||||||
children: [],
|
|
||||||
isPlaceholder: true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function makeCategoryLoadMoreNode(
|
export function makeCategoryLoadMoreNode(
|
||||||
parentId: number | null,
|
parentId: number | null,
|
||||||
loadedCount: number,
|
loadedCount: number,
|
||||||
|
|||||||
@@ -1,60 +1,53 @@
|
|||||||
import test from 'node:test'
|
import test from 'node:test'
|
||||||
import assert from 'node:assert/strict'
|
import assert from 'node:assert/strict'
|
||||||
import { readSource } from './helpers.ts'
|
import { readSource } from './helpers.ts'
|
||||||
import { isCategoryPlaceholderNode, toProductCategoryNode, makeCategoryPlaceholderNode, makeCategoryLoadMoreNode } from '../src/pages/asin/product-category-model.ts'
|
import { makeCategoryLoadMoreNode, toProductCategoryNode } from '../src/pages/asin/product-category-model.ts'
|
||||||
|
|
||||||
// 验收反馈:类目树太紧凑、没有折叠展开、父子级效果对齐旧版(旧版为缩进树形表格带 +/- 展开)。
|
// 验收反馈(两轮):类目树显示不对,参考旧版页面 —— 改为树形表格:
|
||||||
// 不变式:childCount>0 的未加载节点带占位子级(children 非空),el-tree 因此显示展开箭头;
|
// 缩进层级 + [+/-] 展开折叠标记 + 名称/层级路径/排序/来源/说明/操作列,懒加载与加载更多保留。
|
||||||
// 节点间距加大(:indent 28 + node-row padding),父子级视觉与旧版缩进树一致。
|
|
||||||
|
|
||||||
test('align_category_tree_normal_primary_path', () => {
|
test('align_category_tree_table_normal_primary_path', () => {
|
||||||
// 有子级但未返回 children 数据时,解析为带占位子级的节点(el-tree 显示展开箭头)。
|
const page = readSource('src/pages/asin/ProductCategoryPage.vue')
|
||||||
const node = toProductCategoryNode({ id: 5, name: '父类目', child_count: 3, path: '父类目' })
|
assert.ok(!page.includes('<el-tree'), '不再使用 el-tree,改为树形表格')
|
||||||
assert.ok(node && node.children.length > 0, 'childCount>0 节点需带占位子级以显示展开箭头')
|
assert.match(page, /tree-node-mark/, '树形展开折叠标记(+/−)')
|
||||||
assert.ok(node && node.children.every((child) => isCategoryPlaceholderNode(child)), '占位子级可识别')
|
assert.match(page, /tree-indent/, '层级缩进')
|
||||||
})
|
})
|
||||||
|
|
||||||
test('align_category_tree_normal_variant_input', () => {
|
test('align_category_tree_table_normal_variant_input', () => {
|
||||||
// 占位节点 id 与真实节点不冲突(负数域),且不可被误认为真实类目。
|
const page = readSource('src/pages/asin/ProductCategoryPage.vue')
|
||||||
const placeholder = makeCategoryPlaceholderNode(5)
|
assert.match(page, /expandedIds/, '客户端展开状态集合')
|
||||||
assert.ok(placeholder.id < 0, '占位节点 id 用负数域避免与真实 id 冲突')
|
assert.match(page, /flatRows|treeRows/, '按展开状态扁平化行集')
|
||||||
assert.ok(isCategoryPlaceholderNode(placeholder))
|
for (const col of ['层级路径', '排序', '来源', '说明']) {
|
||||||
assert.equal(isCategoryPlaceholderNode({ id: 1, parentId: null, name: '真实', categoryKey: '', sortOrder: 0, description: '', isBuiltin: false, childCount: 0, level: 0, path: '', children: [] }), false)
|
assert.match(page, new RegExp(col), `树形表格含「${col}」列`)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
test('align_category_tree_boundary_empty_input', () => {
|
test('align_category_tree_table_boundary_empty_input', () => {
|
||||||
// 无子级节点保持叶子形态(children 空)。
|
// 展开折叠标记:childCount>0 显示 +/−;叶子无标记(对齐旧版 tree-node-mark is-leaf)。
|
||||||
|
const page = readSource('src/pages/asin/ProductCategoryPage.vue')
|
||||||
|
assert.match(page, /childCount/, '标记按 childCount 判断')
|
||||||
|
assert.match(page, /is-leaf/, '叶子标记态样式')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('align_category_tree_table_boundary_single_item', () => {
|
||||||
|
// 懒加载保留:展开时未加载子级先拉取第一页;加载更多合成行保留。
|
||||||
|
const page = readSource('src/pages/asin/ProductCategoryPage.vue')
|
||||||
|
assert.match(page, /loadChildrenPage/, '展开懒加载保留')
|
||||||
|
assert.match(page, /isCategoryLoadMoreNode/, '加载更多行保留')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('align_category_tree_table_repeated_operation_is_idempotent', () => {
|
||||||
|
// 合成节点 id 仍唯一(loadMore 独立负数域,避免 node key 冲突致行错位)。
|
||||||
|
const ids = new Set([makeCategoryLoadMoreNode(null, 1, 2).id, makeCategoryLoadMoreNode(1, 1, 2).id, makeCategoryLoadMoreNode(2, 1, 2).id])
|
||||||
|
assert.equal(ids.size, 3)
|
||||||
|
// 叶子节点 children 为空数组(无占位注入)。
|
||||||
const leaf = toProductCategoryNode({ id: 9, name: '叶子', child_count: 0, path: '叶子' })
|
const leaf = toProductCategoryNode({ id: 9, name: '叶子', child_count: 0, path: '叶子' })
|
||||||
assert.ok(leaf && leaf.children.length === 0, '叶子节点无占位子级')
|
assert.ok(leaf && leaf.children.length === 0)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('align_category_tree_boundary_single_item', () => {
|
test('align_category_tree_table_dependency_failure_returns_actionable_message', () => {
|
||||||
|
// 视觉复刻旧版:20px 圆角方块标记、缩进宽度变量、名称加粗。
|
||||||
const page = readSource('src/pages/asin/ProductCategoryPage.vue')
|
const page = readSource('src/pages/asin/ProductCategoryPage.vue')
|
||||||
assert.match(page, /:indent="28"/, '树缩进加大(28px/级)')
|
assert.match(page, /tree-node-mark/, '展开标记样式存在')
|
||||||
assert.match(page, /isCategoryPlaceholderNode/, '模板渲染占位节点分支')
|
assert.match(page, /var\(--indent|--indent/, '缩进变量')
|
||||||
// 展开加载需识别"只有占位子级"的节点(children 全占位也触发懒加载)。
|
|
||||||
assert.match(page, /some\(.*isCategoryPlaceholderNode/, '展开判断兼容占位子级')
|
|
||||||
})
|
|
||||||
|
|
||||||
test('align_category_tree_dependency_failure_returns_actionable_message', () => {
|
|
||||||
// 视觉:节点行加 padding 不再紧凑;箭头样式放大加色。
|
|
||||||
const page = readSource('src/pages/asin/ProductCategoryPage.vue')
|
|
||||||
assert.match(page, /\.node-row\s*\{[^}]*padding/, '节点行有 padding')
|
|
||||||
assert.match(page, /el-tree-node__expand-icon|expand-icon/, '展开图标样式定制')
|
|
||||||
})
|
|
||||||
|
|
||||||
test('align_category_tree_synthetic_ids_are_unique', () => {
|
|
||||||
// 修复回归:合成节点 id 唯一——根级 loadMore 与父级1的 loadMore 不得同 key(曾因 -1 撞 key 致子级挂错父级)。
|
|
||||||
const ids = new Set<number>()
|
|
||||||
const samples = [
|
|
||||||
makeCategoryLoadMoreNode(null, 1, 2).id,
|
|
||||||
makeCategoryLoadMoreNode(1, 1, 2).id,
|
|
||||||
makeCategoryLoadMoreNode(2, 1, 2).id,
|
|
||||||
makeCategoryPlaceholderNode(null).id,
|
|
||||||
makeCategoryPlaceholderNode(1).id,
|
|
||||||
makeCategoryPlaceholderNode(2).id,
|
|
||||||
]
|
|
||||||
for (const id of samples) ids.add(id)
|
|
||||||
assert.equal(ids.size, samples.length, '合成节点 id 两两唯一')
|
|
||||||
assert.ok(makeCategoryLoadMoreNode(null, 1, 2).id !== -1, '根级 loadMore 不得使用 -1(与父级1的旧式 -parentId 撞 key)')
|
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user