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

83 KiB
Raw Blame History

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。

目录

  • 使用说明(给人类读者,不投喂给模型)
    1. 给 ChatGPT 的任务声明(从本节开始执行)
    1. 项目总纲:产品定位、总体体验与系统架构
    • 1.1 你的角色与最终目标
    • 1.2 产品定位
    • 1.3 目标用户与典型场景
    • 1.4 十条不可妥协的体验原则
    • 1.5 功能全景(必须实现的模块)
    • 1.6 总体技术架构(ClassHelper 栈)
    • 1.7 信息架构与导航
    • 1.8 首次启动与默认状态
    • 1.9 本提示词的使用方式与验收总则
    1. 技术栈与工程结构
    • 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 部分)
    1. 设计系统 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 验收标准
    1. 主界面(岛):窗口、行、背景、进度、遮罩、编辑模式
    • 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 验收标准
    1. 组件系统:全部内置组件逐个规格
    • 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 验收标准
    1. 课程表 / 时间表 / 档案 / 科目 / 周次 / 调课 / 导入导出
    • 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 验收标准
    1. 提醒系统(队列、提供者、气泡、强提醒、音效与 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 天气服务与数据模型
    1. 自动化(触发器 / 行动 / 规则集)
    • 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 验收标准
    1. 主题系统(内置主题、主题包格式、壁纸取色、主题预览、自定义颜色)
    • 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 验收标准
    1. 设置中心
    • 10.1 设置窗口外壳(必须逐像素对齐)
    • 10.2 导航结构(左侧 NavigationView)
    • 10.3 页面容器与通用控件(必须映射到 Element Plus)
    • 10.4 每个设置页的逐项规格
    • 10.5 设置持久化、缓存与叠层(行为级要求)
    • 10.6 键盘与交互细节
    • 10.7 验收标准
    1. 其它窗口与向导(闪屏 / 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 验收标准
    1. 插件系统、跨进程通信、单实例与自动更新
    • 12.1 目标与边界
    • 12.2 ClassIsland 插件体系事实(必须对齐的语义)
    • 12.3 本实现的插件规范(必须逐项落地)
    • 12.4 跨进程通信(IPC)设计(必须完整实现)
    • 12.5 单实例与 URI 协议(必须实现)
    • 12.6 自动更新(必须按此流程实现)
    • 12.7 崩溃自愈与看门狗(必须实现)
    • 12.8 验收标准(第 12 部分)
    1. 数据模型、存储、备份与迁移
    • 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 部分)
    1. 动画与动效总表
    • 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 验收标准
    1. 资源清单(图标 / 字体 / 音效 / 天气图标 / 教程 / 本地化)
    • 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 验收标准
    1. 质量门禁与验收清单
    • 16.1 工程与构建门禁
    • 16.2 像素级视觉验收(主界面)
    • 16.3 功能行为验收(按模块)
    • 16.4 动画验收
    • 16.5 数据完整性与容错
    • 16.6 性能与资源
    • 16.7 平台与兼容性
    • 16.8 安全验收
    • 16.9 交付物清单
    1. 开发里程碑与实现顺序
    • 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 天)
    • 里程碑总览表
    1. 禁止事项与实现边界
    • 18.1 关于「不打扰」的硬性要求
    • 18.2 技术红线
    • 18.3 数据与安全红线
    • 18.4 质量与流程红线
    • 18.5 本提示词未明确时的裁决顺序
    1. 结语(给实现者的最后要求)

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 直接失败:

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 的组织方式。

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 被调用)。

补充验收项(工程结构层面):

  1. 仓库内不得出现硬编码绝对路径(C:\、D:\、/home/),所有路径必须经 app.getPath、path.join、process.resourcesPath 解析;代码审查用 grep -rn "C:\\\\" packages/ 应为零命中。
  2. 图标资源必须"源文件 + 派生文件"分离:设计导出的 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。
  3. 托盘必须存在且行为固定:左键/双击显示主窗口;右键菜单至少含「打开设置」「编辑岛」「隐藏/显示岛」「重启应用」「退出」;托盘图标缺失时必须用空图标兜底并记 warn,不得崩溃。依据:repos/ClassHelper-master/packages/desktop-client/src/main/tray.ts:31-54。
  4. 开机自启必须"配置值"与"系统登录项"双向一致:设置页读的是 app.getLoginItemSettings().openAtLogin,不是配置文件里的值;写入失败时配置仍保存并提示"下次启动重试"。依据:src/main/ipc.ts:66-99。
  5. 所有代码必须通过 Prettier 检查(LF 换行、单引号),且不得为了过检查对历史文件做批量格式化;提交前 pnpm format:check 必须通过。