commit 22c1784babc71febdd558e0900126a0ecff833c3 Author: ClassIsland Prompt Kit Date: Thu Nov 12 21:59:58 2026 +0800 feat: ClassIsland / ClassHelper 灵动岛提示词套件 - 重构版主提示词(14 parts,面向 ClassHelper 灵动岛重构) - ClassIsland 全量规格提示词(20 parts,含分卷与逐章版本) - 投喂指南、分卷、逐节文件 - 调研记录、一致性基线、生成与质检脚本 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3d8c4ed --- /dev/null +++ b/.gitignore @@ -0,0 +1,14 @@ +# 上游克隆与构建产物(不属于本仓库交付物) +repos/ +node_modules/ +.cache/ +.pnpm-store/ +*.zip +*.log +*.tmp +.DS_Store +Thumbs.db + +# 本地编辑器/系统文件 +.idea/ +.vscode/ diff --git a/ClassHelper灵动岛重构-给ChatGPT的完整提示词.md b/ClassHelper灵动岛重构-给ChatGPT的完整提示词.md new file mode 100644 index 0000000..2d32ba6 --- /dev/null +++ b/ClassHelper灵动岛重构-给ChatGPT的完整提示词.md @@ -0,0 +1,4301 @@ +# ClassHelper 灵动岛重构:给 ChatGPT 的完整实现提示词 + +> **文档性质**:这是一份「提示词」而非分析报告。请把本文件(或按章节分卷)投喂给 ChatGPT,要求它在 **ClassHelper 现有仓库**(`LoveChengke/ClassHelper`,pnpm monorepo,Electron + Vue 3)中,以 ClassIsland(`ClassIsland/ClassIsland`,C#/Avalonia)的灵动岛(主界面)为基准,**重构 ClassHelper 的灵动岛子系统**。不是从零新建项目,不重写服务端与管理端,不更换技术栈。 +> +> **信息来源**:ClassHelper 工作区源码(`repos/ClassHelper-master`)与 ClassIsland v2.1 源码(`repos/ClassIsland`)。文中所有确切数值、文件名、行为均尽量标注 `packages/...:行号` 或 `repos/ClassIsland/...:行号`;无源码依据处标注【推断】。ClassIsland 的源码只作为行为与视觉基准,**不得**把其 C#/Avalonia 代码、XAML、本地档案体系引入 ClassHelper。 +> +> **重要纪律**:本重构的一切自动验证(构建、冒烟、截图、动画检查)必须**无头/离屏运行,禁止弹出可见窗口、禁止播放声音、禁止干扰用户桌面**。ClassHelper 仓库默认的 `verify:desktop` / `verify:packaged` 就是后台快速检查,只有显式 `--visual` 才允许可见验收——重构后必须保持这一约定(见第 11、12 部分)。 + +## 使用说明(给人类读者,不投喂给模型) + +1. **投喂方式**:全文约 12 万 tokens,建议按 `out/投喂指南-重构版.md` 的五卷/逐章方式投喂;一次性投喂需 ≥ 128k 上下文模型。投喂前请把 ClassHelper 仓库(或至少 `packages/desktop-client`、`packages/shared`、`packages/ui`、`docs/`)提供给 ChatGPT(作为代码上下文或附件)。 +2. **推荐开场**:「请先阅读第 0–2 部分,复述你对现状、目标与 7 条裁决的理解;然后给出文件级重构计划(含迁移与回滚),等我确认后再按 M0–M7 逐阶段实现。」 +3. **证据约定**:`packages/...:行号` 指 ClassHelper 仓库相对路径;`repos/ClassIsland/...:行号` 是 ClassIsland 基准来源(相对本工作区),只用于理解行为,不是运行时依赖。 +4. **验收纪律**:任何「已完成」的声明都必须附带无头验证输出与第 12 部分的逐项勾选结果;未验证项必须标注。 + +## 目录 + +- 使用说明(给人类读者,不投喂给模型) +- 0. 给 ChatGPT 的任务声明(从本节开始执行) +- 1. 重构目标与范围 + - 1.1 为什么重构、以什么为基准 + - 1.2 目标状态(重构完成后用户看到什么) + - 1.3 范围(In / Out) + - 1.4 模块级 Keep / Change / Add / Remove + - 1.5 关键设计裁决(必须逐条执行) + - 1.6 成功标准(重构完成的定义) + - 1.7 主要风险与对策 + - 1.8 阅读顺序提示 +- 2. ClassHelper 灵动岛现状基线(重构的"现状地图") + - 2.0 阅读与使用方式 + - 2.1 进程、窗口与装配全景 + - 2.2 主进程岛控制器分区地图(`src/main/island.ts`,共 2445 行) + - 2.3 窗口几何:固定包围盒 + 岛内形变 + - 2.4 命中、穿透、触摸与"点不动"防护 + - 2.5 消息状态机(fixed 经典模式) + - 2.6 经典模式渲染(`src/island/IslandApp.vue`,683 行) + - 2.7 自定义布局(custom)运行时与数据 + - 2.8 课程提醒与课程组件 + - 2.9 天气现状 + - 2.10 课件传输覆盖 + - 2.11 渲染入口与组件清单(现状文件级地图) + - 2.12 主窗口 → 主进程桥接(`src/renderer/island/bridge.ts`) + - 2.13 配置与数据契约现状 + - 2.14 IPC 现状(重构必须保持的契约) + - 2.15 编辑器与设置页现状 + - 2.16 既有硬约束(重构不得破坏,逐条对应 AGENTS.md §4) + - 2.17 现状问题清单(重构动因,逐条对应 04 的改造项) + - 2.18 验收标准(第 2 部分) +- 3. ClassIsland 灵动岛行为基准(重构对照规格) + - 3.1 主界面行(行为模型) + - 3.2 组件系统(11 类 + 容器) + - 3.3 编辑模式(行为语义) + - 3.4 隐藏与显示规则(完整判定) + - 3.5 多显示器、停靠与点击穿透 + - 3.6 时间状态机与跨天(课程计算的唯一事实源) + - 3.7 课程表组件的行为(重点) + - 3.8 提醒队列与优先级(行为) + - 3.9 通知两层 UI(气泡 + 强提醒) + - 3.10 天气展示(行为) + - 3.11 主题语义(Fluent vs Classic 的差异) + - 3.12 动画语义总则(实现时必须遵守) + - 3.13 本轮不实现的 ClassIsland 能力(必须写进实现说明,不得遗漏标注) + - 3.14 验收标准(行为级) +- 4. 目标架构与文件级改造清单 + - 4.0 重构原则(必须逐条遵守) + - 4.1 目标模块边界与目录 + - 4.2 文件级 Keep / Change / Add / Remove 总表 + - 4.3 关键文件改造详情(逐个文件:现状 → 目标 → 接口 → 风险) + - 4.4 IPC 契约 v2(增量、不改旧通道语义) + - 4.5 迁移与门面的落地顺序(必须按序提交) + - 4.6 风险清单与强制缓解 + - 4.7 验收标准(第 4 部分) +- 5. 数据契约、schema v2 与配置迁移 + - 5.0 总则(必须遵守) + - 5.1 落点与版本治理 + - 5.2 `appearance` v2(`config.json.island`) + - 5.3 `islandLayout` v2(`config.json.islandLayout`) + - 5.4 v1 → v2 迁移算法(必须逐步实现,可重入) + - 5.5 校验与夹取(`normalizeIslandLayoutV2` / `normalizeAppearanceV2`) + - 5.6 DTO 映射(ClassHelper 数据 → ClassIsland 语义) + - 5.7 IPC payload 版本与兼容 + - 5.8 编辑器预览与临时状态(不持久化) + - 5.9 回滚与降级 + - 5.10 版本变更记录与兼容矩阵 + - 5.11 验收标准(第 5 部分) +- 6. 设计 tokens 与视觉基线(双风格:ClassIsland 默认 / winisland 兼容) + - 6.0 双风格模型(必须先建,再谈 token) + - 6.1 变量分层与命名规范 + - 6.1.1 `--ci-*` 变量速查表(必须在 `tokens.css` 中一次性定义齐) + - 6.2 颜色 token 表 + - 6.3 字体 token + - 6.4 圆角 token + - 6.5 几何与间距 token + - 6.6 阴影 token + - 6.7 透明与不透明度 token + - 6.8 层级(z-index)token + - 6.9 DPI 与缩放 token + - 6.10 深浅色 token + - 6.11 两套风格差异总表(默认选择点) + - 6.12 配置迁移与兼容策略(token 相关部分) + - 6.13 文件落点与实施顺序 + - 6.14 常见错误与自查清单 + - 6.15 验收标准 +- 7. 岛结构与组件规格(ClassHelper 落地版) + - 7.0 改造口径与文件级改动清单 + - 7.1 岛窗口与几何 + - 7.2 行、背景、进度条、遮罩与淡出(ClassIsland 规格:custom 恒定 / fixed 默认) + - 7.3 外观族:`family='classisland'`(默认)与 `family='winisland'`(兼容,仅 fixed) + - 7.4 组件逐个规格(11 内置 + 3 消息入口 + 课件覆盖) + - 7.5 编辑器渲染侧结构(组件库 / 拖拽 / 选中框 / 行设置) + - 7.6 验收标准 +- 8. 消息、提醒、课程提醒、强提醒特效、天气与课件覆盖(ClassHelper 落地规格) + - 8.1 数据契约(复用为主,最小扩展) + - 8.2 去重、合并与排序(ClassHelper 业务规则,必须保留) + - 8.3 队列与展示状态机(三形态对照 + 伪代码) + - 8.4 ClassIsland 风格气泡(`fixed` 默认形态,两阶段) + - 8.5 强提醒全屏特效(Topmost) + - 8.6 课程提醒五类(prepare / on-class / break / ending / progress) + - 8.7 下课前蓝色秒数徽标(重点规格) + - 8.8 课程进度条(两处,不得混用) + - 8.9 天气简报(Open-Meteo,保留 ClassHelper 语义) + - 8.10 课件传输覆盖(最高优先级内容) + - 8.11 教师端 / 班级端差异 + - 8.12 冲突裁决表(ClassHelper 业务优先,ClassIsland 视觉优先) + - 8.13 边界与失败降级 + - 8.14 验收标准(可勾选,全部通过才算完成) +- 9. 编辑器与设置页重构(IslandSettingsView / IslandEditorView / IslandComponentSettings) + - 9.1 口径与术语(与 parts-r/01、05、07 对齐) + - 9.2 文件级改造清单(只列本 part 负责的文件) + - 9.3 设置页信息架构(重构后) + - 9.4 视觉风格(family)与两套参数组 + - 9.5 classisland 族外观参数(逐项) + - 9.6 winisland 族外观参数(兼容保留,不得删) + - 9.7 课程提醒设置(并入本页) + - 9.8 保存、实时预览与失败重试(设置页) + - 9.9 编辑器总体布局(像素级) + - 9.10 方案栏(保留并升级) + - 9.11 行与行设置(ClassIsland 编辑模式对齐) + - 9.12 组件库(11 类,保留既有 DOM 契约) + - 9.13 拖拽规格(三类源,必须保留现有实现) + - 9.14 选中、组件设置抽屉(四页签) + - 9.15 组件设置项清单(与 parts-r/07 组件契约一一对应) + - 9.16 预览与「进入编辑模式」 + - 9.17 键盘、触摸与焦点 + - 9.18 数据流、契约与失败处理 + - 9.19 状态、空态、主题与性能约束 + - 9.20 既有实现与测试契约(重构不得破坏) + - 9.21 验收标准 +- 10. 动画与动效映射(ClassHelper 现状 → ClassIsland 目标;双风格默认值) + - 10.0 总则与不可协商的规则 + - 10.1 ClassHelper 现状动画清单(重构前基线,必须保留语义) + - 10.2 映射总表(目标规格;每条给出双风格默认值) + - 10.2.1 消息气泡完整时间线(按毫秒对齐,验收时逐点核对) + - 10.3 冲突与选择点(必须按默认执行,并保留开关) + - 10.4 性能预算与降级矩阵 + - 10.5 实现方式规范(逐条遵守) + - 10.6 逐文件改动清单 + - 10.7 验收标准 +- 11. 兼容性、性能、平台与测试基线 + - 11.1 兼容矩阵(必须逐项通过) + - 11.2 配置迁移与回滚(硬要求) + - 11.3 IPC 与契约兼容 + - 11.4 性能预算(超预算必须优化) + - 11.5 平台差异与窗口护栏 + - 11.6 测试基线(无头优先) + - 11.7 观测与诊断 + - 11.8 文档与交付说明 + - 11.9 本部分验收标准 +- 12. 验收清单(逐项勾选,不得跳过) + - 12.1 工程门禁 + - 12.2 视觉验收(像素级) + - 12.3 功能验收 + - 12.4 动画验收 + - 12.5 兼容与迁移验收 + - 12.6 无头与不打扰验收 + - 12.7 性能验收 + - 12.8 文档与交付物 + - 12.9 禁止事项(验收时再次确认) +- 13. 重构里程碑(M0–M7) + - M0 基线固化与安全网(0.5–1 天) + - M1 共享层 v2:schema、常量、迁移、tokens(1–2 天) + - M2 渲染引擎:行/组件/tokens(2–3 天) + - M3 消息、课程提醒、强提醒、天气、课件(2–3 天) + - M4 设置页与编辑器重构(2–3 天) + - M5 动画、主题、细节打磨(1–2 天) + - M6 迁移、兼容与回归(1–2 天) + - M7 全量验收与交付(1–2 天) + - 里程碑风险提示 + + +## 0. 给 ChatGPT 的任务声明(从本节开始执行) + +### 0.1 你的任务 + +你是一名资深 Electron + Vue 3 + TypeScript 桌面工程师,熟悉透明置顶窗口、逐像素自绘 UI、CSS 动画与主进程/渲染进程协作。你的任务是在 **ClassHelper 现有 monorepo** 中重构灵动岛(island)子系统: + +- 保留 ClassHelper 的工程结构、技术栈、服务端数据来源、既有业务规则与配置体系; +- 以 ClassIsland 的灵动岛为**视觉与行为基准**,重构 ClassHelper 的 `fixed`(经典)与 `custom`(自定义常驻)两套布局的渲染、交互、动画、设置与编辑器; +- 新增 `winisland` 兼容风格开关,使老用户可保留现有观感;默认使用 ClassIsland 风格; +- 不新建应用、不重写服务端/数据库/Web 管理端、不引入新的 UI 框架。 + +最终交付是**可合并进 ClassHelper 的代码改动 + 数据迁移 + 文档 + 无头测试 + 验收报告**,而不是示例代码或原型。 + +### 0.2 不可协商的技术与工程约束 + +1. 技术栈原样沿用:pnpm@11.8.0 monorepo;Electron ^44.3.0;Vue ^3.5.42;Element Plus ^2.14.5(仅设置/编辑器界面);Pinia ^4.0.3;Vue Router ^5.3.1;Vite ^8.3.0;TypeScript ~5.9.3;主进程/preload 用 esbuild,渲染层用 Vite。 +2. 包边界不变:`packages/shared`(类型/常量/纯函数/岛 schema)、`packages/ui`(共享控件/tokens)、`packages/desktop-client`(Electron 主进程 + preload + 主窗口渲染 + 独立岛渲染 `island.html`);服务端 `packages/server` 与 Web 管理端 `packages/web-admin` **不在本次改动范围**。 +3. 岛窗口仍是**独立透明 BrowserWindow + 独立 `island.html` 渲染入口 + 独立 island preload**;主窗口设置页只作为编辑器入口,编辑预览不创建额外桌面窗口。 +4. 渲染层零 Node 权限:preload 仅暴露 contextBridge 白名单,不暴露 `ipcRenderer`;`nodeIntegration:false`、`contextIsolation:true`、`sandbox:true`。 +5. 禁止 `setBackgroundMaterial()` / `vibrancy` / `backdrop-filter` 等会破坏逐像素透明的 API;透明、圆角、阴影全部由 CSS 自绘。 +6. 数据来源不变:课表/时间点/通知/作业/叫人/课件仍来自 ClassHelper 服务端解析结果(`ScheduleDto`、`IslandTimePointDto`、`IslandState`、`IslandNotification` 等);不得引入 ClassIsland 的本地 Profile/ClassPlan 档案体系。 +7. 配置仍写入 `config.json.islandLayout`(本机,不同步服务端);新增字段必须可迁移、可回滚,旧 `fixed`/`custom` 配置无损升级。 +8. 既有硬约束必须保留:`winisland` 兼容形态下的连续圆角与投影常驻、触摸输入、失焦宽限、固定窗口几何护栏;所有形态下消息按 id 去重、通知/叫人双播不重复、课件传输优先、默认验证不创建可见岛或托盘、冒烟假执行不真关机。 +9. 所有自动验证无头执行;不得修改验收标准来「通过」;未验证项必须标注。 + +### 0.3 交付物 + +- 代码:`packages/shared`、`packages/desktop-client`(main/preload/island/renderer)的改动;如涉及 `packages/ui` 的 token 复用也需提交。 +- 数据迁移:`config.json.islandLayout` 的版本化迁移函数、旧配置升级、失败回滚路径;不得丢失用户的 schemes/rows/nodes/外观设置。 +- 文档:更新 `docs/app/island.md` 与 `docs/dev/implementation.md` 的岛章节;新增本次重构的 `docs/dev/island-refactor.md`(架构、数据契约、动画表、迁移说明)。 +- 测试:无头单元测试(schema 迁移、normalize、消息队列、课程状态机)+ 组件测试(岛渲染快照)+ 既有无头 `verify:desktop` 的适配;视觉验收仅在 `--visual` 下执行。 +- 报告:第 12 部分验收清单逐项勾选;与 ClassIsland 的差异说明;未实现项与原因;性能数据(启动、空闲 CPU、内存、动画帧率)。 + +### 0.4 工作方式 + +1. 先通读本提示词与 ClassHelper 相关源码,输出:①你对现状与目标的理解复述;②文件级重构计划(含每个文件的改动类型、接口变化、迁移与回滚);③你识别出的歧义点(一次性提问);④M0 的无头验证方案。 +2. 严格按第 13 部分的 M0–M7 顺序推进;每个里程碑结束都交付「可无头启动 + 通过该阶段验收 + 不回归既有功能」的状态。 +3. 每个模块完成后写清:改了什么、证据(文件/行号/行为变化)、未完成/推断项、验证方法与结果。 +4. 遇到本提示词未覆盖的细节:ClassHelper 既有行为优先(业务语义与既有交互),ClassIsland 规格优先(视觉、像素、动画);仍不明确时在代码与交付说明中标注【推断】。 +5. 不得顺手重构无关模块;保持 diff 可审阅、可回滚。 + +### 0.5 术语表 + +- **灵动岛 / 岛(island)**:ClassHelper 的独立透明窗口与 `island.html` 渲染的悬浮界面。 +- **fixed(经典)**:ClassHelper 现有「胶囊 + 详情卡」消息型布局。 +- **custom(自定义常驻)**:ClassHelper 现有「多行 + 组件 + 组件方案」布局,常驻显示。 +- **winisland**:ClassHelper 现有观感(WinIsland 风格:连续圆角、胶囊 268×44、纯黑/毛玻璃/主题色渐变);作为兼容风格保留。 +- **ClassIsland 风格**:本重构的默认观感,按 ClassIsland 主界面的行/组件/像素/动画规格实现。 +- **行(row / line)**:承载组件的横向容器(对应 ClassIsland `MainWindowLine`)。 +- **组件(component / node)**:`IslandComponentNode`,最小显示单元(日期/时钟/天气/倒计时/课程表/文本/容器等)。 +- **方案(scheme)**:`IslandLayoutScheme`,一套常态 `rows` 与无消息展开 `expandedRows` 的组件布局。 +- **消息(message)**:通知/作业/叫人的统称(`IslandNotification`)。 +- **强提醒(Topmost effect)**:ClassIsland 的全屏强调特效窗口;本次以 Electron 独立窗口实现,不抢焦点、不阻塞。 +- **无头验证**:不创建可见窗口/托盘/声音的自动化检查(ClassHelper 的 `verify:desktop` 默认模式)。 + +### 0.6 阅读顺序 + +0 → 1 → 2 → 3 → 4 → 5 → 6 → 7 → 8 → 9 → 10 → 11 → 12 → 13。第 2 部分先建立现状地图,第 3 部分给出行为基准,第 4/5 部分给改造清单与数据迁移,第 6/7/10 部分给视觉、组件、动画规格,第 8/9 部分给消息与编辑器,第 11/12/13 部分给兼容、验收、里程碑。 + +--- + +## 1. 重构目标与范围 + +### 1.1 为什么重构、以什么为基准 + +ClassHelper 的灵动岛已经具备完整的工程骨架:独立透明窗口、`fixed`/`custom` 两种布局、多方案组件编辑器、消息队列、课程提醒、天气、课件覆盖。但它的视觉语言来自 WinIsland(连续圆角、小胶囊、弹簧形变、纯黑/毛玻璃),与 ClassIsland 的「Fluent 信息行 + 组件化 + 提醒气泡 + 强提醒特效 + 精细过渡」存在系统性差距。本次重构以 **ClassIsland v2.1 的灵动岛(MainWindow / MainWindowLine / Components / Notification / TopmostEffect)为行为与视觉基准**,把 ClassHelper 的岛升级为同等精细度,同时保留 ClassHelper 的工程结构、数据来源与业务规则。 + +你必须记住三条基准优先级: + +1. **ClassHelper 业务语义优先**:消息去重、优先级、上课暂存、紧急展开、叫人胶囊、已读回执、班级设备、课件传输优先等规则不得改变; +2. **ClassIsland 视觉与动画优先**:像素、字号、圆角、间距、颜色、动画时长与曲线以本提示词第 6、7、10 部分为准; +3. **ClassHelper 既有工程与配置不得推翻**:pnpm monorepo、独立岛渲染进程、`config.json.islandLayout`、`IslandComponentNode`/`IslandLayoutScheme` 数据模型、编辑器入口、无头验证纪律继续有效。 + +### 1.2 目标状态(重构完成后用户看到什么) + +1. **fixed(经典)默认变成 ClassIsland 风格的课表信息行**:屏幕顶部/底部悬浮一条或多条信息行(默认 1 行:日期 + 课程表;行高 40、圆角 8、背景不透明度 0.5、字号 14/16/18/20、底部 3px 进度条),显示当天课程、当前课程、剩余分钟/倒计时;不再是 268×44 的小胶囊。 +2. **消息到来时的表现**:普通通知/作业在岛内展开为 ClassIsland 风格的气泡(行内遮罩 + 内容两阶段:遮罩扫入 360ms + 错峰 120ms,文字延迟 260ms 淡入并 1.1→1.0 缩放 750ms),紧急消息立即展开;保留 ClassHelper 的上课暂存、普通叫人可点胶囊、紧急立即展开等规则。 +3. **强提醒**:可配置的 ClassIsland 强提醒(Topmost 特效)在岛所在显示器全屏/大区域显示,超时自动关闭,不抢焦点、不阻塞操作;默认仅对"强调提醒"启用。 +4. **custom(自定义常驻)**:保留现有方案/行/组件/编辑器,但渲染与交互升级为 ClassIsland 规格:行高 40、行间距 5、组件外边距 6/6、行设置抽屉、选中框 1.5px 虚线、拖拽把手、隐藏规则、行/组件外观覆盖、进入编辑模式(实际内容预览)、退出保存。 +5. **winisland 兼容风格(只作用于 fixed)**:`appearance.family = 'winisland'` 时保留现有经典 fixed 观感(连续圆角、胶囊 268×44、纯黑/毛玻璃/主题色渐变、弹簧形变);`custom` 不随 family 变化,始终使用 ClassIsland 规格(其编辑器/行/组件结构保持不变,只按 07 细化像素)。默认新装 `classisland`,老用户升级迁移为 `winisland`。 +6. **设置与编辑器**:灵动岛设置页新增风格选择、ClassIsland 外观参数(字号阶梯、行间距、进度条、强提醒开关、主题 Fluent/Classic 选择);自定义方案编辑器增加"编辑模式"与行设置抽屉;所有改动即时保存、失败可重试、退出不丢改动。 +7. **数据与兼容**:旧 `config.json.islandLayout` 无损迁移;用户的 schemes/rows/nodes、外观值、编辑器习惯全部保留;迁移失败可回滚;旧版本客户端读到新配置时按"忽略未知字段 + 默认值"处理(或明确拒绝并提示升级)。 + +### 1.3 范围(In / Out) + +**必须做(In)**: +- 岛窗口与渲染层:`island.html`、`LayoutIslandApp.vue`、`IslandApp.vue`、`CustomIslandRuntime.vue`、`ComponentNode.vue`、`ClassicNotifications.vue`、`LessonReminderContent.vue`、`CoursewareTransfer.vue`、`spring.ts`、`squircle.ts`、各 CSS。 +- 主进程岛控制器:`src/main/island.ts`(窗口几何、命中、触摸、消息队列、课程提醒、天气、课件、编辑预览、保存配置、预览/应用)。 +- preload 与 bridge:`src/preload/island.ts`、`src/renderer/island/bridge.ts`、`src/types/desktop.d.ts` 的岛契约。 +- 共享层:`packages/shared/src/{island-layout,constants,motion}.ts` 的 schema、常量、归一化、迁移。 +- 设置/编辑器:`IslandSettingsView.vue`、`IslandEditorView.vue`、`IslandComponentSettings.vue`、`IslandEditorNode.vue`、`island-editor.css`,以及 `router` 中岛编辑路由的入口。 +- 配置:`config.json.islandLayout` 版本化迁移、原子写、回滚。 +- 文档与无头测试。 + +**不做(Out,最多在交付说明中标注为后续)**: +- 不重写服务端/数据库/Web 管理端;不改变服务端接口字段与权限。 +- 不引入 ClassIsland 的 Profile/TimeLayout 本地档案、插件系统、自动化系统、管理端、主题市场、Excel 导入、更新器。 +- 不引入新框架/新 UI 库/新状态库;不更换构建管线。 +- 不把岛改成多窗口;仍只有一个原生岛窗口 + 一个可选的强提醒特效窗口。 +- 不改动主窗口(设置/作业/成绩/通知中心)布局,除编辑器入口与预览联动外。 + +### 1.4 模块级 Keep / Change / Add / Remove + +| 模块 | 处理 | 说明 | +|---|---|---| +| 岛窗口创建/几何/命中/触摸/失焦宽限 | **Keep + Change** | 保留固定包围盒与透明窗口护栏;按 ClassIsland 行高/宽度/停靠语义重算几何;fixed 从胶囊尺寸改为信息行尺寸;custom 保持测量适配但按 ClassIsland 行模型约束 | +| `IslandApp.vue`(经典) | **Change(大改)** | 从"胶囊+详情卡"重构为"ClassIsland 信息行 + 通知气泡 + 进度条";保留消息状态机与紧急/上课规则 | +| `CustomIslandRuntime.vue`(自定义) | **Change** | 保留测量/缩放/行渲染;行高、行距、组件边距、背景、圆角、进度条、选中框、拖拽按 ClassIsland 规格;编辑模式重做 | +| `ComponentNode.vue` / 组件库 | **Change + Add** | 11 类组件的默认样式/尺寸/字号/空数据按 ClassIsland 规格;新增进度条/倒计时徽标/课程时间点展开等子结构 | +| `ClassicNotifications.vue` | **Change** | 气泡两阶段(斜切遮罩/文字/缩放)与 ClassIsland 参数对齐;保留 ClassHelper 消息类型/排序/回执 | +| `LessonReminderContent.vue` | **Change** | 课程提醒条/倒计时徽标按 ClassIsland 布局与字号;下课前蓝色圆形秒数徽标(含 `-1`) | +| `CoursewareTransfer.vue` | **Keep** | 覆盖优先逻辑不变,只按新视觉加外壳/动画 | +| `spring.ts` / `squircle.ts` | **Keep(条件使用)** | ClassIsland 风格默认用 CSS transition + 标准圆角;winisland 风格继续用弹簧/连续圆角;不得把弹簧用于 ClassIsland 规格动画 | +| `main/island.ts` | **Change** | 窗口几何、状态模式、消息队列、课程提醒、天气、编辑预览按新交互调整;新增强提醒窗口控制、编辑模式、迁移调用;保留单实例/恢复/限流 | +| `shared/island-layout.ts` | **Change(v2)** | 新增 `version`、风格/主题/强提醒/进度条等字段;新增 ClassIsland 组件设置字段;normalize/migrate;旧配置无损升级 | +| 设置页/编辑器 | **Change + Add** | 新增风格切换、ClassIsland 外观项、编辑模式、行设置抽屉;保留方案/行/组件/导入导出 | +| 文档/测试 | **Change + Add** | 更新 docs;新增无头测试;`--visual` 保持可选 | + +### 1.5 关键设计裁决(必须逐条执行) + +1. **固定形态风格双轨、默认 ClassIsland**:`appearance.family: 'classisland' | 'winisland'` 只决定 **fixed** 的形态(信息行 vs 旧胶囊);`custom` 始终 ClassIsland 规格。新装默认 `classisland`;旧用户升级保持 `winisland`(避免 fixed 观感突变);设置页在 fixed 下提供切换并即时预览。 +2. **fixed 重定义**:fixed 不再是"小胶囊消息岛",而是 ClassIsland 风格信息行(默认 1 行,日期+课程表;消息时行内气泡)。原胶囊形态只在 `winisland` 风格下保留。 +3. **custom 保留数据模型**:schemes/rows/nodes/expandedRows 不变,只扩展字段与渲染;已有用户方案无需重做即可获得新视觉。 +4. **数据不迁移**:继续消费 ClassHelper 的课表/通知/作业/叫人/课件数据;ClassIsland 的 Lesson/Notification 语义只作为行为参考。 +5. **强提醒是新增窗口而非替换岛**:ClassIsland 的 TopmostEffect 以独立透明窗口实现(无边框、置顶、不抢焦点、超时关闭);只在消息的"强调提醒"开关开启时出现;编辑/预览模式不创建。 +6. **配置版本化**:`islandLayout.version`(整数,当前 2);`normalizeIslandLayouts()` 先迁移再归一化;迁移前备份 `config.json`,失败回滚并提示。 +7. **验证无头**:所有自动验证必须后台执行;`--visual` 才允许可见岛、动画、截图验收;不得为验收创建真实班级/发送测试通知/重置密码。 + +### 1.6 成功标准(重构完成的定义) + +- **视觉**:第 6/7 部分的像素值在 1920×1080@100%、1080p@125%/150%、深/浅色、ClassIsland Fluent/Classic 两套主题、ClassHelper winisland 风格下逐项对齐(容差:颜色 ±2/255,尺寸/位置 ±1px,透明度 ±1%)。 +- **动画**:第 10 部分动画表逐项实现(时长误差 ≤ 16ms、曲线一致);60fps;减少动画/失焦/隐藏页暂停生效。 +- **功能**:消息/课程提醒/天气/课件/编辑器/设置/迁移全部可用;无回归(见第 12 部分)。 +- **兼容**:旧配置升级成功率 100%(本仓库 fixtures 内);回滚可用;旧字段 `residentEnabled` 继续兼容。 +- **性能**:岛渲染进程启动 ≤ 500ms;空闲 CPU ≤ 1%;主+岛进程内存 ≤ 250MB;消息风暴 20 条/分钟无卡顿/丢失/重复。 +- **纪律**:`verify:desktop` 默认不创建可见岛/托盘;无 GUI 打扰。 + +### 1.7 主要风险与对策 + +| 风险 | 对策 | +|---|---| +| 透明窗口改尺寸导致闪烁 | 固定包围盒 + 窗内形变(保留现有策略);`setBounds` 仅模式/设置/显示器变化且节流 ≥100ms | +| fixed 重定义导致老用户不习惯 | `winisland` 风格保留 + 升级默认沿用旧风格 + 设置页可切换 | +| 消息状态机重构引入竞态 | 先写状态机单测(去重/优先级/暂存/紧急/回执),再改 UI;所有状态变更走单一入口 | +| 编辑模式拖拽与触摸冲突 | 编辑模式暂停指针避让/消息展开;触摸与鼠标分别走命中/宽限路径 | +| 配置迁移破坏用户方案 | 迁移前备份;schema 校验 + 归一化;失败回滚;fixtures 覆盖 fixed/custom/旧版字段 | +| 多显示器/DPI 偏差 | 全部使用 DIP;屏幕变化事件驱动重算;截图验收用 devicePixelRatio 换算 | +| 动画掉帧 | 只用 transform/opacity;弹簧仅限 winisland;长任务移出主线程;帧率监控 | + +### 1.8 阅读顺序提示 + +- 先读第 2 部分(ClassHelper 现状)与第 3 部分(ClassIsland 行为基准),确认"保留什么、改什么"。 +- 第 4/5 部分是施工图与数据迁移,必须在写代码前读完。 +- 第 6/7/10 部分是像素与动画的硬规格,逐条落实。 +- 第 8/9 部分处理消息、课程、强提醒与编辑器。 +- 第 11/12/13 部分是兼容、验收与里程碑,按顺序执行。 + +--- + +## 2. ClassHelper 灵动岛现状基线(重构的"现状地图") + +### 2.0 阅读与使用方式 + +1. 你在重构前必须先读懂本部分。本部分不是评价现状好坏,而是**逐文件、逐行号**给出"现在到底是什么样"。重构的每一处改动都必须能在本部分找到对应的现状依据。 +2. 本部分所有行号以 `repos/ClassHelper-master/` 为工作区相对根,写作 `packages/desktop-client/...:行号`;与 ClassIsland 对照时写 `repos/ClassIsland/...:行号`。行号是**行区间起点**,读源码时按主题名检索同样有效。 +3. 现状基线里出现的所有数值(尺寸、时长、阈值、限制)都是**重构时必须先保持、再按新规格替换**的对象;不得因为"要改成 ClassIsland 风格"就整体删除,至少要先在迁移层保留等价语义。 + +### 2.1 进程、窗口与装配全景 + +灵动岛是**独立渲染进程 + 独立透明 BrowserWindow**,与主窗口并存,不占任务栏: + +- 主进程在 `app.whenReady()` 里 `registerIpcHandlers()`、`registerIslandIpc()`,随后调用 `island.init(devUrl ? `${devUrl}/island.html` : null)`;生产环境加载 `dist/renderer/island.html`。依据:`packages/desktop-client/src/main/index.ts:188-221`、`packages/desktop-client/src/main/island.ts:613-617`。 +- 主进程把三件事接给灵动岛:`island.setOpenAppHandler`(打开应用)、`island.setEditLayoutHandler`(打开编辑器)、`island.setMarkReadHandler/setMarkAllReadHandler`(把"标为已读"转给主窗口渲染进程)。依据:`src/main/index.ts:221-245`。 +- 托盘菜单提供「编辑灵动岛布局」,调用 `island.editLayout()`;退出清理时 `island.destroy()`。依据:`src/main/tray.ts:42`、`src/main/index.ts:304-307`。 +- 灵动岛窗口创建参数(重构后仍必须保留这些窗口级语义):`frame:false`、`transparent:true`、`resizable:false`、`movable:false`、`minimizable:false`、`maximizable:false`、`fullscreenable:false`、`skipTaskbar:true`、`hasShadow:false`、`focusable:true`、`alwaysOnTop:true`、`type:'toolbar'`,`webPreferences` 带独立 `preload`(`../preload/island.js`)、`contextIsolation:true`、`nodeIntegration:false`、`sandbox:true`、`backgroundThrottling:false`。依据:`src/main/island.ts:480-512`。 +- 创建后设置 `setAlwaysOnTop(appearance.alwaysOnTop,'screen-saver')`、`setVisibleOnAllWorkspaces(true,{visibleOnFullScreen:true})`、拒绝所有新开窗口与非法导航、默认 `setIgnoreMouseEvents(true,{forward:true})`。依据:`src/main/island.ts:514-527`;导航白名单实现见 `:32-50`。 +- **严禁**调用 `setBackgroundMaterial()`。现状代码在 `src/main/island.ts:1726-1748` 有整段踩坑说明:一旦调用(包括 `'none'`)窗口就不再逐像素透明,卡片外圈会永久留白底;`glass` 风格因此改用 CSS 半透明色 `rgba(32,32,36,0.804)` 降级(`src/island/classic-island.css:71-77`)。 + +### 2.2 主进程岛控制器分区地图(`src/main/island.ts`,共 2445 行) + +这是本轮重构最需要拆分的文件。按现状行区间整理如下(ChatGPT 必须按此表定位旧实现): + +| 行区间 | 现状职责 | 关键符号 | +|---|---|---| +| 1-130 | 导入、开发/生产 URL 白名单、窗口尺寸基线与形态增量、收回动画时长、各状态超时表 | `prodIslandUrl`、`isAllowedIslandNavigation`、`BASE`、`ISLAND_SIZE_DELTA`、`CLOSE_ANIMATION_MS=360`、`TIMEOUTS` | +| 134-166 | 对外载荷/动作类型 | `IslandPushContext`、`IslandClassStatePayload`、`IslandActionName` | +| 168-460 | `IslandController` 字段与 getter:课程提醒、计时器、窗口、状态、外观、触摸/常驻/编辑、命中、预览、心跳、恢复 | `lessonCue`、`collapseTimer`、`closeTimer`、`fadeToken`、`touchMode`、`residentShapeTimer`、`previewId`、`recoverAt` | +| 468-634 | `init()`:恢复外观、算尺寸、建窗、导航限制、失焦/无响应/心跳监听、加载页面、首帧同步 | `init`、`render-process-gone`、`unresponsive`、`console-message` | +| 636-680 | 窗口/课表/时间点读取、状态组装、课件状态、就绪判断 | `getWindow`、`getState`、`setCoursewareTransfer` | +| 684-767 | 消息语义:立即展开判定、可点开判定、双播合并、待处理排序、列表布局 | `isImmediate`、`isOpenable`、`mergeNotification`、`pendingNotifications`、`listLayout` | +| 770-915 | 视口高度、收到通知的分流、上课/下课状态迁移、下课补弹 | `refreshViewport`、`pushNotification`、`setClassState`、`flushQueueAfterClass` | +| 929-1077 | 用户动作状态机:展开/收起/展开更多/知道了/整批/标已读/打开应用 | `handleAction` | +| 1079-1214 | 隐藏、收回动画、立即隐藏、销毁、移除通知 | `hide`、`beginClose`、`finishClose`、`hideImmediately`、`destroy`、`removeNotification` | +| 1216-1322 | 关闭/激活/收起定时/清理/预览收尾 | `dismissActive`、`clearPendingForClose`、`activate`、`scheduleCollapse`、`clearTimers` | +| 1324-1410 | 锚点与目标屏、换屏重排、状态落地与推送 | `resolveAnchor`、`targetDisplay`、`syncAnchorDisplay`、`setState`、`emit` | +| 1412-1512 | 可见性/焦点/透明度总协调(含上课隐藏守卫) | `syncWindow` | +| 1514-1648 | 固定包围盒/触摸贴合/重排/窗口矩形 | `windowBox`、`syncTouchLayout`、`relayout`、`applyWindowLayout`、`windowBounds` | +| 1650-1724 | 焦点策略、淡入淡出、取消几何动画 | `applyFocusable`、`fadeIn`、`fadeOut`、`cancelBoundsAnimation` | +| 1749-1783 | 外观热更新 | `setAppearance` | +| 1785-1910 | 命中开关、岛体矩形、常驻控件/触摸、鼠标避让淡化、触摸模式 | `setInteractive`、`setHitRect`、`updateResidentPointer`、`setTouchMode` | +| 1912-2047 | 冒烟注入点、坐标换算、兜底岛体矩形、岛体命中判定 | `setTestInputPassthrough`、`setHitTestCursor`、`cursorLocal`、`expectedHitRect`、`isCursorOnIsland`、`syncHitFromCursor` | +| 2049-2157 | 60ms 命中轮询、渲染心跳、窗口自愈(10 分钟 3 次) | `startHitPoll`、`checkRendererAlive`、`recoverWindow` | +| 2159-2184 | 冒烟读取器(交互/窗口/外观) | `getInteractive`、`getWindowBounds`、`getAppearance`、`emitAppearance` | +| 2186-2264 | 尺寸换算、细缝尺寸、外观夹紧 | `recomputeSizes`、`getSliverSize`、`normalizeAppearance` | +| 2266-2334 | 留白/失焦宽限/预览时长/恢复限流/接口类型 | `ISLAND_MARGIN=8`、`BLUR_GRACE_MS=500`、`BLUR_GRACE_TOUCH_MS=1200`、`PREVIEW_DURATION_MS=30000`、`MIN_CARD_HEIGHT=120`、`RENDERER_ALIVE_TIMEOUT_MS=20000` | +| 2335-2445 | 单例导出与 IPC 注册 | `island`、`registerIslandIpc`、`ISLAND_SIZES/URGENT/CALL/TIMEOUTS` 再导出 | + +1. 重构时**不得**在这个文件里继续堆逻辑。你必须按 04 部分的模块边界把它拆成 `island/` 目录(controller/window/hit/touch/messages/lessons/weather/courseware/ipc),并保留 `src/main/island.ts` 作为再导出门面(`export { island, registerIslandIpc } from './island/index.js'`),以免一次性改动 `main/index.ts`、`tray.ts`、`courseware.ts`、`smoke.ts` 的调用点。 + +### 2.3 窗口几何:固定包围盒 + 岛内形变 + +现状采用「窗口一次建大、岛在画布内形变」的模型(照搬 WinIsland): + +- 形态基线:胶囊 `216×34`,展开 = 胶囊 + `ISLAND_SIZE_DELTA.expanded`,紧急/叫人各再加大。依据:`src/main/island.ts:72-96`、`packages/shared/src/constants.ts:344-351`。 +- `recomputeSizes(appearance)` 按用户可调宽高联动所有形态,并计算 `BBOX_SIZE = 最大形态 + 2×ISLAND_SHADOW_PAD`(留白 10)。依据:`src/main/island.ts:2187-2215`、`packages/shared/src/constants.ts:351`。 +- 普通(鼠标)模式窗口尺寸恒为包围盒;触摸模式窗口贴合当前形态;列表形态允许窗口先长高。依据:`src/main/island.ts:1521-1553`。 +- 窗口位置由锚点推出:居中用 `center - round(width/2)`,左/右贴边留 `pad`,顶/底同理;自定义模式再夹到工作区内。依据:`src/main/island.ts:1615-1648`。 +- 渲染进程侧同样维护一套弹簧目标尺寸:胶囊取外观宽高,展开/紧急/叫人取增量,细缝取 6px 宽。依据:`src/island/IslandApp.vue:188-204`、`packages/shared/src/constants.ts:336`。 +- 连续圆角:`squirclePath(w,h,r)` 用超椭圆指数 4.2、每角 14 段;胶囊圆角取 `min(r, h/2)`,展开取 `min(48*r/20, w/2, h/2)`。依据:`src/island/squircle.ts:21-24,44-81`、`src/island/IslandApp.vue:206-212,343-344`。 +- 弹簧:`SpringValue` 默认 `stiffness=0.10`、`damping=0.68`、`snapThreshold=0.05px`、换向保留 35% 动量、单帧位移 ≤ 剩余距离 20%、`MAX_DT=6` 帧;渲染进程按 `delta * appearance.speed` 缩放帧时间。依据:`src/island/spring.ts:26-114`、`src/island/IslandApp.vue:287-332`。 + +### 2.4 命中、穿透、触摸与"点不动"防护 + +现状把"命中"当成一级问题处理,重构必须原样保留这些防护: + +1. 窗口默认整块穿透(`setIgnoreMouseEvents(true,{forward:true})`),由渲染进程 `mousemove` 命中后上报 `island:set-interactive(true)`;**关闭命中一律由主进程按真实光标位置决定**(双向下发会让窗口在穿透/接收之间抖动,表现为"点了没反应")。依据:`src/main/island.ts:2412-2418`、`src/preload/island.ts` 的 `setInteractive`、`src/island/IslandApp.vue:456-487`。 +2. 主进程 60ms 轮询读系统光标,并与渲染进程上报的岛体矩形比对来校正命中(Windows 下 mousemove 转发不可靠)。依据:`src/main/island.ts:2049-2078,2026-2047`。 +3. 岛体矩形由渲染进程限流上报(最小间隔 50ms,尾随保证最后一次送达)。依据:`src/island/IslandApp.vue:412-454`。 +4. 触摸屏:渲染进程按 `navigator.maxTouchPoints > 0` 或首次 `touchstart` 上报 `setTouchMode(true)`;主进程据此让窗口**贴合岛体**并始终接收输入,且命中判定的入参被强制为 true。依据:`src/island/IslandApp.vue:538-549`、`src/main/island.ts:245-257,1795-1822,1891-1902`。 +5. Windows 专属 `setShape` 用于触摸模式下把窗口裁成岛体+8px 留白;**非 Windows 平台没有该能力**,重构必须按 04 的降级方案处理(整窗穿透 + 岛可见区命中),不得依赖 `setShape` 才能点。依据:`src/main/island.ts:278-316`。 +6. 失焦收起:展开态获取焦点,点到别处 `blur` → 回缩胶囊;但必须有两层防护——指针仍在岛上则不收起;刚展开的宽限期内不收起(鼠标 500ms、触摸 1200ms)。依据:`src/main/island.ts:557-578,2287-2297,2020-2023`。 +7. 窗口「始终可激活」(`focusable:true`),收起时用 `blur()` 而不是关掉可激活性——Windows 下 `WS_EX_NOACTIVATE` 会丢 `mousedown`,胶囊就点不动。依据:`src/main/island.ts:1655-1672`。 + +### 2.5 消息状态机(fixed 经典模式) + +1. 状态载体 `IslandState`:`mode: 'hidden'|'pill'|'expanded'`、`active`、`queued`、`inClass`、`currentPeriodEnd`、`reason`、`listExpanded`、`maxCardHeight`、`lessonReminder`、`coursewareTransfer`、`updatedAt`。依据:`packages/shared/src/types.ts:1198-1230`。 +2. 分流规则(必须作为重构的行为基线):紧急(`priority==='URGENT'`)立即展开、无视上课;上课期间普通通知只入队并隐藏;上课期间**普通叫人**保持胶囊可点、不自动展开;上课期间用户主动点开的通知允许显示(`explicitShowId`);预览模式(设置页"预览效果")直接展开、失焦不收起、30 秒自动收尾。依据:`src/main/island.ts:684-702,794-867,2338-2380`。 +3. 双链路合并:同一 id 经 `notification:new` 与 `call:new` 双播时必须合并——类型取"更特殊"(call 优先)、优先级取更高,避免叫人被降级成普通通知。依据:`src/main/island.ts:704-724`。 +4. 排序:按优先级权重(`PRIORITY_RANK`:LOW 0 / NORMAL 1 / HIGH 2 / URGENT 3)降序,同级按 `createdAt` 倒序。依据:`packages/shared/src/constants.ts:183-209`、`src/main/island.ts:726-749`。 +5. 超时表(毫秒):胶囊 15000、下课后展开 14000、下课后胶囊 25000、展开 20000、紧急 45000、叫人 90000;关闭动画 360;收回时先切胶囊、待动画后再淡出隐藏。依据:`src/main/island.ts:121-132,1106-1128`。 +6. 收起的判定:只要还有未处理消息(当前或队列)就保持胶囊;队列清空/用户点了知道了/标为已读/进入上课才彻底隐藏。依据:`src/main/island.ts:1266-1296`。 +7. 多条消息:展开态且待处理 ≥2 时进入列表形态;默认显示 3 条 + 「展开更多(还有 N 条)」;放不下时提示"更多请前往应用内操作";卡片高度由共享 `islandListLayout()` 计算,主进程与渲染进程必须算同一个数。依据:`src/main/island.ts:755-765,770-785`、`packages/shared/src/constants.ts:379-471`、`src/island/ClassicNotifications.vue:34-58`。 +8. 动作:`expand`、`collapse`、`expand-list`、`dismiss`、`dismiss-all`、`mark-read`、`mark-all-read`、`open-app`;其中 `mark-read/mark-all-read` 通过主进程回调转给主窗口通知中心;作业卡 id 是本地合成 `homework-`,整批已读时必须排除。依据:`src/main/island.ts:150-166,1044-1070`、`src/renderer/island/bridge.ts:191-211`。 +9. 预览/冒烟注入口:`preview`、`setTestInputPassthrough`、`setHitTestCursor`、`setBlurCollapseEnabled`、`setTouchMode`、`getWindowBounds`、`getHitRect`、`getInteractive`、`isRecovering` 等 **重构后必须继续存在**(大量冒烟断言依赖),不得改名或删除。依据:`src/main/island.ts:187-190,416,927-1930,2154-2183`。 + +### 2.6 经典模式渲染(`src/island/IslandApp.vue`,683 行) + +1. 卡片结构:`.island-root > .island-card > svg.shape + span.content >(tint + pill-layer + ClassicNotifications + LessonReminderContent)`。依据:`src/island/IslandApp.vue:573-681`。 +2. 内容交叉淡入:胶囊层 `1 - progress*1.5` 退净(进度 2/3 处),展开层 `progress²` 进入;`progress` 由高度差推导。依据:`:304-309,346-351`。 +3. 胶囊标题按类型:叫人→"老师叫你"、作业→"新作业"、紧急→"紧急通知"、普通→"新消息";多条时汇总为「新消息:叫人/作业/通知(共 N 条)」。依据:`:115-146`。 +4. 停靠:`data-anchor` 决定内容贴边(左贴左/右贴右/居中);居中用 `left:50% + translateX(-50%)`,左右/上下停靠留 `ISLAND_SHADOW_PAD`。依据:`:353-383`、`src/island/classic-island.css:207-233`。 +5. 形状与投影:路径 `fill: var(--wn-bg)` + 1px 描边;投影**始终声明**、只在展开态改参数(避免加/去 filter 造成开合闪一帧)。依据:`src/island/classic-island.css:102-153`。 +6. 状态色:紧急红 `#ff453a`、叫人橙 `#ff9f0a`、作业用 accent;只做内层径向微染 + 描边,不外溢。依据:`src/island/IslandApp.vue:151-157`、`classic-island.css:155-185`。 +7. 内容层裁剪:`.content{clip-path:var(--card-path)}`;圆角路径由 SVG `path()` 注入 CSS 变量 `--card-path`。依据:`classic-island.css:127-133`、`IslandApp.vue:363-371`。 +8. 轮廓保护:`island-root.embedded` 用于设置页内嵌预览(去掉绝对定位)。依据:`classic-island.css:41-47`、`IslandApp.vue:578-581`。 + +### 2.7 自定义布局(custom)运行时与数据 + +1. 数据根:`config.json.islandLayout`,默认 `layout:'fixed'`;`residentEnabled` 仅作兼容字段,`custom` 即常驻。依据:`packages/desktop-client/src/main/config.ts:26-36,198-233`、`packages/shared/src/island-layout.ts:131-139,384-403`。 +2. 组件方案 `IslandLayoutSettings`:`layout`、`residentEnabled`、`pointerFadeEnabled/Reversed`、`lessonReminders`、`activeSchemeId`、`schemes[]`。依据:`packages/shared/src/island-layout.ts:131-150`。 +3. 方案 `IslandLayoutScheme`:`id/name/rows/expandedRows/scale/color/foreground/opacity/radius/separated/spacing`;`rows` 为常态行、`expandedRows` 为"无消息时点击展开"的内容。依据:`island-layout.ts:109-130`。 +4. 行 `IslandLayoutRow`:`id/name/main/allowNotifications/gap/nodes`;`main` 行唯一(normalize 会强制只留一个)。依据:`island-layout.ts:109-116,334-356`。 +5. 节点 `IslandComponentNode`:`id/kind/settings/appearance/advanced/children`;`kind` 14 类(`date/clock/weather/countdown/schedule/text/separator/slide/rolling/group/stack/notification/homework/call`)。依据:`island-layout.ts:3-18,77-108`。 +6. `settings`:文本/模板、12 小时制、秒、倒计时目标与目标日、地点/经纬度、显示模式(当前/下一节/今日)、显示数量、显示起止时间、显示进度、排列方向、间距、轮播间隔、滚动速度。依据:`island-layout.ts:80-99`。 +7. `appearance`:资源覆盖开关、四档字号、前景/背景/透明度/圆角覆盖、整体透明度;`advanced`:隐藏规则(`in-class/no-message/no-class`)、对齐、固定宽/最大宽。依据:`island-layout.ts:61-76,100-107`。 +8. 归一化边界(重构后仍必须保留的硬边界):最多 12 行、200 节点、6 层、方案 ≤20;颜色必须 `#rrggbb`;非法 kind 丢弃;重复 id 重命名;`main` 行唯一。依据:`island-layout.ts:256-403`(尤其 `:269,337,358`)。 +9. 自定义运行时:先测量自然内容(compact/details/lesson 三份),再按工作区与方案 `scale` 适配;两个页面层(常态/详情)交叉淡入,第三层承载课程提醒;铃铛显示未处理数(>99 显示 `99+`)。依据:`src/island/CustomIslandRuntime.vue:58-124,238-323,338-527`。 +10. 鼠标避让淡化:常驻模式按指针是否在岛内切换透明度到 `CLASSISLAND_ISLAND_METRICS.fadedOpacity=0.05`(可反转);触摸/编辑/课程提醒期间暂停。依据:`src/main/island.ts:1851-1877`、`island-layout.ts:35`。 +11. 编辑布局时窗口 `interview` 行为:`editingLayout` 下不参与命中轮询、不自动收起;`island:edit-layout` 与托盘入口都走 `editLayout()`。依据:`src/main/island.ts:350-356,1430-1433`。 +12. 常驻窗口几何:窗口固定为 `min(960, 工作区宽) × min(640, 工作区高 - marginY)`,内容在画布内弹簧形变;**常驻开合期间窗口不做逐帧 setBounds**。依据:`src/main/island.ts:1522-1528`、`CustomIslandRuntime.vue:214-216`。 + +### 2.8 课程提醒与课程组件 + +1. `LessonReminderTracker` 只用服务端解析后的 `ScheduleDto` 计算五类提醒:准备(默认提前 60s)、上课、课间、下课前倒数(默认 60s)、课中进度;每种提醒的进入/退出时间由 `maskUntil/endsAt` 表达。依据:`src/main/lesson-reminder.ts:14-110`、`packages/shared/src/island-layout.ts:140-158`。 +2. 主进程每秒刷新一次提醒;**自定义布局下 `in-class` 提醒不接管岛体**(课程组件持续显示进度),`ending` 提醒只作为课程名右侧秒数徽标。依据:`src/main/island.ts:171-213`、`docs/app/island.md:65-70`。 +3. 提醒条 UI:高 40、圆角 8、底色 `#20202080`;5 个斜切区域(30° 偏移)覆盖式打开/关闭,区段 360ms、错峰延迟 `[0,90,120,90,0]`(开)/反向(关);开 `1-(1-p)^4`、关 `p²`;文字延迟 260ms、scale 1.1→1.0、750ms;进度条 2px、`transform 50ms linear`。依据:`src/island/LessonReminderContent.vue:59-123,133-137,195-317`。 +4. 经典模式临近下课用课程名右侧蓝色圆形徽标显示剩余秒数(剩余 1 秒显示 `-1`),不显示"即将下课"遮罩。依据:`docs/app/island.md:68-70`、`CustomIslandRuntime.vue:30-33`、`ComponentNode.vue:259-266`。 +5. 关闭"即将下课倒数"后不得显示徽标;阈值沿用本机提醒设置。依据:`docs/app/island.md:69`、`lesson-reminder.ts:54-60`。 + +### 2.9 天气现状 + +1. 天气只在主进程抓取(固定服务、固定参数、不接受渲染层传 URL):`api.open-meteo.com/v1/forecast`,取 `temperature_2m/weather_code/wind_speed_10m`,8 秒超时、`redirect:'error'`、响应 ≤64KB;成功缓存 15 分钟,失败保留旧值并标 `stale`/退避 60 秒,缓存最多 32 个坐标。依据:`src/main/island-weather.ts:1-73`。 +2. 渲染层 `WeatherBrief.vue` 每分钟刷新一次;天气码映射文案(晴/少云/多云/阴/雾/毛毛雨/雨/雪/雷雨);断网或陈旧时显示"· 缓存";未配置坐标提示"请设置天气地点"。依据:`src/island/WeatherBrief.vue:12-90`。 +3. 冒烟模式禁止访问外部天气(`ELECTRON_SMOKE_TEST==='1'` 且使用真实 fetch 时直接抛错)。依据:`island-weather.ts:33-34`。 + +### 2.10 课件传输覆盖 + +1. 主进程持有 `coursewareTransfer`,`coursewareShowing` 时优先覆盖岛内容;`syncWindow` 分支强制显示、取消淡入、不接收鼠标;`formSize()` 返回课件固定尺寸。依据:`src/main/island.ts:661-676,1420-1429,1956`、`src/main/courseware.ts:36`。 +2. 课件卡尺寸 `424×132`,内边距 `17px 24px`、圆角 24、底色 `rgba(20,20,24,.97)`、进度条 5px、字号 13/16/12/11;显示文件名、类型、教师、已收/总 MB、进度%、错误行。依据:`packages/shared/src/courseware.ts:4`、`src/island/CoursewareTransfer.vue:12-132`。 + +### 2.11 渲染入口与组件清单(现状文件级地图) + +| 文件 | 行数 | 现状职责 | +|---|---|---| +| `src/island/main.ts` | 30 | 岛渲染入口:引入 `tokens.css`、`applyMotionTokens()`、挂载 `LayoutIslandApp` | +| `src/island/LayoutIslandApp.vue` | 76 | 读取 layout/state/appearance/schedules/timePoints,选择经典/自定义/课件三种渲染 | +| `src/island/IslandApp.vue` | 683 | 经典岛:胶囊+展开卡+形变+命中+课程提醒 | +| `src/island/CustomIslandRuntime.vue` | 528 | 自定义常驻:测量适配+三页交叉淡入+铃铛+消息面板 | +| `src/island/ComponentNode.vue` | 456 | 14 类组件的渲染与课程表折叠规则;编辑态拖拽手柄 | +| `src/island/ComponentIcon.vue` | 36 | 组件图标(编辑库与铃铛) | +| `src/island/ClassicNotifications.vue` | 234 | 单条详情卡/多条列表卡/按钮行(打开应用/标为已读/知道了) | +| `src/island/LessonReminderContent.vue` | 318 | 课程提醒条 + 5 区斜切遮罩 + 进度条 + 徽标 | +| `src/island/CoursewareTransfer.vue` | 132 | 课件传输覆盖卡 | +| `src/island/WeatherBrief.vue` | 91 | 天气简报组件 | +| `src/island/spring.ts` | 115 | SpringValue 帧积分弹簧 | +| `src/island/squircle.ts` | 81 | 连续圆角路径 | +| `src/island/classic-island.css` | 757 | 经典岛全部样式(胶囊/展开/列表/按钮/状态色) | +| `src/island/custom-island.css` | 205 | 自定义行/组件/铃铛基础样式 | +| `src/island/custom-runtime.css` | 255 | 常驻三页/停靠/分隔/消息宿主样式 | +| `src/island/lesson-ending.css` | 16 | 课程名右侧秒数徽标 | +| `src/renderer/island/bridge.ts` | 473 | 主窗口→主进程的岛数据桥:课表/上课状态/通知/作业/叫人/补弹/已读 | +| `src/preload/island.ts` | 123 | 岛窗口 contextBridge 白名单 | +| `src/preload/index.ts` | 101 | 主窗口 contextBridge(含岛推送 API) | +| `src/types/desktop.d.ts` | 294 | 桥接契约与 config 形状 | + +### 2.12 主窗口 → 主进程桥接(`src/renderer/island/bridge.ts`) + +1. 桥接职责:从 IndexedDB 缓存/服务端取当天课表与完整时间点,每秒复算"是否上课",变化时通过 `window.desktop.islandSetClassState` 下发给主进程;主进程据此决定通知立即弹/暂存/下课补弹。依据:`src/renderer/island/bridge.ts:21-56,148-172,416-435`。 +2. 课表来源 `/schedules/active-week`,同时构建 `IslandTimePointDto[]`(含课间/分割线/隐藏标记),课程名优先取日课表条目、无条目再回退时间点默认课程;离线缓存只有条目时退化为"只有上课时间点"。依据:`bridge.ts:57-96,107-136`。 +3. 通知投递:普通通知、新作业(带"本机录入免打扰"指纹去重)、叫人(带"请尽快前往"副文案)分别打包成 `IslandNotification`,随 `inClass/currentPeriodEnd` 上下文推给主进程。依据:`bridge.ts:174-289`。 +4. 离线补弹:恢复联网时取最近 5 条离线未读补投,推完清离线起点;冒烟模式跳过。依据:`bridge.ts:291-364`。 +5. 已读回流:主进程收到岛的"标为已读"后转发给主窗口,桥接订阅并调用通知 store 标记已读(离线先本地改)。依据:`bridge.ts:366-405`、`src/main/index.ts:237-245`。 +6. 生命周期:登录后启动、退出登录/切班时停止;停止时必须清定时器、解订阅、清本地课表并主动同步 `inClass:false`。依据:`bridge.ts:437-473`、`src/renderer/App.vue:64-93`、`src/renderer/layouts/ClientLayout.vue:396-405`。 + +### 2.13 配置与数据契约现状 + +1. `config.json` 的岛相关字段:`islandLayout`(完整方案)与 `island`(外观);两者都有逐字段归一化与默认值。依据:`src/main/config.ts:26-84,124-163,198-233,334-346,435-523`;配置文件路径 `userData/config.json` 见 `:165-167`。 +2. 写入语义:内存缓存 + 120ms 合并写 + 退出前 `flushConfigSync`;不允许每次读取都同步读盘。依据:`config.ts:235-346`。 +3. 外观契约 `IslandAppearance`:height/width/radius/opacity/accent/fontSize/animations/speed/position/alwaysOnTop/style/idleSliver/marginX/marginY/followCursorDisplay;范围 36–72 / 220–420 / 8–32 / 0.4–1 / 11–20 / 0.5–2 / 0–200 / 0–160。依据:`packages/shared/src/types.ts:1145-1195`、`constants.ts:265-302`。 +4. 经典外观默认:`width 268`、`height 44`、`radius 20`、`fontSize 13`、`opacity 1`、`accent #0a84ff`、`style 'black'`、`position 'top-center'`、`marginX/Y 8`。依据:`constants.ts:265-290`。 +5. 语义色与排版系数:`ISLAND_TYPE_SCALE`(title 1.08/body 0.95/meta 0.78/badge 0.76/pillTitle 0.92/pillSub 0.74)必须与 `classic-island.css` 顶部注释一致;所有字号由 `--island-font` 推导。依据:`constants.ts:354-364`、`classic-island.css:1-7`。 +6. `CLASSISLAND_ISLAND_METRICS`(重构后的目标几何基线,已存在于 shared):rowHeight 40、水平内边距 12、圆角 8(最大 20)、背景不透明度 0.5、缩放 1(0.75–2.5)、字号 14/16/18/20、行间距 5(最大 150)、避让淡出 0.05。依据:`packages/shared/src/island-layout.ts:19-36`。 + +### 2.14 IPC 现状(重构必须保持的契约) + +1. 主窗口桥接:`getConfig/saveConfig/clearConfig/appInfo/openExternal`;岛:`islandPush/islandRemoveNotification/islandSetClassState/islandGetState/islandSetAppearance/islandGetAppearance/onIslandMarkRead/onIslandMarkAllRead`;另有 `islandEditLayout/onIslandEditorOpen`。依据:`src/preload/index.ts:25-99`。 +2. 岛窗口桥接:`getLayout/saveLayout/onLayout`、`getEditing/setEditing/onEditing`、`getVisible/onVisibility`、`reportSize/reportResidentControl/noteResidentTouch/onViewport`、`getSchedules/onSchedules`、`getTimePoints/onTimePoints`、`getState/onState/sendAction`、`getAppearance/onAppearance/setInteractive/setHitRect/setTouchMode/alive`。依据:`src/preload/island.ts:14-121`。 +3. 主进程侧注册:`island:get-schedules`、`island:get-time-points`、`island:get-layout`、`island:save-layout`、`island:edit-layout`、`island:get-editing`、`island:get-visible`、`island:resident-control`、`island:resident-touch`、`island:set-editing`、`island:content-size`、`island:remove-notification`、`island:push`、`island:class-state`、`island:alive`、`island:get-state`、`island:set-appearance`、`island:get-appearance`、`island:set-interactive`、`island:set-hit-rect`、`island:set-touch-mode`、`island:action`。依据:`src/main/island.ts:2338-2438`。 +4. 安全规则:所有 `invoke` 必须校验发送方是岛窗口(`event.sender === island.getWindow()?.webContents`);渲染进程不能读写配置(`get-layout` 是唯一读取口,`save-layout` 由主进程做归一化)。依据:`src/main/island.ts:2339-2356,2345-2350`。 + +### 2.15 编辑器与设置页现状 + +1. 设置页「灵动岛」:布局二选一(fixed/custom)、自定义方案入口、鼠标避让两个开关、外观滑块(宽/高/圆角/透明度/字号/速度)、主题色、视觉风格、位置、左右/上下边距、跟随鼠标屏幕、动画开关、始终置顶、空闲细缝、"测试灵动岛"(本地推一条 30 秒预览通知)。依据:`src/renderer/views/settings/IslandSettingsView.vue:26-160,162-392`。 +2. 设置页改动即生效:外观走 `window.desktop.islandSetAppearance` 实时下发,保存才落盘;自定义模式下圆角滑块会同步写当前方案圆角。依据:`IslandSettingsView.vue:112-133`。 +3. 编辑器是主窗口的设置子路由 `/settings/island/editor`(不创建额外桌面窗口):上方渲染组件标签/预览、右上角方案菜单(新建/复制/删除/重置/JSON 导入导出)、下方组件库(11 类)+ 检查器;分别编辑常态 `rows` 与无消息展开 `expandedRows`。依据:`src/renderer/views/settings/IslandEditorView.vue:25-70,290-337,541+`、`docs/app/island.md:36-48`、`src/renderer/router/index.ts:69-74`。 +4. 编辑器保存:深拷贝 + `normalizeIslandLayouts` + 串行保存队列(`saveQueue`)+ 修订号(只有最后一次成功才算 saved)+ 路由离开前等待保存;上限:组件总数 200、方案 20。依据:`IslandEditorView.vue:81-105,338-347,379-383`。 +5. 拖动/放置:指针事件实现拖拽(`beginDrag/startDrag/dragTarget/moveDrag/dropPreview`),编辑标签节点用 `IslandEditorNode.vue`,支持 before/after/inside 三种落点。依据:`IslandEditorView.vue:443-539`、`src/renderer/components/IslandEditorNode.vue:1-124`。 +6. 组件设置抽屉:内容/外观/高级三页;内容按 kind 提供字段,外观含独立字号/前景/背景/透明度/圆角/整体透明度,高级含对齐/固定宽/最大宽/隐藏条件;底部支持包裹到容器、复制、删除。依据:`src/renderer/components/IslandComponentSettings.vue:26-307`。 +7. 编辑预览:编辑器内嵌预览区(`preview-canvas/island-preview`)用岛内组件渲染,缩放滑块控制;**不创建第二个桌面窗口**,也不自动切到自定义布局(点"应用"才切)。依据:`IslandEditorView.vue:290-299`、`docs/app/island.md:47`、`src/renderer/styles/island-editor.css:125-249`。 + +### 2.16 既有硬约束(重构不得破坏,逐条对应 AGENTS.md §4) + +1. 保留经典岛连续圆角、投影常驻、触摸输入、失焦宽限、固定窗口几何护栏。依据:`repos/ClassHelper-master/AGENTS.md` §4「灵动岛」。 +2. 禁止 `setBackgroundMaterial()`;毛玻璃只能是 CSS 半透明。依据:`src/main/island.ts:1726-1748`。 +3. 消息按 id 去重;通知/叫人双播不得重复;本机录入作业不上岛(指纹 + id 双判定)。依据:`src/main/island.ts:713-724`、`bridge.ts:191-267`。 +4. 编辑预览不创建额外桌面窗口;打开编辑页不自动切布局,应用后才启用自定义方案。依据:`docs/app/island.md:47`。 +5. 默认验证不创建可见岛/托盘;只有显式 `--visual` 才做桌面像素/动画验收;冒烟模式跳过外部天气与离线补弹。依据:`docs/dev/testing.md:9,29-32`、`island-weather.ts:33-34`、`bridge.ts:326-331`。 +6. 经典岛与自定义岛共用一个原生窗口,切布局不得新开第二个岛。依据:`docs/app/island.md:8-10`、`src/main/island.ts:468-634`(单 `this.win`)。 +7. 左右边距只在左/右停靠生效,居中锚点永远是屏幕中线;`marginX` 对居中无效必须在 UI 说明。依据:`docs/app/island.md:54`、`IslandSettingsView.vue:88-94`。 +8. 所有验证不得打扰真实桌面(不弹窗、不发系统通知、不挪真实鼠标);冒烟注入点只能改"逻辑光标"。依据:`src/main/island.ts:1910-1930`。 + +### 2.17 现状问题清单(重构动因,逐条对应 04 的改造项) + +1. `src/main/island.ts` 单文件 2445 行,窗口/命中/消息/课程/课件/外观全部耦合,难以按 ClassIsland 规格替换视觉与交互。 +2. 几何与尺寸常量分散在主进程 `BASE/SIZES`、shared `ISLAND_SIZE_DELTA`、渲染进程 `targetSize()` 三处,主/渲染各算一次,改动时容易漂移。 +3. 经典与自定义是两套独立运行时,但共享消息面板、课程提醒、命中上报;重构需要抽出公共"岛壳 + 消息面板 + 课程条"层,避免两套 UI 各改一遍。 +4. 数据里没有版本号:`config.islandLayout` 无 `version`,新增 ClassIsland 行/组件/外观字段时无法做确定性迁移与回滚。 +5. 现状行/组件缺少 ClassIsland 的行级外观(背景色/不透明度/圆角/字号覆盖)、行级隐藏规则、行级进度条等字段;只有节点级覆盖。 +6. 提醒只有"条状提醒 + 徽标",没有 ClassIsland 的两阶段气泡与强提醒 Topmost 特效;需要在视觉层新增但不得改变现有消息队列语义。 +7. 主进程状态机把"提醒优先级、上课守卫、预览、课件"混在一个 `syncWindow` 里,重构需要拆成"状态投影 + 可见性决策 + 几何/命中"三层。 +8. 编辑器保存/校验逻辑在 Vue 侧,主进程只做 `normalizeIslandLayouts`;重构应把 schema 校验/迁移收口到 shared,编辑器和主进程共用。 + +### 2.18 验收标准(第 2 部分) + +1. 你能在不运行程序的情况下,仅凭本部分定位:岛窗口创建位置、消息分流规则、命中轮询、触摸模式、失焦宽限、收回动画、课程提醒、课件覆盖、设置页与编辑器的入口文件与行号。 +2. 你能列出重构前必须保留的 8 条硬约束与 8 条现状问题,并说明每条对应到 04 的哪个改造项。 +3. 你能复述现状数据流:服务端 `ScheduleDto` → `bridge.ts` → `islandSetClassState` → 主进程状态机 → `island:state` → 渲染进程;以及通知/作业/叫人三条投递链路的差异。 +4. 你能指出任何一处"主进程与渲染进程各算一次"的几何/布局逻辑(窗口包围盒、列表高度、命中矩形、细缝尺寸、字号推导),并确认 04 已把它们收口到单一事实来源。 +5. 本部分引用到的每个数值都能追到 `packages/...:行号` 或 `packages/shared/...:行号`;没有"凭印象"的数值。 + +--- + +## 3. ClassIsland 灵动岛行为基准(重构对照规格) + +本节是**行为基准**,不是像素规范。实现者(ChatGPT)在 ClassHelper 现有仓库内重构灵动岛时,必须让「`fixed` 默认风格(ClassIsland 信息行)」与「`custom` 常驻模式」在**行为语义**上与 ClassIsland 主界面(MainWindow / MainWindowLine / 组件系统 / 提醒系统)对齐;`fixed` 的 `winisland` 兼容风格只保留原胶囊交互即可,不要求对齐 ClassIsland。具体颜色、间距、圆角、阴影、字号、动画曲线上限等像素值一律以第 6 章(tokens)、第 7 章(结构与组件)、第 10 章(动画)为准,本节只给出「必须实现什么行为、判定顺序、默认值与边界」。凡本节与 ClassIsland 源码冲突,以源码行为为准;凡与 ClassHelper 既有业务规则冲突,以第 8 章的裁决表为准(ClassHelper 业务优先、ClassIsland 视觉优先)。 + +#### 3.0.1 与 ClassHelper 三种运行形态的映射(口径,必须一致) + +| ClassHelper 形态 | 目标语义 | 说明 | +|---|---|---| +| `fixed` + 默认风格 `classisland` | **ClassIsland 风格信息行** | 默认 1 行 = 日期 + 课程表;行高 40、圆角 8、背景不透明度 0.5、底部 3px 进度条;消息到来时**在同一行内**展开 ClassIsland 风格气泡(遮罩阶段 + 正文阶段),暂存/紧急/叫人/已读/去重规则沿用 ClassHelper | +| `fixed` + 风格 `winisland` | **原「胶囊 + 详情卡」形态** | 老用户升级默认保持 `winisland`;只做必要的动画/修 bug,不按 ClassIsland 规格重做几何与气泡 | +| `custom` | **常驻多行** | 保留 schemes/rows/nodes/expandedRows/编辑器数据模型,按 ClassIsland 的行/组件/编辑/隐藏/动画规格重构渲染与交互 | + +风格枚举落盘字段以 parts-r/05 为准(本节统一写作 `appearance.family = 'classisland' | 'winisland'`,对应配置键 `islandAppearance.style`);新安装默认 `classisland`,升级用户默认 `winisland`。下文凡写「`fixed`」而未特别说明,均指默认风格 `classisland` 的信息行。 + +证据写法:`repos/ClassIsland/...` 指本工作区内的 ClassIsland 源码;`packages/...` 指 ClassHelper 源码。未在源码中确认的写【推断】。 + +### 3.1 主界面行(行为模型) + +**必须实现的模型**:主界面是一个**多行容器**,每行(MainWindowLine)包含「背景层 + 内容层(组件集合) + 提醒层(遮罩/正文) + 进度条」。行是提醒的宿主,也是编辑的最小单位。 + +1. **行高固定 40**,且同一行内所有组件共享该高度;组件区左右内边距 12、最小内容宽 20(`repos/ClassIsland/ClassIsland/Controls/MainWindowLine.axaml:16`;`repos/ClassIsland/ClassIsland/XamlThemes/FluentTheme/Styles.axaml:110`)。 +2. **行数无硬上限**,但默认只有 1 行;行间距默认 5(`repos/ClassIsland/ClassIsland/Models/Settings.cs:219`,范围 0–150、步进 5)。 +3. **必须存在且只存在一行「主行」**(MainLine):删除到只剩 1 行时不得再删,必须提示「至少需要保留 1 个主界面行。」(`repos/ClassIsland/ClassIsland/Views/EditModeView.axaml:428` 文案,`MainWindow.axaml.cs` 的删除守卫)。 +4. **行模板三分支**(必须按条件选,不得混用,`repos/ClassIsland/ClassIsland/XamlThemes/FluentTheme/Styles.axaml:275-278`): + - Full:`IsNotificationEnabled = true`,含遮罩/正文/进度条; + - Min:`IsNotificationEnabled = false`,只有背景与内容; + - 分体(Separated):`IsIslandSeperated=true` 或行级 `IslandSeparationMode=2`,整行背景改为每个顶级组件各自胶囊。 +5. **行的可见性**(必须逐条实现,`repos/ClassIsland/ClassIsland/Controls/MainWindowLine.axaml:29-49`): + +``` +visible = ( (!IsAllComponentsHid || IsOverlayOpen) && (HideOnRule ? IsVisible : true) ) || IsEditMode +``` + + - `IsAllComponentsHid`:该行所有组件都被隐藏时整行消失,不能留一条空胶囊; + - `IsOverlayOpen`:该行正在播提醒时必须可见(即使组件全隐藏); + - `HideOnRule`:该行命中隐藏规则集时隐藏;规则集变化要实时重算(订阅规则状态更新); + - 编辑模式下**永远可见**。 +6. **行级不透明度**默认 1.0(范围 0.1–1、步进 0.05),**只作用于行内容**;背景不透明度是另一个独立设置(默认 0.5),**只作用于背景层**,文字必须始终 100% 不透明(`repos/ClassIsland/ClassIsland/Models/Settings.cs:64,183-184`)。 +7. **鼠标淡出**(必须实现):`IsMouseInFadingEnabled`(默认 true)与 `IsMouseInFadingReversed`(默认 false)决定「指针不在行内时行变淡到 0.05」,判定用行的屏幕矩形(含 DPI 与缩放),编辑模式不淡出(`repos/ClassIsland/ClassIsland/Controls/MainWindowLine.cs:503-509,619-637`)。 +8. **行背景宽度 = 内容实际宽度**,不是窗口宽度;内容宽度变化必须平滑跟随(Fluent 300ms / Classic 550ms),且**不得触发窗口尺寸变化**(`repos/ClassIsland/ClassIsland/Controls/MainWindowLine.cs:614-617`;动画细节见第 10 章)。 +9. **行的提醒宿主能力**:`AcceptsNotificationRequests = IsNotificationEnabled && !IsAllComponentsHid && !_isOverlayOpen && !_isUnloading`——只有同时满足这些条件的行才能接收提醒;多行时由提醒分发规则决定落到哪一行(`repos/ClassIsland/ClassIsland/Controls/MainWindowLine.cs:814`)。 +10. **行级设置**(必须保留在编辑器中可改):不透明度、是否允许显示提醒、隐藏规则、圆角/间距(继承全局或覆盖)、附加设置(见第 7 章)。 + +### 3.2 组件系统(11 类 + 容器) + +必须实现 ClassIsland 的 11 种内置组件与 4 种容器(`repos/ClassIsland/ClassIsland/Controls/Components/`),并保留 ClassHelper 现有的同名组件作为承载: + +| 组件 | 职责(行为) | 关键默认值 | +|---|---|---| +| 日期 `DateComponent` | 显示「今天」或自定义格式日期;支持农历/自定义模板(模板语法按组件设置) | 显示今天 | +| 时钟 `ClockComponent` | 显示当前时间;支持 12/24 小时、秒、自定义格式 | 24 小时制、显示秒 | +| 倒计时 `CountDownComponent` | 目标时间倒计时;目标可固定日期或「按课表倒数至上课/下课/放学」 | 见 3.7 | +| 课程表 `ScheduleComponent` | 今日时间点/当前课程/下一节,见 3.7 | 见 3.7 设置表 | +| 天气简报 `WeatherComponent` | 天气图标 + 温度 + 文本;多种主信息模式 | 见 3.10 | +| 文本 `TextComponent` | 静态文本,支持模板变量与滚动 | 见组件设置 | +| 滚动容器 `RollingComponent` | 单行内容超宽时循环滚动 | 见组件设置 | +| 轮播容器 `SlideComponent` | 多个子组件按间隔轮播 | 见组件设置 | +| 分组容器 `GroupComponent` | 子组件横向成组,统一间距 | 见组件设置 | +| 堆叠容器 `StackComponent` | 子组件纵向堆叠 | 见组件设置 | +| 分割线 `SeparatorComponent` | 竖线分隔,2px 宽 25px 高(视觉细节归第 7 章) | — | + +**必须实现的组件通用语义**: + +1. **组件宿主间距**:行内相邻组件水平间距 12(每个组件 `Margin="6 0"`);容器组件内部用 `Margin="-6 0"` 抵消,避免嵌套累积(`repos/ClassIsland/ClassIsland/MainWindow.axaml:396`)。 +2. **组件可见性**:组件自身有 `IsVisible`;行内「全部组件都不可见」→ 整行 `IsAllComponentsHid`(见 3.1)。 +3. **组件级隐藏规则**(`custom` 模式必须对齐):按 `in-class`(上课中)、`no-message`(无消息)、`no-class`(无课/放学后)三个条件隐藏;ClassHelper 现有 `advanced.hideRules` 的三值语义与之一一对应,必须保留。 +4. **组件宽高**:`advanced.align/width/maxWidth` 必须生效;`stretch` 为默认,剩余空间按 flex 分配。 +5. **组件的附加设置**:附加设置可挂在科目/时间点/课表/时间表(ClassIsland 语义,见第 7 章),本重构只保留 ClassHelper 已有的「课程控件附加设置」语义,不引入 ClassIsland 本地档案。 +6. **编辑态**:所有组件必须可被点击选中、可拖动排序、可跨行移动;容器组件展开子项供选择(第 9 章编辑器规范)。 +7. **空数据兜底**:任何组件在无数据时必须渲染占位(不得空白、不得抛错),占位文案与场景见第 7 章。 + +### 3.3 编辑模式(行为语义) + +必须实现「编辑模式」作为岛的唯一配置入口,行为如下(`repos/ClassIsland/ClassIsland/MainWindow.axaml.cs:510-528,1391-1403`): + +1. **进入条件**:菜单「编辑主界面…」或 URI `classisland://app/edit`;进入前必须通过权限校验(ClassHelper 侧对应「已登录班级设备」判断)。 +2. **进入步骤(按序)**:置编辑标志 → 激活窗口 → 挂载编辑视图 → 同步特效窗口的编辑态(编辑期间**不播强提醒特效**)→ 更新窗口层级 → 编辑模式下窗口必须接收鼠标。 +3. **编辑画布**:画布覆盖整个屏幕;岛内容在画布内按停靠位置排布,并预留底部操作栏空间(操作栏高度 64 的语义见第 7/9 章);编辑模式内容整体垂直居中。 +4. **编辑态可见性**:所有行、所有组件(含被隐藏规则隐藏的)都必须可见可选中;隐藏规则命中时以半透明/斜纹提示,而不是直接消失。 +5. **底部操作栏(行为)**:重置视图、外观抽屉、添加组件抽屉、组件配置方案、完成;「完成」退出并**保存**。 +6. **两级抽屉**:主抽屉(外观/添加组件/配置方案)与次抽屉(组件设置);抽屉打开时挂载「逐项入场」动画;拖拽组件时主抽屉自动临时收起,结束后恢复。 +7. **退出**:关闭抽屉 → 清编辑标志 → 清教程提示 → **保存组件配置**(保存必须幂等、原子:写临时文件再替换);退出后恢复逐像素透明与点击穿透。 +8. **编辑期间**:不得创建额外桌面窗口;不得因编辑导致提醒被丢弃(提醒进队列,退出后按规则补播);强提醒特效丢弃但提醒本体照常。 +9. **配置方案**:支持多套组件配置方案(ClassHelper 现有 `schemes`),方案切换即时生效并持久化;方案 JSON 导入导出走本机配置,不上服务端。 + +### 3.4 隐藏与显示规则(完整判定) + +必须实现两档 `hideMode`(默认 `0`,`repos/ClassIsland/ClassIsland/Models/Settings.cs:185`;`repos/ClassIsland/ClassIsland/MainWindow.axaml:150-163`): + +- **0 基本**:三个独立开关,全部默认 `false`: + - `HideOnClass`:当前时间状态为「上课」时隐藏; + - `HideOnMaxWindow`:前台窗口最大化时隐藏; + - `HideOnFullscreen`:前台窗口全屏时隐藏。 +- **1 高级**:用规则集 `HideRules` 求值(规则集/逻辑见第 8 章的规则语义;ClassHelper 侧只保留独立开关 + 上课状态条件,不引入完整规则集)。 + +**检测数据源与频率**: + +1. 全屏/最大化状态在**每个主计时器 tick** 检测,基准屏幕必须是「岛所在屏幕」(`repos/ClassIsland/ClassIsland/MainWindow.axaml.cs:451-456`)。 +2. 排除进程名单 `ExcludedFullscreenWindow` 默认 `["explorer"]`:这些进程在前台时不算全屏/最大化(`repos/ClassIsland/ClassIsland/Models/Settings.cs:56-59`)。 +3. **强制显示**(覆盖隐藏):正在播放提醒或处于编辑模式时,即使命中隐藏条件也必须显示,并临时置顶(见 3.9 的置顶锁语义)。 +4. **手动隐藏**:`IsMainWindowVisible`(默认 true)为用户手动开关;手动隐藏时同时不接受新提醒(与 3.1 第 9 条一致)。 +5. **全部隐藏**:所有行都隐藏时,可见内容 `opacity:0`,**窗口包围盒不变、窗口不销毁**,保证提醒到来能立刻显示(`repos/ClassIsland/ClassIsland/MainWindow.axaml:135-141`)。 +6. **ClassHelper 映射要求**: + - `custom` 模式的「常驻」不能被 `HideOnClass` 直接隐藏——上课时只允许按 `hideRules:in-class` 隐藏**指定组件**; + - `fixed` + `classisland` 信息行按 ClassIsland 默认(`HideOnClass=false`)**上课也保持可见**,只是「普通消息暂存、紧急/叫人例外」;不得回到「上课即整岛隐藏」; + - `fixed` + `winisland` 兼容形态保留 ClassHelper 原规则(上课时普通消息暂存并隐藏岛体,紧急与叫人例外,见 3.9 与第 8 章)。 + +### 3.5 多显示器、停靠与点击穿透 + +1. **6 个停靠位置**:顶/底 × 左/中/右(`0/1/2` 贴工作区上边、`3/4/5` 贴下边;左/中/右分别为贴左/居中/贴右),窗口始终横跨整屏,岛在窗口内形变(`repos/ClassIsland/ClassIsland/Models/Settings.cs:48`)。 +2. **偏移**:`WindowDockingOffsetX/Y` 必须生效;顶部停靠时正值向下偏移不得被屏幕边缘裁掉(偏移吞进安全区)。 +3. **多显示器**:`WindowDockingMonitorIndex` 选择目标屏;可选「跟随鼠标所在屏」(ClassHelper 现有 `followCursorDisplay` 保留)。 +4. **DPI**:所有几何与命中必须按「物理像素 × 缩放」换算,4K@150% 与 1080p@100% 下不得出现 1px 白边、黑边、圆角锯齿;圆角为 0 时是直角,为最大值时左右边缘为半圆。 +5. **点击穿透**:非编辑模式下,岛默认鼠标穿透(点击落到桌面应用);只有指针真正在岛体上时才接收鼠标;编辑模式下必须接收(`repos/ClassIsland/ClassIsland/MainWindow.axaml.cs:839`;ClassHelper `packages/desktop-client/src/main/island.ts:2000-2080` 的命中轮询必须保留)。 +6. **置顶锁**:提醒播放期间临时置顶,多提醒时以「锁计数」方式管理——只有最后一个锁释放才还原层级,避免闪烁(`repos/ClassIsland/ClassIsland/MainWindow.axaml.cs:1056-1092`)。 + +### 3.6 时间状态机与跨天(课程计算的唯一事实源) + +1. **主计时器周期 50ms**,优先级 Render;每 tick 顺序:`PreMainTimerTicked` → 计算 → `PostMainTimerTicked`(`repos/ClassIsland/ClassIsland/Services/LessonsService.cs:39-45,373-388`)。ClassHelper 映射为**主进程单一 1s/50ms 混合节拍**(秒级 UI 用 1s,跨点判定用 50ms【推断:可降到 200ms,见 3.13 性能非功能】)。 +2. **时间状态枚举**:`None / OnClass / PrepareOnClass(预留) / Breaking / AfterSchool`(`repos/ClassIsland/ClassIsland.Shared/Enums/TimeState.cs:6-27`)。 +3. **每 tick 计算顺序**(必须逐条实现): + 1. 清理过期临时课表/临时层/临时课表群; + 2. 若「加载课表」关闭 → 当前课表为空; + 3. 选当天课表(预定 > 临时 > 常规,详见 parts/06 §6.2); + 4. `now = 当前本地时间`; + 5. 当前时间点 = 有效时间点中第一个 `TimeType∈{0,1}` 且 `StartTime ≤ now ≤ EndTime`; + 6. 上课 → `CurrentState=OnClass` 并映射科目;课间 → `CurrentState=Breaking`,`CurrentSubject=课间科目`(名称=课间名); + 7. 下一节 = 第一个 `TimeType=0 && EndTime ≥ now`;下一个课间 = 第一个 `TimeType=1 && EndTime ≥ now`; + 8. 剩余时间:上课时算「距下课」,否则算「距上课」;**下限钳 0**; + 9. 下一节与下一课间都不存在 → `AfterSchool`; + 10. 统一回写(未算出用默认值);状态变化时才发事件,顺序固定「先 `CurrentTimeStateChanged`,再按新状态发 `OnClass` / `OnBreakingTime` / `OnAfterSchool`」。 +4. **有效时间点的双向裁剪**:禁用一节早课 → 其前所有时间点不显示;禁用一节晚课 → 其后所有时间点不显示(`repos/ClassIsland/ClassIsland.Shared/Models/Profile/ClassPlan.cs:54-98`)。ClassHelper 侧映射为「`ScheduleDto` 投影 + 当前时间点/下一节,由服务端解析结果决定」,**不得在客户端重写轮换规则**。 +5. **跨天**:每次 tick 检测日期变化;跨天必须重新取当天课表、清除「今天」的已提醒标记、重置课程提醒状态机(`LessonReminderTracker` 的 `dayKey` 变化),不得沿用昨天的「已提醒」状态。 +6. **时间突变**(手动改系统时间、NTP 校正、睡眠唤醒):下一次 tick 必须按新时间重新判定状态;不得补发错过的提醒,也不得因时间回退而重复发出同一提醒(用「状态变化」而非「时间比较」触发)。 + +### 3.7 课程表组件的行为(重点) + +课程表组件是「一行多条课程」的核心,ClassHelper 的 `schedule` 组件必须按下列行为实现(视觉像素见第 7 章): + +#### 3.7.1 显示哪些时间点(必须逐条判定,`repos/ClassIsland/ClassIsland/Controls/Components/ScheduleComponent.axaml.cs:243-251`) + +对当天每个时间点: + +1. `TimeType ∈ {2 分割线, 3 行动}` 不作为课程项显示(分割线由专门的竖线控件渲染;行动不显示); +2. `HideFinishedClass=true` 且 `EndTime < now` → 不显示; +3. `IsHideDefault=true` 且不是当前选中项 → 不显示; +4. `TimeType==1`(课间)且不是当前选中项 → 不显示; +5. `ShowCurrentLessonOnlyOnClass=true` 且当前选中项是上课类型且该项不是当前项 → 不显示。 + +**映射到 ClassHelper(`custom` 模式的 `schedule` 组件)**:`display:'today'` 必须实现「只当前项展开、其余折叠」的 ClassIsland 语义: + +- 上课点折叠只显示课程名;**当前时间点展开显示「课程名 + 起止时间」**; +- 课间与 `isHideDefault` 的上课点**折叠时完全隐藏**,轮到它们成为当前项时才出现; +- 分割线 `DIVIDER` 渲染为一条竖线;`ACTION` 完全不显示(`packages/shared/src/class-plan.ts:4,70-87`;`packages/desktop-client/src/island/ComponentNode.vue:48-80`); +- 无时间点数据时**回退按 `ScheduleDto` 条目显示课程**(`docs/app/island.md:69`); +- **倒计时/徽标只在「当前时间点临近结束」时出现**,不得常驻。 + +#### 3.7.2 三种课程呈现 + +必须实现三种课程项(`repos/ClassIsland/ClassIsland.Core/Controls/LessonsControls/`): + +- **展开型**:科目名(强调字号)+ 附加信息 + 进度条;左右留白按「课程表文本间距」缩放(默认 1,范围 0.2–1,左右各 16×间距); +- **最小型**:只显示科目首字(`Subject.Initial`),用于 `IsHideDefault` 时间点或「上课时仅显示当前课程」; +- **分割线**:2px 竖线、高 25、次要色、透明度 0.8。 + +**已结束课程淡化**:`FadeCompletedClasses` 默认 true,透明度 0.6,过渡 150ms(`CubicEaseInOut`);`HighlightChangedClass` 默认 false,换课课程高亮(强调色块)。 + +#### 3.7.3 附加信息与倒计时(必须实现 6 种 `ExtraInfoType`) + +`ShowExtraInfoOnTimePoint=true` 时,当前课程可显示(`repos/ClassIsland/ClassIsland/Controls/Components/LessonControlExpanded.axaml:85-171`): + +| 值 | 显示 | 格式 | +|---|---|---| +| 0 | 起止时间 | `hh:mm-hh:mm` | +| 1 | 已过时间 | `-mm:ss` | +| 2 | 剩余时间 | `-mm:ss` | +| 3 | 进度百分比 | `N%` | +| 4 | 模糊倒计时 | `-<1min`(按 `CountdownSeconds` 向上取整) | +| 5 | 精确倒计时 | `-mm:ss` | + +**当前课程剩余时间药丸**:`IsCountdownEnabled=true` 时,当前时间点剩余 ≤ `CountdownSeconds`(默认 60)才出现;精确模式 `-mm:ss`,非精确模式 `<<数值`(`IsNonExactCountdownEnabled=false`,`LessonControlExpanded.axaml:173-214`)。 + +**课程进度条**:贴底、`Maximum=1`、值为课程进度、高度 3px(视觉细节归第 7 章);进度更新必须按「比例插值」而非每帧重算布局。 + +#### 3.7.4 空状态与明天课表 + +- 空占位(默认显示,`ShowPlaceholderOnEmptyClassPlan=true`): + - 今天没有课表 → `"今天没有课程。"`; + - 今日课程全部结束且 `HideFinishedClass=true` → `"今日课程已全部结束。"`; + - 垂直居中、正文色;占位与课程列表互斥。 +- 明天课表 `TomorrowScheduleShowMode` 默认 `1`:`0` 不显示、`1` 放学后显示、`2` 总是显示、`3` 今天无可显示课程时显示;显示时左侧出现「明天」药丸、主列表隐藏。 +- `AfterSchool` 判定 = 当前时间状态为放学后**或**今天没有课表。 + +#### 3.7.5 课程表组件设置默认值(必须照抄) + +| 字段 | 默认 | 说明 | +|---|---|---| +| `ShowExtraInfoOnTimePoint` | `true` | 显示附加信息 | +| `ExtraInfoType` | `0` | 0–5 | +| `IsCountdownEnabled` | `true` | 结束倒计时药丸 | +| `CountdownSeconds` | `60` | 阈值(秒) | +| `IsNonExactCountdownEnabled` | `false` | 模糊倒计时 | +| `ScheduleSpacing` | `1` | 0.2–1 | +| `ShowCurrentLessonOnlyOnClass` | `false` | 上课仅显示当前课程 | +| `HideFinishedClass` | `false` | 不显示已结束课程 | +| `FadeCompletedClasses` | `true` | 淡化已上课程 | +| `HighlightChangedClass` | `false` | 标记换课 | +| `ShowPlaceholderOnEmptyClassPlan` | `true` | 空态占位 | +| `PlaceholderTextNoClass` | `"今天没有课程。"` | 文案 | +| `PlaceholderTextAllClassEnded` | `"今日课程已全部结束。"` | 文案 | +| `TomorrowScheduleShowMode` | `1` | 0–3 | + +(证据:`repos/ClassIsland/ClassIsland/Models/ComponentSettings/LessonControlSettings.cs:8-23`;`repos/ClassIsland/ClassIsland/Models/Settings.cs:103-109,208-209,221`。) + +### 3.8 提醒队列与优先级(行为) + +ClassIsland 的提醒是**统一管线**,必须实现以下行为: + +1. **一切提醒都进同一管线**:上下课、放学、天气、集控、行动触发的提醒共用队列与消费者;不得各写一套弹窗。 +2. **两层 UI**:遮罩(Mask,短文案,第一阶段)+ 正文(Overlay,详细内容,第二阶段);无正文时只播遮罩(`repos/ClassIsland/ClassIsland.Core/Models/Notification/NotificationRequest.cs:40-63`)。 +3. **队列与优先级**: + - 提供方优先级:`NotificationProvidersPriority` 数组下标越小越先播;设置页可拖动排序。 + - 同提供方内先入队先播(FIFO)。 + - ClassIsland 源码的 `IsPriorityOverride` 比较器存在取反异常(`repos/ClassIsland/ClassIsland.Shared/Models/Notification/NotificationPriority.cs:47-50`);**本轮目标语义**按第 8 章裁决:三级排序键(提供方下标 → 通知等级 Emergency>High>Normal>Low → 入队序),不照抄异常行为。 +4. **去重**:同一 request 对象只入队一次;出队用「已弹出集合」避免链式头节点重复;天气预警按 `Detail` 会话内去重;ClassHelper 侧另有「同 id 通知/叫人双播合并」规则(见第 8 章),两者不冲突。 +5. **链式提醒**:数组请求串成链,任一子请求取消即取消全部;准备上课 + 上课是典型链(`repos/ClassIsland/ClassIsland/Services/NotificationHostService.cs:205-254`)。 +6. **消费者**:按优先级排序,只有 `AcceptsNotificationRequests && QueuedNotificationCount<=0` 才推送;无消费者时退回队列;播放完主动拉取后续(`NotificationHostService.cs:337-419`)。 +7. **播放状态机**:`None → Queued → Playing →(Paused/Cancelled/Completed)`;取消/完成分别触发对应令牌回调;被中断但未被取消的提醒必须**重新入队**(`NotificationHostService.cs:488-522`)。 +8. **取消与清理**:`CancelAllNotifications` 清空队列 + 取消所有正在播放的请求;用户入口为主界面右键菜单「清除全部提醒」,仅在「有提醒正在播放」时可见(`repos/ClassIsland/ClassIsland/MainWindow.axaml:86-94`);**不实现**独立「清除提醒浮动按钮窗口」(源码 `#if false` 遗留)。 + +### 3.9 通知两层 UI(气泡 + 强提醒) + +#### 3.9.1 气泡(行内)行为 + +1. 遮罩阶段:背景切换为强调色 + 斜切遮罩扫入 + 遮罩文字入场;此阶段不显示详情。 +2. 遮罩结束:有正文则切正文(背景恢复、正文淡入、底部进度条显示剩余时间);无正文则直接收起。 +3. 正文阶段:内容 + 底部进度条(`Maximum=1`、`Value=剩余进度`、值过渡 50ms 线性);显示时长由内容 `Duration` 或 `EndTime` 决定。 +4. 结束时:正文淡出 → 遮罩退出 → 行恢复原状;队列里还有提醒则继续播下一条。 +5. **提醒期间行的可见性**:强制可见(覆盖隐藏规则),并置顶;提醒结束释放置顶锁。 +6. **关键动画参数**(语义;完整表见第 10 章):斜切遮罩 5 区段、区段 360ms、错峰 120ms、开 `QuarticEaseOut`/关 `QuadraticEaseIn`;遮罩文字 `delay 260ms`、淡入 250ms、缩放 1.1→1.0、750ms,曲线 `(0.25,1,0.5,1)`;退出文字 200ms、缩放 1.0→1.1、曲线 `(0.5,0,0.75,0)`;正文淡入淡出 200ms、`SineEaseIn`。Classic 主题是另一套(上滑/下压)语义,必须两套都保留。 +7. **音效与 TTS**:ClassIsland 支持提醒音效与语音(默认关闭语音、音效音量 1.0);**本轮重构不实现音效/TTS**(ClassHelper 无音频通道,见 3.13),只保留视觉提醒。 + +#### 3.9.2 强提醒(Topmost 全屏特效)行为 + +1. 触发:提醒开始且生效设置开启「强调特效」、主界面可见、行未全部隐藏、本次遮罩尚未播过声音。 +2. 表现:从行中心向全屏扩散的涟漪(默认强调色),扩散 600ms + 淡出 600ms,750ms 后移除元素;特效窗口为**独立透明置顶窗口**,逐屏覆盖主界面所在屏幕,按 `NotificationEffectRenderingScale`(默认 1.0)缩放。 +3. 生命周期:特效元素集合为空即隐藏窗口,不销毁;非 `OSShutdown/ApplicationShutdown` 时禁止关闭窗口;编辑模式下丢弃新特效。 +4. **必须支持多屏**:每个显示器一个特效窗口,屏幕/DPI 变化时重算位置与尺寸。 +5. ClassHelper 映射:`custom`/`fixed` 共用同一套特效窗口;动画速度倍率缩放 600/750ms;`animations=false` 时不播特效只播气泡。 + +### 3.10 天气展示(行为) + +1. **数据频率**:天气服务每 5 分钟刷新一次,启动立即刷新一次;刷新成功才置「已刷新」标志,所有依赖天气的提醒/规则先判该标志(`repos/ClassIsland/ClassIsland/Services/WeatherService.cs:41-44,69-71,386-389`)。 +2. **缓存**:最近一次天气持久化在设置中,重启先展示旧数据并标注更新时间;刷新失败保留旧数据,不清空。 +3. **组件展示**:天气组件有多种主信息模式(图标 + 温度 + 文本;默认展示当前天气+温度);无数据时显示占位。 +4. **提醒**:气象预警与天气预报的提醒在「上课 / 课间」事件时触发;预警按 `Detail` 会话内去重、显示时长按文本长度钳制 10–90 秒;预报正文 15 秒;预警要求数据已刷新。 +5. **本轮范围**:ClassHelper 已有 Open-Meteo 天气简报(15 分钟缓存、断网标「缓存」、**无官方气象预警**)。重构后必须**保留 ClassHelper 数据源与行为**,ClassIsland 的「气象预警/三天预报/逐小时预报提醒」**不实现**(见第 8 章冲突裁决)。 + +### 3.11 主题语义(Fluent vs Classic 的差异) + +必须保留两套「风格」的概念(ClassHelper `islandAppearance.style` 默认 `classisland`,回退 `winisland`),差异只体现在**行为参数**上: + +| 维度 | Fluent(默认目标) | Classic(可选回退) | +|---|---|---| +| 行宽过渡 | 300ms、`(0.65,0,0.35,1)` | 550ms、`(0,1,0.5,1.1)`(带过冲) | +| 遮罩入场 | 斜切 5 区段扫入 + 文字延迟 260ms | 整层上滑/下压(`Y -50→0`,200ms `QuadraticEaseIn`) | +| 遮罩退场 | 文字淡出 200ms + 缩放 | 下移 `0→50`、200ms `QuadraticEaseOut`、延迟 100ms | +| 行列表面 | 中性填充 + 1px 描边 + 阴影 `0 4 8 2 #48000000` | 类似,但强调色/描边更强 | +| 分体 | 每组件独立胶囊(min-width 40、padding 0 16) | 同语义,参数略异 | +| 主题色来源 | 系统强调色/自定义 | 同 | + +**硬约束**:背景不透明度只作用于背景层;文字始终不透明;投影必须常驻声明、只改参数(禁止在开合瞬间增删 `filter`);两套主题共用同一行为状态机,只切换视觉参数。 + +### 3.12 动画语义总则(实现时必须遵守) + +1. **进场慢、退场快**:进场 200–300ms,退场 100–150ms;提醒遮罩文字淡入 250ms、淡出 200ms,属于「进场慢退场快」的允许例外(遮罩退场本身另有 100–200ms 的层退出)。 +2. **延迟表达因果**:先扫入(0–360ms)、再文字(+260ms)、再投影(+600ms);不得所有元素同时启动。 +3. **错峰封顶**:列表入场每项 25–35ms,超过 10 项必须封顶;不得线性累加到 1 秒以上。 +4. **可打断**:进/出必须成对、可反向,反向时新动画直接覆盖旧动画(不排队);不得用 JS 计时器模拟状态机。 +5. **动画不改业务**:动画只表达状态,不得在动画结束回调里发请求或用动画时长当业务计时。 +6. **最终几何一致**:动画结束后元素几何必须与「无动画直跳终态」逐像素一致。 +7. **动画级别**:`AnimationLevel` 0/1/2(默认 1);级别 0 禁用动画但**主界面提醒动画不在此列**(设置项描述明确);同时响应系统「减少动态效果」(视为级别 0 + 时长 1ms,加载指示器放慢不停)。 +8. **性能禁忌**:不对包含文字的容器做 `width/margin` 过渡(用绝对定位背景层或 `clip-path`/`scaleX`);卡片投影用 `box-shadow` 不用 `filter: drop-shadow`;透明窗口**禁用** `setBackgroundMaterial()`。 + +### 3.13 本轮不实现的 ClassIsland 能力(必须写进实现说明,不得遗漏标注) + +以下能力在 ClassIsland 基准里存在,但**本轮重构明确不实现**,实现者不得自行扩展、也不得因缺失判为缺陷: + +1. **自动化系统**(触发器/行动/规则集/时间点行动):不实现;岛内不出现自动化入口。 +2. **插件系统与插件市场**:不实现;不引入 `.cipx`、不加载第三方 DLL/模块。 +3. **主题市场/主题包/壁纸取色**:不实现(壁纸取色在 ClassIsland 也已是 P2 停用状态);只做岛内两套风格(`classisland`/`winisland`)。 +4. **集控/管理端**(ManagementServer、策略、审计、授权级别):不实现;ClassHelper 用服务端权限体系替代。 +5. **Excel 导入/导出、档案导入导出、CSES 互转**:不实现(属 ClassIsland 本地档案能力);ClassHelper 继续用服务端课表/YAML 导入。 +6. **自动更新器、启动闪屏、OOBE、欢迎向导、教程中心、崩溃恢复窗口、作者/致谢窗口**:不实现(属其它 part/非岛范围)。 +7. **提醒音效与 TTS(系统 TTS / Edge TTS)**:不实现;本轮只做视觉提醒。若未来需要,作为独立扩展项,默认关闭。 +8. **ClassIsland 的「即将下课」遮罩**:不实现——课程临近下课的提示统一走 ClassHelper 的「课程名右侧蓝色秒数徽标」(见第 8 章裁决)。 +9. **本地档案/Profile/TimeLayout/CSES 体系**:不实现;数据继续用 ClassHelper 服务端解析结果(`ScheduleDto`/`IslandTimePointDto`)。 +10. **通知音效音量、静音时段、语音源切换**:不实现(ClassIsland 有,ClassHelper 无对应通道)。 + +### 3.14 验收标准(行为级) + +实现者必须逐条自查,全部通过才算完成本节: + +1. 多行场景:1→5→1 行增删后,行可见性与「主行」约束正确;只剩 1 行时删除被拒绝且提示「至少需要保留 1 个主界面行。」。 +2. 行可见性表达式三态:组件全隐藏时整行消失;提醒期间强制可见;编辑模式下隐藏行也可见可选。 +3. 隐藏规则:`hideMode=0` 三个开关各自生效,`explorer` 前台时不算全屏;提醒/编辑时覆盖隐藏;手动隐藏后不再接收新提醒;全部隐藏时内容透明度为 0 但窗口不销毁。 +4. 时间状态机:构造「上课→课间→放学」一天,状态与事件顺序正确(状态改变事件先于具体事件);禁用一节早课/晚课后,时间点的双向裁剪与「今天没有课程」占位一致。 +5. 课程表组件:6 种附加信息、倒计时药丸阈值 60 秒、已结束课程淡化 0.6、明天课表 4 种模式、无课表/全部结束两种占位文案全部正确。 +6. 提醒:两条提醒先后入队,第一条播完自动播第二条;提醒期间行强制可见并置顶,结束后释放;右键「清除全部提醒」仅在有提醒时出现并立即清空。 +7. 强提醒:开启特效时从岛中心扩散涟漪,600ms 扩散 + 600ms 淡出、750ms 后消失;编辑模式下不播;多屏时覆盖岛所在屏。 +8. 主题:Fluent 与 Classic 两套的遮罩入场/退场行为不同(斜切 vs 上下滑),行宽过渡 300ms vs 550ms;切换主题不改变任何业务状态。 +9. 动画:`AnimationLevel=0` 时除岛提醒外无动画;系统「减少动态效果」下时长压到 1ms 且加载指示器不停;动画结束后几何与直跳终态一致。 +10. 非目标确认:界面上不存在自动化、插件、主题市场、集控、Excel 导入、音效/TTS 入口;代码中不存在对应依赖。 +11. 形态映射:新安装默认 `family='classisland'` 的信息行(日期+课程表、行高 40、底部 3px 进度条),升级用户默认 `family='winisland'` 的胶囊形态;`custom` 仍走 schemes/rows/nodes;三种形态切换不丢消息、不重建第二个岛窗口。 + +--- + +## 4. 目标架构与文件级改造清单 + +### 4.0 重构原则(必须逐条遵守) + +1. **只在现有仓库内重构**:不得新建工程、不得换框架、不得引入服务端依赖;沿用 pnpm@11.8.0 monorepo 与 Electron ^44.3.0 + Vue ^3.5.42 + Element Plus ^2.14.5 + Pinia ^4.0.3 + Vite ^8.3.0 + TS ~5.9.3。 +2. **渐进式替换,不一次性推倒**:每次改动后应用必须仍可启动、旧 `config.json` 仍可加载;用"门面(facade)+ 新模块"的方式把旧调用点隔离,避免一次改 `src/main/index.ts`、`tray.ts`、`smoke.ts` 的全部 import。 +3. **单一事实来源**:任何"主进程与渲染进程各算一次"的数值(窗口包围盒、列表高度、命中矩形、细缝尺寸、字号推导)必须收口到 `packages/shared/src/island/`,两端只调用同一函数。现状三处漂移见 02 §2.3。 +4. **保留现有业务语义**:消息去重、上课暂存、紧急展开、已读回执、作业免打扰指纹、课件覆盖优先级、离线补弹,全部保持(02 §2.5/§2.12);ClassIsland 只改变"视觉与布局表现"。 +5. **两套风格族(family)共存,且 family 只作用于 fixed**:`appearance.family` 取值 `'classisland' | 'winisland'`;**默认 `classisland`**;`winisland` 保留原胶囊+详情卡外观;老用户升级默认 `winisland`(迁移见 05)。**`custom` 布局始终按 ClassIsland 规格渲染,不受 family 影响**(family 只决定 fixed 用哪套渲染器)。 +6. **`fixed` 重新定义**:在 `family='classisland'` 下,`fixed` 不再是胶囊+详情卡,而是 **ClassIsland 风格信息行**(默认 1 行 = 日期 + 课程表,行高 40、圆角 8、背景不透明度 0.5、3px 进度条、消息以行内气泡呈现)。原"胶囊+详情卡"只在 `family='winisland'` 下保留。这是本轮最重要的语义变化,所有文件改造必须按此分叉。 +7. **一个原生窗口**:多种布局/风格共用一个 `BrowserWindow`;切换 family/layout 不得新开窗口(现状约束,见 `docs/app/island.md:8-10`、`src/main/island.ts:468-634`)。 +8. **不打扰纪律**:默认验证无头/离屏,不创建可见岛或托盘;只有显式 `--visual` 才做桌面像素验收。依据:`docs/dev/testing.md:9,29-32`。 +9. **调色板与风格分离**:`style: black|glass|tinted` 继续保留,写为 winisland 调色板字段(canonical `appearance.winislandStyle`,旧 `style` 作为兼容镜像,不删除);family 与 style 独立,切换 family 时保留用户 style 原值。 +10. **外观新增定稿字段**:`colorScheme: 'light'|'dark'|'system'`(默认 system)、`theme: 'fluent'|'classic'`(默认 fluent)、`effectEnabled`(默认 true)、`effectScale: 1|0.75|0.5`(默认 1)、`progressBar:{height:3,show:true}`;`fontSize` 语义随 family:classisland=正文基准(新装默认 16,派生 14/18/20),winisland=旧语义(默认 13,迁移保留原值)。 +11. **版本只保留一个**:落盘版本只有 `islandLayout.version=2`;`schemaVersion` 仅允许出现在 IPC payload 作运行时校验,不得落盘。 +12. **fixed 消息/提醒口径**:行内气泡同一时刻只显示 1 条 active;多条折叠为「还有 N 条」并可打开应用内通知中心;`prepare/on-class/break` 以行内容接管呈现、`ending` 只用蓝色圆形秒数徽标;队列/去重/暂存规则不变。 + +### 4.1 目标模块边界与目录 + +必须把灵动岛拆成四个边界清晰的部分。目录与职责如下(标注「新增」的目录/文件由本轮创建;其余为移动或改造): + +```text +packages/shared/src/island/ # 新增:岛的唯一契约层(纯 TS,无 DOM/Electron) +├─ index.ts # 统一导出(供 desktop-client 与 web-admin 可复用) +├─ schema.ts # IslandLayoutV2 / IslandAppearanceV2 / IslandRowV2 / IslandNodeV2 类型 +├─ defaults.ts # ClassIsland 规格默认值(family/行/组件/外观) +├─ normalize.ts # normalizeIslandLayoutV2 / normalizeAppearanceV2(校验+夹取) +├─ migrate.ts # migrateIslandLayoutV1ToV2(旧 config → v2) +├─ metrics.ts # CLASSISLAND_ISLAND_METRICS + WINISLAND_ISLAND_METRICS + 排版系数 +├─ geometry.ts # 锚点/包围盒/细缝/触摸贴合尺寸的纯函数(主/渲染共用) +├─ messages.ts # 去重、排序、列表布局、行内气泡分组(纯函数) +├─ lessons.ts # 课程提醒计算(从 main/lesson-reminder.ts 上移) +├─ dto.ts # IslandState/IslandNotification/ScheduleDto 等对外 DTO 与映射 +└─ ipc.ts # IPC 通道名与 payload 类型(主进程与 preload 共用) + +packages/desktop-client/src/main/island/ # 新增:主进程岛引擎(从 island.ts 拆出) +├─ index.ts # 门面:export { island, registerIslandIpc, ... }(旧 import 不变) +├─ controller.ts # 状态机编排:消息、上课状态、预览、动作分发 +├─ window.ts # 建窗、固定包围盒、锚点布局、可见性/焦点/透明度 +├─ hit-test.ts # 命中轮询、岛体矩形、interactive 决策(含 setShape 降级) +├─ touch.ts # 触摸模式、贴合、失焦宽限、常驻控件 +├─ messages.ts # 队列存储 + 合并 + 超时计时器(无 UI) +├─ lessons.ts # 每秒推进课程提醒 +├─ weather.ts # 从 island-weather.ts 移入 +├─ courseware.ts # 课件覆盖状态桥 +├─ preview.ts # 设置页"测试灵动岛"的预览注入 +└─ ipc.ts # island:* 全部 handler(从 island.ts 尾部搬来) + +packages/desktop-client/src/island/ # 渲染进程(现有目录内重组) +├─ main.ts # Keep:tokens + motion + mount +├─ LayoutIslandApp.vue # Change:分发(custom→CustomIslandRuntime;fixed 按 family 二选一) +├─ family/ +│ ├─ classisland/ +│ │ ├─ FixedInfoRow.vue # 新增:ClassIsland 信息行(fixed 新默认) +│ │ ├─ InlineMessageBubble.vue # 新增:行内消息气泡(fixed 下替代详情卡) +│ │ └─ ClassislandTheme.css # 新增:Fluent/Classic 两套行样式与动画 +│ └─ winisland/ +│ ├─ ClassicIsland.vue # Change:由 IslandApp.vue 迁移并改名(原胶囊+详情卡) +│ └─ winisland.css # Change:由 classic-island.css 迁移(数值不变,仅 fixed 使用) +├─ custom/ +│ ├─ CustomIslandRuntime.vue # Change:始终按 ClassIsland 行/组件规格重写测量与适配(不看 family) +│ ├─ CustomRows.vue # 新增:常态行 / 展开行渲染(行级外观) +│ ├─ CustomRow.vue # 新增:单行(行设置、隐藏规则、进度条) +│ └─ CustomRowSettingsDrawer.vue# 新增:行设置抽屉(背景/字号/圆角/隐藏;字段由 09 定义) +├─ shell/ +│ ├─ IslandShell.vue # 新增:squircle 形状 + 弹簧形变 + 命中上报(fixed 两族共用) +│ ├─ MessagePanel.vue # Change:由 ClassicNotifications.vue 改造(单条/列表/行内气泡) +│ └─ LessonBar.vue # Change:由 LessonReminderContent.vue 改造(ClassIsland 遮罩) +├─ components/ # 新增:按 kind 拆分的组件渲染器 +│ ├─ index.ts # node.kind → 渲染器映射 +│ ├─ DateNode.vue / ClockNode.vue / TextNode.vue / CountdownNode.vue +│ ├─ ScheduleNode.vue / WeatherNode.vue / SeparatorNode.vue +│ └─ ContainerNode.vue # group/stack/slide/rolling(rolling 支持 mode continuous|pause) +├─ motion/ # Keep:spring.ts、squircle.ts(可加适配器) +├─ styles/ # 新增:shared.css / classisland.css / custom.css(custom 始终 classisland 命名空间) +└─ Legacy/ # 可选:保留旧文件名再导出门面(ComponentNode.vue 等) + +packages/desktop-client/src/renderer/island/ # 主窗口侧的桥(现有) +├─ bridge.ts # Keep:数据桥(课表/上课状态/通知/作业/叫人/补弹/已读) +└─ editor/ # 新增:编辑器逻辑 composables(从 IslandEditorView 抽出) + ├─ useIslandDraft.ts # 草稿、脏标记、保存队列、修订号 + ├─ useIslandDrag.ts # 指针拖拽/落点/自动滚动 + └─ useIslandPreview.ts# 内嵌预览缩放与测量 + +packages/desktop-client/src/renderer/ # 设置/编辑器页面(现有) +├─ views/settings/IslandSettingsView.vue # Change:family 选择器 + 条件外观 +├─ views/settings/IslandEditorView.vue # Change:调用新版架构 + 行设置 +├─ components/IslandComponentSettings.vue # Change:ClassIsland 组件字段 +├─ components/IslandEditorNode.vue # Keep(可加行级拖拽) +├─ components/IslandRowSettingsDrawer.vue # 新增:行设置抽屉 +└─ styles/island-editor.css # Change:ClassIsland 编辑器视觉 +``` + +### 4.2 文件级 Keep / Change / Add / Remove 总表 + +| 文件/目录 | 类型 | 一句话目标 | +|---|---|---| +| `packages/shared/src/island-layout.ts` | Change | 保留导出名兼容;实现迁移到 `island/` 新目录,本文件仅再导出 | +| `packages/shared/src/constants.ts` | Change | 保留旧 WinIsland 常量;新增 ClassIsland 度量与 family 常量(迁移见 05) | +| `packages/shared/src/types.ts` | Change | `IslandAppearance` 增加 `family`/`colorScheme`/`effectEnabled`/`effectScale`/`progressBar`/`theme`/`winislandStyle`;新增 `IslandState.family/pendingStaged/bubblePhase/bubbleDeadline`、`IslandNotification.level`、`IslandLessonReminder.progress/leftSeconds`(运行期,不落盘) | +| `packages/shared/src/island/ipc.ts` | Add | IPC 通道名与 payload 类型;`island:push` 增加 `source`/`dedupeKey` | +| `packages/shared/src/motion.ts` | Keep | 不动令牌数值(03/10 部分引用) | +| `packages/shared/src/class-plan.ts` | Keep | `IslandTimePointDto`/`TimePointType` 不动 | +| `src/main/island.ts` | Change | 2445 行 → 门面(约 30 行),实际逻辑移入 `main/island/*` | +| `src/main/island/*.ts` | Add | 见 4.1(controller/window/hit-test/touch/messages/lessons/weather/courseware/preview/ipc) | +| `src/main/config.ts` | Change | 启动时执行 v2 迁移钩子(唯一版本=`islandLayout.version`);写盘前校验 `family/colorScheme/fontSize/effect*`;保留合并写/退出 flush | +| `src/main/index.ts` | Change | `island.init()` 改调新门面;注册编辑器/预览所需 IPC | +| `src/main/ipc.ts` | Change | 岛相关 handler 全部委托 `main/island/ipc.ts`;主窗口配置剔除规则保留 | +| `src/main/courseware.ts` | Change | 改为调用 `island.setCoursewareTransfer`(门面保留同名方法) | +| `src/main/tray.ts` | Change | 菜单文案与入口不变;`island.editLayout()` 改由新控制器实现 | +| `src/main/island/messages.ts` | Add | 消息队列 + 合并 + 超时 + **作业指纹/去重表**(见 4.3.13) | +| `src/main/island/ipc.ts` | Add | 校验 `island:push` 的 `source/dedupeKey`,投递前查重 | +| `src/main/smoke.ts` | Change | 保留全部既有读取器与注入点(名称/语义不变,见 4.7 第 11 条);新增 v2 断言(行高/行数/气泡/迁移);改造归属 part 11 测试基线 | +| `src/preload/index.ts` | Change | 新增 `family/colorScheme` 感知的配置读写;`islandPush` 透传 `source/dedupeKey`;保留全部旧方法名 | +| `src/preload/island.ts` | Change | 新增行/编辑器预览相关事件;旧事件名与语义不变 | +| `src/types/desktop.d.ts` | Change | 配置形状加外观新字段与 `islandLayout.version`;新增 v2 DTO 类型;`islandPush` payload 扩展 | +| `src/island/LayoutIslandApp.vue` | Change | 分发:`custom` → CustomIslandRuntime;`fixed` 按 family 二选一(见 4.3.1) | +| `src/island/IslandApp.vue` | Change | 迁出为 `family/winisland/ClassicIsland.vue`;旧文件保留再导出(防断裂) | +| `src/island/CustomIslandRuntime.vue` | Change | **始终按 ClassIsland 行/组件规格**重写测量、行渲染、行设置抽屉;不受 family 影响 | +| `src/island/ComponentNode.vue` | Change | 拆为 `components/*Node.vue`;本文件保留兼容导出;滚动组件新增 `settings.mode('continuous'|'pause')` | +| `src/island/ClassicNotifications.vue` | Change | 改为 `shell/MessagePanel.vue`;新增行内气泡模式(同一时刻 1 条 active) | +| `src/island/LessonReminderContent.vue` | Change | 改为 `shell/LessonBar.vue`;遮罩参数按 ClassIsland 重算;fixed 下 prepare/on-class/break 行内容接管、ending 仅徽标 | +| `src/island/CoursewareTransfer.vue` | Keep | 课件覆盖卡保持现状(424×132 等,见 02 §2.10) | +| `src/island/WeatherBrief.vue` | Keep | 天气简报保持现状(Open-Meteo/15 分钟/缓存标记);**本轮不引入 ClassIsland 天气服务** | +| `src/island/spring.ts`、`squircle.ts` | Keep | 弹簧与连续圆角数值不动(10 部分仍按此写) | +| `src/island/classic-island.css` | Change | 改名 `family/winisland/winisland.css`(fixed 专用);数值不变,选择器按新 DOM 调整 | +| `src/island/custom-runtime.css`、`custom-island.css` | Change | 移到 `styles/custom.css` 与 `custom/*`;**始终按 ClassIsland 行规格重写**(custom 不受 family 影响) | +| `src/island/lesson-ending.css` | Keep | 秒数徽标样式不动 | +| `src/island/main.ts` | Keep | 入口与令牌注入顺序不动 | +| `src/renderer/views/settings/IslandSettingsView.vue` | Change | 顶部加 `family` 选择器(只影响 fixed);新增 colorScheme/theme/effectEnabled/effectScale/progressBar;fontSize 语义随 family 展示 | +| `src/renderer/views/settings/IslandEditorView.vue` | Change | 数据改为 v2;行设置抽屉;预览按 ClassIsland 规格;保存队列/上限保留;拆分节奏按 09 文件计划(见 4.3.11) | +| `src/renderer/components/IslandComponentSettings.vue` | Change | 组件字段与 ClassIsland 规格对齐(含滚动 mode);旧字段保留只读兼容 | +| `src/renderer/components/IslandEditorNode.vue` | Keep | 标签节点可复用;若加行级拖拽则小幅 Change | +| `src/renderer/components/IslandRowSettingsDrawer.vue` | Add | 行级设置的唯一入口(字段清单由 09 定义) | +| `src/renderer/island/bridge.ts` | Change | 作业投递增加 `source:'homework-local'|'realtime'|'replay'` 与 `dedupeKey`;保留本机指纹快速路径(见 4.3.13) | +| `src/renderer/views/HomeworkView.vue` | Keep | `markHomeworkCreatedLocally` 调用点保留(录入前/后登记指纹,见 4.3.13) | +| `src/renderer/styles/island-editor.css` | Change | 编辑器视觉按 ClassIsland(间距/分隔/虚线框/预览画布) | +| `packages/desktop-client/scripts/smoke.mjs` | Change | 视觉/几何断言改为按 family 分套;默认 quick 仍无头 | +| `docs/app/island.md`、`docs/dev/implementation.md` | Change | 口径更新为 family 与 fixed 新语义(文档不是代码,但必须与实现同步) | + +### 4.3 关键文件改造详情(逐个文件:现状 → 目标 → 接口 → 风险) + +#### 4.3.1 `src/island/LayoutIslandApp.vue`(Change,按 layout + family 分发) + +- **现状**:只按 `layout==='custom'` 二选一(`CustomIslandRuntime` / `FixedIsland`),课件优先覆盖。依据:`packages/desktop-client/src/island/LayoutIslandApp.vue:56-75`。 +- **目标**(定稿分发规则): + - `layout==='custom'` → **始终** `custom/CustomIslandRuntime.vue`(ClassIsland 行/组件规格),**不看 family**; + - `layout==='fixed'` 且 `appearance.family==='classisland'` → 新增 `family/classisland/FixedInfoRow.vue`(信息行 + 行内气泡); + - `layout==='fixed'` 且 `appearance.family==='winisland'` → `family/winisland/ClassicIsland.vue`(原胶囊+详情卡,行为不变); + - 课件传输仍最高优先级,覆盖以上所有分支。 +- **接口**:props 保持 `settings/state/schedules/timePoints/appearance/nativeVisible`;新增计算 `family = appearance.family ?? 'classisland'` 仅用于 fixed 分支;不新增 IPC 通道。 +- **为什么**:family 只影响 fixed,custom 不需要两套渲染;分发规则收窄后,custom 的数据与视觉可以独立演进。 +- **风险**:切换 family 只改变 fixed 渲染器,custom 不受影响——必须保证切换时若当前 layout=custom 不重建 custom 运行时(避免方案展开态/测量状态丢失);切换命中矩形时 `reportHit(null)` 并 `syncTargets(true)`。 + +#### 4.3.2 `src/island/IslandApp.vue` → `family/winisland/ClassicIsland.vue`(Change,大改) + +- **现状**:683 行,胶囊层 + 展开层 + 课程提醒 + 命中上报 + 弹簧形变全在一个文件;窗口固定包围盒内的岛由 `morph` 驱动。依据:`src/island/IslandApp.vue:44-570`。 +- **目标**: + 1. 保留并迁移全部 winisland 行为(胶囊/展开/紧急/叫人/多条列表/课程提醒/命中),**视觉与交互不变**(老用户回退路径)。 + 2. 把"壳"(squircle 路径、弹簧、`--card-path`、命中上报)抽到 `shell/IslandShell.vue`,把消息展示抽到 `shell/MessagePanel.vue`,把课程提醒抽到 `shell/LessonBar.vue`;`ClassicIsland.vue` 只负责"胶囊内容 + 展开内容"的编排。 + 3. 文件顶部保留兼容导出:`export { default } from './family/winisland/ClassicIsland.vue'`,避免旧 import 断裂。 +- **接口**:props 必须兼容现有 `{ embedded?, suspended? }`;继续调用 `window.island.sendAction/setInteractive/setHitRect`;继续使用 `ISLAND_SIZE_DELTA`/`ISLAND_LIST_METRICS`。 +- **为什么**:ClassIsland fixed 取代胶囊后,旧实现仍要作为 `winisland` 保留;抽壳后两族共用同一套命中/形变,减少重复实现。 +- **风险**:抽壳会改变 DOM 层级,现有 CSS 选择器(`.island-card .content`、`.pill-inner`)与冒烟断言依赖 DOM;必须保留 `.island-card`、`.island-root`、`data-anchor`、`data-style` 等关键钩子。 + +#### 4.3.3 `family/classisland/FixedInfoRow.vue`(Add,fixed 的新默认) + +- **目标**:ClassIsland 信息行的固定布局:默认 1 行,行高 40、行内水平内边距 12、行间距 5、圆角 8、背景不透明度 0.5(背景色取主题 token + `appearance.winislandStyle`/surface 材质)、1px 描边、字号由 `appearance.fontSize`(classisland 下=正文基准,默认 16)派生 14/18/20、进度条高度取 `appearance.progressBar.height`(默认 3)、`progressBar.show=false` 时不显示;默认行 = 日期组件 + 课程表组件;消息以**行内气泡**呈现(普通/重要/紧急/叫人/作业),不再弹"详情卡";强提醒特效仅在 `effectEnabled=true` 时叠加,尺寸按 `effectScale`。 +- **接口**:props `{ state, appearance, schedules, timePoints, now, editing? }`;内部复用 `components/*Node.vue` 与 `shell/MessagePanel.vue`(行内模式);只读 `appearance.colorScheme` 决定行底色/文字对比。 +- **为什么**:ClassIsland 的主界面就是"一行行信息",fixed 只有变成信息行才符合重构目标。 +- **风险**:fixed 原有"点胶囊展开卡片"的点击目标消失,必须保证消息可读(行内气泡可点开详情面板);命中矩形上报必须按真实行矩形,不能用旧胶囊尺寸(窗口包围盒由 geometry.ts 计算)。 + +#### 4.3.4 `family/classisland/InlineMessageBubble.vue`(Add,行内气泡) + +- **目标**:**同一时刻只显示 1 条 active 气泡**;点击展开详情/操作;有多条未处理消息时折叠为「还有 N 条」,点击打开应用内通知中心(沿用 ClassHelper 现有通知中心路由),不并列展示多条;两阶段表现为先横向展开成条、再展开详情面板(动画由 08/10 定义);普通/重要/紧急/叫人/作业五类样式;紧急消息仍按现有业务规则"立即展开、无视上课"。 +- **接口**:props `{ active, pendingCount, mode, listExpanded }`;动作只通过 `window.island.sendAction`,不得直接改状态;数据来源是 `IslandState.active/queued` 与 `IslandNotification.level`。 +- **为什么**:ClassIsland 的提醒是"行内/气泡"而非独立卡片;不改变业务队列,只换表现。 +- **风险**:行内气泡只改变行内内容测量与截断(正文最多 2 行、超出省略),**不改变窗口包围盒**(fixed 窗口尺寸固定为 min(工作区宽,960) × (40+2×pad));「还有 N 条」跳转必须带未读上下文,不得丢失已读回执。 + +#### 4.3.5 `custom/CustomIslandRuntime.vue`(Change,大改;始终 ClassIsland 规格) + +- **现状**:三页(compact/details/lesson)交叉淡入 + 自然内容测量 + 铃铛 + 命中上报;行/组件来自 `scheme.rows`。依据:`src/island/CustomIslandRuntime.vue:52-336,338-527`。 +- **目标**:保留"测量 + 三页"骨架,**统一按 ClassIsland 行规格**重写(**不区分 family**):单行基准高 40、行间距 5、行内水平留白 12、行背景/圆角/不透明度/字号覆盖、行级隐藏规则、行级进度条(3px,含义见 05 §5.3);行设置抽屉入口(字段由 09 定义);`rows`/`expandedRows` 的编辑语义不变;滚动组件支持 `settings.mode: 'continuous'|'pause'`(pause 时到边界停顿 `settings.interval`)。 +- **接口**:props 不变;新增读取 `row.appearance/row.hideRules/row.progress`;`window.island.reportResidentControl` 继续上报铃铛矩形;`window.island.reportSize` 继续上报适配后尺寸;CSS 使用 `classisland` 命名空间(custom 不受 family 影响)。 +- **为什么**:ClassIsland 组件库/行模型与本应用 schemes 高度相似,且 family 最终只作用于 fixed;custom 统一到 ClassIsland 规格可避免"两套自定义外观"。 +- **风险**:老 custom 用户升级后视觉会变化(自定义布局统一到 ClassIsland 规格),必须在发布说明与设置页提示;`ResizeObserver` + `requestAnimationFrame` 测量链对行数敏感,改写后的几何只由 metrics 计算,不用动画中的壳反推(现状注释见 `:73-79`)。 + +#### 4.3.6 `src/main/island.ts` → `main/island/*`(Change + Add,大重构) + +- **现状**:2445 行单文件(分区见 02 §2.2)。 +- **目标**: + - `index.ts`:门面导出 `island`、`registerIslandIpc`、`recomputeSizes`、`normalizeAppearance`、`getSliverSize`、`ISLAND_SIZES` 等旧导出名,**保证 `main/index.ts`、`tray.ts`、`courseware.ts`、`smoke.ts` 的 import 不改**。 + - `controller.ts`:状态与编排;持有 `IslandState`;消息/上课/预览/动作分发。 + - `window.ts`:建窗、固定包围盒/触摸贴合、锚点、可见性/焦点/透明度、`syncWindow` 拆分后的"可见性决策"。 + - `hit-test.ts`:60ms 轮询、`setInteractive`、`setHitRect`、`expectedHitRect`、`setShape` 降级(非 Windows 见 4.6)。 + - `messages.ts`:`pushNotification` 分流、合并、排序、超时计时器、`dismissActive`、`clearPendingForClose`、`handleAction`。 + - `lessons.ts`:`LessonReminderTracker` 每秒推进与 `lessonShowing` 判定。 + - `ipc.ts`:`registerIslandIpc()` 全部 handler(含发送方校验与外观合并节流 16ms)。 +- **接口**:必须保留全部旧公开方法与冒烟读取器(02 §2.5 第 9 条清单);新增 `getMetrics()`、`getLayoutVersion()` 供编辑器/冒烟断言。 +- **为什么**:不拆文件无法安全替换 fixed 渲染、无法让 engine 复用 shared 纯函数,也无法让 ChatGPT 在单文件里做可靠改动。 +- **风险**:拆分过程中最容易出现"私有字段跨模块互相引用";必须先把 `IslandState`、`WindowBox`、`HitRect`、`Metrics` 定义到 `types`,模块间只经接口通信;拆完立即用现有 `verify:desktop`(无头)回归。 + +#### 4.3.7 `src/island/ComponentNode.vue` → `components/*Node.vue`(Change) + +- **现状**:456 行,14 类 kind 的条件渲染 + 课程表折叠算法 + 编辑手柄 + 行内样式。依据:`src/island/ComponentNode.vue:29-210,212-293`。 +- **目标**:按 kind 拆文件,`components/index.ts` 提供 `resolveNodeRenderer(kind)`;每个组件只负责自己的内容与样式;课程表的"折叠/展开/隐藏/进度/徽标"逻辑放进 `ScheduleNode.vue` 并复用 02 §2.8/§2.9 规则。 +- **接口**:保持 `IslandComponentNode` 数据结构不变(字段名/默认值见 `packages/shared/src/island-layout.ts:77-108`);编辑态仍用 `node-handle` 拖拽句柄。 +- **为什么**:ClassIsland 组件规格要为每类组件补字段(字号覆盖、进度条、显示模式),单文件条件链会继续膨胀。 +- **风险**:`