- 重构版主提示词(14 parts,面向 ClassHelper 灵动岛重构) - ClassIsland 全量规格提示词(20 parts,含分卷与逐章版本) - 投喂指南、分卷、逐节文件 - 调研记录、一致性基线、生成与质检脚本
83 KiB
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 部分)。
使用说明(给人类读者,不投喂给模型)
- 整体投喂:全文约 200k tokens,需使用上下文 ≥ 200k 的模型(如 GPT-5 级长上下文 / Gemini 2.5 Pro)才能一次粘贴;将本文件从「## 0.」到结尾整体粘贴,尾附一句:「请严格按此提示词实现,先给出实施计划与文件树,再按里程碑推进。」
- 分卷投喂:按
## 0+## 1+## 2(基础)→## 3## 5(视觉与组件)→## 6## 8(业务)→## 9## 13(系统与数据)→## 14## 19(动画、资源、验收)的顺序分批发送;每批发完后要求模型复述关键参数,避免长上下文丢失。 - 推荐追问:「请先输出 20 个你认为最可能歧义的点」「请给出 packages/desktop-client 的完整文件树与每个文件的职责」「请先实现 M0/M1 并给出无头验证脚本」。
- 证据约定:文中
repos/ClassIsland/...与repos/ClassHelper-master/...是规格来源路径,不是让你照抄的运行时依赖;实现时应把这些规格转写为 TypeScript/CSS。
目录
- 使用说明(给人类读者,不投喂给模型)
-
- 给 ChatGPT 的任务声明(从本节开始执行)
-
- 项目总纲:产品定位、总体体验与系统架构
- 1.1 你的角色与最终目标
- 1.2 产品定位
- 1.3 目标用户与典型场景
- 1.4 十条不可妥协的体验原则
- 1.5 功能全景(必须实现的模块)
- 1.6 总体技术架构(ClassHelper 栈)
- 1.7 信息架构与导航
- 1.8 首次启动与默认状态
- 1.9 本提示词的使用方式与验收总则
-
- 技术栈与工程结构
- 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 部分)
-
- 设计系统 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.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.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.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 验收标准
-
- 提醒系统(队列、提供者、气泡、强提醒、音效与 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.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.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.1 设置窗口外壳(必须逐像素对齐)
- 10.2 导航结构(左侧 NavigationView)
- 10.3 页面容器与通用控件(必须映射到 Element Plus)
- 10.4 每个设置页的逐项规格
- 10.5 设置持久化、缓存与叠层(行为级要求)
- 10.6 键盘与交互细节
- 10.7 验收标准
-
- 其它窗口与向导(闪屏 / 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.1 目标与边界
- 12.2 ClassIsland 插件体系事实(必须对齐的语义)
- 12.3 本实现的插件规范(必须逐项落地)
- 12.4 跨进程通信(IPC)设计(必须完整实现)
- 12.5 单实例与 URI 协议(必须实现)
- 12.6 自动更新(必须按此流程实现)
- 12.7 崩溃自愈与看门狗(必须实现)
- 12.8 验收标准(第 12 部分)
-
- 数据模型、存储、备份与迁移
- 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.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.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.1 工程与构建门禁
- 16.2 像素级视觉验收(主界面)
- 16.3 功能行为验收(按模块)
- 16.4 动画验收
- 16.5 数据完整性与容错
- 16.6 性能与资源
- 16.7 平台与兼容性
- 16.8 安全验收
- 16.9 交付物清单
-
- 开发里程碑与实现顺序
- 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.1 关于「不打扰」的硬性要求
- 18.2 技术红线
- 18.3 数据与安全红线
- 18.4 质量与流程红线
- 18.5 本提示词未明确时的裁决顺序
-
- 结语(给实现者的最后要求)
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 硬性纪律(违反即不合格)
- 所有自动验证必须无头/离屏执行;不得显示可见窗口、托盘气泡、系统通知或声音;需要人工观察的验证用显式
--visual参数开启且不得作为默认。 - 渲染层零 Node 权限:preload 只暴露 contextBridge 白名单,不暴露
ipcRenderer;nodeIntegration=false、contextIsolation=true、sandbox=true。 - 主窗口(岛)不得抢焦点、默认不进入任务栏;不得使用
setBackgroundMaterial()等破坏逐像素透明的 API。 - 动画必须 60fps:优先 transform/opacity,禁止
setInterval逐帧驱动、禁止逐帧改布局属性。 - 核心功能离线可用;数据写入原子化并自动备份;迁移前先备份。
- 不得伪造验收结果;未验证项必须显式标注。
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 工作方式
- 通读本提示词后,先输出:实施计划、完整文件树、你识别出的歧义点(一次性提问)。
- 按第 17 部分的 M0→M8 顺序实现;每个里程碑结束都交付「可无头启动 + 通过该阶段验收」的状态。
- 每完成一个模块,写清楚:实现了什么、证据(文件与行号)、未实现/推断项、如何验证。
- 遇到本提示词未覆盖的细节:按第 18.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 目标用户与典型场景
- 班主任/任课老师:用 Excel 导入课表,设置单双周/多周轮换、临时换课;设置上下课提醒音效与语音;用自动化在午休时关闭屏幕。
- 电教委员/学生:日常只看岛,不需要任何操作;断网、重启后应用自动恢复;不误触、不遮挡投影。
- 学校管理员:通过主题/配置批量部署;可选管理端下发策略(本提示词的 P2 可选模块,默认不依赖服务器)。
- 插件开发者:按插件规范扩展组件、提醒提供者、自动化触发器/行动、设置页。
1.4 十条不可妥协的体验原则
- 像素级还原本提示词的视觉规格:圆角、透明度、字号阶梯、间距、进度条、阴影必须逐项落实;默认参数见第 3、4 部分。
- 绝不打扰授课:主窗口默认不抢焦点(
show: false显示、focusable: false)、不进入任务栏、可配置鼠标穿透;任何自动验证/冒烟必须无头运行,不得创建可见窗口。 - 60fps 丝滑动画:所有过渡优先使用 CSS transform/opacity(合成层),禁止用 JS 逐帧改布局;弹簧动画采用本提示词给定的物理参数。
- 时间必须准:所有课程状态以「带补偿的当前时间」计算;支持 NTP 对时、手动时间偏移、时间倍速调试;跨天、跨周、闰秒、系统休眠唤醒都要正确。
- 离线优先、数据安全:配置、课表、提醒全部本地存储;写入采用原子替换 + 自动备份;任何迁移前先备份,迁移失败可回滚。
- 组件化与可组合:主界面的每一行、每一个显示单元都是组件;组件可嵌套(分组/堆叠/滚动/滑动)、可单独设置、可按规则隐藏。
- 默认即好看:首次启动走完 OOBE 后,即使不导入课表也要显示一个可用的岛(日期、时间、欢迎信息),不能白屏。
- 可恢复:渲染进程崩溃、GPU 崩溃、配置损坏、误删数据时,应用必须能自愈或进入恢复模式;崩溃后不得反复重启刷屏。
- 可扩展:插件/主题/提醒提供者/自动化行动全部通过注册表扩展,核心不写死;公共 API 保持向后兼容。
- 可访问与本地化:中文为默认语言(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(独立岛渲染进程)。 - 进程模型(必须三进程分离):
- 主进程
src/main:窗口管理、托盘、全局快捷键、单实例、配置读写、调度(时间引擎/提醒调度/自动化)、自动更新、系统集成、IPC 服务端。 - 岛渲染进程
island.html→src/island:只负责画「岛」,常驻内存,通过 contextBridge 暴露的最小 API 接收状态;窗口透明、点击穿透由主进程控制。 - 应用窗口渲染进程
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.2s 内渐显 Logo 与应用名,可显示加载状态),随后显示 OOBE。
- OOBE 完成后写
config.json(并兼容导出 ClassIsland 的Settings.json)的IsWelcomeWindowShowed=true,主界面以默认参数出现:位置=中上侧(WindowDockingLocation=1)、缩放 100%、背景不透明度 0.5、圆角 8px、字体默认系统字体(Windows 为微软雅黑 UI 回退链)、主体字号 16、次要 14、强调 18、超大 20、行间距 5px。 - 默认组件配置为 1 行 2 组件:日期(DF3F8295-21F6-482E-BADA-FA0E5F14BB66)+ 课程表(1DB2017D-E374-4BC6-9D57-0B4ADF03A6B8,依据 ComponentsService.cs:33-52)。未加载档案时课程表组件显示「今天没有课程。」等占位符,而不是白屏或欢迎文本;加载课表后显示课程信息。
- 默认设置必须保证:岛可见、不穿透、不抢焦点、不遮挡全屏应用(
HideOnFullscreen=false,可开启)、提醒开启、音效开启但音量 100%、调试关闭。
1.9 本提示词的使用方式与验收总则
- 本提示词按「总纲 → 技术栈 → 设计系统 → 主界面 → 组件 → 课表/提醒/自动化 → 主题/设置/窗口 → 动画/资源 → 插件/数据 → 验收」组织。实现时必须全部通读,再按第 17 部分的里程碑顺序开发。
- 每一部分末尾都有「验收标准」;你必须逐条自测并在交付说明中报告结果。自测必须无头执行(
--headless/ 隐藏窗口 / CI 模式),不得弹出可见窗口、不得占用用户桌面、不得发出声音;像素级验收使用 Playwright/electron的离屏截图模式完成。 - 任何你没有把握的数值,优先服从本提示词;本提示词未覆盖的细节,按「Fluent Design + 灵动岛」的直觉补齐,并在代码注释中说明。
- 交付必须包含:完整源码、
README.md(含运行/构建/打包命令)、docs/(数据 schema 与插件 API)、至少 1 个示例主题与 1 个示例插件(P1)、以及一套无头自动化测试(单元测试 + 关键交互的组件测试)。
2. 技术栈与工程结构
2.1 技术选型总则(必须逐条遵守)
- 你必须用 TypeScript + Electron + Vue 3 实现本应用,工程形态必须是 pnpm monorepo,不得做成单包项目、不得用 Webpack、不得用 Electron Forge、不得用
electron-vite之外的脚手架替代本文规定的手写构建管线。 - 你必须完整沿用 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。 - 你不得把 ClassHelper 的服务端(Express/Prisma/Socket.IO)引入主流程。ClassIsland 是本地单机应用,一切业务数据必须落在本机;仅当实现「可选管理端联动」时才允许把
packages/server作为可裁剪子包,且主流程不得依赖网络。依据:repos/ClassHelper-master/packages/server/package.json:19-40的依赖清单,以及总纲的 P2 定义。 - 你必须把「版本」收敛为单一来源:根
package.json的version是唯一手写值,所有子包版本由脚本同步;日常功能提交不得涨版本。依据:repos/ClassHelper-master/AGENTS.md「根 package.json 是版本单一来源」与repos/ClassHelper-master/docs/dev/release.md:8-10。 - 你必须保持「渲染层零 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 进程模型(必须五类进程分离)
- 主进程(
src/main/index.ts):唯一持有窗口、托盘、配置、定时器、文件系统、自动更新、插件宿主的进程。它必须承载 ClassIsland 的「Core 服务层」职责:时间引擎、课表状态机、提醒宿主、自动化、存储与备份、插件注册表。 - 应用窗口渲染进程(
index.html):设置中心、档案编辑器、OOBE、关于页、日志、调试、恢复。按需创建;关闭窗口必须只隐藏不退出(ClassIsland 主窗口关闭即隐藏的逻辑改成「设置窗口关闭=销毁,托盘常驻」)。 - 岛渲染进程(
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 库)。 - 插件/自动化隔离进程(
utilityProcess,Electron 44 内置):插件代码、用户自动化脚本、外部命令执行必须跑在这个进程里,崩溃不得拖垮主进程;与主进程之间用MessagePort传纯数据。ClassIsland 用PluginLoadContext做程序集隔离(repos/ClassIsland/ClassIsland/Services/PluginService.cs:186-235),在 Electron 下等价物就是 utilityProcess + 动态import()。 - 独立更新助手(
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.jsdist/preload/index.jsdist/preload/island.jsdist/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/发布前按序执行,任何一步失败即中止):
pnpm build:shared(先建共享包,再建下游——依据 ClassHelper 根脚本package.json:11-17)。pnpm typecheck(vue-tsc --noEmit+tsc --noEmit -p tsconfig.node.json,依据desktop-client/package.json:16)。pnpm lint(ESLint + 自定义 authz/资源检查)。pnpm check:icons(图标资源完整性)。pnpm build:desktop(= check-icons + build:main + vite build + check:secrets,依据desktop-client/package.json:10-13)。
2.6 应用启动时序(必须按此顺序,不得提前读配置)
- 解析命令行参数(
--hidden静默启动、--safe安全模式、--diagnostic诊断模式、--recovery恢复模式、--importV1/--importV2导入)。 - 若存在
ELECTRON_SMOKE_PROFILE(仅自动化验收使用),app.setPath('userData', profile);正常启动不得设置。 app.requestSingleInstanceLock();拿不到锁立刻app.quit(),拿到锁后监听second-instance并聚焦/唤起已有窗口。依据:src/main/index.ts:176-182。app.whenReady()之后依次:logger.setVerbose(config.developerMode)→registerIpcHandlers()→registerIslandIpc()→ 应用开机自启登录项 → 创建设置窗口(隐藏态可配)→installCrashGuard()→ 加载渲染页 → 注册退出回调 →island.init()→ 创建托盘。依据:src/main/index.ts:184-262。- 退出时统一资源清理:停止定时器 →
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 必须按以下顺序执行,顺序错一个都会出现白闪或首帧无动画:
createApp(App);- 注册用到的 Element Plus 图标(只注册用到的,不全量 293 个);
app.use(createPinia());app.use(router);app.use(ElementPlus, { locale: zhCn });registerSharedUi(app)(来自@classhelper/ui);- 冒烟钩子(若
window.desktop?.smokeTest,用await import()动态加载,禁止静态 import 进主 chunk); applyMotionTokens()(把动效令牌写进:root,必须在mount之前);await router.isReady();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 视觉对齐)
- 岛内不得引入 Element Plus、不得引入任何图标字体;只用 Vue + 手写 CSS + 内联 SVG。依据:
src/island/main.ts:6-10。 island.html必须带与主窗口一致的 CSP,且html, body { margin:0; padding:0; background:transparent }。依据:repos/ClassHelper-master/packages/desktop-client/island.html:6-21。- 岛必须再注入一次动效令牌(两个渲染进程不共享
:root),漏掉会让transition简写整条失效。依据:src/island/main.ts:13-24、docs/dev/motion.md:58-59。 - 动画时长与曲线以第 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 部分。 - 圆角实现:默认必须使用 CSS
border-radius(等同 AvaloniaCornerRadius,半径取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。 - 岛组件的几何基线必须集中到
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。 - 岛形态尺寸增量必须集中常量:
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。 - 参考值(不是本应用默认值):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 胶囊默认值。 - 岛内
:hover规则必须包在@media (hover: hover) and (pointer: fine)内,否则希沃触摸屏会出现"点过之后永远高亮"的幽灵 hover。依据:docs/dev/motion.md:80-81。 - 阴影常驻、只随形态改参数,不得在形态切换时增删
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)的工程约定
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。tsconfig的lib不得包含DOM:共享包会被主进程(Node)与渲染进程(浏览器)同时消费,任何document/window/localStorage引用都会让主进程侧编译失败。依据:repos/ClassHelper-master/packages/shared/src/motion.ts:10-16的显式约束与docs/dev/motion.md:30-37。- 入口必须单一:
packages/shared/src/index.ts只做export */export type *转发;新增模块必须同步加一行导出。依据:packages/shared/src/index.ts:1-16。 - 命名约定:类型用
PascalCase;常量全大写下划线;函数用camelCase;持久化 JSON 字段必须与 ClassIsland 原名一致(如TimeLayouts、ClassPlans、WeekCountDivTotal),以便直接导入导出 ClassIsland 档案;IPC 内部 DTO 用camelCase并在types.ts注明与持久化字段的映射。【推断】(ClassIsland 的 JSON 为 PascalCase,见repos/ClassIsland/examples/example.json:4-19。) - 全部"归一化"函数必须放在 shared:例如
normalizeIslandLayouts(input)负责把任意 IPC/导入数据夹紧到合法区间(最多 12 行、200 节点、6 层、颜色必须#rrggbb、未知 kind 丢弃、重复 id 重命名)。依据:packages/shared/src/island-layout.ts:256-403。 motion.ts必须保持"纯常量 + 纯函数":只导出缓动曲线、弹簧参数、时长令牌、motionCssVars();DOM 注入放各端composables/motion.ts。依据:packages/shared/src/motion.ts:136-241。
2.15 共享 UI 包(packages/ui)与样式令牌工程
packages/ui必须以源码形式被消费:exports直接指向./src/index.ts、./src/theme.css、./src/tokens.css,不产出 dist;peerDependencies只允许vue与element-plus。依据:repos/ClassHelper-master/packages/ui/package.json。tokens.css只放"基础令牌"(品牌色、圆角、间距、阴影),且必须提供html.dark覆盖;不得在html.dark里改圆角与动效令牌(几何必须两套主题一致,否则切换时布局跳动)。依据:packages/ui/src/tokens.css:1-21与docs/dev/motion.md:123-130。- 基础令牌必须至少包含:
--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。 - 语义色令牌必须与总纲第 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。 - 改令牌时必须"只改值",禁止对样式表跑批量替换:把
#303133全量替换成var(--ch-text)会把令牌定义本身改成自引用,导致整条自定义属性失效。依据:docs/dev/motion.md:128-130。 - Element Plus 的全站过渡必须改写
--el-transition-*变量(300/200ms → 200/140ms,缓动换成EASE_OUT),不得改组件源码。依据:docs/dev/motion.md:65-66。
2.16 主进程并发与 I/O 纪律(直接决定"卡不卡")
- 主进程是 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的注释。
- 配置写入必须"内存缓存 + 异步合并写 + 退出同步兜底":写盘合并窗口
- 禁止在主进程做 CPU 重活:Excel 解析、主题包解压、插件加载、备份打包必须放
utilityProcess或node:worker_threads;主进程只负责调度与 IPC 转发。 - 定时器纪律:时间引擎的 tick 周期固定 200ms(ClassIsland 主计时器同量级【推断】),每次 tick 只用"当前时间 - 时间点边界"做判断,禁止累加 count 推断时间;系统休眠唤醒后必须立即重算一次(监听
powerMonitor的resume)。 - 写入磁盘必须原子:所有 JSON 先写
<file>.tmp,fsync后rename覆盖;备份进程写<Auto_*.zip>.tmp再改名。依据:repos/ClassIsland/ClassIsland/Shared/Helpers/ConfigureFileHelper.cs:182-188(FileStream.Flush(true)后关闭)。 - 数据目录结构固定:
<userData>/下只允许出现config.json、data/、logs/、updates/、backups/、themes/、plugins/、cache/八个顶层条目;不得把临时文件写到安装目录(安装目录可能只读)。 - 退出清理必须幂等:托盘退出、
before-quit、will-quit都可能触发同一次清理,清理函数必须可重入(ClassHelper 用isQuitting标志 + 各资源自带isDestroyed()判断,见src/main/index.ts:287-341)。
2.17 安全基线(不得为了"方便"削弱任何一条)
- 渲染进程必须启用
contextIsolation: true、nodeIntegration: false、sandbox: true、webSecurity: true;两个窗口都要。依据:src/main/index.ts:79-87、src/main/island.ts:505-511。 - preload 只通过
contextBridge.exposeInMainWorld暴露显式白名单方法,绝不暴露ipcRenderer本体;不得暴露require、process、Buffer。依据:src/preload/index.ts:17-101、src/preload/island.ts:10-123。 - 通道名必须集中在零依赖叶子模块
src/main/channels.ts:preload 在sandbox: true下不能require('node:fs')等内建模块,一旦 import 了主进程模块,esbuild 会把整条依赖链打进 preload 导致"加载即失败、window.desktop === undefined"。依据:src/main/channels.ts:1-28的注释与实现。 - 跨桥接只能传纯数据:传 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。 - 导航必须白名单精确比对:开发态比对
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。 - 两个 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。 - 敏感数据(登录令牌、管理凭据)必须用
safeStorage.encryptString加密后 base64 落盘;safeStorage不可用时必须降级明文并写 warn 日志(不得静默)。依据:src/main/config.ts:169-196。 - 更新域名/下载 URL 必须来自配置白名单,渲染进程不得指定任意 URL 下载或执行;安装包路径必须校验"位于
updates目录内且 realpath 一致"。依据:auto-update.ts:477-490。 - 插件代码必须在隔离进程运行、只能访问 manifest 声明的权限(文件读写范围、网络域名、能否弹提醒、能否执行外部程序);未声明即拒绝。见 12 部分。
- 发布前必须跑
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 本地化与文案工程
- 默认语言 zh-CN;文案必须集中在
packages/shared/src/i18n/(或locales/zh-CN.json),组件内禁止硬编码中文字符串作为唯一来源(允许以 key 调用t('...'))。 - 日期/星期/时间格式必须走
Intl.DateTimeFormat(zh-CN),星期文本必须为"周一…周日";Settings.ShowDate默认 true(repos/ClassIsland/ClassIsland/Models/Settings.cs:44)。 - 数字与百分比必须用
Intl.NumberFormat;倒计时显示格式为mm:ss,超过 1 小时显示hh:mm:ss。 - 文案长度必须按"最长的中文条目"预留宽度,禁止用英文长度估算导致换行溢出。
- 所有错误提示必须给出"发生了什么 + 用户可以做什么",不得只抛出异常类名;配置加载失败必须落到"使用默认配置并提示可恢复备份"。
2.21 验收标准(第 2 部分)
实现者必须逐条自查,全部通过才算完成本节:
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 表逐项一致(用
pnpm list -r --depth 0核对),Node 版本>=20.19.0,pnpm --version输出11.8.0。 - 仓库为 pnpm workspace,
packages/*至少含shared、ui、desktop-client三个包,根package.json是唯一手写版本号。 - 主进程启动时序与 2.6 一致;
config.json在app.setPath('userData')之后才被首次读取(代码审查可验证)。 - 岛窗口参数与 2.7 列表逐项一致(逐行对照
new BrowserWindow选项),且代码中不出现setBackgroundMaterial。 - 岛渲染进程产物中不含 Element Plus 与图标字体(检查
dist/renderer/assets体积与 import 图)。 applyMotionTokens()在应用窗口与岛入口都被调用,且都在app.mount()之前。- 打包产物通过
electron-builder --win nsis portable生成 setup 与 portable 两个 exe,语言包仅含zh-CN.pak,安装包内不含任何凭据字符串。 - 单实例:第二次启动不产生第二套窗口/托盘,而是唤起已有实例。
- 主进程日志写入
app.getPath('logs')/main.log,超过 1MB 轮转为main.log.1,且退出前配置已同步落盘(flushConfigSync被调用)。
补充验收项(工程结构层面):
- 仓库内不得出现硬编码绝对路径(
C:\、D:\、/home/),所有路径必须经app.getPath、path.join、process.resourcesPath解析;代码审查用grep -rn "C:\\\\" packages/应为零命中。 - 图标资源必须"源文件 + 派生文件"分离:设计导出的
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。 - 托盘必须存在且行为固定:左键/双击显示主窗口;右键菜单至少含「打开设置」「编辑岛」「隐藏/显示岛」「重启应用」「退出」;托盘图标缺失时必须用空图标兜底并记 warn,不得崩溃。依据:
repos/ClassHelper-master/packages/desktop-client/src/main/tray.ts:31-54。 - 开机自启必须"配置值"与"系统登录项"双向一致:设置页读的是
app.getLoginItemSettings().openAtLogin,不是配置文件里的值;写入失败时配置仍保存并提示"下次启动重试"。依据:src/main/ipc.ts:66-99。 - 所有代码必须通过 Prettier 检查(LF 换行、单引号),且不得为了过检查对历史文件做批量格式化;提交前
pnpm format:check必须通过。