Files
ClassHelper/scripts/nsis/server-installer.nsi
T
LoveChengke f759aac0a2 feat(web-admin): 手机小屏适配(1Panel 风格抽屉导航)并补齐移动端自动化验证
## 布局与交互(沿用 1Panel 风格)

- 新增 `src/composables/useResponsive.ts`:断点 768 / 992px + resize/orientationchange 监听
- `AdminLayout.vue`:
  - >992px 桌面保持原样(210px 深色侧栏 + 顶栏连接状态 + "已接收 N 条实时事件")
  - <=768px 手机:侧边栏不再渲染,顶栏出现汉堡按钮(`aria-expanded` 标记状态),
    点击唤出 240px 深色抽屉菜单,点菜单项自动收起;顶栏显示当前页标题,
    用户菜单收成头像图标(身份信息移入下拉菜单首行)
  - 复用同一套 `.ch-sidebar` 样式,抽屉内容与桌面侧栏视觉一致
- `styles/index.css` 增加全局响应式规则:工具栏控件铺满整行、`.table-card` 表格卡片内横向滚动、
  弹窗 `calc(100vw - 24px)`、消息框/抽屉自适应、`.stat-grid` 小屏两列、分页居中、标签页可横滑
- 各视图:7 个表格卡片加 `.table-card`;班级详情/作业详情的 `el-descriptions` 小屏降为单列;
  登录页卡片自适应宽度并加安全内边距

## 自动化验证(scripts/ui-smoke)

- 新增 `runMobileChecks`:把窗口真实缩到 390×844,用真实点击验证 7 项——
  侧边栏收起 + 汉堡出现、抽屉滑出且菜单完整、抽屉内点击"通知发布"跳转并收起、
  页面无横向溢出(表格自身滚动不计入)、表格在卡片内可滚动、工具栏控件铺满、弹窗宽度自适应
- 移动端截图留档到 `docs/screenshots/web-mobile/`(`UI_SMOKE_SHOTS_DIR` 可覆盖)
- 踩坑修复:隐藏窗口下 Chromium 会冻结 CSS transition,Vue `<Transition>` 收不到 transitionend,
  Element Plus 抽屉会卡在 `enter-from`(表现为"点了没反应")——移动端检查改为 `showInactive()` 并断言
  "抽屉滑到位/收起到位",而不是只看元素是否存在

## 安装包升级健壮性

- `scripts/nsis/server-installer.nsi`:覆盖安装前先 `RMDir /r web|server|node_modules`,
  避免旧 hash 分片、旧 dist 模块、旧依赖残留导致"新旧版本混装"
  (此前 web/assets 里累积了 4 份 AdminLayout 分片)

## 验证结果(均在安装后的生产实例上)

- `pnpm verify:web` 15/15 ✅(8 项原有 + 7 项移动端)
- `pnpm verify:e2e` 62/62 ✅
- 覆盖安装:`.env` 与数据库保持不变,`web/assets` 只剩当前版本分片,服务 `healthz` 正常
- `pnpm lint` / `format:check` / `typecheck` 全部通过
2026-09-12 10:09:11 +08:00

164 lines
6.8 KiB
NSIS

