feat: 生产化改造(服务端安装程序/PWA/Docker+HTTPS)并修复侧边栏点击无反应、SPA 静态托管 dotfiles 两个缺陷
- 修复 web-admin 与 desktop-client 侧边栏点击无反应:el-menu 的 index 是路径,不能当路由名使用 - 修复 Web 产物目录含点号目录(如 .cache)时 res.sendFile 因 dotfiles:ignore 返回 500 - 后端生产模式:静态托管 Web 端 + SPA 回退、helmet、限流(通用/登录)、trust proxy、 /healthz 与 /readyz、请求耗时日志、生产环境变量强校验、首启动自动迁移与创建管理员、PID 文件 - Web 管理端 PWA:manifest + service worker + 图标,可在浏览器安装为应用 - 新增服务端 + Web 管理端 Windows 安装程序(内置 Node 运行时、启停与服务脚本、开机自启、卸载保留数据) - 新增 Docker/Compose/Nginx 部署样例与 docs/production.md - 新增 UI 回归测试:Web 端 Electron 驱动真实点击 7 项、客户端冒烟 11 项(含侧边栏点击与离线回退)
This commit is contained in:
1 parent
6e4c7c1050
commit
5545ef6f55
46 files changed
+3020
-152
No files matched your search
@@ -31,10 +31,12 @@ export async function runSmokeTest(win: BrowserWindow): Promise<void> {
|
||||
appChildren: number;
|
||||
hasLoginPage: boolean;
|
||||
hasInput: boolean;
|
||||
hasLayout: boolean;
|
||||
text: string;
|
||||
} | null = null;
|
||||
for (let attempt = 0; attempt < 56; attempt += 1) {
|
||||
dom = await win.webContents.executeJavaScript(
|
||||
|
||||
const snapshot = async (): Promise<typeof dom> =>
|
||||
win.webContents.executeJavaScript(
|
||||
`(() => {
|
||||
const app = document.querySelector('#app');
|
||||
return {
|
||||
@@ -42,15 +44,37 @@ export async function runSmokeTest(win: BrowserWindow): Promise<void> {
|
||||
appChildren: app ? app.children.length : 0,
|
||||
hasLoginPage: Boolean(document.querySelector('.login-page')),
|
||||
hasInput: Boolean(document.querySelector('input')),
|
||||
hasLayout: document.querySelectorAll('.el-menu-item').length > 0,
|
||||
text: (document.body.innerText || '').replace(/\\s+/g, ' ').slice(0, 80),
|
||||
};
|
||||
})()`,
|
||||
);
|
||||
// 等到登录页真正渲染出来(而不是停在启动过渡页)
|
||||
if (dom?.hasLoginPage && dom?.hasInput) break;
|
||||
|
||||
for (let attempt = 0; attempt < 56; attempt += 1) {
|
||||
dom = await snapshot();
|
||||
// 等到登录页或主布局真正渲染出来(而不是停在启动过渡页)
|
||||
if ((dom?.hasLoginPage && dom?.hasInput) || dom?.hasLayout) break;
|
||||
await new Promise((resolve) => setTimeout(resolve, 250));
|
||||
}
|
||||
|
||||
// 上次运行可能残留登录态(客户端会直接进主界面)——先退出登录,再校验登录页
|
||||
if (dom?.hasLayout && !dom?.hasLoginPage) {
|
||||
console.log('[SMOKE] 检测到已保存的登录态,先退出登录再校验登录页');
|
||||
await win.webContents.executeJavaScript(
|
||||
`(async () => {
|
||||
if (!window.__classhelperSmoke__) return false;
|
||||
await window.__classhelperSmoke__.sessionCleanup();
|
||||
location.hash = '#/login';
|
||||
return true;
|
||||
})()`,
|
||||
);
|
||||
for (let attempt = 0; attempt < 40; attempt += 1) {
|
||||
dom = await snapshot();
|
||||
if (dom?.hasLoginPage && dom?.hasInput) break;
|
||||
await new Promise((resolve) => setTimeout(resolve, 250));
|
||||
}
|
||||
}
|
||||
|
||||
record('渲染进程挂载(#app 有子节点)', (dom?.appChildren ?? 0) > 0, `children=${dom?.appChildren ?? 0}`);
|
||||
record(
|
||||
'窗口标题正确',
|
||||
@@ -120,10 +144,27 @@ export async function runSmokeTest(win: BrowserWindow): Promise<void> {
|
||||
})()`,
|
||||
);
|
||||
record(
|
||||
'联网集成(登录 + 四类数据 + Socket.IO + 缓存写入)',
|
||||
'联网集成(登录 + 布局挂载 + 四类数据 + Socket.IO + 缓存写入)',
|
||||
Boolean(online?.ok),
|
||||
String(online?.detail ?? ''),
|
||||
);
|
||||
|
||||
// 侧边栏点击导航(回归测试:曾因把 index 当路由名导致点击无反应)
|
||||
const navigation = await win.webContents.executeJavaScript(
|
||||
`(async () => {
|
||||
if (!window.__classhelperSmoke__) return { ok: false, detail: '渲染进程未注册冒烟钩子' };
|
||||
return await window.__classhelperSmoke__.layoutNavigationSelfTest();
|
||||
})()`,
|
||||
);
|
||||
record('侧边栏点击导航(逐一点击 5 个菜单)', Boolean(navigation?.ok), String(navigation?.detail ?? ''));
|
||||
|
||||
const cleanup = await win.webContents.executeJavaScript(
|
||||
`(async () => {
|
||||
if (!window.__classhelperSmoke__) return { ok: false, detail: '渲染进程未注册冒烟钩子' };
|
||||
return await window.__classhelperSmoke__.sessionCleanup();
|
||||
})()`,
|
||||
);
|
||||
record('冒烟收尾:清理登录态', Boolean(cleanup?.ok), String(cleanup?.detail ?? ''));
|
||||
}
|
||||
|
||||
const failed = results.filter((item) => !item.ok);
|
||||
|
||||
Reference in new issue
Block a user