feat(W2): 栏目树修订-虚拟根(学校名/子站名)+工具栏收敛4按钮+高度链修复,T-W2-01..10 绿 44/44

This commit is contained in:
chenxing
2026-09-08 15:14:09 +08:00
parent 1e887906eb
commit 7b99ec398d
6 changed files with 224 additions and 36 deletions
+69
View File
@@ -0,0 +1,69 @@
/**
* W2 栏目树修订:虚拟根装配纯函数(方案 §6.3 T-W2-01..05)。
* rootLabelOf:根 label 随站点上下文(主站→学校名,缺省回退 mainSiteName;子站→子站名);
* wrapTreeWithRoot:单根包装 {id:0,name:label,virtual:true,children};
* initialSelectId:虚拟根下钻首个真实栏目 id,空树 null。
*/
import { describe, expect, it } from "vitest";
import { initialSelectId, rootLabelOf, wrapTreeWithRoot } from "@/utils/categoryTree";
const school = (overrides: Partial<any> = {}) => ({
schId: 1,
schName: "演示学校",
mainSiteId: 1,
mainSiteCode: "demo1",
mainSiteName: "演示主站",
...overrides,
});
describe("rootLabelOf(T-W2-01..03)", () => {
it("T-W2-01: 主站上下文 → schName", () => {
expect(rootLabelOf({ currentSchool: school(), currentSite: { id: 1, code: "demo1", name: "演示主站" } }))
.toBe("演示学校");
});
it("T-W2-02: 主站 schName 缺省 → 回退 mainSiteName", () => {
expect(rootLabelOf({ currentSchool: school({ schName: "" }), currentSite: { id: 1, code: "demo1", name: "演示主站" } }))
.toBe("演示主站");
expect(rootLabelOf({ currentSchool: school({ schName: undefined as any }), currentSite: { id: 1, code: "demo1", name: "演示主站" } }))
.toBe("演示主站");
});
it("T-W2-03: 子站上下文 → 子站名", () => {
expect(rootLabelOf({ currentSchool: school(), currentSite: { id: 2, code: "jdxy", name: "机械学院" } }))
.toBe("机械学院");
});
});
describe("wrapTreeWithRoot(T-W2-04)", () => {
it("返回单根虚拟节点,原树挂 children", () => {
const tree = [
{ id: 5, parentId: 0, name: "新闻中心", children: [] },
{ id: 6, parentId: 0, name: "学校简介", children: [] },
];
const root = wrapTreeWithRoot(tree, "演示学校");
expect(root).toMatchObject({ id: 0, name: "演示学校", virtual: true });
expect(root.children).toHaveLength(2);
expect(root.children[0].id).toBe(5);
});
it("空树也有根(children=[])", () => {
const root = wrapTreeWithRoot([], "演示学校");
expect(root).toMatchObject({ id: 0, name: "演示学校", virtual: true, children: [] });
expect(wrapTreeWithRoot(null as any, "演示学校").children).toEqual([]);
});
});
describe("initialSelectId(T-W2-05)", () => {
it("虚拟根下钻首个真实栏目 id(非 0)", () => {
const root = wrapTreeWithRoot([
{ id: 5, parentId: 0, name: "新闻中心", children: [] },
{ id: 6, parentId: 0, name: "学校简介", children: [] },
], "演示学校");
expect(initialSelectId(root)).toBe(5);
});
it("空树 → null", () => {
expect(initialSelectId(wrapTreeWithRoot([], "演示学校"))).toBeNull();
});
});
+37
View File
@@ -0,0 +1,37 @@
/**
* W2 栏目树修订:虚拟根装配纯函数(方案 §6.4 步骤 1)。
* 虚拟根约定(D5):id=0 的前端虚拟节点包裹真实栏目树;新增子栏目→parentId=0(顶级)、
* 子栏目排序→parentId=0、编辑/删除→拒绝并提示(CategoryManage 既有 !node?.id 守卫天然拦截)。
*/
import type { CurrentSchool, CurrentSite } from "@/types/cms";
/** 根节点 label:主站上下文=学校名(schName 缺省回退 mainSiteName);子站上下文=该子站名 */
export function rootLabelOf(ctx: {
currentSchool: CurrentSchool | null;
currentSite: CurrentSite | null;
}): string {
const school = ctx.currentSchool;
const site = ctx.currentSite;
if (site && school && site.id !== school.mainSiteId) {
return site.name || "";
}
const label = school?.schName?.trim();
return label || school?.mainSiteName || "";
}
/** 把真实栏目树包装到虚拟根下(单根;空树也有根,children 恒为数组) */
export function wrapTreeWithRoot(tree: any[] | null | undefined, label: string): any {
return { id: 0, name: label, virtual: true, children: Array.isArray(tree) ? tree : [] };
}
/** 初始选中:虚拟根下钻首个真实栏目 id;空树 → null(不落到根 id=0) */
export function initialSelectId(wrapped: any): number | null {
if (wrapped == null) {
return null;
}
const root = Array.isArray(wrapped) ? wrapped[0] : wrapped;
if (root?.virtual) {
return root.children?.length ? root.children[0].id : null;
}
return root?.id ?? null;
}