; 班级小助手服务端 + Web 管理端 Windows 安装程序
; 由 scripts/dist-server.mjs 生成(占位符在打包时替换)
;
; 设计要点:
; - 当前用户级安装($LOCALAPPDATA\Programs),无需管理员权限即可完成安装
; - 内置 Node 运行时,目标机无需安装 Node.js
; - 快捷方式:启动/停止/重启服务、打开管理端、卸载
; - 默认加入当前用户开机自启(注册表 HKCU\...\Run),随时可用
; - 卸载时保留 data 目录(数据库)以避免误删数据
Unicode true
SetCompressor /SOLID lzma
!include "MUI2.nsh"
!include "FileFunc.nsh"
!define PRODUCT "@PRODUCT@"
!define VERSION "@VERSION@"
!define STAGING "@STAGING@"
!define OUT_FILE "@OUTFILE@"
!define ICON_FILE "@ICON@"
!define APP_PORT "@PORT@"
Name "${PRODUCT} ${VERSION}"
OutFile "${OUT_FILE}"
InstallDir "$LOCALAPPDATA\Programs\ClassHelperServer"
InstallDirRegKey HKCU "Software\ClassHelperServer" "InstallDir"
RequestExecutionLevel user
ShowInstDetails show
ShowUninstDetails show
!define MUI_ICON "${ICON_FILE}"
!define MUI_UNICON "${ICON_FILE}"
!define MUI_ABORTWARNING
!define MUI_FINISHPAGE_TITLE "安装完成"
!define MUI_FINISHPAGE_TEXT "班级小助手服务端已安装完成。$\r$\n$\r$\n安装程序已将服务加入当前用户的开机自启项,浏览器打开 http://127.0.0.1:${APP_PORT}/ 即可使用管理端。$\r$\n$\r$\n默认账号:admin / admin123(首次登录后请立即修改密码)"
!define MUI_FINISHPAGE_RUN
!define MUI_FINISHPAGE_RUN_TEXT "立即启动服务并打开管理端"
!define MUI_FINISHPAGE_RUN_FUNCTION LaunchService
!define MUI_LICENSEPAGE_TEXT_TOP "请阅读以下说明后继续安装。"
!insertmacro MUI_PAGE_WELCOME
!insertmacro MUI_PAGE_LICENSE "${STAGING}\LICENSE.txt"
!insertmacro MUI_PAGE_DIRECTORY
!insertmacro MUI_PAGE_INSTFILES
!insertmacro MUI_PAGE_FINISH
!insertmacro MUI_UNPAGE_CONFIRM
!insertmacro MUI_UNPAGE_INSTFILES
!insertmacro MUI_LANGUAGE "SimpChinese"
Function LaunchService
ExecShell "open" "$INSTDIR\start.cmd" "" SW_SHOWMINIMIZED
Sleep 3000
ExecShell "open" "http://127.0.0.1:${APP_PORT}/"
FunctionEnd
; ------------------------------------------------------------------ 安装
Section "主程序" SecMain
SectionIn RO
SetOutPath "$INSTDIR"
; 若目标目录已有旧版本,先停止正在运行的服务,避免文件占用
IfFileExists "$INSTDIR\data\server.pid" 0 +3
nsExec::ExecToLog '"$INSTDIR\stop.cmd"'
Sleep 1500
; 升级安装时保留原有配置(含 JWT 密钥与端口设置),避免用户被强制重新登录
IfFileExists "$INSTDIR\.env" 0 +2
Rename "$INSTDIR\.env" "$INSTDIR\.env.previous"
; 先清掉旧版本的代码目录再铺新文件:
; 否则 web/assets 里的旧 hash 分片、server/dist 旧模块、node_modules 旧依赖会残留,
; 出现"新旧版本混装"(例如 shared/dist 是旧的,服务启动直接报找不到导出)。
RMDir /r "$INSTDIR\web"
RMDir /r "$INSTDIR\server"
RMDir /r "$INSTDIR\node_modules"
File /r "${STAGING}\*.*"
IfFileExists "$INSTDIR\.env.previous" 0 +3
Delete "$INSTDIR\.env"
Rename "$INSTDIR\.env.previous" "$INSTDIR\.env"
WriteRegStr HKCU "Software\ClassHelperServer" "InstallDir" "$INSTDIR"
WriteRegStr HKCU "Software\ClassHelperServer" "Version" "${VERSION}"
WriteUninstaller "$INSTDIR\uninstall.exe"
; 开始菜单
CreateDirectory "$SMPROGRAMS\${PRODUCT}"
CreateShortCut "$SMPROGRAMS\${PRODUCT}\启动服务.lnk" "$INSTDIR\start.cmd" "" "$INSTDIR\node.exe" 0 SW_SHOWMINIMIZED
CreateShortCut "$SMPROGRAMS\${PRODUCT}\停止服务.lnk" "$INSTDIR\stop.cmd" "" "$INSTDIR\node.exe" 0 SW_SHOWMINIMIZED
CreateShortCut "$SMPROGRAMS\${PRODUCT}\重启服务.lnk" "$INSTDIR\restart.cmd" "" "$INSTDIR\node.exe" 0 SW_SHOWMINIMIZED
CreateShortCut "$SMPROGRAMS\${PRODUCT}\打开管理端.lnk" "http://127.0.0.1:${APP_PORT}/"
CreateShortCut "$SMPROGRAMS\${PRODUCT}\使用说明.lnk" "$INSTDIR\README.txt"
CreateShortCut "$SMPROGRAMS\${PRODUCT}\卸载.lnk" "$INSTDIR\uninstall.exe"
; 桌面快捷方式(管理端进浏览器)
CreateShortCut "$DESKTOP\${PRODUCT}.lnk" "http://127.0.0.1:${APP_PORT}/"
; 开机自启(当前用户)
WriteRegStr HKCU "Software\Microsoft\Windows\CurrentVersion\Run" "ClassHelperServer" '"$INSTDIR\start.cmd"'
; 注册到"应用和功能"列表,便于通过系统设置卸载
${GetSize} "$INSTDIR" "/S=0K" $0 $1 $2
WriteRegStr HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\ClassHelperServer" "DisplayName" "${PRODUCT} ${VERSION}"
WriteRegStr HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\ClassHelperServer" "DisplayIcon" "$INSTDIR\uninstall.exe"
WriteRegStr HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\ClassHelperServer" "DisplayVersion" "${VERSION}"
WriteRegStr HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\ClassHelperServer" "Publisher" "Class Helper"
WriteRegStr HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\ClassHelperServer" "InstallLocation" "$INSTDIR"
WriteRegStr HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\ClassHelperServer" "UninstallString" '"$INSTDIR\uninstall.exe"'
WriteRegDWORD HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\ClassHelperServer" "EstimatedSize" "$0"
; 首次启动即初始化数据库并开始提供服务
ExecShell "open" "$INSTDIR\start.cmd" "" SW_SHOWMINIMIZED
SectionEnd
; ------------------------------------------------------------------ 卸载
Section "Uninstall"
; 停止服务
IfFileExists "$INSTDIR\data\server.pid" 0 +3
nsExec::ExecToLog '"$INSTDIR\stop.cmd"'
Sleep 1200
; 兜底:按命令行特征结束残留进程
nsExec::ExecToLog 'taskkill /F /FI "WINDOWTITLE eq ClassHelperServer*"'
DeleteRegValue HKCU "Software\Microsoft\Windows\CurrentVersion\Run" "ClassHelperServer"
DeleteRegKey HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\ClassHelperServer"
DeleteRegKey HKCU "Software\ClassHelperServer"
Delete "$DESKTOP\${PRODUCT}.lnk"
RMDir /r "$SMPROGRAMS\${PRODUCT}"
; 保留数据目录,避免误删数据库:重命名为 data.backup
IfFileExists "$INSTDIR\data\classhelper.db" 0 +3
RMDir /r "$INSTDIR\data.backup"
Rename "$INSTDIR\data" "$INSTDIR\data.backup"
RMDir /r "$INSTDIR\server"
RMDir /r "$INSTDIR\web"
RMDir /r "$INSTDIR\node_modules"
RMDir /r "$INSTDIR\logs"
Delete "$INSTDIR\node.exe"
Delete "$INSTDIR\package.json"
Delete "$INSTDIR\package-lock.json"
Delete "$INSTDIR\LICENSE.txt"
Delete "$INSTDIR\start.cmd"
Delete "$INSTDIR\stop.cmd"
Delete "$INSTDIR\restart.cmd"
Delete "$INSTDIR\status.cmd"
Delete "$INSTDIR\install-service.cmd"
Delete "$INSTDIR\uninstall-service.cmd"
Delete "$INSTDIR\README.txt"
Delete "$INSTDIR\uninstall.exe"
; 若用户未保留 .env,则一并清理目录
Delete "$INSTDIR\.env"
RMDir "$INSTDIR"
SectionEnd