feat(桌面与监控): 完善工作台导航和登录活动定位
- 优化桌面标签、上下文标题、前进后退、导航栏隐藏和原生菜单体验 - 补充登录会话 IP 采集、地理位置回退、管理端展示及数据库迁移 - 更新桌面发布检查、运维文档和前后端测试覆盖
This commit is contained in:
@@ -37,6 +37,8 @@ describe("desktop layout shell", () => {
|
||||
expect(source).toContain("<DesktopLayout v-if=\"isDesktop\" />");
|
||||
expect(source).toContain("<WebLayout v-else />");
|
||||
expect(app).toContain("const isDesktopRuntime = isTauriRuntime();");
|
||||
expect(app).toContain("initializeDesktopThemePreference();");
|
||||
expect(app).toContain("initializeDesktopMenuShortcutSync();");
|
||||
expect(app).toContain('document.body.classList.add("is-desktop-runtime")');
|
||||
expect(app).toContain('document.body.classList.remove("is-desktop-runtime")');
|
||||
});
|
||||
@@ -98,9 +100,14 @@ describe("desktop layout shell", () => {
|
||||
expect(source).toContain("desktopToolbarSubtitle");
|
||||
expect(source).not.toContain("desktopBreadcrumbs");
|
||||
expect(source).not.toContain("breadcrumb-chip");
|
||||
expect(source).not.toContain("history-controls");
|
||||
expect(source).not.toContain('title="后退"');
|
||||
expect(source).not.toContain('title="前进"');
|
||||
expect(source).toContain('class="workspace-history-controls"');
|
||||
expect(source).toContain('title="后退(当前任务)"');
|
||||
expect(source).toContain('title="前进(当前任务)"');
|
||||
expect(source).toContain(':disabled="!canNavigateWorkspaceTabBack"');
|
||||
expect(source).toContain(':disabled="!canNavigateWorkspaceTabForward"');
|
||||
expect(source).toContain('class="toolbar-history-divider"');
|
||||
expect(source).toContain('v-for="(item, index) in desktopToolbarContextItems"');
|
||||
expect(source).toContain('class="toolbar-context-link"');
|
||||
expect(source).not.toContain('title="刷新当前视图"');
|
||||
expect(source).not.toContain("const sidebarTitle");
|
||||
expect(source).not.toContain('title="搜索命令"');
|
||||
@@ -135,6 +142,29 @@ describe("desktop layout shell", () => {
|
||||
expect(layout).not.toContain(["@tauri-apps", "api", "window"].join("/"));
|
||||
});
|
||||
|
||||
it("uses a native macOS application menu and rebuilds a closed main window from the Dock", () => {
|
||||
const tauriLib = readTauriLibSource();
|
||||
|
||||
expect(tauriLib.indexOf("package.name.clone()")).toBeLessThan(tauriLib.indexOf('"文件"'));
|
||||
expect(tauriLib).toContain('Some("关于 CTMS")');
|
||||
expect(tauriLib).toContain("short_version: Some(String::new())");
|
||||
expect(tauriLib).toContain("icon: handle.default_window_icon().cloned()");
|
||||
expect(tauriLib).toContain('"ctms.desktop.preferences"');
|
||||
expect(tauriLib).toContain("PredefinedMenuItem::services");
|
||||
expect(tauriLib).toContain("PredefinedMenuItem::hide_others");
|
||||
expect(tauriLib).toContain("PredefinedMenuItem::show_all");
|
||||
expect(tauriLib).toContain("WINDOW_SUBMENU_ID");
|
||||
expect(tauriLib).toContain("HELP_SUBMENU_ID");
|
||||
expect(tauriLib).toContain("TOGGLE_SIDEBAR_COMMAND_ID");
|
||||
expect(tauriLib).toContain('"显示/隐藏导航栏"');
|
||||
expect(tauriLib).toContain("RunEvent::Reopen");
|
||||
expect(tauriLib).toContain("WebviewWindowBuilder::from_config");
|
||||
expect(tauriLib).toContain('find(|config| config.label == "main")');
|
||||
expect(tauriLib).not.toContain("WindowEvent::CloseRequested");
|
||||
expect(tauriLib).not.toContain("api.prevent_close()");
|
||||
expect(tauriLib).not.toContain("window.hide()");
|
||||
});
|
||||
|
||||
it("keeps the desktop sidebar dense enough for navigation-heavy screens", () => {
|
||||
const layout = readDesktopLayoutSource();
|
||||
const sidebarHeadStart = layout.indexOf('<header class="sidebar-head">');
|
||||
@@ -169,6 +199,24 @@ describe("desktop layout shell", () => {
|
||||
expect(layout).not.toContain("padding: 44px 12px 12px;");
|
||||
});
|
||||
|
||||
it("persists a fully hidden desktop sidebar with a macOS-safe restore control", () => {
|
||||
const layout = readDesktopLayoutSource();
|
||||
|
||||
expect(layout).toContain("const desktopSidebarVisible = ref(readDesktopSidebarVisible());");
|
||||
expect(layout).toContain("desktopSidebarVisible.value = setDesktopSidebarVisible(!desktopSidebarVisible.value);");
|
||||
expect(layout).toContain("'is-sidebar-hidden': !desktopSidebarVisible");
|
||||
expect(layout).toContain("'is-macos': desktopMetadata.platform === 'macos'");
|
||||
expect(layout).toContain('class="toolbar-nav-button sidebar-toggle-button"');
|
||||
expect(layout).toContain(':title="desktopSidebarToggleLabel"');
|
||||
expect(layout).toContain('<Fold v-if="desktopSidebarVisible" /><Expand v-else />');
|
||||
expect(layout).toContain('command === "ctms.desktop.toggleSidebar"');
|
||||
expect(layout).toContain(".desktop-workbench.is-sidebar-hidden {");
|
||||
expect(layout).toContain("grid-template-columns: 0 minmax(0, 1fr);");
|
||||
expect(layout).toContain(".desktop-workbench.is-macos.is-sidebar-hidden .desktop-toolbar {");
|
||||
expect(layout).toContain("padding-left: 88px;");
|
||||
expect(layout).toContain("pointer-events: none;");
|
||||
});
|
||||
|
||||
it("renders desktop preferences as a split settings panel", () => {
|
||||
const preferences = readDesktopPreferencesSource();
|
||||
const desktopLayout = readDesktopLayoutSource();
|
||||
@@ -183,6 +231,10 @@ describe("desktop layout shell", () => {
|
||||
expect(preferences).toContain("scrollbar-gutter: stable;");
|
||||
expect(preferences).toContain('{ id: "connection", label: "连接"');
|
||||
expect(preferences).toContain('{ id: "appearance", label: "外观"');
|
||||
expect(preferences).toContain('{ value: "system" as const, label: "跟随系统"');
|
||||
expect(preferences).toContain("grid-template-columns: repeat(3, minmax(0, 1fr));");
|
||||
expect(preferences).toContain("width: min(100%, 336px);");
|
||||
expect(preferences).toContain("white-space: nowrap;");
|
||||
expect(preferences).toContain('{ id: "shortcuts", label: "快捷键"');
|
||||
expect(preferences).toContain('{ id: "notifications", label: "通知"');
|
||||
expect(preferences).toContain('{ id: "updates", label: "更新"');
|
||||
@@ -249,7 +301,7 @@ describe("desktop layout shell", () => {
|
||||
|
||||
it("keeps workspace tabs stable and draggable like browser tabs", () => {
|
||||
const source = readDesktopLayoutSource();
|
||||
const tabsStart = source.indexOf('<div v-if="workspaceTabs.length" ref="workspaceTabsElement" class="workspace-tabs">');
|
||||
const tabsStart = source.indexOf('<div v-if="workspaceTabs.length > 1" class="workspace-tabs">');
|
||||
const tabsEnd = source.indexOf('<main class="desktop-content"', tabsStart);
|
||||
const tabsTemplate = source.slice(tabsStart, tabsEnd);
|
||||
|
||||
@@ -292,6 +344,8 @@ describe("desktop layout shell", () => {
|
||||
expect(source).toContain("replaceWorkspaceTabRoute(current.path, fullPath);");
|
||||
expect(source).toContain("currentWorkspaceTabPath(workspaceTabHistories.value[tabPath], tabPath)");
|
||||
expect(source).toContain("moveWorkspaceTabHistory(workspaceTabHistories.value[tabPath], offset)");
|
||||
expect(source).toContain("canMoveWorkspaceTabHistory(activeWorkspaceTabHistory.value, -1)");
|
||||
expect(source).toContain("canMoveWorkspaceTabHistory(activeWorkspaceTabHistory.value, 1)");
|
||||
expect(source).toContain("navigateBack: () => navigateCurrentWorkspaceTabHistory(-1)");
|
||||
expect(source).toContain("navigateForward: () => navigateCurrentWorkspaceTabHistory(1)");
|
||||
expect(source).toContain('id: "desktop:tab-back"');
|
||||
@@ -300,6 +354,25 @@ describe("desktop layout shell", () => {
|
||||
expect(source).not.toContain("navigateForward: () => router.forward()");
|
||||
});
|
||||
|
||||
it("updates a detail workspace tab after its business context has loaded", () => {
|
||||
const source = readDesktopLayoutSource();
|
||||
|
||||
expect(source).toContain("buildWorkspaceTabTitle,");
|
||||
expect(source).toContain("buildWorkspaceToolbarTitle,");
|
||||
expect(source).toContain("resolveWorkspaceTabContextTitle,");
|
||||
expect(source).toContain('} from "./layout/workspaceTabTitle";');
|
||||
expect(source).toContain("const workspaceTabContexts = ref<Record<string, WorkspaceTabContext>>({});");
|
||||
expect(source).toContain("title: buildWorkspaceTabTitle({");
|
||||
expect(source).toContain("const pageTitle = resolveWorkspaceTabContextTitle({");
|
||||
expect(source).toContain("() => [study.viewContext?.pageTitle, study.viewContext?.objectType] as const,");
|
||||
expect(source).toContain("[activeMenu.value]: {");
|
||||
expect(source).toContain("([pageTitle, objectType]) => {");
|
||||
expect(source).toContain("if (current) addWorkspaceTab(current);");
|
||||
expect(source).toContain('{ flush: "sync" },');
|
||||
expect(source).toContain("if (!previousRoute || previousRoute.path !== route.path) {");
|
||||
expect(source).toContain("const removeWorkspaceTabContext = (tabPath: string) => {");
|
||||
});
|
||||
|
||||
it("allows desktop shortcuts to be customized from system preferences", () => {
|
||||
const preferences = readDesktopPreferencesSource();
|
||||
const tauriLib = readTauriLibSource();
|
||||
@@ -313,30 +386,45 @@ describe("desktop layout shell", () => {
|
||||
expect(preferences).toContain('window.addEventListener("pointerdown", cancelDesktopShortcutCaptureOnPointerDown, { capture: true })');
|
||||
expect(preferences).toContain("event.stopImmediatePropagation()");
|
||||
expect(preferences).toContain("恢复默认");
|
||||
expect(tauriLib).not.toContain('Some("CmdOrCtrl+R")');
|
||||
expect(tauriLib).not.toContain('Some("CmdOrCtrl+[")');
|
||||
expect(tauriLib).not.toContain('Some("CmdOrCtrl+]")');
|
||||
expect(tauriLib).toContain('Some("CmdOrCtrl+R")');
|
||||
expect(tauriLib).toContain('Some("CmdOrCtrl+[")');
|
||||
expect(tauriLib).toContain('Some("CmdOrCtrl+]")');
|
||||
expect(tauriLib).toContain("desktop_menu_set_shortcuts");
|
||||
expect(tauriLib).toContain("set_menu_accelerator");
|
||||
});
|
||||
|
||||
it("moves overflowing workspace tabs into a fixed dropdown without horizontal scrolling", () => {
|
||||
it("shows the four most recently opened tasks with a searchable all-tasks menu after five tabs", () => {
|
||||
const source = readDesktopLayoutSource();
|
||||
const tabsStart = source.indexOf('<div v-if="workspaceTabs.length" ref="workspaceTabsElement" class="workspace-tabs">');
|
||||
const tabsStart = source.indexOf('<div v-if="workspaceTabs.length > 1" class="workspace-tabs">');
|
||||
const tabsEnd = source.indexOf('<main class="desktop-content"', tabsStart);
|
||||
const tabsTemplate = source.slice(tabsStart, tabsEnd);
|
||||
|
||||
expect(tabsTemplate).toContain('v-for="item in visibleWorkspaceTabs"');
|
||||
expect(tabsTemplate).toContain('v-if="overflowWorkspaceTabs.length"');
|
||||
expect(tabsTemplate).toContain('v-if="hasWorkspaceTabsOverflow"');
|
||||
expect(tabsTemplate).toContain('popper-class="workspace-tabs-overflow-popper"');
|
||||
expect(tabsTemplate).toContain('{{ overflowWorkspaceTabs.length }}');
|
||||
expect(tabsTemplate).toContain('<span>全部任务</span>');
|
||||
expect(tabsTemplate).toContain('{{ workspaceTabs.length }}');
|
||||
expect(tabsTemplate).toContain('@command="navigateOverflowWorkspaceTab"');
|
||||
expect(tabsTemplate).toContain('gridTemplateColumns: `repeat(${visibleWorkspaceTabs.length}, minmax(0, 238px))`');
|
||||
expect(tabsTemplate).toContain('class="workspace-tabs-overflow-header"');
|
||||
expect(source).toContain("const visibleWorkspaceTabCapacity = computed(() => {");
|
||||
expect(tabsTemplate).toContain('v-model="workspaceTabsSearchQuery"');
|
||||
expect(tabsTemplate).toContain('v-for="item in filteredWorkspaceTabs"');
|
||||
expect(tabsTemplate).toContain('@click.stop="closeOtherWorkspaceTabs(activeMenu)"');
|
||||
expect(tabsTemplate).toContain('@click.stop="closeAllWorkspaceTabs"');
|
||||
expect(source).toContain("const DESKTOP_WORKSPACE_TABS_OVERFLOW_THRESHOLD = 5;");
|
||||
expect(source).toContain("const DESKTOP_WORKSPACE_TABS_RECENT_VISIBLE_COUNT = 4;");
|
||||
expect(source).toContain("const workspaceTabsByRecentUse = computed(() =>");
|
||||
expect(source).toContain("const visibleWorkspaceTabs = computed(() => {");
|
||||
expect(source).toContain("const overflowWorkspaceTabs = computed(() => {");
|
||||
expect(source).toContain('workspaceTabsResizeObserver ||= new ResizeObserver(updateWorkspaceTabsAvailableWidth);');
|
||||
expect(source).toContain("const DESKTOP_WORKSPACE_TABS_OVERFLOW_WIDTH = 106;");
|
||||
expect(source).toContain("const recentVisiblePaths = new Set(");
|
||||
expect(source).toContain("workspaceTabsByRecentUse.value.slice(-DESKTOP_WORKSPACE_TABS_RECENT_VISIBLE_COUNT)");
|
||||
expect(source).toContain("return workspaceTabs.value.filter((item) => recentVisiblePaths.has(item.path));");
|
||||
expect(source).toContain("const filteredWorkspaceTabs = computed(() => {");
|
||||
expect(source).toContain("const closeAllWorkspaceTabs = () => {");
|
||||
expect(source).not.toContain("visibleWorkspaceTabCapacity");
|
||||
expect(source).not.toContain("workspaceTabsResizeObserver");
|
||||
expect(source).toContain(".workspace-tabs-list {\n display: grid;");
|
||||
expect(source).toContain("padding: 0 10px;");
|
||||
expect(source).toContain("padding: 0 14px;");
|
||||
expect(source).toContain("overflow: hidden;");
|
||||
expect(source).not.toContain("overflow-x: auto;");
|
||||
});
|
||||
@@ -584,6 +672,8 @@ describe("desktop layout shell", () => {
|
||||
expect(profile).toContain("overflow: hidden;");
|
||||
expect(profile).not.toContain("overflow: auto;");
|
||||
expect(profile).toContain("overflow-wrap: anywhere;");
|
||||
expect(profile).toContain(".dialog-close:hover");
|
||||
expect(profile).toContain(".dialog-close :deep(.el-icon)");
|
||||
expect(preferences).toContain("flex-wrap: wrap;");
|
||||
expect(preferences).toContain("overflow-wrap: anywhere;");
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user