Files
LoveChengke 3a9f8812fc feat: 客户端灵动岛 + 上课时段紧急通知二次确认,并修复打包/校验脚本在 Electron 宿主下的多个缺陷
## 客户端灵动岛(动态岛)

- 新增独立透明置顶窗口:桌面中上方常驻、不占任务栏、不抢焦点(268x44 胶囊 / 404x316 详情 / 424x344 紧急)
- 三种形态与动画:隐藏 ↔ 胶囊 ↔ 详情,尺寸用 easeOutCubic 260ms 逐帧缓动、透明度单独淡入淡出
  (尺寸与淡入使用各自动画令牌,避免互相打断导致窗口卡在胶囊尺寸)
- 行为:普通通知变「新消息 · 共 N 条」胶囊,点击展开详情;上课时段自动隐藏并进入待发队列,
  下课后自动弹出;紧急通知无论是否上课立即展开(红色呼吸光晕 + 紧急角标,无需点击)
- 上课状态来自 GET /api/schedules/current(10s 心跳 + 下课时长定时器),
  新增 src/main/island.ts(控制器/IPC)、src/preload/island.ts、src/island/IslandApp.vue、
  src/renderer/island/bridge.ts、island.html 与多页 Vite 构建

## 上课时段发布紧急通知的二次确认

- 服务端:priority=URGENT 且当前处于上课时间段且未带 confirmDuringClass 时返回
  409 URGENT_DURING_CLASS(携带 current/next/week/serverTime),绕过界面调接口同样被拦截
- 新增 GET /api/schedules/current(inClass/current/next,at 参数用于诊断与测试)
- Web 管理端:全屏警告遮罩(UrgentClassWarning.vue),显示正在上的课时段与待发布标题,
  确认按钮带 3 秒倒计时(圆环进度,倒计时结束前禁用),取消即不发;并发命中 409 同样兜底弹窗

## 打包与验证脚本修复(Electron 宿主环境)

- scripts/lib/node-runtime.mjs:定位真正的 node.exe。此前经 pnpm 运行时 process.execPath 指向
  Electron,dist:server 会把 Electron 复制成安装包内的 node.exe,dist:win 会让 electron-builder 报
  Unknown argument: .../cli.js(改为显式使用真实 Node)
- scripts/lib/electron-env.mjs:启动 Electron 前清理继承的 ELECTRON_RUN_AS_NODE(否则 Electron 退化为
  纯 Node,require('electron') 直接 MODULE_NOT_FOUND),并从 path.txt 定位 electron.exe
  (process.versions.electron 存在时 import 'electron' 返回的是 API 对象而非路径)
- scripts/dist-server.mjs:新增构建产物新鲜度校验(曾出现 server 已重建但打进安装包的 shared/dist 是旧的,
  安装后服务启动即报 does not provide an export named MAX_TERM_WEEK)
- scripts/ui-smoke:新增 live-probe.mjs 真实制造/清理上课时段,Electron 驱动真实点击断言全屏警告文案、
  按钮禁用与 3 秒倒计时时长(实测 2966ms),测试后自动删除探针课表与测试通知

## 验证结果

- pnpm verify:e2e 62/62(新增上课时段拦截、二次确认后发布、普通通知不受限等 8 项)
- pnpm verify:web 8/8(新增上课时段紧急通知全屏警告 + 倒计时)
- pnpm verify:desktop 18/18(新增灵动岛窗口/上课隐藏/下课弹出/紧急立即展开/胶囊点击展开 + 截图)
- 打包后 EXE 冒烟 15/15(packaged: true,退出码 0)
- 服务端安装程序升级安装:.env 与数据库保持不变,/api/schedules/current 正常
- pnpm lint / format:check / typecheck 全部通过
2026-09-12 08:41:08 +08:00

43 lines
1.9 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 启动 Electron 应用时的公共辅助:环境变量清理 + 可执行文件定位。
*
* 背景(本机真实踩过的两个坑,都发生在"宿主终端本身就是 Electron"的场景,
* 例如 DSH 桌面端内置终端 / VS Code 扩展宿主):
*
* 1. 宿主会带着 `ELECTRON_RUN_AS_NODE=1` 运行子进程(本机的 `pnpm` 运行在
* Electron 内置 Node 上,因此经过 `pnpm run` 的脚本都会带着它)。
* 该变量会一路继承到我们用 `spawn` 拉起的 Electron 上,使其**退化成纯 Node**:
* `process.versions.electron` 仍在,但 `require('electron')` 抛 MODULE_NOT_FOUND,
* 表现为"冒烟测试/开发模式一启动就报 Cannot find module 'electron'"。
* 2. 同样因为 `process.versions.electron` 存在,`require('electron')` / `import electronPath
* from 'electron'` 返回的是 **API 对象而不是可执行文件路径**,直接 spawn 会报
* `ERR_INVALID_ARG_TYPE: The "file" argument must be of type string`。
*
* 因此:启动 Electron 一律用 `resolveElectronEnv()` + `resolveElectronExecutable()`,
* 不要直接 import 'electron'。
*/
import fs from 'node:fs';
import path from 'node:path';
/** 清掉会让 Electron 退化成纯 Node 的继承变量,可附加自定义变量 */
export function resolveElectronEnv(extra = {}) {
const env = { ...process.env, ...extra };
delete env.ELECTRON_RUN_AS_NODE;
delete env.ELECTRON_NO_ATTACH_CONSOLE;
return env;
}
/**
* 由 `node_modules/electron` 目录定位真正的 Electron 可执行文件。
* @param {string} electronDir `node_modules/electron` 的绝对路径
*/
export function resolveElectronExecutable(electronDir) {
const pathFile = path.join(electronDir, 'path.txt');
const binaryName = fs.existsSync(pathFile)
? fs.readFileSync(pathFile, 'utf8').trim()
: process.platform === 'win32'
? 'electron.exe'
: 'electron';
return path.join(electronDir, 'dist', binaryName);
}