- 重构版主提示词(14 parts,面向 ClassHelper 灵动岛重构) - ClassIsland 全量规格提示词(20 parts,含分卷与逐章版本) - 投喂指南、分卷、逐节文件 - 调研记录、一致性基线、生成与质检脚本
221 lines
34 KiB
Markdown
221 lines
34 KiB
Markdown
## 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-<id>`,整批已读时必须排除。依据:`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/...:行号`;没有"凭印象"的数值。
|