From a99a310f50cd7c3cc0ed72134d44369716ca7f8c Mon Sep 17 00:00:00 2001 From: xuans Date: Fri, 25 Sep 2026 13:40:15 +0800 Subject: [PATCH] =?UTF-8?q?=E5=8F=82=E8=80=83=E8=AE=BE=E7=BD=AE=E9=A1=B5?= =?UTF-8?q?=E6=8B=86=E5=88=86=E7=AE=A1=E7=90=86=E6=9D=BF=E5=9D=97=E5=B9=B6?= =?UTF-8?q?=E4=BC=98=E5=8C=96=E5=8D=95=E9=A1=B5=E5=B8=83=E5=B1=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 增加分组侧栏和按权限显示的用户入口,保留首页基本信息与现有弹窗;补齐证书导入绑定。新增板块切换及完整模板渲染回归,98项前端测试及生产构建通过。 --- .../src/views/Dashboard/Dashboard.test.js | 114 +++++++++- frontend/src/views/Dashboard/Dashboard.vue | 55 ++++- .../src/views/Dashboard/DashboardLayout.css | 213 ++++++++++++++++++ .../views/Dashboard/DashboardNavigation.css | 118 ++++++++++ .../views/Dashboard/DashboardNavigation.js | 31 +++ .../views/Dashboard/DashboardNavigation.vue | 62 +++++ 6 files changed, 583 insertions(+), 10 deletions(-) create mode 100644 frontend/src/views/Dashboard/DashboardLayout.css create mode 100644 frontend/src/views/Dashboard/DashboardNavigation.css create mode 100644 frontend/src/views/Dashboard/DashboardNavigation.js create mode 100644 frontend/src/views/Dashboard/DashboardNavigation.vue diff --git a/frontend/src/views/Dashboard/Dashboard.test.js b/frontend/src/views/Dashboard/Dashboard.test.js index 794e4ed..aa4b675 100644 --- a/frontend/src/views/Dashboard/Dashboard.test.js +++ b/frontend/src/views/Dashboard/Dashboard.test.js @@ -1,9 +1,121 @@ import assert from "node:assert/strict"; import test from "node:test"; -import { nextTick } from "vue"; +import { nextTick, ref } from "vue"; import { useDashboard } from "./Dashboard.js"; +import { useDashboardNavigation } from "./DashboardNavigation.js"; import { aliyunProvider, deferred, mountComposable, verifiedResponse } from "../../test-support/mount.js"; +test("板块导航默认只选中基本信息,超级管理员可切换全部板块", () => { + const navigation = useDashboardNavigation(ref({ role: "super_admin" })); + assert.equal(navigation.activeSection.value, "overview"); + assert.equal(navigation.sections.value.length, 6); + for (const section of navigation.sections.value) { + navigation.selectSection(section.id); + assert.equal(navigation.activeSection.value, section.id); + assert.equal(navigation.activeLabel.value, section.label); + } + navigation.selectSection("invalid"); + assert.equal(navigation.activeSection.value, "users"); +}); + +test("用户管理导航按权限显示,权限变化后回到基本信息", () => { + const user = ref(null); + const navigation = useDashboardNavigation(user); + assert.equal(navigation.sections.value.some((section) => section.id === "users"), false); + navigation.selectSection("users"); + assert.equal(navigation.activeSection.value, "overview"); + user.value = { role: "super_admin" }; + navigation.selectSection("users"); + assert.equal(navigation.activeSection.value, "users"); + user.value = { role: "admin" }; + assert.equal(navigation.activeSection.value, "overview"); + assert.equal(navigation.sections.value.length, 5); +}); + +test("单板块布局使用互斥条件,切换重置滚动且保留弹窗入口", async () => { + const { readFile } = await import("node:fs/promises"); + const template = await readFile(new URL("./Dashboard.vue", import.meta.url), "utf8"); + const stylesheet = await readFile(new URL("./DashboardLayout.css", import.meta.url), "utf8"); + for (const id of ["overview", "activity", "domains", "servers", "keys", "users"]) { + assert.ok(template.includes(`v-if="activeSection === '${id}'"`)); + } + assert.match(template, /:key="activeSection"/); + assert.match(template, /role="region"\s+:aria-label="activeLabel"\s+tabindex="0"/); + for (const modal of ["SettingsModal", "DomainModal", "ServerModal", "SSHKeyModal"]) { + assert.ok(template.includes(`<${modal} v-if=`)); + } + assert.match(stylesheet, /height: 100dvh/); + assert.match(stylesheet, /\.dashboard-content\s*\{[^}]*overflow: auto/); +}); + +test("侧栏支持键盘焦点、当前项标记和悬浮说明", async () => { + const { readFile } = await import("node:fs/promises"); + const template = await readFile(new URL("./DashboardNavigation.vue", import.meta.url), "utf8"); + const stylesheet = await readFile(new URL("./DashboardNavigation.css", import.meta.url), "utf8"); + assert.match(template, /