feat(site-layer): 学校站点分层方案C落地(docs/13)+栏目管理增强+管理端三修复+首页视觉升级管理端+审计复检T1~T5(docs/15)整合批次-URL站点段/site/{siteCode}无component镜像层(去name防重名,router-view跳过推进渲染)+身份条SiteSwitcher自绘(徽章#577afb/#ff9900+分组下拉✓+path次级列+Esc/外点收起)+URL唯一事实源syncSiteFromRoute(非法/跨校/主站码当子站段告警打回同页主站,localStorage降级刷新首帧兜底)+菜单剥段高亮stripSiteSeg/withSiteSeg补段+menuRoute镜像层防重影+跳转收敛adminPathOf(工作台进入/全部站点总览/站点管理行进入管理纯URL切站);栏目管理-上级栏目可选可改(禁选自身及后代防成环)+批量添加BatchAddCategory(行序写排序号单行失败不中断)+单页wangEditor富文本预览字数分区+categoryIds契约前端/types/mock三处统一+SchoolLayout高度链.layout-wrap修复+ArticleListPanel fixed模式;管理端三修复-皮肤预览Modal全屏(ThemePreviewFrame可选fill prop)+SiteManage补themeUnregistered声明+长图文编辑器随内容增高;管理端视觉升级配套-SiteConfigForm 12新键+BannerManage gallery展示位切换+栏目homeStyle下拉+PortalPreview侧栏同步;审计复检-新增siteRouter.spec真实路由集成7用例(镜像层matched3层/守卫子站码定向主站前缀/越权回工作台/旧链带id重定向)+sitePath空白容错+siteLayer持久化round-trip与身份条次级入口(docs/15);vitest 99/99+build通过+S1~S10 GUI走查全过
build / build (push) Successful in 2m43s

This commit is contained in:
chenxing
2026-09-10 11:11:23 +08:00
parent 9ba4cb135a
commit 6ee9ab5684
26 changed files with 1931 additions and 222 deletions
+91
View File
@@ -0,0 +1,91 @@
// @vitest-environment jsdom
/**
* docs/13 方案 C 复检:真实 vue-router 集成(docs/15 §3 T1/T4)。
* siteLayer.spec.ts 全程 router 桩(vi.mock vue-router),覆盖不到镜像层真实匹配与守卫重定向,
* 本文件装载 routes.ts 全量路由 + router/index.ts 守卫补位:
* ① 路由结构:/site/{code} 无 component 镜像层存在、镜像去 name、meta.modId 保留、空段 redirect 兜底;
* ② 镜像层真实 resolve 命中(渲染层由 router-view 对无 component 记录跳过推进,此处验匹配链);
* ③ 守卫:子站编码当学校段 → 定向主站前缀且余部原样保留(含站点段),并恢复学校上下文;
* ④ legacyRedirect 旧链带 id 重定向。
*/
import { beforeEach, describe, expect, it } from "vitest";
import { createPinia, setActivePinia } from "pinia";
import { useCmsStore } from "@/store";
import router from "@/router";
import { routes } from "@/router/routes";
const SITES = [
{ id: 1, code: "demo", name: "云海大学门户", parentSiteId: 0, schId: 1001, schName: "云海大学" },
{ id: 2, code: "jdxy", name: "机电学院", parentSiteId: 1, schId: 1001, schName: "云海大学" },
];
let store: ReturnType<typeof useCmsStore>;
beforeEach(() => {
localStorage.clear();
const pinia = createPinia();
setActivePinia(pinia);
store = useCmsStore(pinia);
store.user = { superAdmin: true, sites: SITES } as any;
});
describe("docs/13 路由结构(真实 routes 数组,docs/15 T1)", () => {
it("① /site/{code} 无 component 镜像层存在;镜像去 name、meta.modId 保留、空段 redirect 到 article", () => {
const sch = routes.find(r => r.path === "/sch/:schCode")!;
const siteLayer = sch.children!.find(r => r.path === "site/:siteCode") as any;
expect(siteLayer).toBeTruthy();
expect(siteLayer.component).toBeUndefined();
expect(siteLayer.children![0].path).toBe("");
expect(siteLayer.children![0].redirect).toBeTruthy();
const mirror = siteLayer.children!.find((c: any) => c.path === "category");
expect(mirror!.name).toBeUndefined();
expect((mirror!.meta as any).modId).toBe(2);
});
it("①b 主站清单与镜像层不重名(防重复注册告警)", () => {
expect(router.getRoutes().filter(r => r.name === "CategoryManage")).toHaveLength(1);
expect(router.getRoutes().filter(r => r.name === "ArticleList")).toHaveLength(1);
});
it("② 子站 URL 真实 resolve 命中镜像记录;主站 URL 命中主记录", () => {
const sub = router.resolve("/sch/demo/site/jdxy/category");
expect(sub.matched).toHaveLength(3);
expect(sub.name).toBeUndefined();
expect(sub.meta.modId).toBe(2);
const main = router.resolve("/sch/demo/category");
expect(main.matched).toHaveLength(2);
expect(main.name).toBe("CategoryManage");
});
});
describe("docs/13 守卫与旧链兼容(真实 router.push,docs/15 T4)", () => {
it("③ 子站编码当学校段 → 重定向主站前缀(余部保留)并恢复学校上下文", async () => {
await router.push("/sch/jdxy/article");
expect(router.currentRoute.value.path).toBe("/sch/demo/article");
expect(store.currentSchool?.mainSiteCode).toBe("demo");
});
it("③b 子站编码当学校段且 URL 已带站点段 → 站点段原样保留", async () => {
await router.push("/sch/jdxy/site/jdxy/category");
expect(router.currentRoute.value.path).toBe("/sch/demo/site/jdxy/category");
expect(router.currentRoute.value.params.siteCode).toBe("jdxy");
});
it("③c 学校编码不在用户可管理站点内 → 回工作台", async () => {
await router.push("/sch/other-school/article");
expect(router.currentRoute.value.path).toBe("/workbench");
});
it("④ 旧链 /article/edit/:id → 当前学校上下文主站形态(id 后缀保留)", async () => {
store.currentSchool = {
schId: 1001,
schName: "云海大学",
mainSiteId: 1,
mainSiteCode: "demo",
mainSiteName: "云海大学门户",
} as any;
await router.push("/article/edit/9");
expect(router.currentRoute.value.path).toBe("/sch/demo/article/edit/9");
});
});