优化顶栏导航与登录请求体验

This commit is contained in:
Cheng Zhou
2026-06-17 17:22:22 +08:00
parent 061792c73f
commit 53249daf8d
9 changed files with 1479 additions and 203 deletions
File diff suppressed because it is too large Load Diff
@@ -8,7 +8,7 @@ describe("Layout breadcrumbs", () => {
it("shows the file version module before document detail titles", () => {
const source = readLayout();
expect(source).toContain('"/documents": { label: TEXT.menu.fileVersionManagement');
expect(source).toContain('match: ["/file-versions", "/documents", "/trial/"]');
expect(source).toContain("study.viewContext?.pageTitle");
expect(source).toContain("items.push({ label: study.viewContext.pageTitle, path: route.path })");
});
@@ -28,6 +28,171 @@ describe("Layout breadcrumbs", () => {
expect(source).toContain('path: "/admin/projects"');
});
it("restores the permission management parent for admin permission sub-pages", () => {
const source = readLayout();
expect(source).toContain('if (route.path.startsWith("/admin/permissions/"))');
expect(source).toContain("label: TEXT.menu.permissionManagement");
});
it("injects the project name level on the admin sites page", () => {
const source = readLayout();
expect(source).toContain('if (route.path.includes("/sites"))');
expect(source).toContain("studies.value.find((s) => String(s.id) === pid)");
expect(source).toContain("items.push({ label: proj.name, path: `/admin/projects/${pid}` })");
});
it("keeps the admin root breadcrumb as a non-clickable label", () => {
const source = readLayout();
expect(source).toContain("items.push({ label: TEXT.menu.admin });");
expect(source).not.toContain('path: isAdmin.value ? "/admin/users" : "/admin/projects"');
});
it("enables sibling navigation dropdowns on admin breadcrumbs", () => {
const source = readLayout();
expect(source).toContain("item.navDropdown && item.siblings?.length");
expect(source).toContain('{ type: \'nav\', value: sib.path }');
expect(source).toContain("if (cmd.type === 'nav')");
expect(source).toContain("siblings: topItems");
});
it("builds project breadcrumbs from a permission-filtered nav tree", () => {
const source = readLayout();
expect(source).toContain("const navTree: NavNode[] = [");
expect(source).toContain("label: TEXT.menu.sharedLibrary");
expect(source).toContain("label: TEXT.menu.riskIssues");
expect(source).toContain("label: TEXT.menu.materialManagement");
});
it("renders shared-library items as a third-level group, not top-level", () => {
const source = readLayout();
// 三级子项(医学咨询等)必须挂在顶级分组的 children 内,而非顶级数组
expect(source).toContain("children: [");
expect(source).toContain("canAccessProjectPath(c.path)");
expect(source).toContain("childSiblings.length > 1");
});
it("renders a header center region with project status but not ambiguous phase", () => {
const source = readLayout();
expect(source).toContain('class="header-center"');
expect(source).toContain("const headerProjectInfo = computed");
expect(source).toContain("statusTone");
expect(source).toContain('class="header-status-pill"');
expect(source).toContain("header-status-dot");
expect(source).not.toContain("headerProjectInfo.roleLabel");
expect(source).not.toContain("headerProjectInfo.phase");
expect(source).not.toContain("TEXT.common.labels.phase");
});
it("renders the project role next to the account menu", () => {
const source = readLayout();
expect(source).toContain('class="header-account-role"');
expect(source).toContain("accountProjectRoleLabel");
expect(source).toContain("v-if=\"accountProjectRoleLabel\"");
});
it("uses chip styling instead of vertical divider lines for header metadata", () => {
const source = readLayout();
expect(source).toContain("class=\"header-meta-item header-metric\"");
expect(source).toContain("class=\"header-context-rail\"");
expect(source).toContain("border-radius: 14px");
expect(source).toContain("border: 1px solid transparent");
expect(source).toContain("background: transparent");
expect(source).toContain("box-shadow: none");
expect(source).toContain("overflow: visible");
expect(source).toContain("class=\"header-meta-value\"");
expect(source).toContain("font-variant-numeric: tabular-nums");
expect(source).toContain("border: 1px solid rgba(213, 224, 237, 0.94)");
expect(source).not.toContain("0 5px 14px rgba(31, 109, 80, 0.14)");
expect(source).not.toContain("border-left: 1px solid #e2e8f0");
expect(source).not.toContain("border-right: 1px solid #e2e8f0");
});
it("does not duplicate the account identity in the admin header center", () => {
const source = readLayout();
expect(source).not.toContain("const headerAdminInfo = computed");
expect(source).not.toContain("headerAdminInfo.email");
expect(source).not.toContain("header-admin-email");
});
it("renders a denser project context rail with planned site and enrollment metrics", () => {
const source = readLayout();
expect(source).toContain('class="header-context-rail"');
expect(source).toContain("headerProjectInfo.metrics");
expect(source).toContain("plannedSiteCount");
expect(source).toContain("plannedEnrollmentCount");
expect(source).toContain("headerOverviewStats");
expect(source).toContain("fetchProjectOverview");
expect(source).toContain("formatHeaderProgress");
expect(source).toContain("headerMetric.key");
expect(source).toContain("TEXT.common.labels.plannedSites");
expect(source).toContain("TEXT.common.labels.plannedEnrollment");
expect(source).toContain("TEXT.common.labels.dataUpdatedAt");
expect(source).toContain("loadHeaderOverviewStats");
expect(source).toContain("study.currentStudy?.id !== studyId");
expect(source).toContain("headerClockNow");
expect(source).toContain("formatHeaderDateTime");
expect(source).toContain("window.setInterval");
expect(source).toContain("window.clearInterval");
});
it("adds a compact project reminder dropdown for overdue risks", () => {
const source = readLayout();
expect(source).toContain('class="header-reminder-dropdown"');
expect(source).toContain("headerReminderTotal");
expect(source).toContain("headerReminderItems");
expect(source).toContain("fetchOverdueAesCount");
expect(source).toContain("listMonitoringVisitIssues");
expect(source).toContain("TEXT.common.labels.projectReminders");
expect(source).toContain("TEXT.common.labels.overdueAes");
expect(source).toContain("TEXT.common.labels.overdueMonitoringIssues");
expect(source).toContain("handleReminderCommand");
});
it("styles the app header as a fixed chrome layer above page content", () => {
const source = readLayout();
expect(source).toContain("position: sticky");
expect(source).toContain("top: 0");
expect(source).toContain("z-index: 30");
expect(source).toContain(".layout-header::after");
expect(source).toContain("0 10px 26px rgba(15, 23, 42, 0.08)");
expect(source).toContain("border-bottom: 1px solid rgba(203, 213, 225, 0.78)");
});
it("omits visual breadcrumb separators and truncates long breadcrumb labels", () => {
const source = readLayout();
expect(source).not.toContain("breadcrumb-separator");
expect(source).not.toContain("TEXT.common.separators.dot");
expect(source).toContain("text-overflow: ellipsis");
expect(source).toContain("max-width: 52vw");
});
it("styles header breadcrumbs as compact navigation controls", () => {
const source = readLayout();
expect(source).toContain('class="breadcrumb-item-wrapper is-link is-study"');
expect(source).toContain("'is-current': index === breadcrumbs.length - 1");
expect(source).toContain(".breadcrumb-item-wrapper.is-study");
expect(source).toContain(".breadcrumb-item-wrapper.is-current");
expect(source).toContain("border-color: rgba(198, 214, 235, 0.92)");
expect(source).toContain("height: 26px");
expect(source).toContain("border-radius: 7px");
expect(source).toContain("gap: 2px");
});
it("uses stable breadcrumb keys when context titles change", () => {
const source = readLayout();