feat(桌面与监控): 完善工作台导航和登录活动定位
- 优化桌面标签、上下文标题、前进后退、导航栏隐藏和原生菜单体验 - 补充登录会话 IP 采集、地理位置回退、管理端展示及数据库迁移 - 更新桌面发布检查、运维文档和前后端测试覆盖
This commit is contained in:
@@ -174,18 +174,56 @@ const verifyRustBoundary = async () => {
|
||||
singleInstanceIndex,
|
||||
dialogIndex > singleInstanceIndex ? dialogIndex : singleInstanceIndex + 600,
|
||||
);
|
||||
const restoreWindowStart = libSource.indexOf("fn restore_main_window");
|
||||
const restoreWindowEnd = libSource.indexOf("fn is_allowed_shortcut_key", restoreWindowStart);
|
||||
const restoreWindowSource = libSource.slice(restoreWindowStart, restoreWindowEnd);
|
||||
const restoreWindowTokens = ['get_webview_window("main")', "window.unminimize()", "window.show()", "window.set_focus()"];
|
||||
assert(restoreWindowStart >= 0, "Desktop runtime must define a shared main-window restore helper.");
|
||||
assert(
|
||||
singleInstanceSource.includes("restore_main_window(app)"),
|
||||
"Single-instance duplicate launch handler must use the shared main-window restore helper.",
|
||||
);
|
||||
for (const token of restoreWindowTokens) {
|
||||
assert(singleInstanceSource.includes(token), `Single-instance duplicate launch handler must call ${token}.`);
|
||||
assert(restoreWindowSource.includes(token), `Main-window restore helper must call ${token}.`);
|
||||
}
|
||||
assert(
|
||||
singleInstanceSource.indexOf("window.unminimize()") < singleInstanceSource.indexOf("window.show()") &&
|
||||
singleInstanceSource.indexOf("window.show()") < singleInstanceSource.indexOf("window.set_focus()"),
|
||||
"Single-instance duplicate launch handler must restore, show, then focus the main window.",
|
||||
restoreWindowSource.indexOf("window.unminimize()") < restoreWindowSource.indexOf("window.show()") &&
|
||||
restoreWindowSource.indexOf("window.show()") < restoreWindowSource.indexOf("window.set_focus()"),
|
||||
"Main-window restore helper must restore, show, then focus the main window.",
|
||||
);
|
||||
assert(libSource.includes("RunEvent::Reopen"), "macOS Dock reopen events must restore the main window.");
|
||||
assert(libSource.includes("WebviewWindowBuilder::from_config"), "Dock reopen must rebuild a main window that was actually closed.");
|
||||
assert(libSource.includes('find(|config| config.label == "main")'), "Main-window rebuild must use only the configured main window.");
|
||||
assert(!libSource.includes("WindowEvent::CloseRequested"), "The macOS red close button must keep its native close-window semantics.");
|
||||
assert(!libSource.includes("api.prevent_close()"), "The macOS red close button must not be converted into a hide action.");
|
||||
assert(!libSource.includes("window.hide()"), "The macOS red close button must not hide the main window.");
|
||||
|
||||
const appMenuIndex = libSource.indexOf('package.name.clone()');
|
||||
const fileMenuIndex = libSource.indexOf('"文件"');
|
||||
assert(appMenuIndex >= 0 && appMenuIndex < fileMenuIndex, "macOS application menu must precede the File menu.");
|
||||
for (const token of [
|
||||
'Some("关于 CTMS")',
|
||||
"short_version: Some(String::new())",
|
||||
"icon: handle.default_window_icon().cloned()",
|
||||
'"ctms.desktop.preferences"',
|
||||
"PredefinedMenuItem::services",
|
||||
"PredefinedMenuItem::hide",
|
||||
"PredefinedMenuItem::hide_others",
|
||||
"PredefinedMenuItem::show_all",
|
||||
"PredefinedMenuItem::quit",
|
||||
"WINDOW_SUBMENU_ID",
|
||||
"HELP_SUBMENU_ID",
|
||||
"TOGGLE_SIDEBAR_COMMAND_ID",
|
||||
'"显示/隐藏导航栏"',
|
||||
]) {
|
||||
assert(libSource.includes(token), `Desktop menu must include ${token}.`);
|
||||
}
|
||||
|
||||
const handlerSource = libSource.match(/generate_handler!\s*\\?\[([\s\S]*?)\]/)?.[1] || "";
|
||||
const commands = handlerSource.match(/[a-z_]+::[a-z_]+/g) || [];
|
||||
const commands = handlerSource
|
||||
.split(",")
|
||||
.map((command) => command.trim())
|
||||
.filter(Boolean);
|
||||
const allowedCommands = [
|
||||
"credentials::credential_get",
|
||||
"credentials::credential_set",
|
||||
@@ -195,6 +233,8 @@ const verifyRustBoundary = async () => {
|
||||
"credentials::login_credential_delete",
|
||||
"updates::desktop_update_check",
|
||||
"updates::desktop_update_install",
|
||||
"desktop_menu_set_shortcuts",
|
||||
"desktop_window_set_theme",
|
||||
];
|
||||
const unexpected = commands.filter((command) => !allowedCommands.includes(command));
|
||||
const missing = allowedCommands.filter((command) => !commands.includes(command));
|
||||
@@ -202,6 +242,20 @@ const verifyRustBoundary = async () => {
|
||||
assert(missing.length === 0, `Missing expected Tauri commands: ${missing.join(", ") || "<none>"}.`);
|
||||
};
|
||||
|
||||
const verifyDesktopUiNativeSync = async () => {
|
||||
const menuSource = await readFile(resolve(sourceDir, "runtime/desktopMenu.ts"), "utf8");
|
||||
const preferencesSource = await readFile(resolve(sourceDir, "runtime/desktopUiPreferences.ts"), "utf8");
|
||||
const appSource = await readFile(resolve(sourceDir, "App.vue"), "utf8");
|
||||
|
||||
assert(menuSource.includes('invoke("desktop_menu_set_shortcuts"'), "Desktop shortcuts must sync through the controlled Tauri command.");
|
||||
assert(menuSource.includes("DESKTOP_SHORTCUTS_CHANGED_EVENT"), "Native menu shortcuts must track preference changes.");
|
||||
assert(preferencesSource.includes('"system" | "light" | "dark"'), "Desktop theme must support following the system appearance.");
|
||||
assert(preferencesSource.includes('invoke("desktop_window_set_theme"'), "Desktop window theme must sync through the controlled Tauri command.");
|
||||
assert(preferencesSource.includes('matchMedia("(prefers-color-scheme: dark)")'), "System theme changes must update the WebView appearance.");
|
||||
assert(appSource.includes("initializeDesktopThemePreference()"), "Desktop theme synchronization must initialize at app startup.");
|
||||
assert(appSource.includes("initializeDesktopMenuShortcutSync()"), "Native menu shortcut synchronization must initialize at app startup.");
|
||||
};
|
||||
|
||||
const verifySourceSafety = async () => {
|
||||
const sourceExtensions = new Set([".ts", ".tsx", ".vue", ".js", ".jsx", ".rs"]);
|
||||
const files = [
|
||||
@@ -358,6 +412,7 @@ const verifyWorkflowGates = async () => {
|
||||
await verifyTauriConfig();
|
||||
await verifyCapabilities();
|
||||
await verifyRustBoundary();
|
||||
await verifyDesktopUiNativeSync();
|
||||
await verifySourceSafety();
|
||||
await verifyNotificationBoundary();
|
||||
await verifySessionBoundary();
|
||||
|
||||
Reference in New Issue
Block a user