Files
classhelper-island-refactor…/out/vol1-总纲与技术栈(00-02).md
ClassIsland Prompt Kit 22c1784bab feat: ClassIsland / ClassHelper 灵动岛提示词套件
- 重构版主提示词(14 parts,面向 ClassHelper 灵动岛重构)
- ClassIsland 全量规格提示词(20 parts,含分卷与逐章版本)
- 投喂指南、分卷、逐节文件
- 调研记录、一致性基线、生成与质检脚本
2026-11-12 21:59:58 +08:00

840 lines
83 KiB
Markdown
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.
# ClassIsland 复刻工程:给 ChatGPT 的完整实现提示词(ClassHelper 技术栈版)
> **文档性质**:这是一份「提示词」而非分析报告。请把本文件(或按章节分卷)直接投喂给 ChatGPT,要求它按第 17 部分的里程碑从零实现一个 ClassIsland 等价应用;实现技术栈采用 ClassHelper 桌面端(Electron 44 + Vue 3.5 + Element Plus + Pinia + Vite 8 + TypeScript 5.9 + pnpm monorepo),以等价替换 ClassIsland 原项目的 C# / .NET 8 / Avalonia / FluentAvalonia / XAML。
>
> **信息来源**:ClassIsland v2.1 源码(`repos/ClassIsland`,C#/Avalonia)与 ClassHelper 源码(`repos/ClassHelper-master`,TypeScript/Electron)。文中所有确切数值、资源名、行为均尽量标注 `文件:行号` 证据;无源码依据处标注【推断】。ClassIsland 的截图与文档仅用于校对视觉语义,不复制其代码。
>
> **重要说明**:本提示词要求实现者的一切自动验证(构建、冒烟、截图、动画检查)必须**无头/离屏运行,禁止弹出可见窗口、禁止播放声音、禁止干扰用户桌面**。这是本项目的强制纪律,也是验收的一部分(见第 18 部分)。
## 使用说明(给人类读者,不投喂给模型)
1. **整体投喂**:全文约 200k tokens,需使用上下文 ≥ 200k 的模型(如 GPT-5 级长上下文 / Gemini 2.5 Pro)才能一次粘贴;将本文件从「## 0.」到结尾整体粘贴,尾附一句:「请严格按此提示词实现,先给出实施计划与文件树,再按里程碑推进。」
2. **分卷投喂**:按 `## 0` + `## 1` + `## 2`(基础)→ `## 3`~`## 5`(视觉与组件)→ `## 6`~`## 8`(业务)→ `## 9`~`## 13`(系统与数据)→ `## 14`~`## 19`(动画、资源、验收)的顺序分批发送;每批发完后要求模型复述关键参数,避免长上下文丢失。
3. **推荐追问**:「请先输出 20 个你认为最可能歧义的点」「请给出 packages/desktop-client 的完整文件树与每个文件的职责」「请先实现 M0/M1 并给出无头验证脚本」。
4. **证据约定**:文中 `repos/ClassIsland/...` 与 `repos/ClassHelper-master/...` 是规格来源路径,不是让你照抄的运行时依赖;实现时应把这些规格转写为 TypeScript/CSS。
## 目录
- 使用说明(给人类读者,不投喂给模型)
- 0. 给 ChatGPT 的任务声明(从本节开始执行)
- 1. 项目总纲:产品定位、总体体验与系统架构
- 1.1 你的角色与最终目标
- 1.2 产品定位
- 1.3 目标用户与典型场景
- 1.4 十条不可妥协的体验原则
- 1.5 功能全景(必须实现的模块)
- 1.6 总体技术架构(ClassHelper 栈)
- 1.7 信息架构与导航
- 1.8 首次启动与默认状态
- 1.9 本提示词的使用方式与验收总则
- 2. 技术栈与工程结构
- 2.1 技术选型总则(必须逐条遵守)
- 2.2 依赖清单(版本必须按此锁定)
- 2.3 仓库目录结构(必须逐项落地)
- 2.4 进程模型(必须五类进程分离)
- 2.5 构建管线(命令与产物必须一致)
- 2.6 应用启动时序(必须按此顺序,不得提前读配置)
- 2.7 窗口参数(像素级基线)
- 2.8 主进程核心模块(ClassIsland 服务层在 Electron 下的等价物)
- 2.9 渲染层挂载顺序(必须逐行对齐)
- 2.10 岛渲染进程的技术约定(必须与 ClassIsland 视觉对齐)
- 2.11 自动更新与打包(工程结构的一部分)
- 2.12 ClassIsland → ClassHelper 技术映射决策表(必须按此实现)
- 2.13 第三方库等价映射(.NET/Avalonia → npm,必须按此实现)
- 2.14 共享包(`packages/shared`)的工程约定
- 2.15 共享 UI 包(`packages/ui`)与样式令牌工程
- 2.16 主进程并发与 I/O 纪律(直接决定"卡不卡")
- 2.17 安全基线(不得为了"方便"削弱任何一条)
- 2.18 环境变量、命令行参数与调试开关
- 2.19 性能预算(超预算必须优化,不得降级功能)
- 2.20 本地化与文案工程
- 2.21 验收标准(第 2 部分)
- 3. 设计系统 Token 总表(颜色 / 字体 / 圆角 / 间距 / 阴影 / 层级 / DPI / 深浅色)
- 3.1 颜色 Token
- 3.2 字体 Token
- 3.3 圆角 Token
- 3.4 间距 Token
- 3.5 透明与模糊 Token
- 3.6 层级(z-index)Token
- 3.7 DPI 与缩放规则
- 3.8 深浅色规则
- 3.9 ClassHelper 侧落点(实现映射表)
- 3.10 落地顺序与常见错误
- 3.11 验收标准
- 4. 主界面(岛):窗口、行、背景、进度、遮罩、编辑模式
- 4.1 术语与实现映射总则
- 4.2 窗口(BrowserWindow)逐项规格
- 4.3 几何:包围盒、可见内容盒与编辑画布
- 4.4 DOM 层级与 CSS 变量
- 4.5 主界面行(MainWindowLine)
- 4.6 主界面的缩放(裁决:只做缩放,不做平移)
- 4.7 提醒宿主、遮罩与进度条(行内)
- 4.8 编辑模式
- 4.9 隐藏与显示规则(完整判定)
- 4.10 右键菜单(原生菜单)与托盘
- 4.11 本部分动画总表(照此实现,不得自创)
- 4.12 验收标准
- 5. 组件系统:全部内置组件逐个规格
- 5.1 组件系统总则
- 5.2 日期(DateComponent)
- 5.3 时钟(ClockComponent)
- 5.4 倒计时(CountDownComponent)
- 5.5 课程表(ScheduleComponent)
- 5.6 天气简报(WeatherComponent)
- 5.7 文本(TextComponent)
- 5.8 滚动容器(RollingComponent)
- 5.9 轮播容器(SlideComponent)
- 5.10 分组容器(GroupComponent)
- 5.11 堆叠容器(StackComponent)
- 5.12 分割线(SeparatorComponent)
- 5.13 组件设置 UI(组件设置页 + 高级设置页)
- 5.14 空数据与异常兜底(逐组件)
- 5.15 Vue 组件结构与 CSS 变量
- 5.16 验收标准
- 6. 课程表 / 时间表 / 档案 / 科目 / 周次 / 调课 / 导入导出
- 6.1 数据结构(必须逐字段实现)
- 6.2 当天应使用哪张课表:选择算法(必须逐分支实现)
- 6.3 时间状态机与课程计算(主循环)
- 6.4 周次轮换(单双周 / 多周)
- 6.5 时间表编辑(档案编辑窗口 · 时间表页)
- 6.6 课表编辑页与「换课 / 调课 / 临时层」
- 6.7 档案管理(多档案)
- 6.8 科目管理
- 6.9 导入 / 导出
- 6.10 附加设置(AttachedObjects)模型
- 6.11 档案分析(可视化辅助)
- 6.12 边界与异常(必须处理)
- 6.13 验收标准
- 7. 提醒系统(队列、提供者、气泡、强提醒、音效与 TTS)
- 7.1 核心数据结构
- 7.2 队列、优先级与去重(最终确定语义)
- 7.3 提供者与渠道注册
- 7.4 内置提供者行为(逐个实现)
- 7.5 消费者(谁负责显示)
- 7.6 播放流程(气泡两阶段 + 状态机)
- 7.7 气泡视觉与动画参数(像素级)
- 7.8 强提醒:全屏强调特效
- 7.9 声音、语音与音量
- 7.10 取消与清理
- 7.11 设置页(提醒)UI 规格
- 7.12 边界与异常(必须处理)
- 7.13 验收标准
- 7.14 天气服务与数据模型
- 8. 自动化(触发器 / 行动 / 规则集)
- 8.1 概念与总开关
- 8.2 数据结构(必须逐字段实现)
- 8.3 加载与生命周期(必须实现资源纪律)
- 8.4 执行语义(触发 / 规则 / 恢复 / 中断)
- 8.5 内置行动(逐个实现,共 8 个)
- 8.6 内置触发器(逐个实现,共 12 个)
- 8.7 规则集求值(算法必须逐字实现)
- 8.8 内置规则(逐个实现,共 15 个)
- 8.9 时间点行动(TimeType = 3)
- 8.10 设置页 UI(自动化)
- 8.11 边界与异常(必须处理)
- 8.12 验收标准
- 9. 主题系统(内置主题、主题包格式、壁纸取色、主题预览、自定义颜色)
- 9.1 应用主题(明暗 + 强调色)
- 9.2 内置主界面主题
- 9.3 主题包格式(必须实现安装/卸载/打包/市场)
- 9.4 主题包在 ClassHelper 栈中的落地格式(CSS 版)
- 9.5 XAML 主题 → CSS 主题的映射规则
- 9.6 壁纸/屏幕取色
- 9.7 自定义颜色(用户手动覆盖)
- 9.8 主题预览卡片(主题市场 UI)
- 9.9 ClassHelper 侧映射
- 9.10 外观设置页(主题相关控件的精确规格)
- 9.11 主题调试、兼容性与安全边界
- 9.12 验收标准
- 10. 设置中心
- 10.1 设置窗口外壳(必须逐像素对齐)
- 10.2 导航结构(左侧 NavigationView)
- 10.3 页面容器与通用控件(必须映射到 Element Plus)
- 10.4 每个设置页的逐项规格
- 10.5 设置持久化、缓存与叠层(行为级要求)
- 10.6 键盘与交互细节
- 10.7 验收标准
- 11. 其它窗口与向导(闪屏 / OOBE / 崩溃 / 恢复 / 更新 / 换课 / 导入导出 / 迁移 / 教程 / 授权 / 日志)
- 11.1 通用窗口规则(所有窗口必须遵守)
- 11.2 欢迎向导(OOBE)`WelcomeWindow`
- 11.3 启动闪屏 `SplashWindow`
- 11.4 崩溃窗口 `CrashWindow`
- 11.5 恢复模式 `RecoveryWindow` 与恢复页
- 11.6 更新相关窗口与流程
- 11.7 换课窗口 `ClassChangingWindow`
- 11.8 课表详细信息 `ClassPlanDetailsWindow`
- 11.9 Excel 导入向导 `ExcelImportWindow`
- 11.10 Excel 导出窗口 `ExcelExportWindow`
- 11.11 数据迁移 `DataTransferWindow` / `DataTransferPage`
- 11.12 授权窗口 `AuthorizeWindow` 与集控加入对话框
- 11.13 教学(教程)窗口组
- 11.14 日志窗口 `AppLogsWindow`
- 11.15 其余窗口与对话框(逐个规格)
- 11.16 全局快捷键与关闭行为总表
- 11.17 验收标准
- 12. 插件系统、跨进程通信、单实例与自动更新
- 12.1 目标与边界
- 12.2 ClassIsland 插件体系事实(必须对齐的语义)
- 12.3 本实现的插件规范(必须逐项落地)
- 12.4 跨进程通信(IPC)设计(必须完整实现)
- 12.5 单实例与 URI 协议(必须实现)
- 12.6 自动更新(必须按此流程实现)
- 12.7 崩溃自愈与看门狗(必须实现)
- 12.8 验收标准(第 12 部分)
- 13. 数据模型、存储、备份与迁移
- 13.1 存储总则(必须遵守)
- 13.2 目录与文件清单(必须逐项命名一致)
- 13.3 `config.json` 完整 schema(字段级,含默认值)
- 13.4 档案(Profile)schema — 与 ClassIsland 完全同构
- 13.5 组件布局(ComponentProfile)schema
- 13.6 自动化配置 schema
- 13.7 提醒数据
- 13.8 主题数据
- 13.9 其他存储
- 13.10 备份、恢复与迁移(必须实现)
- 13.11 读写一致性与错误处理
- 13.12 天气数据 schema(与 07 天气服务对齐)
- 13.13 验收标准(第 13 部分)
- 14. 动画与动效总表
- 14.0 全局动效规则
- 14.1 A. 主界面(岛)动效
- 14.2 B. 提醒与强提醒动效
- 14.3 C. 启动、OOBE、欢迎动效
- 14.4 D. 弹层、抽屉、列表与设置动效
- 14.5 E. 组件动效
- 14.6 F. ClassHelper 既有动效(必须复用,不得重造)
- 14.7 实现方式规范(必须遵守)
- 14.8 与源码的差异说明(必须写进实现注释)
- 14.9 验收标准
- 15. 资源清单(图标 / 字体 / 音效 / 天气图标 / 教程 / 本地化)
- 15.1 应用图标
- 15.2 字体
- 15.3 音效
- 15.4 天气图标(4 套模板)
- 15.5 贴纸资源(HoYoStickers)
- 15.6 教程资源
- 15.7 本地化
- 15.8 其他资源与文档
- 15.9 ClassHelper 侧资源落点
- 15.10 资源命名、加载与缓存规范
- 15.11 资源版本与许可核对
- 15.12 验收标准
- 16. 质量门禁与验收清单
- 16.1 工程与构建门禁
- 16.2 像素级视觉验收(主界面)
- 16.3 功能行为验收(按模块)
- 16.4 动画验收
- 16.5 数据完整性与容错
- 16.6 性能与资源
- 16.7 平台与兼容性
- 16.8 安全验收
- 16.9 交付物清单
- 17. 开发里程碑与实现顺序
- M0 工程脚手架与进程骨架(0.5–1 天)
- M1 岛窗口 + 时钟引擎 + 基础显示(1–2 天)
- M2 组件系统(2–3 天)
- M3 课程表与档案系统(3–4 天)
- M4 提醒系统(2–3 天)
- M5 设置中心 + OOBE(2–3 天)
- M6 主题系统(+ 可选壁纸取色 P2)(1–2 天)
- M7 自动化 + 天气 + 高级窗口(2–3 天)
- M8 插件、更新、恢复与打磨(2–4 天)
- 里程碑总览表
- 18. 禁止事项与实现边界
- 18.1 关于「不打扰」的硬性要求
- 18.2 技术红线
- 18.3 数据与安全红线
- 18.4 质量与流程红线
- 18.5 本提示词未明确时的裁决顺序
- 19. 结语(给实现者的最后要求)
## 0. 给 ChatGPT 的任务声明(从本节开始执行)
### 0.1 你的任务
从零实现一个名为 **ClassIsland**(实现代号 `classisland-ts`)的跨平台桌面应用:它常驻班级多媒体屏幕,以悬浮「岛」显示当天课表、当前课程、倒计时、日期时间、天气等信息;在上下课等重要时间点发出包含音效、语音与全屏特效的提醒;并提供课表编辑、档案管理、组件系统、主题、自动化、插件、自动更新等完整能力。灵感来源是 iOS 灵动岛(Dynamic Island),但它是为教室大屏与「不打扰授课」场景重新设计的桌面工具。
你必须交付**可安装、可离线运行、可长时间无人值守运行**的 Electron 应用,而不是网页 Demo 或原型。视觉与交互规格必须严格按本提示词执行,尤其是第 3(设计 tokens)、4(主界面)、5(组件)、14(动画)部分中的数值。
### 0.2 不可协商的技术栈
- pnpm@11.8.0 monorepo;Node.js >= 20.19;TypeScript ~5.9.3(strict)。
- Electron ^44.3.0 + electron-builder ^26.15.3;主进程/preload 用 esbuild 打包,渲染层用 Vite ^8.3.0 多入口(`index.html` 应用窗口、`island.html` 岛)。
- Vue ^3.5.42 + Vue Router ^5.3.1 + Pinia ^4.0.3;Element Plus ^2.14.5 仅用于设置/管理类界面,**不得进入岛的渲染进程**。
- 三个进程角色:主进程(窗口/调度/配置/更新/IPC)、岛渲染进程(常驻、透明、逐像素自绘)、应用窗口渲染进程(按需创建)。
- 复用 ClassHelper 已验证的实现模式:独立 `island.html` + 独立透明 BrowserWindow、连续圆角 `squircle.ts`、弹簧动画 `spring.ts`、`config.json` 原子读写、contextBridge 白名单、`tokens.css` 设计变量、自研 `auto-update.ts`(禁止 electron-updater)、崩溃守卫 `crash-guard.ts`。
- 不引入服务端依赖;所有业务数据本地存储;可选管理端联动只能作为 P2 可裁剪子包。
### 0.3 硬性纪律(违反即不合格)
1. 所有自动验证必须无头/离屏执行;不得显示可见窗口、托盘气泡、系统通知或声音;需要人工观察的验证用显式 `--visual` 参数开启且不得作为默认。
2. 渲染层零 Node 权限:preload 只暴露 contextBridge 白名单,不暴露 `ipcRenderer`;`nodeIntegration=false`、`contextIsolation=true`、`sandbox=true`。
3. 主窗口(岛)不得抢焦点、默认不进入任务栏;不得使用 `setBackgroundMaterial()` 等破坏逐像素透明的 API。
4. 动画必须 60fps:优先 transform/opacity,禁止 `setInterval` 逐帧驱动、禁止逐帧改布局属性。
5. 核心功能离线可用;数据写入原子化并自动备份;迁移前先备份。
6. 不得伪造验收结果;未验证项必须显式标注。
### 0.4 交付物
- 完整源码 + `pnpm-lock.yaml` + 构建/打包脚本;`README.md` 含运行、构建、打包、数据目录说明。
- `docs/architecture.md`、`docs/data-schema.md`、`docs/plugin-api.md`、`docs/theme-api.md`。
- 至少 1 个示例课表档案、1 个示例主题、1 个示例插件(P1)。
- 无头测试(单元 + 组件 + 端到端)与第 16 部分的验收报告。
### 0.5 工作方式
1. 通读本提示词后,先输出:实施计划、完整文件树、你识别出的歧义点(一次性提问)。
2. 按第 17 部分的 M0→M8 顺序实现;每个里程碑结束都交付「可无头启动 + 通过该阶段验收」的状态。
3. 每完成一个模块,写清楚:实现了什么、证据(文件与行号)、未实现/推断项、如何验证。
4. 遇到本提示词未覆盖的细节:按第 18.5 节的优先级裁决,并在代码注释标注「推断」。
5. 不得把不同模块混在一起「顺手重构」;保持提交/变更范围单一、可回滚。
### 0.6 术语与阅读顺序
- **岛(Island / MainWindow)**:常驻悬浮主界面;由一行或多行「行(Line)」组成。
- **行(MainWindowLine)**:承载组件的横向容器,拥有自己的背景、进度条、遮罩与提醒气泡。
- **组件(Component)**:最小显示单元(日期、时钟、倒计时、课程表、天气等),可嵌套。
- **档案(Profile)**:一套课表/时间表/科目的配置集合,对应一个 JSON 文件。
- **课表(ClassPlan)** 与 **时间表(TimeLayout)**:见第 6 部分。
- **提醒(Notification)**、**自动化(Automation)**、**规则集(Ruleset)**、**主题(XamlTheme→CSS 主题包)**、**插件(Plugin)**:见对应章节。
- 阅读顺序:0 → 1 → 2 → 3 → 4 → 5 → 6 → 7 → 8 → 9 → 10 → 11 → 12 → 13 → 14 → 15 → 16 → 17 → 18 → 19。
---
## 1. 项目总纲:产品定位、总体体验与系统架构
### 1.1 你的角色与最终目标
你是一名资深跨平台桌面应用工程师,精通 Electron、Vue 3、TypeScript 与现代桌面 UI 动效实现。你的任务是从零实现一个**班级多媒体屏幕课表信息显示工具**,本提示词称其为 **ClassIsland(实现代号 `classisland-ts`)**。它常驻在教室大屏/讲台电脑上,以悬浮「岛」的形式显示当天课表、当前课程、倒计时、天气、日期时间等信息,并在上课、下课、课前准备、放学、天气变化等重要时间点发出提醒,同时提供课表编辑、组件自定义、主题、自动化、插件扩展等一整套能力。
最终交付必须是一个**可安装、可离线运行、可长时间无人值守运行**的 Windows / macOS / Linux 桌面应用,而不是网页 Demo。你必须严格按本提示词的数值、交互、动画与数据结构实现;凡本提示词给出确切数值的地方,不得用「差不多」的默认样式替代。
### 1.2 产品定位
- 一句话定位:**给班级多媒体屏幕用的、灵感来自 iOS 灵动岛的课表与班级信息悬浮显示工具。**
- 使用环境:Windows 10+(主目标)、macOS 11+、Linux X11(不支持 Wayland);屏幕常见 1920×1080、2560×1440、3840×2160,缩放 100%–200%,也可能是希沃等触摸一体机。
- 使用方式:开机自启,常驻桌面最上层;上课时悬浮在课件上方但不遮挡关键内容;教师可通过右键菜单/托盘快速进入编辑、换课、设置;放学后可由自动化或管理策略隐藏。
- 核心价值:**零打扰**(不弹系统级窗口、不抢焦点、可点击穿透)、**秒级准确**(网络对时 + 本地时钟补偿)、**一眼可读**(大字号、强对比、信息分层)、**完全可定制**(组件、主题、课表、提醒、自动化)。
- 明确不是:不是 Web 管理系统、不是手机 App、不是需要登录云服务的 SaaS。除天气、更新、可选管理端外,全部功能必须离线可用。
### 1.3 目标用户与典型场景
1. **班主任/任课老师**:用 Excel 导入课表,设置单双周/多周轮换、临时换课;设置上下课提醒音效与语音;用自动化在午休时关闭屏幕。
2. **电教委员/学生**:日常只看岛,不需要任何操作;断网、重启后应用自动恢复;不误触、不遮挡投影。
3. **学校管理员**:通过主题/配置批量部署;可选管理端下发策略(本提示词的 P2 可选模块,默认不依赖服务器)。
4. **插件开发者**:按插件规范扩展组件、提醒提供者、自动化触发器/行动、设置页。
### 1.4 十条不可妥协的体验原则
1. **像素级还原本提示词的视觉规格**:圆角、透明度、字号阶梯、间距、进度条、阴影必须逐项落实;默认参数见第 3、4 部分。
2. **绝不打扰授课**:主窗口默认不抢焦点(`show: false` 显示、`focusable: false`)、不进入任务栏、可配置鼠标穿透;任何自动验证/冒烟必须无头运行,不得创建可见窗口。
3. **60fps 丝滑动画**:所有过渡优先使用 CSS transform/opacity(合成层),禁止用 JS 逐帧改布局;弹簧动画采用本提示词给定的物理参数。
4. **时间必须准**:所有课程状态以「带补偿的当前时间」计算;支持 NTP 对时、手动时间偏移、时间倍速调试;跨天、跨周、闰秒、系统休眠唤醒都要正确。
5. **离线优先、数据安全**:配置、课表、提醒全部本地存储;写入采用原子替换 + 自动备份;任何迁移前先备份,迁移失败可回滚。
6. **组件化与可组合**:主界面的每一行、每一个显示单元都是组件;组件可嵌套(分组/堆叠/滚动/滑动)、可单独设置、可按规则隐藏。
7. **默认即好看**:首次启动走完 OOBE 后,即使不导入课表也要显示一个可用的岛(日期、时间、欢迎信息),不能白屏。
8. **可恢复**:渲染进程崩溃、GPU 崩溃、配置损坏、误删数据时,应用必须能自愈或进入恢复模式;崩溃后不得反复重启刷屏。
9. **可扩展**:插件/主题/提醒提供者/自动化行动全部通过注册表扩展,核心不写死;公共 API 保持向后兼容。
10. **可访问与本地化**:中文为默认语言(ClassIsland 2.1 的界面文案为硬编码中文);支持高对比、减少动画(`AnimationLevel`)、键盘操作、触摸操作;文案集中到常量/词条文件,预留 i18n key 体系,不得把字符串散落在组件里。
### 1.5 功能全景(必须实现的模块)
> 优先级:P0 = 首版必须;P1 = 首个稳定版必须;P2 = 可分期但提示词要求预留架构。
| 模块 | 能力清单 | 优先级 |
|---|---|---|
| 主界面(岛) | 多行岛、背景/圆角/透明度/毛玻璃、课程信息行、进度条、倒计时、遮罩/提醒宿主、编辑模式、隐藏规则、点击穿透、多显示器停靠、缩放、窗口置顶 | P0 |
| 组件系统 | 日期、时钟、倒计时、课程表、天气、文本、滚动、滑动、堆叠、分组、分隔线、容器;组件设置、显示/隐藏规则、多行组件、轮播 | P0 |
| 课程表与档案 | 时间表(节次/课间/分割线/行动)、课表(单日/多日/周模板)、多周轮换(1–9 周)、科目与教师、临时课表/预定课表、单日与跨天换课、CSES 导入导出(P0)、Excel 导入导出(P1,用 xlsx 重建;ClassIsland 2.1 源码中该功能被 `#if false` 停用,布局仅作参考)、多档案 | P0/P1 |
| 提醒系统 | 提醒队列与优先级、上/下课/课前/放学提醒、天气类提醒、自定义提醒、气泡与强提醒特效、音效、TTS 语音、提醒历史与清除(ClassIsland 2.1 无独立「静音时段」;只有全局提醒开关与 `-q` 静默启动后 10s 丢弃提醒,不得虚构静音时段,P2 扩展需默认关闭) | P0 |
| 设置中心 | 通用/外观/组件/提醒/自动化/天气/窗口/时钟/主题/更新/存储/插件/管理/隐私/高级/关于/调试,即时保存、设置覆盖层、按注册顺序导航(ClassIsland 2.1 无搜索框,本实现不编造搜索;如需可标 P2 增强) | P0 |
| OOBE 向导 | 8 页:欢迎、许可、翻新/迎新、基本、颜色、外观、系统、完成;可跳过、可重复打开 | P0 |
| 主题系统 | 内置 Fluent/Classic 两套主题、主题包(CSS 变量 + 资源 + 预览图)、自定义强调色/背景色/前景色、深浅色跟随系统;壁纸取色在 ClassIsland 2.1 中已整体停用(`WallpaperPickingService` 全文 `#if false`,设置页对应项禁用隐藏),本实现只作为 P2 实验性能力(默认关闭、给算法与数据结构、不纳入 P1 验收) | P1(壁纸取色 P2) |
| 自动化 | 触发器(时间点/CRON/信号/URI/托盘菜单/窗口状态/当前课程/天气/日出日落)、行动(提醒/运行程序/打开文件网页/关机/睡眠/重启/修改设置/广播信号/切换课表等)、规则集(嵌套条件) | P1 |
| 天气 | 当前天气、逐小时/未来 3 天预报、降水提醒、极端天气预警、定位/城市选择、天气图标主题 | P1 |
| 插件系统 | 插件包格式、清单、权限、生命周期、组件/提醒/触发器/行动/设置页扩展点、插件市场(可选)、启用/禁用/更新 | P1 |
| 调试与诊断 | 日志查看、配置校验、崩溃上报(可选 Sentry)、渲染模式切换、时间调试、动画调试、窗口规则调试、教程系统 | P1 |
| 更新与运维 | 自动检查更新、镜像/通道、断点下载、哈希校验、静默安装、崩溃恢复、单实例、开机自启 | P1 |
| 管理端联动 | 可选远程策略、管理凭据、通知通道(P2,默认关闭且不得成为运行前置依赖) | P2 |
### 1.6 总体技术架构(ClassHelper 栈)
**必须采用 ClassHelper 的技术栈与工程约定**(来源:`repos/ClassHelper-master/package.json`、`pnpm-workspace.yaml`、各 `packages/*/package.json`、`AGENTS.md`):
- 包管理器:`pnpm@11.8.0`,Node.js >= 20.19;根 `package.json` 为版本单一来源。
- 语言:TypeScript ~5.9.3,开启 `strict`;Vue 3.5 SFC(`<script setup lang="ts">`),禁止 `any` 泛滥。
- 桌面壳:Electron ^44.3.0 + electron-builder ^26.15.3;Windows 目标 NSIS/portable,macOS dmg/zip,Linux AppImage/deb(任选其一即可,但构建脚本必须齐全)。
- 渲染层:Vue ^3.5.42 + Vue Router ^5.3.1 + Pinia ^4.0.3 + Element Plus ^2.14.5(仅设置/管理类界面使用 Element Plus;主界面「岛」使用手写 CSS 与自定义组件,**不得**让 Element Plus 的默认样式污染岛)。
- 构建:Vite ^8.3.0(`@vitejs/plugin-vue` ^6.0.8),多入口构建:`index.html`(设置/主窗口)与 `island.html`(独立岛渲染进程)。
- 进程模型(必须三进程分离):
1. **主进程 `src/main`**:窗口管理、托盘、全局快捷键、单实例、配置读写、调度(时间引擎/提醒调度/自动化)、自动更新、系统集成、IPC 服务端。
2. **岛渲染进程 `island.html` → `src/island`**:只负责画「岛」,常驻内存,通过 contextBridge 暴露的最小 API 接收状态;窗口透明、点击穿透由主进程控制。
3. **应用窗口渲染进程 `index.html` → `src/renderer`**:设置中心、档案编辑、OOBE 向导、关于、日志、调试、恢复等;按需创建,关闭即销毁(pinia 状态持久化到主进程)。
- 共享包:`packages/shared`(类型、常量、i18n 词条、数据 schema、纯函数业务逻辑:时间/周次/课表解析)、`packages/ui`(CSS tokens、基础 Vue 控件、主题)。
- 目录结构必须为 pnpm monorepo(`packages/*`),至少包含:`packages/desktop-client`(Electron 应用)、`packages/shared`、`packages/ui`。若实现插件宿主,可增加 `packages/plugin-sdk`(P1)。
- 复用 ClassHelper 已验证的桌面岛关键实现(可直接参照其代码组织与参数):
- 独立 `island.html` 渲染入口 + 独立透明 BrowserWindow(见 `repos/ClassHelper-master/packages/desktop-client/src/main/island.ts`)。
- `squircle.ts` 连续圆角(超椭圆指数 4.2,每角 14 段,半径 `min(r, w/2, h/2)`)。
- `spring.ts` 弹簧动画(`stiffness=0.10`、`damping=0.68`、换向动量保留 35%、单帧位移 ≤ 剩余距离 20%、snap 阈值 0.05px、60fps 帧单位)。
- `config.json` + `config.ts` 的原子读写、版本迁移、默认值合并(参照 `src/main/config.ts`)。
- preload 只暴露 contextBridge 白名单,不暴露 `ipcRenderer`;渲染层不得直接访问 `fs`/`child_process`。
- `renderer/composables/motion.ts` 的统一动效 token;`packages/ui/src/tokens.css` 的颜色/圆角/动效变量体系。
- **不得引入 ClassHelper 的服务端依赖**:本应用是本地单机应用;Express/Prisma/Socket.IO 仅在你选择实现 P2「管理端联动」时才可作为可选子包,且主流程不得依赖网络。
- 允许使用成熟开源库完成非核心能力(如 `xlsx` 解析 Excel、`yaml` 读取插件清单、`adm-zip` 解压插件/主题包);**更新器必须自研**(参照 ClassHelper 的 `src/main/auto-update.ts` + `update-installer.ts`,禁止引入 electron-updater);不要引入 `node-pty`、`sharp` 等原生模块。所有依赖必须列明版本并锁定 `pnpm-lock.yaml`。
**逻辑分层(进程内)**:
```
main
├─ config/ 配置读写、迁移、备份(Settings/Profiles/Automations/Components/Themes)
├─ windows/ island / app / splash / topmost-effect / class-changing / oobe ...
├─ engine/
│ ├─ clock NTP 对时 + 偏移 + 倍速;对外只暴露当前时间
│ ├─ lessons 课表解析、当前/下一节、时间点状态机、周次计算
│ ├─ notification 提醒队列、提供者、优先级、去重、TTS/音效
│ ├─ automation 触发器轮询、行动执行、规则集求值
│ ├─ weather 天气拉取、缓存、预警
│ └─ update 更新检查/下载/安装
├─ ipc/ contextBridge 白名单 + 通道校验 + 节流
└─ tray/ 托盘菜单、开机自启、单实例
renderer(island) IslandApp.vue + 组件渲染器 + 动画
renderer(app) SettingsWindow / ProfileEditor / OOBE / About / Logs / Recovery ...
```
### 1.7 信息架构与导航
- 主界面(岛)是唯一常驻界面,无标题栏、无任务栏图标、无边框。右键(或长按触摸)弹出**原生上下文菜单**:ClassIsland(关于)、教学…、帮助文档…、显示/隐藏主界面、清除全部提醒、更多选项…、编辑档案…、加载临时课表…、换课…、编辑主界面…、应用设置…、重启、退出(调试模式下追加 dev__Debug 子菜单)。菜单项文案、图标、可见条件必须与第 4 部分一致。
- 托盘图标提供左键行为(默认打开主菜单/打开档案编辑/显示隐藏主界面/打开换课/打开设置,见设置项 `TaskBarIconClickBehavior` 默认值:Windows 0,其他平台 4)。
- 应用窗口为 Fluent 风格的单窗口 + 页面栈:设置中心(左侧导航列表 + 面包屑返回;ClassIsland 2.1 **无设置搜索框**,本实现不得编造搜索,如做需明确标 P2 增强)、档案编辑器(左侧档案树 + 右侧编辑区)、OOBE(无侧栏的 8 页步骤向导)、关于、日志、调试、恢复模式;提醒不设独立「通知中心」窗口,由主界面行内气泡 + 强提醒特效承载。
- 所有次级窗口居中显示、可用 Esc 关闭、标题栏使用系统样式(Fluent 主题下自绘标题栏);关闭设置窗口不退出应用,仅隐藏。
- URL Scheme:`classisland://app/...` 用于深链(关于页、设置页等),由单实例机制转发到已运行实例。
### 1.8 首次启动与默认状态
1. 启动时先显示无边框闪屏(可选,默认关闭;打开时 1.2s 内渐显 Logo 与应用名,可显示加载状态),随后显示 OOBE。
2. OOBE 完成后写 `config.json`(并兼容导出 ClassIsland 的 `Settings.json`)的 `IsWelcomeWindowShowed=true`,主界面以默认参数出现:位置=中上侧(`WindowDockingLocation=1`)、缩放 100%、背景不透明度 0.5、圆角 8px、字体默认系统字体(Windows 为微软雅黑 UI 回退链)、主体字号 16、次要 14、强调 18、超大 20、行间距 5px。
3. 默认组件配置为 1 行 2 组件:日期(DF3F8295-21F6-482E-BADA-FA0E5F14BB66)+ 课程表(1DB2017D-E374-4BC6-9D57-0B4ADF03A6B8,依据 ComponentsService.cs:33-52)。未加载档案时课程表组件显示「今天没有课程。」等占位符,而不是白屏或欢迎文本;加载课表后显示课程信息。
4. 默认设置必须保证:岛可见、不穿透、不抢焦点、不遮挡全屏应用(`HideOnFullscreen=false`,可开启)、提醒开启、音效开启但音量 100%、调试关闭。
### 1.9 本提示词的使用方式与验收总则
- 本提示词按「总纲 → 技术栈 → 设计系统 → 主界面 → 组件 → 课表/提醒/自动化 → 主题/设置/窗口 → 动画/资源 → 插件/数据 → 验收」组织。实现时必须全部通读,再按第 17 部分的里程碑顺序开发。
- 每一部分末尾都有「验收标准」;你必须逐条自测并在交付说明中报告结果。自测必须无头执行(`--headless` / 隐藏窗口 / CI 模式),**不得弹出可见窗口、不得占用用户桌面、不得发出声音**;像素级验收使用 Playwright/`electron` 的离屏截图模式完成。
- 任何你没有把握的数值,优先服从本提示词;本提示词未覆盖的细节,按「Fluent Design + 灵动岛」的直觉补齐,并在代码注释中说明。
- 交付必须包含:完整源码、`README.md`(含运行/构建/打包命令)、`docs/`(数据 schema 与插件 API)、至少 1 个示例主题与 1 个示例插件(P1)、以及一套无头自动化测试(单元测试 + 关键交互的组件测试)。
---
## 2. 技术栈与工程结构
### 2.1 技术选型总则(必须逐条遵守)
1. 你必须用 **TypeScript + Electron + Vue 3** 实现本应用,工程形态必须是 **pnpm monorepo**,不得做成单包项目、不得用 Webpack、不得用 Electron Forge、不得用 `electron-vite` 之外的脚手架替代本文规定的手写构建管线。
2. 你必须**完整沿用 ClassHelper 桌面客户端的工程约定**:主进程/preload 用 esbuild 打包为 CommonJS,渲染进程用 Vite 多入口打包,打包用 electron-builder;共享类型、常量、纯函数放 `packages/shared`;共享 Vue 控件与样式令牌放 `packages/ui`;Electron 应用全部代码放 `packages/desktop-client`。依据:`repos/ClassHelper-master/pnpm-workspace.yaml:1-2`、`repos/ClassHelper-master/packages/desktop-client/package.json:8-18`。
3. 你**不得**把 ClassHelper 的服务端(Express/Prisma/Socket.IO)引入主流程。ClassIsland 是本地单机应用,一切业务数据必须落在本机;仅当实现「可选管理端联动」时才允许把 `packages/server` 作为可裁剪子包,且主流程不得依赖网络。依据:`repos/ClassHelper-master/packages/server/package.json:19-40` 的依赖清单,以及总纲的 P2 定义。
4. 你必须把「版本」收敛为**单一来源**:根 `package.json` 的 `version` 是唯一手写值,所有子包版本由脚本同步;日常功能提交不得涨版本。依据:`repos/ClassHelper-master/AGENTS.md`「根 package.json 是版本单一来源」与 `repos/ClassHelper-master/docs/dev/release.md:8-10`。
5. 你必须保持「渲染层零 Node 能力」:渲染进程不得 `require('node:fs')`、不得访问 `process` 环境变量之外的 Node API,所有文件、系统、更新、托盘能力一律经主进程 IPC。依据:`repos/ClassHelper-master/AGENTS.md`「preload 仅暴露 contextBridge 白名单,不暴露 ipcRenderer」。
### 2.2 依赖清单(版本必须按此锁定)
以下版本是硬约束,`pnpm-lock.yaml` 必须锁定到这些 semver 区间;不得因为"想用新 API"而升级大版本。
| 用途 | 包名 | 版本 | 证据 |
|---|---|---|---|
| 包管理器 | pnpm | `11.8.0`(`packageManager` 字段) | `repos/ClassHelper-master/package.json:6` |
| Node 运行时 | Node.js | `>=20.19.0` | `repos/ClassHelper-master/package.json:7-9` |
| 桌面壳 | `electron` | `^44.3.0` | `repos/ClassHelper-master/packages/desktop-client/package.json:32` |
| 打包 | `electron-builder` | `^26.15.3` | 同上 `:34` |
| UI 框架 | `vue` | `^3.5.42` | 同上 `:26` |
| 路由 | `vue-router` | `^5.3.1` | 同上 `:27` |
| 状态 | `pinia` | `^4.0.3` | 同上 `:24` |
| 组件库(仅设置/管理界面) | `element-plus` | `^2.14.5` | 同上 `:23` |
| 图标 | `@element-plus/icons-vue` | `^2.3.2` | 同上 `:21` |
| 构建 | `vite` | `^8.3.0` | 同上 `:37` |
| Vue 插件 | `@vitejs/plugin-vue` | `^6.0.8` | 同上 `:32` |
| 主进程打包 | `esbuild` | `^0.28.2` | 同上 `:35` |
| 语言 | `typescript` | `~5.9.3` | 根 `package.json:74` |
| 类型检查 | `vue-tsc` | `^3.3.11` | `desktop-client/package.json:38` |
| Node 类型 | `@types/node` | `^22.20.2` | 同上 `:31` |
允许额外引入的第三方库(必须写进依赖清单并锁版本):`xlsx`(Excel 导入导出;**P1 必须实现**,用 xlsx 库重建 ClassIsland 的 ReoGrid/WPF 版导入导出,ClassHelper 服务端同款 `0.18.5`,见 `packages/server/package.json:38`)、`adm-zip` 或 `yauzl`(`.cipx`/主题包解压)、`yaml`(读取插件 `manifest.yml`,见 `packages/server/package.json:40`)、`electron-log` 可选但**不强制**(本文要求自研 `logger.ts`,理由见 2.8)。禁止引入:`electron-updater`(本文的更新模型是自研的哈希校验 + 独立安装助手,见 2.11 与 12 部分)、`better-sqlite3` 等原生模块(`electron-builder.yml:37-38` 明确 `npmRebuild: false`、无原生模块)、任何需要在线服务的 SDK。
`pnpm-workspace.yaml` 必须显式声明允许执行构建脚本的依赖,否则 pnpm 11 会因 `ERR_PNPM_IGNORED_BUILDS` 直接失败:
```yaml
packages:
- 'packages/*'
allowBuilds:
esbuild: true
electron: true
vue-demi: true
electron-winstaller: false
verifyDepsBeforeRun: false
```
依据:`repos/ClassHelper-master/pnpm-workspace.yaml:1-26`(含注释解释 electron-winstaller 为何必须显式为 false)。
### 2.3 仓库目录结构(必须逐项落地)
你必须在仓库根建立如下结构;标注「新增」的是本应用特有模块,其余照抄 ClassHelper 的组织方式。
```text
classisland-ts/
├─ package.json # 版本单一来源、pnpm@11.8.0、engines>=20.19
├─ pnpm-workspace.yaml
├─ pnpm-lock.yaml
├─ tsconfig.base.json # target ES2023、strict、verbatimModuleSyntax
├─ .prettierrc.json # LF、单引号、100 列
├─ scripts/
│ ├─ version.mjs # 版本唯一修改入口(--bump patch|minor|major)
│ ├─ check-icons.mjs # 图标资源完整性门禁
│ └─ lib/electron-env.mjs # 跨平台解析 electron 可执行文件
├─ packages/
│ ├─ shared/ # 三端共享:类型/常量/schema/纯函数
│ │ └─ src/
│ │ ├─ index.ts # 统一导出(照抄 packages/shared/src/index.ts)
│ │ ├─ constants.ts # 尺寸/默认值/枚举/映射表
│ │ ├─ types.ts # DTO 与持久化类型
│ │ ├─ motion.ts # OK 动效令牌(纯常量+纯函数)
│ │ ├─ island-layout.ts # 岛行/组件方案 schema + normalize(新增扩展)
│ │ ├─ schedule.ts # 时间表/课表/周次纯函数(新增)
│ │ ├─ time.ts # 本地日期/日键/时间补偿(新增)
│ │ ├─ notifications.ts # 提醒 DTO/优先级(新增)
│ │ ├─ automation.ts # 自动化 DTO(新增)
│ │ ├─ plugins.ts # 插件清单/权限/扩展点类型(新增)
│ │ └─ ipc.ts # IPC 通道名与 payload 类型(新增,主进程与 preload 共用)
│ ├─ ui/ # 共享 Vue 控件与样式令牌
│ │ └─ src/
│ │ ├─ index.ts # registerSharedUi(app)
│ │ ├─ tokens.css # 基础色/圆角/间距(照抄)
│ │ ├─ theme.css # 语义色 + html.dark(照抄后扩展)
│ │ └─ *.vue # DatePicker/PageHeader/FullScreenGuide 等
│ └─ desktop-client/ # Electron 应用
│ ├─ package.json # main: dist/main/index.js
│ ├─ electron-builder.yml
│ ├─ index.html # 应用窗口(设置/编辑/OOBE)
│ ├─ island.html # 岛渲染入口(必须独立)
│ ├─ vite.config.mts # 双入口、base './'、outDir dist/renderer、5174
│ ├─ tsconfig.json # 渲染层(DOM lib)
│ ├─ tsconfig.node.json # 主进程/preload(node lib)
│ ├─ scripts/
│ │ ├─ dev.mjs # vite 就绪后起 electron(VITE_DEV_SERVER_URL)
│ │ ├─ build-main.mjs # esbuild -> dist/main + dist/preload
│ │ ├─ build-update-helper.mjs # 编译独立更新助手(新增,见 2.11)
│ │ ├─ dist-win.mjs # electron-builder 包装 + 产物归档
│ │ └─ check-bundle-secrets.mjs # 打包不得含凭据门禁
│ └─ src/
│ ├─ main/
│ │ ├─ index.ts # 启动编排(照抄 ClassHelper 顺序)
│ │ ├─ config.ts # config.json 读写/归一化/合并写
│ │ ├─ channels.ts # IPC 通道名叶子模块(零 import)
│ │ ├─ ipc.ts # ipcMain.handle 注册表
│ │ ├─ island.ts # 岛窗口控制器(ClassIsland 主界面宿主)
│ │ ├─ main-window.ts # 设置/编辑窗口管理(新增)
│ │ ├─ tray.ts # 系统托盘
│ │ ├─ logger.ts # main.log + 1MB 轮转
│ │ ├─ crash-guard.ts # 崩溃自愈
│ │ ├─ auto-update.ts # 下载/校验/空闲安装
│ │ ├─ update-installer.ts # 安装助手就绪/提交/回滚
│ │ ├─ storage.ts # 数据文件读写/备份/迁移(新增,见 13 部分)
│ │ ├─ scheduler.ts # 时间引擎:上课/下课/准备/放学(新增)
│ │ ├─ automation.ts # 自动化运行时(新增)
│ │ ├─ notifications.ts # 提醒队列/宿主(新增)
│ │ ├─ plugins.ts # 插件宿主(新增,见 12 部分)
│ │ └─ platform/ # 开机自启、单实例、DPI、全屏检测
│ ├─ preload/
│ │ ├─ index.ts # contextBridge 暴露 window.desktop
│ │ └─ island.ts # contextBridge 暴露 window.island
│ ├─ island/ # 岛渲染进程(手写 CSS,零 Element Plus)
│ │ ├─ main.ts # 注入动效令牌后 mount
│ │ ├─ LayoutIslandApp.vue # 选择经典/自定义渲染
│ │ ├─ spring.ts # SpringValue 帧积分
│ │ ├─ squircle.ts # 连续圆角路径
│ │ ├─ classic-island.css # 经典岛样式
│ │ └─ components/ # 岛内组件(日期/时钟/课程/进度/提醒)
│ ├─ renderer/ # 应用窗口渲染进程
│ │ ├─ main.ts # 挂载顺序见 2.9
│ │ ├─ router/index.ts # createWebHashHistory
│ │ ├─ stores/ # pinia:ui/notifications/onboarding/profile/settings
│ │ ├─ views/ # 设置中心、档案编辑、OOBE、关于…
│ │ ├─ composables/motion.ts # applyMotionTokens/useReducedMotion/useCountUp…
│ │ ├─ api/ # 本地数据访问(经 preload IPC,不含网络)
│ │ └─ styles/index.css # 语义色令牌 + 全局样式
│ └─ types/desktop.d.ts # 主进程↔渲染进程桥接契约
```
依据:`repos/ClassHelper-master/packages/desktop-client/package.json:7-18`(入口与脚本)、`vite.config.mts`(入口与产物)、`src/main/*`、`src/preload/*`、`src/island/*`、`src/renderer/*` 的既有布局。
### 2.4 进程模型(必须五类进程分离)
1. **主进程(`src/main/index.ts`)**:唯一持有窗口、托盘、配置、定时器、文件系统、自动更新、插件宿主的进程。它必须承载 ClassIsland 的「Core 服务层」职责:时间引擎、课表状态机、提醒宿主、自动化、存储与备份、插件注册表。
2. **应用窗口渲染进程(`index.html`)**:设置中心、档案编辑器、OOBE、关于页、日志、调试、恢复。按需创建;关闭窗口必须只隐藏不退出(ClassIsland 主窗口关闭即隐藏的逻辑改成「设置窗口关闭=销毁,托盘常驻」)。
3. **岛渲染进程(`island.html`)**:常驻内存,只画岛。必须独立 `BrowserWindow`,独立 preload(`dist/preload/island.js`),独立 CSS,**不得**加载 Element Plus。依据:`repos/ClassHelper-master/packages/desktop-client/src/main/island.ts:480-512`(窗口参数与 preload)、`src/island/main.ts:1-30`(零 UI 库)。
4. **插件/自动化隔离进程(`utilityProcess`,Electron 44 内置)**:插件代码、用户自动化脚本、外部命令执行必须跑在这个进程里,崩溃不得拖垮主进程;与主进程之间用 `MessagePort` 传纯数据。ClassIsland 用 `PluginLoadContext` 做程序集隔离(`repos/ClassIsland/ClassIsland/Services/PluginService.cs:186-235`),在 Electron 下等价物就是 utilityProcess + 动态 `import()`。
5. **独立更新助手(`update-helper.exe`)**:Windows 下由构建脚本生成的小型可执行程序,负责"旧进程退出后替换文件、失败回滚、写 `install-result.json`"。不得由主进程自己替换正在运行的 exe。依据:`repos/ClassHelper-master/packages/desktop-client/src/main/auto-update.ts:516-564`(就绪→commit→退出)与 `scripts/build-main.mjs:12`(构建时产出助手)。
### 2.5 构建管线(命令与产物必须一致)
**主进程与 preload**:用 esbuild 打成 CJS,`platform: 'node'`、`target: 'node20'`、`external: ['electron']`、`define process.env.NODE_ENV`;产物固定为:
- `dist/main/index.js`
- `dist/preload/index.js`
- `dist/preload/island.js`
- `dist/main/update-helper.exe`(构建时由 `build-update-helper.mjs` 生成)
依据:`repos/ClassHelper-master/packages/desktop-client/scripts/build-main.mjs:14-46`。
**渲染进程**:用 Vite 双入口构建:`index.html`(应用窗口)与 `island.html`(岛);`base: './'`(Electron 以 `file://` 加载,必须相对路径)、`outDir: 'dist/renderer'`、`emptyOutDir: true`、`sourcemap: false`、`chunkSizeWarningLimit: 2000`;dev 服务器固定 `127.0.0.1:5174`、`strictPort: true`。依据:`repos/ClassHelper-master/packages/desktop-client/vite.config.mts:13-39`。
**开发模式**:`scripts/dev.mjs` 必须先探测 5174 端口可用,再以 `VITE_DEV_SERVER_URL=http://127.0.0.1:5174` 启动 Electron;主进程读到该变量时 `loadURL` 并打开 DevTools,读不到时 `loadFile('../renderer/index.html')`;岛的开发地址是 `${devUrl}/island.html`。依据:`src/main/index.ts:18`、`:137-145`、`:221`;`scripts/dev.mjs:21-60`。
**打包**:`electron-builder.yml` 必须包含:
| 项 | 值 | 证据 |
|---|---|---|
| appId | `com.classisland.desktop`(从 ClassHelper 改名) | `electron-builder.yml:1` |
| productName | `ClassIsland` | `:2` |
| 语言包 | 仅 `zh-CN`(删除其余 54 个 locales) | `:14-15` |
| files | `dist/**/*`、`package.json`、排除 `node_modules`、排除 `*.map` | `:30-34` |
| asar | `true` | `:36` |
| npmRebuild | `false`(无原生模块) | `:37-38` |
| compression | `normal` | `:39` |
| win target | `nsis` x64 + `portable` x64 | `:42-47` |
| artifactName | `${productName}-${version}-${arch}.${ext}` | `:52` |
| nsis | `oneClick:false`、`perMachine:false`、`allowToChangeInstallationDirectory:true`、桌面+开始菜单快捷方式 | `:54-61` |
| portable | `${productName}-${version}-${arch}-portable.${ext}` | `:63-64` |
**必须建立的门禁脚本**(在 CI/发布前按序执行,任何一步失败即中止):
1. `pnpm build:shared`(先建共享包,再建下游——依据 ClassHelper 根脚本 `package.json:11-17`)。
2. `pnpm typecheck`(`vue-tsc --noEmit` + `tsc --noEmit -p tsconfig.node.json`,依据 `desktop-client/package.json:16`)。
3. `pnpm lint`(ESLint + 自定义 authz/资源检查)。
4. `pnpm check:icons`(图标资源完整性)。
5. `pnpm build:desktop`(= check-icons + build:main + vite build + check:secrets,依据 `desktop-client/package.json:10-13`)。
### 2.6 应用启动时序(必须按此顺序,不得提前读配置)
1. 解析命令行参数(`--hidden` 静默启动、`--safe` 安全模式、`--diagnostic` 诊断模式、`--recovery` 恢复模式、`--importV1/--importV2` 导入)。
2. 若存在 `ELECTRON_SMOKE_PROFILE`(仅自动化验收使用),`app.setPath('userData', profile)`;正常启动不得设置。
3. `app.requestSingleInstanceLock()`;拿不到锁立刻 `app.quit()`,拿到锁后监听 `second-instance` 并聚焦/唤起已有窗口。依据:`src/main/index.ts:176-182`。
4. `app.whenReady()` 之后依次:`logger.setVerbose(config.developerMode)` → `registerIpcHandlers()` → `registerIslandIpc()` → 应用开机自启登录项 → 创建设置窗口(隐藏态可配)→ `installCrashGuard()` → 加载渲染页 → 注册退出回调 → `island.init()` → 创建托盘。依据:`src/main/index.ts:184-262`。
5. 退出时统一资源清理:停止定时器 → `flushConfigSync()`(配置同步落盘)→ 销毁托盘 → 销毁岛 → 销毁全部窗口 → `app.quit()`,并用 `setTimeout(() => app.exit(0), 1500)` 兜底。依据:`src/main/index.ts:291-325`。
**关键纪律**:配置必须在 `app.setPath('userData')` 之后**懒加载**,不得在模块顶层读盘(否则读到错误 profile 的 config.json)。依据:`src/main/config.ts:261-272` 的注释与实现。
### 2.7 窗口参数(像素级基线)
**岛窗口(ClassIsland 主界面)**必须满足:
- `width/height` 使用"固定包围盒 + 窗内形变",**以第 4 部分 4.3 为唯一实现口径**:`winW = 目标屏幕宽(DIP)`,`winH = contentMaxHeight + 2 × safePadding`(`safePadding ≥ 20`,其中包含阴影/光晕余量,参考 ClassHelper 的 `ISLAND_SHADOW_PAD = 10` 双侧口径);窗口在开合、行增删与提醒展开过程中尺寸不变,形变全部发生在渲染层画布内,只有显示器/DPI 变化、行数超出上限、编辑模式进出、偏移/缩放/停靠设置变化时才 `setBounds` 且节流 ≥100ms。依据:`repos/ClassHelper-master/packages/desktop-client/src/main/island.ts:72-112,480-512`(固定包围盒思想)与第 4 部分 4.3(ClassIsland 全屏宽窗口适配)。
- 窗口标志:`frame:false`、`transparent:true`、`resizable:false`、`movable:false`、`minimizable:false`、`maximizable:false`、`fullscreenable:false`、`skipTaskbar:true`、`hasShadow:false`、`focusable:false`、`alwaysOnTop:true`、`type:'toolbar'`。依据:`src/main/island.ts:480-504`;其中 `focusable` 必须为 `false`(ClassIsland 主窗口不抢焦点,见 04 §4.2)。
- `webPreferences`:`preload` 指向 `dist/preload/island.js`、`contextIsolation:true`、`nodeIntegration:false`、`sandbox:true`、`backgroundThrottling:false`。依据:`:505-511`。
- 创建后必须:`setAlwaysOnTop(appearance.alwaysOnTop,'screen-saver')`、`setVisibleOnAllWorkspaces(true,{visibleOnFullScreen:true})`、`setWindowOpenHandler(()=>({action:'deny'}))`、`will-navigate` 白名单校验、默认 `setIgnoreMouseEvents(true,{forward:true})`、**禁止** `setBackgroundMaterial()`。依据:`:514-527`。
- 定位:按 ClassIsland 的 6 个停靠位(`WindowDockingLocation`:0 左上 / 1 中上 / 2 右上 / 3 左下 / 4 中下 / 5 右下)+ `WindowDockingOffsetX/Y`(默认 0)+ `WindowDockingMonitorIndex`(默认 0);ClassHelper 的 `top-center` 等 6 个枚举与 `marginX/marginY=8` 只作参考,**不得**替代 ClassIsland 的停靠与偏移语义。依据:`repos/ClassIsland/ClassIsland/Models/Settings.cs:1810-1833`。
- **坐标口径(强制)**:所有窗口 bounds、停靠偏移、边距、组件尺寸与间距必须使用 **DIP(CSS px)**;`BrowserWindow` 的 `x/y/width/height`、`screen.getPrimaryDisplay().workArea` 全部按 DIP 传入与读取。**禁止**在业务布局代码里用 `devicePixelRatio` 做坐标换算;只有"截图/像素级视觉验收"时才用 `devicePixelRatio` 把 DIP 换回物理像素。依据:【推断】(与 04 部分统一口径,避免窗口定位与组件布局出现两套坐标系)。
- **非 Windows 平台的命中降级(必须实现)**:`BrowserWindow.setShape()` 仅在 Windows 生效;macOS/Linux 必须降级为"整窗默认 `setIgnoreMouseEvents(true, { forward: true })`,岛可见区域之外不接收鼠标,指针进入岛体时由渲染层上报 `set-interactive` 打开命中",不得为平台差异改变岛的视觉尺寸或停靠位置。依据:`repos/ClassHelper-master/packages/desktop-client/src/main/island.ts:278-316`(`setShape` 只在 `process.platform === 'win32'` 分支执行)。
**设置窗口**必须采用 **ClassIsland 的尺寸(第 10 部分为唯一口径)**:`width 1240`、`height 550`、`minWidth 560`、`frame:false`(Windows 11 21H2+ 可用 Mica,否则退回纯色背景)、`center:true`、`minHeight 550`,并补上 `preload: dist/preload/index.js`、`contextIsolation:true`、`nodeIntegration:false`、`sandbox:true`、`backgroundThrottling:false`。依据:`repos/ClassIsland/ClassIsland/Views/SettingsWindowNew.axaml`(第 10 部分 10.1)与 `src/main/index.ts:63-88`(窗口骨架参考)。
### 2.8 主进程核心模块(ClassIsland 服务层在 Electron 下的等价物)
| ClassIsland 概念 | ClassHelper/Electron 实现位置 | 必须实现的职责 |
|---|---|---|
| `AppBase` / `App` 启动编排 | `src/main/index.ts` | 单实例、窗口、托盘、退出清理、崩溃自愈 |
| `SettingsService`(`Settings.json` → `config.json`) | `src/main/config.ts` | `config.json` 为运行期唯一设置源;`Settings.json` 只做 ClassIsland 格式只读导入/导出兼容层(不双向双写);默认值合并、字段归一化、120ms 合并写、退出同步落盘、字段白名单 |
| `ProfileService`(档案) | `src/main/storage.ts` | 档案/课表/时间表读写、临时层清理(对应 `ProfileService.CleanExpiredTempClassPlan`,`repos/ClassIsland/ClassIsland/Services/ProfileService.cs:295-328`) |
| `ComponentsService` | `src/main/storage.ts` + `src/island/*` | 组件方案 JSON 读写、迁移、注册表驱动渲染 |
| `LessonsService`(时间引擎) | `src/main/scheduler.ts` | 每 200ms 推进一次状态机、计算下一时间点/剩余时间、触发上/下课事件 |
| `NotificationHostService` | `src/main/notifications.ts` | 队列、优先级、播放会话、强提醒遮罩 |
| `AutomationService` | `src/main/automation.ts` | 触发器/行动/规则集、信号与 URI |
| `PluginService` | `src/main/plugins.ts` | 插件发现/加载/启用/卸载/依赖排序 |
| `AppLogger` | `src/main/logger.ts` | 控制台 + `app.getPath('logs')/main.log`,单文件 1MB 轮转、保留 `main.log.1` |
| `IpcService` | `src/main/ipc.ts` + `channels.ts` | 通道注册、事件下发、数据校验 |
日志必须照抄 ClassHelper 的轮转与容错口径:`LOG_MAX_BYTES = 1024*1024`、`main.log → main.log.1`、写入失败静默忽略、`app.getPath('logs')` 在 ready 前调用会抛所以路径必须懒解析。依据:`repos/ClassHelper-master/packages/desktop-client/src/main/logger.ts:21-67`。**不得**用 `console.log` 作为唯一排障手段:打包后的 GUI 没有可见控制台。
### 2.9 渲染层挂载顺序(必须逐行对齐)
应用窗口入口 `src/renderer/main.ts` 必须按以下顺序执行,**顺序错一个都会出现白闪或首帧无动画**:
1. `createApp(App)`;
2. 注册用到的 Element Plus 图标(只注册用到的,不全量 293 个);
3. `app.use(createPinia())`;
4. `app.use(router)`;
5. `app.use(ElementPlus, { locale: zhCn })`;
6. `registerSharedUi(app)`(来自 `@classhelper/ui`);
7. 冒烟钩子(若 `window.desktop?.smokeTest`,用 `await import()` 动态加载,禁止静态 import 进主 chunk);
8. `applyMotionTokens()`(把动效令牌写进 `:root`,必须在 `mount` 之前);
9. `await router.isReady()`;
10. `app.mount('#app')`。
依据:`repos/ClassHelper-master/packages/desktop-client/src/renderer/main.ts:17-57`;动效令牌注入理由见 `docs/dev/motion.md:53-59`。
路由必须用 `createWebHashHistory()`,因为生产环境是 `file://`,history 模式会 404。依据:`src/renderer/router/index.ts:5-8,110-113`。路由守卫必须做两件事:未初始化时进 OOBE;开发者页仅当 `developerMode` 为 true 时可进并做运行时兜底(读取 pinia 状态,不每次跨 contextBridge)。依据:`:115-133`。
### 2.10 岛渲染进程的技术约定(必须与 ClassIsland 视觉对齐)
1. **岛内不得引入 Element Plus、不得引入任何图标字体**;只用 Vue + 手写 CSS + 内联 SVG。依据:`src/island/main.ts:6-10`。
2. `island.html` 必须带与主窗口一致的 CSP,且 `html, body { margin:0; padding:0; background:transparent }`。依据:`repos/ClassHelper-master/packages/desktop-client/island.html:6-21`。
3. 岛必须**再注入一次**动效令牌(两个渲染进程不共享 `:root`),漏掉会让 `transition` 简写整条失效。依据:`src/island/main.ts:13-24`、`docs/dev/motion.md:58-59`。
4. 动画时长与曲线**以第 14 部分为准**(ClassIsland 的 Storyboard/Transition 规格);弹簧动画(`SpringValue`:`stiffness=0.10`、`damping=0.68`、`momentumRetention=0.35`、`maxDistanceRatio=0.2`、`maxDt=6`、`snapThreshold=0.05px`、帧单位 `1000/60`、零过冲钳制)只用于第 14 部分明确标注为「spring/跟手」的交互(如拖拽预览、内容宽度跟随),**不得**用它替代第 14 部分给出确定时长的规格动画。依据:`repos/ClassHelper-master/packages/desktop-client/src/island/spring.ts:26-30,41-47,85-114` 与第 14 部分。
5. 圆角实现:**默认必须使用 CSS `border-radius`(等同 Avalonia `CornerRadius`,半径取 `Settings.RadiusX`,默认 8)**,以保证与 ClassIsland 的直角圆弧逐像素一致;ClassHelper 的连续圆角(squircle:超椭圆指数 `4.2`、每角 `14` 段、半径 `min(r,w/2,h/2)`)只能由主题包显式启用(P2),不得作为默认,否则 8px 角的曲率会与 ClassIsland 原版不同。依据:`repos/ClassIsland/ClassIsland/MainWindow.axaml:282` 与 `XamlThemes/FluentTheme/Styles.axaml:48`;对照实现见 `src/island/squircle.ts:21-24,44-81`。
6. 岛组件的几何基线必须集中到 `packages/shared/src/island-layout.ts` 的 `CLASSISLAND_ISLAND_METRICS`:`rowHeight 40`、`horizontalPadding 12`、`radius 8`、`opacity 0.5`、`scale 1`(0.75–2.5)、`secondaryFontSize 14`、`bodyFontSize 16`、`emphasizedFontSize 18`、`largeFontSize 20`、`rowSpacing 5`、`fadedOpacity 0.05`。依据:`repos/ClassHelper-master/packages/shared/src/island-layout.ts:19-36`。
7. 岛形态尺寸增量必须集中常量:`ISLAND_SIZE_DELTA = { expanded:{w:156,h:186}, urgent:{w:172,h:202}, call:{w:188,h:218} }`、`ISLAND_SHADOW_PAD=10`、`ISLAND_SLIVER_WIDTH=6`。依据:`packages/shared/src/constants.ts:336-351`。
8. **参考值(不是本应用默认值)**:ClassHelper 既有「经典岛」外观默认值 `width 268`、`height 44`、`radius 20`、`fontSize 13`、`opacity 1`、`accent #0a84ff`、`style 'black'`、`position 'top-center'`、`marginX/marginY 8`、`idleSliver false`、`followCursorDisplay false`(范围:高度 36–72、宽 220–420、圆角 8–32、透明度 0.4–1、字号 11–20、速度 0.5–2、marginX 0–200、marginY 0–160,依据 `constants.ts:265-302`)。本应用(ClassIsland 复刻)的默认值**必须**按第 3、4、10 部分:行高 40、圆角 8、字号 14/16/18/20、透明度 0.5、停靠位 `WindowDockingLocation=1`、偏移 0,**不得**套用 ClassHelper 经典岛的 268×44 胶囊默认值。
9. 岛内 `:hover` 规则必须包在 `@media (hover: hover) and (pointer: fine)` 内,否则希沃触摸屏会出现"点过之后永远高亮"的幽灵 hover。依据:`docs/dev/motion.md:80-81`。
10. 阴影常驻、只随形态改参数,不得在形态切换时增删 `filter`(会新建合成表面导致闪帧)。依据:`docs/dev/winisland-design-tokens.md:25-26`。
### 2.11 自动更新与打包(工程结构的一部分)
- 更新目录:`<userData>/updates`;下载先写 `.part` 再校验后 `rename`;`policy.json` 记录服务端下发的更新策略。依据:`src/main/auto-update.ts:59-133`。
- 下载超时 10 分钟;文件名必须匹配 `^[A-Za-z0-9._-]+$`(挡住路径穿越);下载完必须校验 SHA-256,服务端未给哈希则**拒绝安装**。依据:`:124-128,320-391`。
- 安装时机三档:`immediate` 立即;`after-school` 不早于 `updateNotBefore`;`idle` 需主窗口隐藏满 5 分钟(`CLIENT_UPDATE_IDLE_MS = 5*60*1000`)。依据:`packages/shared/src/constants.ts:677`、`auto-update.ts:474-506`。
- 安装必须交给独立助手:先生成/校验助手 → 助手就绪 → 主进程退出 → 助手替换文件 → 写 `install-result.json`(失败时主进程下次启动读回并展示 `failed` 状态)。依据:`auto-update.ts:516-573`。
- **安装目录策略:不做 side-by-side 多版本并存**。必须使用固定安装目录(NSIS 装到固定路径);更新助手在替换前把当前安装目录的关键文件(`ClassIsland.exe`、`resources/app.asar` 等)复制到 `updates/rollback/<版本>/`,替换失败或新版启动失败时用该副本回滚并重新拉起旧版。ClassIsland 的 `app-<版本>` + `.current` 多版本方案(`repos/ClassIsland/ClassIsland.Launcher/Program.cs:30-57`、`UpdateService.cs:841-855`)只作为"可回滚"语义参考,**不照搬目录结构**。**【推断】**
- **来源安全**:更新下载默认只接受 `https://`;镜像/自定义源必须由用户显式开启并提示"非官方来源风险",默认不启用。清单必须校验 SHA-256;签名可选(Ed25519,带 `keyId`,支持公钥轮换),详见 12.6。
- 打包产物必须做"凭据门禁":`dist/renderer` 内不得出现任何账号/口令字面量(`scripts/check-bundle-secrets.mjs`),因为 asar 可直接解包。依据:`repos/ClassHelper-master/packages/desktop-client/package.json:13`、`src/preload/index.ts:43-54`。
### 2.12 ClassIsland → ClassHelper 技术映射决策表(必须按此实现)
| ClassIsland(C#/Avalonia) | 本实现(Electron/Vue) | 关键差异与对策 |
|---|---|---|
| Avalonia `Window` + `TransparencyLevelHint=AcrylicBlur` | `BrowserWindow{transparent:true, frame:false}` + CSS 半透明背景 | Electron 下**禁止** `setBackgroundMaterial()`,也**禁止** `backdrop-filter`(透明窗口下 Chromium 采样不可靠,会闪/发灰);毛玻璃效果用**预混合半透明纯色**实现(ClassHelper 的 glass 同样是半透明色、不采样桌面模糊,见 `docs/dev/winisland-design-tokens.md:23`);编辑模式按第 4 部分用全屏半透明纯色遮罩 |
| XAML `Style`/`ResourceDictionary` 主题 | `packages/ui/src/theme.css` + `html.dark` + CSS 变量 | 主题包改为"CSS 变量清单 + 资源目录 + 预览图",见 9 部分 |
| `Storyboard`/`Transition`(如 `BackgroundWidth 300ms cubic-bezier(0.65,0,0.35,1)`) | CSS `transition`/`@keyframes` + `motion.ts` 令牌 | 令牌数值单一来源放 `packages/shared/src/motion.ts`;不得在组件里手写曲线字面量 |
| `DispatcherTimer` 时间引擎 | 主进程 `setInterval` + `setTimeout` 精确对齐 + 系统休眠唤醒修正 | Node 定时器会漂移:必须以"当前时间与下一时间点差值"重排,不得累加计数 |
| `Mutex("Global\\ClassIsland.Lock")` 单实例 | `app.requestSingleInstanceLock()` | 二次启动必须唤起已有窗口(`second-instance`),不得静默退出 |
| `dotnetCampus.Ipc` 命名管道(`ClassIsland.IPC.v2.Server`) | `ipcMain/ipcRenderer` + contextBridge;**可选**本地只读 HTTP API | 不恢复命名管道。外部只读集成如启用,必须:只监听 `127.0.0.1`、端口随机分配、默认关闭、开启后需一次性 token 鉴权、只提供读接口(当前课程/课表/状态),**不得提供任何写操作**。详见 12.4 |
| 插件 `.cipx`(zip + `manifest.yml` + `.dll`) | 同构 zip(`.cipx`)+ `manifest.yml` + ESM 入口 `.mjs` | 插件必须在 utilityProcess 内 `import()`,不得污染主进程;清单统一 camelCase、兼容读取 PascalCase 旧清单;见 12 部分 |
| `Settings.json`(`CommonDirectories.AppRootFolderPath`) | `<userData>/config.json` 为主 + `Settings.json` 导入/导出兼容层 | 运行期只读写 `config.json`;`Settings.json` 只做 ClassIsland 格式的**只读导入**与导出,**不双向双写**;`IsIslandSeperated` 等历史拼写必须保留(见 13 部分) |
| `Profiles/<name>.json` | `<userData>/data/profiles/<name>.json` | 保留 PascalCase 字段名以兼容导入导出 ClassIsland 档案 |
| `Backups/Auto_*.zip` | `<userData>/backups/` | 自动备份 7 天一次、最多 16 份,包内含 `Config/` + `Profiles/` + `Settings.json`(若存在;见 13 部分) |
| 独立 Launcher + 多版本目录 | electron-builder NSIS + 固定安装目录 + 独立更新助手 + 回滚副本 | 不做 side-by-side 多版本;见 2.11 |
| `PackageRoot` 环境变量 | `app.getPath('userData')` + `process.resourcesPath` | 所有路径必须用 Electron API 解析,禁止硬编码 `C:\` |
| `Color`(Avalonia)序列化为 `#AARRGGBB` | 字符串 `#RRGGBB`/`#AARRGGBB` | 解析必须容忍 6 位与 8 位、容忍大小写,写出统一 8 位(AlphaComponentPosition.Trailing) |
### 2.13 第三方库等价映射(.NET/Avalonia → npm,必须按此实现)
ClassIsland 依赖的一批 .NET 库在本技术栈下必须换成下表等价物;不得因为"找不到同名库"就自研底层协议。
| ClassIsland 依赖 | 本实现替代 | 使用约束 |
|---|---|---|
| `System.Text.Json` + `ConfigureFileHelper` | Node `JSON.parse/stringify` + 自研 `storage.ts` | 必须保留"写入前浅拷贝快照、写入中不阻塞 UI、退出前同步落盘"语义(见 2.16) |
| `YamlDotNet`(插件 `manifest.yml`) | `yaml`(npm,服务端同款) | 解析必须 `ignoreUnknownKeys`:未知字段不得导致插件加载失败(对齐 `repos/ClassIsland/ClassIsland/Services/PluginService.cs:68-72` 的 `IgnoreUnmatchedProperties`) |
| `NAudio`/系统音频播放 | 渲染层 `HTMLAudioElement`,主进程只下发"播放/停止/音量" | 音效文件必须走 `file://` 本地资源;音量 0–1,默认 1.0(`Settings.NotificationSoundVolume`) |
| `System.Speech`/`EdgeTtsSharp` TTS | Windows 用 `System.Speech` 等价方案:优先系统 TTS(Windows `SAPI` 通过 PowerShell 启动会污染进程,改为可插拔 TTS 服务,默认关闭),可选在线 TTS 必须由用户显式开启 | 离线时不得阻塞提醒播放,TTS 失败只记日志 |
| `RestSharp`/`HttpClient` | Node 内置 `fetch`(Electron 44/Node 20+) | 必须显式 `AbortSignal.timeout(...)`、`redirect:'error'`(对齐 `auto-update.ts:193-199`) |
| `Downloader`(分片并行下载) | 自研流式下载 + 4 并发分片 | 必须支持 `.part` 临时文件、断点续传可选、进度节流 250ms(对齐 `auto-update.ts:216-219`) |
| `Octokit`(GitHub API) | 不用:更新源改为自建静态 JSON 清单 + 固定 URL 模板 | 若保留 GitHub 通道,必须只读、无 token、失败回退到主通道 |
| `SharpZipLib`(部署包解压) | `adm-zip` 或 Node `zlib` + 自研 zip 读取 | 解压前必须校验路径不越出目标目录(对齐 `UpdateService.cs:733-743` 的 `IsSafePath`) |
| `CommunityToolkit.Mvvm`(`ObservableObject`/`RelayCommand`) | Vue `ref/reactive/computed` + `watch` | 不得把 Vue 响应式代理跨 contextBridge 传递(见 2.15 数据纯净化) |
| `DynamicData`(集合变换) | `computed` + 普通数组操作 | 大数据量列表必须避免在 `computed` 里做 O(n²) 操作 |
| `MoonSharp`(Lua 教程脚本) | 禁用脚本引擎;教程改为声明式 JSON 步骤 | 不得为了教程引入任意代码执行能力(安全边界见 2.17) |
| `Sentry`(崩溃上报) | 默认关闭;开启时只上报匿名崩溃栈,不含课表与个人信息 | 与 ClassIsland 的 `IsReportingEnabled` 默认值保持一致(默认 true,但必须可在设置中关闭) |
| `dotnetCampus.Ipc` | Electron IPC(见 12 部分) | 通道名必须与 `channels.ts` 单一来源 |
| `HotAvalonia`(热重载) | Vite HMR | 仅开发期启用;生产包不得包含 HMR 客户端 |
### 2.14 共享包(`packages/shared`)的工程约定
1. `packages/shared` 必须是**纯 TypeScript**:`type: module`、`main: ./dist/index.js`、`types: ./dist/index.d.ts`、构建用 `tsc -p tsconfig.json`,产物只含 `dist`。依据:`repos/ClassHelper-master/packages/shared/package.json:4-18`。
2. `tsconfig` 的 `lib` 不得包含 `DOM`:共享包会被主进程(Node)与渲染进程(浏览器)同时消费,任何 `document`/`window`/`localStorage` 引用都会让主进程侧编译失败。依据:`repos/ClassHelper-master/packages/shared/src/motion.ts:10-16` 的显式约束与 `docs/dev/motion.md:30-37`。
3. 入口必须单一:`packages/shared/src/index.ts` 只做 `export *`/`export type *` 转发;新增模块必须同步加一行导出。依据:`packages/shared/src/index.ts:1-16`。
4. 命名约定:类型用 `PascalCase`;常量全大写下划线;函数用 `camelCase`;持久化 JSON 字段必须与 ClassIsland 原名一致(如 `TimeLayouts`、`ClassPlans`、`WeekCountDivTotal`),以便直接导入导出 ClassIsland 档案;IPC 内部 DTO 用 `camelCase` 并在 `types.ts` 注明与持久化字段的映射。**【推断】**(ClassIsland 的 JSON 为 PascalCase,见 `repos/ClassIsland/examples/example.json:4-19`。)
5. 全部"归一化"函数必须放在 shared:例如 `normalizeIslandLayouts(input)` 负责把任意 IPC/导入数据夹紧到合法区间(最多 12 行、200 节点、6 层、颜色必须 `#rrggbb`、未知 kind 丢弃、重复 id 重命名)。依据:`packages/shared/src/island-layout.ts:256-403`。
6. `motion.ts` 必须保持"纯常量 + 纯函数":只导出缓动曲线、弹簧参数、时长令牌、`motionCssVars()`;DOM 注入放各端 `composables/motion.ts`。依据:`packages/shared/src/motion.ts:136-241`。
### 2.15 共享 UI 包(`packages/ui`)与样式令牌工程
1. `packages/ui` 必须以源码形式被消费:`exports` 直接指向 `./src/index.ts`、`./src/theme.css`、`./src/tokens.css`,不产出 dist;`peerDependencies` 只允许 `vue` 与 `element-plus`。依据:`repos/ClassHelper-master/packages/ui/package.json`。
2. `tokens.css` 只放"基础令牌"(品牌色、圆角、间距、阴影),且必须提供 `html.dark` 覆盖;不得在 `html.dark` 里改圆角与动效令牌(几何必须两套主题一致,否则切换时布局跳动)。依据:`packages/ui/src/tokens.css:1-21` 与 `docs/dev/motion.md:123-130`。
3. 基础令牌必须至少包含:`--ch-ui-primary:#5d6bfa`、`--ch-ui-bg:#f7f8fa`、`--ch-ui-surface:#fff`、`--ch-ui-surface-alt:#f3f5f9`、`--ch-ui-border:#e6e9ef`、`--ch-ui-text:#242936`、`--ch-ui-muted:#737c8e`、`--ch-ui-radius:8px`、`--ch-ui-gap:16px`、`--ch-ui-shadow:0 8px 30px rgb(30 40 70 / 8%)`;深色覆盖 `bg #14161a`、`surface #1d2027`、`surface-alt #252934`、`border #343946`、`text #e9edf5`、`muted #a2abbd`、`shadow 0 8px 30px rgb(0 0 0 / 25%)`。依据:`packages/ui/src/tokens.css:1-21`。
4. 语义色令牌必须与总纲第 3 部分一致:`--ch-surface`、`--ch-surface-alt`、`--ch-surface-sunken`、`--ch-surface-hover`、`--ch-text`、`--ch-text-secondary`、`--ch-text-muted`、`--ch-text-placeholder`。组件里一律引用变量,禁止写死 `#fff`/`rgba(0,0,0,...)`(固定深色区域除外:登录页、启动过渡页、岛本身、全屏作业看板、accent 渐变上的白字)。依据:`docs/dev/motion.md:107-138`。
5. 改令牌时必须"只改值",禁止对样式表跑批量替换:把 `#303133` 全量替换成 `var(--ch-text)` 会把令牌定义本身改成自引用,导致整条自定义属性失效。依据:`docs/dev/motion.md:128-130`。
6. Element Plus 的全站过渡必须改写 `--el-transition-*` 变量(300/200ms → 200/140ms,缓动换成 `EASE_OUT`),不得改组件源码。依据:`docs/dev/motion.md:65-66`。
### 2.16 主进程并发与 I/O 纪律(直接决定"卡不卡")
1. **主进程是 Electron 的消息泵**:任何同步文件 I/O 都会同时卡住窗口动画。必须遵守:
- 配置写入必须"内存缓存 + 异步合并写 + 退出同步兜底":写盘合并窗口 `CONFIG_WRITE_DEBOUNCE_MS = 120`,写入串行化(`writing` 标志),写入期间的改动进 `pendingWrite` 再补写一次;`flushConfigSync()` 在 `before-quit` 调用。依据:`repos/ClassHelper-master/packages/desktop-client/src/main/config.ts:235-346`。
- 读取配置必须只读一次内存缓存,不得每次 `getConfig()` 都 `readFileSync + JSON.parse`(ClassHelper 明确记录过这条导致侧栏展开卡顿)。依据:`config.ts:235-250` 的注释。
2. **禁止在主进程做 CPU 重活**:Excel 解析、主题包解压、插件加载、备份打包必须放 `utilityProcess` 或 `node:worker_threads`;主进程只负责调度与 IPC 转发。
3. **定时器纪律**:时间引擎的 tick 周期固定 200ms(ClassIsland 主计时器同量级【推断】),每次 tick 只用"当前时间 - 时间点边界"做判断,禁止累加 count 推断时间;系统休眠唤醒后必须立即重算一次(监听 `powerMonitor` 的 `resume`)。
4. **写入磁盘必须原子**:所有 JSON 先写 `<file>.tmp`,`fsync` 后 `rename` 覆盖;备份进程写 `<Auto_*.zip>.tmp` 再改名。依据:`repos/ClassIsland/ClassIsland/Shared/Helpers/ConfigureFileHelper.cs:182-188`(`FileStream.Flush(true)` 后关闭)。
5. **数据目录结构固定**:`<userData>/` 下只允许出现 `config.json`、`data/`、`logs/`、`updates/`、`backups/`、`themes/`、`plugins/`、`cache/` 八个顶层条目;不得把临时文件写到安装目录(安装目录可能只读)。
6. **退出清理必须幂等**:托盘退出、`before-quit`、`will-quit` 都可能触发同一次清理,清理函数必须可重入(ClassHelper 用 `isQuitting` 标志 + 各资源自带 `isDestroyed()` 判断,见 `src/main/index.ts:287-341`)。
### 2.17 安全基线(不得为了"方便"削弱任何一条)
1. 渲染进程必须启用 `contextIsolation: true`、`nodeIntegration: false`、`sandbox: true`、`webSecurity: true`;两个窗口都要。依据:`src/main/index.ts:79-87`、`src/main/island.ts:505-511`。
2. preload **只**通过 `contextBridge.exposeInMainWorld` 暴露显式白名单方法,绝不暴露 `ipcRenderer` 本体;不得暴露 `require`、`process`、`Buffer`。依据:`src/preload/index.ts:17-101`、`src/preload/island.ts:10-123`。
3. 通道名必须集中在**零依赖叶子模块** `src/main/channels.ts`:preload 在 `sandbox: true` 下不能 `require('node:fs')` 等内建模块,一旦 import 了主进程模块,esbuild 会把整条依赖链打进 preload 导致"加载即失败、`window.desktop === undefined`"。依据:`src/main/channels.ts:1-28` 的注释与实现。
4. 跨桥接只能传**纯数据**:传 Vue `ref`/`reactive`(Proxy)会在进入 contextBridge 之前抛 `An object could not be cloned.`。所有调用点必须展开成字面量,且 preload 返回的订阅函数必须可取消(`onXxx` 返回取消函数或由主进程移除监听)。依据:`src/types/desktop.d.ts:144-152`、`src/preload/index.ts:32-36`。
5. 导航必须白名单精确比对:开发态比对 `origin`,生产态比对"去 hash/search 后的精确 `file://` URL",不得用 `startsWith`。窗口必须 `setWindowOpenHandler(() => ({action:'deny'}))`,外链一律 `shell.openExternal`。依据:`src/main/index.ts:42-61,90-100`、`src/main/island.ts:32-50,516-522`。
6. 两个 HTML 入口都必须带 CSP:`default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; font-src 'self' data:; connect-src 'self' http: https: ws: wss:; object-src 'none'; frame-src 'none'; form-action 'none'; base-uri 'self'`。依据:`repos/ClassHelper-master/packages/desktop-client/index.html:20-23`、`island.html:6-10`。
7. 敏感数据(登录令牌、管理凭据)必须用 `safeStorage.encryptString` 加密后 base64 落盘;`safeStorage` 不可用时必须降级明文并写 warn 日志(不得静默)。依据:`src/main/config.ts:169-196`。
8. 更新域名/下载 URL 必须来自配置白名单,渲染进程不得指定任意 URL 下载或执行;安装包路径必须校验"位于 `updates` 目录内且 realpath 一致"。依据:`auto-update.ts:477-490`。
9. 插件代码必须在隔离进程运行、只能访问 manifest 声明的权限(文件读写范围、网络域名、能否弹提醒、能否执行外部程序);未声明即拒绝。见 12 部分。
10. 发布前必须跑 `check-bundle-secrets`:`dist/renderer` 与 `dist/main` 中不得出现账号/口令/私钥字面量。依据:`repos/ClassHelper-master/packages/desktop-client/package.json:10-13`。
### 2.18 环境变量、命令行参数与调试开关
| 名称 | 作用 | 约束 |
|---|---|---|
| `ELECTRON_SMOKE_PROFILE` | 把 `userData` 指到隔离目录 | 仅自动化验收使用;正常启动必须不设置 |
| `ELECTRON_SMOKE_TEST=1` | 冒烟模式标记 | 必须只影响"不弹窗、不写登录项、不自动重启"等行为,不得影响业务逻辑 |
| `VITE_DEV_SERVER_URL` | 开发态渲染地址 | 生产构建不得定义;主进程读到它才 `loadURL` |
| `--hidden` | 开机自启静默启动 | 因 Electron 44 在 Windows 上 `setLoginItemSettings` 没有 `openAsHidden`,必须自己带参数(依据 `src/main/index.ts:22-27,147-173`) |
| `--safe` | 安全模式 | 禁用全部第三方插件与主题、跳过自动化、禁用更新安装,仅加载内置能力 |
| `--recovery` | 恢复模式 | 允许从备份还原配置/档案,禁用写操作直到用户确认 |
| `--diagnostic` | 诊断模式 | 日志提到 debug、结束时在桌面生成诊断包(配置、日志、版本、插件列表,**不含**个人信息) |
| `--uri <classisland://...>` | 单实例 URI 导航 | 第二次启动时把 URI 转发给已运行实例后退出(参考 `repos/ClassIsland/ClassIsland/Program.cs:43-68,141-159`) |
| `--importV1/--importV2/<path>` | 导入旧版配置 | 导入前必须备份;失败可回滚 |
环境变量与参数必须在 `app.whenReady()` 之前解析完毕,之后不得再读 `process.argv` 做行为分支(避免窗口创建后又改变模式导致状态不一致)。
### 2.19 性能预算(超预算必须优化,不得降级功能)
| 指标 | 预算 | 测量方法 |
|---|---|---|
| 冷启动到岛首帧 | ≤ 2.5s(SSD 教室机) | 主进程打点:`app.ready` → 岛渲染进程 `did-finish-load` → 首帧 `requestAnimationFrame` 回传 |
| 常驻内存(主进程 + 岛渲染 + 设置窗口) | ≤ 350MB(对齐 ClassHelper 文档 250–350MB,见 `docs/get-started/requirements.md`) | 任务管理器观察 30 分钟 |
| 空闲 CPU | ≤ 1%(岛常驻,1 秒一次时钟 tick) | 任务管理器观察 10 分钟 |
| 岛动画帧率 | 60fps(掉帧率 <1%) | DevTools Performance 录制形变过程 |
| 渲染进程包体 | 应用窗口 ≤ 3.5MB(gzip 前)、岛 ≤ 800KB | `dist/renderer/assets` 体积 |
| 设置页首屏 | ≤ 400ms(路由切换) | Performance 面板 |
| 配置写入 | 单次 ≤ 5ms,且不阻塞主线程 > 1 帧 | 主进程日志打点 |
任何一项超标都必须优化实现(例如把计算移到 `computed` 缓存、拆 chunk、减少重渲染),**不得**通过删除动画、降低帧率、去掉 blur 等方式"达标"。
### 2.20 本地化与文案工程
1. 默认语言 zh-CN;文案必须集中在 `packages/shared/src/i18n/`(或 `locales/zh-CN.json`),组件内禁止硬编码中文字符串作为唯一来源(允许以 key 调用 `t('...')`)。
2. 日期/星期/时间格式必须走 `Intl.DateTimeFormat`(`zh-CN`),星期文本必须为"周一…周日";`Settings.ShowDate` 默认 true(`repos/ClassIsland/ClassIsland/Models/Settings.cs:44`)。
3. 数字与百分比必须用 `Intl.NumberFormat`;倒计时显示格式为 `mm:ss`,超过 1 小时显示 `hh:mm:ss`。
4. 文案长度必须按"最长的中文条目"预留宽度,禁止用英文长度估算导致换行溢出。
5. 所有错误提示必须给出"发生了什么 + 用户可以做什么",不得只抛出异常类名;配置加载失败必须落到"使用默认配置并提示可恢复备份"。
### 2.21 验收标准(第 2 部分)
实现者必须逐条自查,全部通过才算完成本节:
1. `pnpm install` 后可成功执行 `pnpm -r typecheck` 与 `pnpm build:desktop`,产物包含 `dist/main/index.js`、`dist/preload/index.js`、`dist/preload/island.js`、`dist/renderer/index.html`、`dist/renderer/island.html`。
2. 依赖版本与 2.2 表逐项一致(用 `pnpm list -r --depth 0` 核对),Node 版本 `>=20.19.0`,`pnpm --version` 输出 `11.8.0`。
3. 仓库为 pnpm workspace,`packages/*` 至少含 `shared`、`ui`、`desktop-client` 三个包,根 `package.json` 是唯一手写版本号。
4. 主进程启动时序与 2.6 一致;`config.json` 在 `app.setPath('userData')` 之后才被首次读取(代码审查可验证)。
5. 岛窗口参数与 2.7 列表逐项一致(逐行对照 `new BrowserWindow` 选项),且代码中不出现 `setBackgroundMaterial`。
6. 岛渲染进程产物中不含 Element Plus 与图标字体(检查 `dist/renderer/assets` 体积与 import 图)。
7. `applyMotionTokens()` 在应用窗口与岛入口都被调用,且都在 `app.mount()` 之前。
8. 打包产物通过 `electron-builder --win nsis portable` 生成 setup 与 portable 两个 exe,语言包仅含 `zh-CN.pak`,安装包内不含任何凭据字符串。
9. 单实例:第二次启动不产生第二套窗口/托盘,而是唤起已有实例。
10. 主进程日志写入 `app.getPath('logs')/main.log`,超过 1MB 轮转为 `main.log.1`,且退出前配置已同步落盘(`flushConfigSync` 被调用)。
**补充验收项(工程结构层面)**:
11. 仓库内不得出现硬编码绝对路径(`C:\`、`D:\`、`/home/`),所有路径必须经 `app.getPath`、`path.join`、`process.resourcesPath` 解析;代码审查用 `grep -rn "C:\\\\" packages/` 应为零命中。
12. 图标资源必须"源文件 + 派生文件"分离:设计导出的 `build/classisland.ico`/`.png` 为唯一源,其余尺寸由 `pnpm icons` 派生;`electron-builder.yml` 必须显式指定 `icon: build/classisland.ico`,不得依赖 electron-builder 的默认约定。依据:`repos/ClassHelper-master/packages/desktop-client/electron-builder.yml:48-52`。
13. 托盘必须存在且行为固定:左键/双击显示主窗口;右键菜单至少含「打开设置」「编辑岛」「隐藏/显示岛」「重启应用」「退出」;托盘图标缺失时必须用空图标兜底并记 warn,不得崩溃。依据:`repos/ClassHelper-master/packages/desktop-client/src/main/tray.ts:31-54`。
14. 开机自启必须"配置值"与"系统登录项"双向一致:设置页读的是 `app.getLoginItemSettings().openAtLogin`,不是配置文件里的值;写入失败时配置仍保存并提示"下次启动重试"。依据:`src/main/ipc.ts:66-99`。
15. 所有代码必须通过 Prettier 检查(LF 换行、单引号),且不得为了过检查对历史文件做批量格式化;提交前 `pnpm format:check` 必须通过。