- 重构版主提示词(14 parts,面向 ClassHelper 灵动岛重构) - ClassIsland 全量规格提示词(20 parts,含分卷与逐章版本) - 投喂指南、分卷、逐节文件 - 调研记录、一致性基线、生成与质检脚本
34 KiB
34 KiB
2. ClassHelper 灵动岛现状基线(重构的"现状地图")
2.0 阅读与使用方式
- 你在重构前必须先读懂本部分。本部分不是评价现状好坏,而是逐文件、逐行号给出"现在到底是什么样"。重构的每一处改动都必须能在本部分找到对应的现状依据。
- 本部分所有行号以
repos/ClassHelper-master/为工作区相对根,写作packages/desktop-client/...:行号;与 ClassIsland 对照时写repos/ClassIsland/...:行号。行号是行区间起点,读源码时按主题名检索同样有效。 - 现状基线里出现的所有数值(尺寸、时长、阈值、限制)都是重构时必须先保持、再按新规格替换的对象;不得因为"要改成 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 再导出 |
- 重构时不得在这个文件里继续堆逻辑。你必须按 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 命中、穿透、触摸与"点不动"防护
现状把"命中"当成一级问题处理,重构必须原样保留这些防护:
- 窗口默认整块穿透(
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。 - 主进程 60ms 轮询读系统光标,并与渲染进程上报的岛体矩形比对来校正命中(Windows 下 mousemove 转发不可靠)。依据:
src/main/island.ts:2049-2078,2026-2047。 - 岛体矩形由渲染进程限流上报(最小间隔 50ms,尾随保证最后一次送达)。依据:
src/island/IslandApp.vue:412-454。 - 触摸屏:渲染进程按
navigator.maxTouchPoints > 0或首次touchstart上报setTouchMode(true);主进程据此让窗口贴合岛体并始终接收输入,且命中判定的入参被强制为 true。依据:src/island/IslandApp.vue:538-549、src/main/island.ts:245-257,1795-1822,1891-1902。 - Windows 专属
setShape用于触摸模式下把窗口裁成岛体+8px 留白;非 Windows 平台没有该能力,重构必须按 04 的降级方案处理(整窗穿透 + 岛可见区命中),不得依赖setShape才能点。依据:src/main/island.ts:278-316。 - 失焦收起:展开态获取焦点,点到别处
blur→ 回缩胶囊;但必须有两层防护——指针仍在岛上则不收起;刚展开的宽限期内不收起(鼠标 500ms、触摸 1200ms)。依据:src/main/island.ts:557-578,2287-2297,2020-2023。 - 窗口「始终可激活」(
focusable:true),收起时用blur()而不是关掉可激活性——Windows 下WS_EX_NOACTIVATE会丢mousedown,胶囊就点不动。依据:src/main/island.ts:1655-1672。
2.5 消息状态机(fixed 经典模式)
- 状态载体
IslandState:mode: 'hidden'|'pill'|'expanded'、active、queued、inClass、currentPeriodEnd、reason、listExpanded、maxCardHeight、lessonReminder、coursewareTransfer、updatedAt。依据:packages/shared/src/types.ts:1198-1230。 - 分流规则(必须作为重构的行为基线):紧急(
priority==='URGENT')立即展开、无视上课;上课期间普通通知只入队并隐藏;上课期间普通叫人保持胶囊可点、不自动展开;上课期间用户主动点开的通知允许显示(explicitShowId);预览模式(设置页"预览效果")直接展开、失焦不收起、30 秒自动收尾。依据:src/main/island.ts:684-702,794-867,2338-2380。 - 双链路合并:同一 id 经
notification:new与call:new双播时必须合并——类型取"更特殊"(call 优先)、优先级取更高,避免叫人被降级成普通通知。依据:src/main/island.ts:704-724。 - 排序:按优先级权重(
PRIORITY_RANK:LOW 0 / NORMAL 1 / HIGH 2 / URGENT 3)降序,同级按createdAt倒序。依据:packages/shared/src/constants.ts:183-209、src/main/island.ts:726-749。 - 超时表(毫秒):胶囊 15000、下课后展开 14000、下课后胶囊 25000、展开 20000、紧急 45000、叫人 90000;关闭动画 360;收回时先切胶囊、待动画后再淡出隐藏。依据:
src/main/island.ts:121-132,1106-1128。 - 收起的判定:只要还有未处理消息(当前或队列)就保持胶囊;队列清空/用户点了知道了/标为已读/进入上课才彻底隐藏。依据:
src/main/island.ts:1266-1296。 - 多条消息:展开态且待处理 ≥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。 - 动作:
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。 - 预览/冒烟注入口:
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 行)
- 卡片结构:
.island-root > .island-card > svg.shape + span.content >(tint + pill-layer + ClassicNotifications + LessonReminderContent)。依据:src/island/IslandApp.vue:573-681。 - 内容交叉淡入:胶囊层
1 - progress*1.5退净(进度 2/3 处),展开层progress²进入;progress由高度差推导。依据::304-309,346-351。 - 胶囊标题按类型:叫人→"老师叫你"、作业→"新作业"、紧急→"紧急通知"、普通→"新消息";多条时汇总为「新消息:叫人/作业/通知(共 N 条)」。依据:
:115-146。 - 停靠:
data-anchor决定内容贴边(左贴左/右贴右/居中);居中用left:50% + translateX(-50%),左右/上下停靠留ISLAND_SHADOW_PAD。依据::353-383、src/island/classic-island.css:207-233。 - 形状与投影:路径
fill: var(--wn-bg)+ 1px 描边;投影始终声明、只在展开态改参数(避免加/去 filter 造成开合闪一帧)。依据:src/island/classic-island.css:102-153。 - 状态色:紧急红
#ff453a、叫人橙#ff9f0a、作业用 accent;只做内层径向微染 + 描边,不外溢。依据:src/island/IslandApp.vue:151-157、classic-island.css:155-185。 - 内容层裁剪:
.content{clip-path:var(--card-path)};圆角路径由 SVGpath()注入 CSS 变量--card-path。依据:classic-island.css:127-133、IslandApp.vue:363-371。 - 轮廓保护:
island-root.embedded用于设置页内嵌预览(去掉绝对定位)。依据:classic-island.css:41-47、IslandApp.vue:578-581。
2.7 自定义布局(custom)运行时与数据
- 数据根:
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。 - 组件方案
IslandLayoutSettings:layout、residentEnabled、pointerFadeEnabled/Reversed、lessonReminders、activeSchemeId、schemes[]。依据:packages/shared/src/island-layout.ts:131-150。 - 方案
IslandLayoutScheme:id/name/rows/expandedRows/scale/color/foreground/opacity/radius/separated/spacing;rows为常态行、expandedRows为"无消息时点击展开"的内容。依据:island-layout.ts:109-130。 - 行
IslandLayoutRow:id/name/main/allowNotifications/gap/nodes;main行唯一(normalize 会强制只留一个)。依据:island-layout.ts:109-116,334-356。 - 节点
IslandComponentNode:id/kind/settings/appearance/advanced/children;kind14 类(date/clock/weather/countdown/schedule/text/separator/slide/rolling/group/stack/notification/homework/call)。依据:island-layout.ts:3-18,77-108。 settings:文本/模板、12 小时制、秒、倒计时目标与目标日、地点/经纬度、显示模式(当前/下一节/今日)、显示数量、显示起止时间、显示进度、排列方向、间距、轮播间隔、滚动速度。依据:island-layout.ts:80-99。appearance:资源覆盖开关、四档字号、前景/背景/透明度/圆角覆盖、整体透明度;advanced:隐藏规则(in-class/no-message/no-class)、对齐、固定宽/最大宽。依据:island-layout.ts:61-76,100-107。- 归一化边界(重构后仍必须保留的硬边界):最多 12 行、200 节点、6 层、方案 ≤20;颜色必须
#rrggbb;非法 kind 丢弃;重复 id 重命名;main行唯一。依据:island-layout.ts:256-403(尤其:269,337,358)。 - 自定义运行时:先测量自然内容(compact/details/lesson 三份),再按工作区与方案
scale适配;两个页面层(常态/详情)交叉淡入,第三层承载课程提醒;铃铛显示未处理数(>99 显示99+)。依据:src/island/CustomIslandRuntime.vue:58-124,238-323,338-527。 - 鼠标避让淡化:常驻模式按指针是否在岛内切换透明度到
CLASSISLAND_ISLAND_METRICS.fadedOpacity=0.05(可反转);触摸/编辑/课程提醒期间暂停。依据:src/main/island.ts:1851-1877、island-layout.ts:35。 - 编辑布局时窗口
interview行为:editingLayout下不参与命中轮询、不自动收起;island:edit-layout与托盘入口都走editLayout()。依据:src/main/island.ts:350-356,1430-1433。 - 常驻窗口几何:窗口固定为
min(960, 工作区宽) × min(640, 工作区高 - marginY),内容在画布内弹簧形变;常驻开合期间窗口不做逐帧 setBounds。依据:src/main/island.ts:1522-1528、CustomIslandRuntime.vue:214-216。
2.8 课程提醒与课程组件
LessonReminderTracker只用服务端解析后的ScheduleDto计算五类提醒:准备(默认提前 60s)、上课、课间、下课前倒数(默认 60s)、课中进度;每种提醒的进入/退出时间由maskUntil/endsAt表达。依据:src/main/lesson-reminder.ts:14-110、packages/shared/src/island-layout.ts:140-158。- 主进程每秒刷新一次提醒;自定义布局下
in-class提醒不接管岛体(课程组件持续显示进度),ending提醒只作为课程名右侧秒数徽标。依据:src/main/island.ts:171-213、docs/app/island.md:65-70。 - 提醒条 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。 - 经典模式临近下课用课程名右侧蓝色圆形徽标显示剩余秒数(剩余 1 秒显示
-1),不显示"即将下课"遮罩。依据:docs/app/island.md:68-70、CustomIslandRuntime.vue:30-33、ComponentNode.vue:259-266。 - 关闭"即将下课倒数"后不得显示徽标;阈值沿用本机提醒设置。依据:
docs/app/island.md:69、lesson-reminder.ts:54-60。
2.9 天气现状
- 天气只在主进程抓取(固定服务、固定参数、不接受渲染层传 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。 - 渲染层
WeatherBrief.vue每分钟刷新一次;天气码映射文案(晴/少云/多云/阴/雾/毛毛雨/雨/雪/雷雨);断网或陈旧时显示"· 缓存";未配置坐标提示"请设置天气地点"。依据:src/island/WeatherBrief.vue:12-90。 - 冒烟模式禁止访问外部天气(
ELECTRON_SMOKE_TEST==='1'且使用真实 fetch 时直接抛错)。依据:island-weather.ts:33-34。
2.10 课件传输覆盖
- 主进程持有
coursewareTransfer,coursewareShowing时优先覆盖岛内容;syncWindow分支强制显示、取消淡入、不接收鼠标;formSize()返回课件固定尺寸。依据:src/main/island.ts:661-676,1420-1429,1956、src/main/courseware.ts:36。 - 课件卡尺寸
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)
- 桥接职责:从 IndexedDB 缓存/服务端取当天课表与完整时间点,每秒复算"是否上课",变化时通过
window.desktop.islandSetClassState下发给主进程;主进程据此决定通知立即弹/暂存/下课补弹。依据:src/renderer/island/bridge.ts:21-56,148-172,416-435。 - 课表来源
/schedules/active-week,同时构建IslandTimePointDto[](含课间/分割线/隐藏标记),课程名优先取日课表条目、无条目再回退时间点默认课程;离线缓存只有条目时退化为"只有上课时间点"。依据:bridge.ts:57-96,107-136。 - 通知投递:普通通知、新作业(带"本机录入免打扰"指纹去重)、叫人(带"请尽快前往"副文案)分别打包成
IslandNotification,随inClass/currentPeriodEnd上下文推给主进程。依据:bridge.ts:174-289。 - 离线补弹:恢复联网时取最近 5 条离线未读补投,推完清离线起点;冒烟模式跳过。依据:
bridge.ts:291-364。 - 已读回流:主进程收到岛的"标为已读"后转发给主窗口,桥接订阅并调用通知 store 标记已读(离线先本地改)。依据:
bridge.ts:366-405、src/main/index.ts:237-245。 - 生命周期:登录后启动、退出登录/切班时停止;停止时必须清定时器、解订阅、清本地课表并主动同步
inClass:false。依据:bridge.ts:437-473、src/renderer/App.vue:64-93、src/renderer/layouts/ClientLayout.vue:396-405。
2.13 配置与数据契约现状
config.json的岛相关字段:islandLayout(完整方案)与island(外观);两者都有逐字段归一化与默认值。依据:src/main/config.ts:26-84,124-163,198-233,334-346,435-523;配置文件路径userData/config.json见:165-167。- 写入语义:内存缓存 + 120ms 合并写 + 退出前
flushConfigSync;不允许每次读取都同步读盘。依据:config.ts:235-346。 - 外观契约
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。 - 经典外观默认:
width 268、height 44、radius 20、fontSize 13、opacity 1、accent #0a84ff、style 'black'、position 'top-center'、marginX/Y 8。依据:constants.ts:265-290。 - 语义色与排版系数:
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。 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 现状(重构必须保持的契约)
- 主窗口桥接:
getConfig/saveConfig/clearConfig/appInfo/openExternal;岛:islandPush/islandRemoveNotification/islandSetClassState/islandGetState/islandSetAppearance/islandGetAppearance/onIslandMarkRead/onIslandMarkAllRead;另有islandEditLayout/onIslandEditorOpen。依据:src/preload/index.ts:25-99。 - 岛窗口桥接:
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。 - 主进程侧注册:
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。 - 安全规则:所有
invoke必须校验发送方是岛窗口(event.sender === island.getWindow()?.webContents);渲染进程不能读写配置(get-layout是唯一读取口,save-layout由主进程做归一化)。依据:src/main/island.ts:2339-2356,2345-2350。
2.15 编辑器与设置页现状
- 设置页「灵动岛」:布局二选一(fixed/custom)、自定义方案入口、鼠标避让两个开关、外观滑块(宽/高/圆角/透明度/字号/速度)、主题色、视觉风格、位置、左右/上下边距、跟随鼠标屏幕、动画开关、始终置顶、空闲细缝、"测试灵动岛"(本地推一条 30 秒预览通知)。依据:
src/renderer/views/settings/IslandSettingsView.vue:26-160,162-392。 - 设置页改动即生效:外观走
window.desktop.islandSetAppearance实时下发,保存才落盘;自定义模式下圆角滑块会同步写当前方案圆角。依据:IslandSettingsView.vue:112-133。 - 编辑器是主窗口的设置子路由
/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。 - 编辑器保存:深拷贝 +
normalizeIslandLayouts+ 串行保存队列(saveQueue)+ 修订号(只有最后一次成功才算 saved)+ 路由离开前等待保存;上限:组件总数 200、方案 20。依据:IslandEditorView.vue:81-105,338-347,379-383。 - 拖动/放置:指针事件实现拖拽(
beginDrag/startDrag/dragTarget/moveDrag/dropPreview),编辑标签节点用IslandEditorNode.vue,支持 before/after/inside 三种落点。依据:IslandEditorView.vue:443-539、src/renderer/components/IslandEditorNode.vue:1-124。 - 组件设置抽屉:内容/外观/高级三页;内容按 kind 提供字段,外观含独立字号/前景/背景/透明度/圆角/整体透明度,高级含对齐/固定宽/最大宽/隐藏条件;底部支持包裹到容器、复制、删除。依据:
src/renderer/components/IslandComponentSettings.vue:26-307。 - 编辑预览:编辑器内嵌预览区(
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)
- 保留经典岛连续圆角、投影常驻、触摸输入、失焦宽限、固定窗口几何护栏。依据:
repos/ClassHelper-master/AGENTS.md§4「灵动岛」。 - 禁止
setBackgroundMaterial();毛玻璃只能是 CSS 半透明。依据:src/main/island.ts:1726-1748。 - 消息按 id 去重;通知/叫人双播不得重复;本机录入作业不上岛(指纹 + id 双判定)。依据:
src/main/island.ts:713-724、bridge.ts:191-267。 - 编辑预览不创建额外桌面窗口;打开编辑页不自动切布局,应用后才启用自定义方案。依据:
docs/app/island.md:47。 - 默认验证不创建可见岛/托盘;只有显式
--visual才做桌面像素/动画验收;冒烟模式跳过外部天气与离线补弹。依据:docs/dev/testing.md:9,29-32、island-weather.ts:33-34、bridge.ts:326-331。 - 经典岛与自定义岛共用一个原生窗口,切布局不得新开第二个岛。依据:
docs/app/island.md:8-10、src/main/island.ts:468-634(单this.win)。 - 左右边距只在左/右停靠生效,居中锚点永远是屏幕中线;
marginX对居中无效必须在 UI 说明。依据:docs/app/island.md:54、IslandSettingsView.vue:88-94。 - 所有验证不得打扰真实桌面(不弹窗、不发系统通知、不挪真实鼠标);冒烟注入点只能改"逻辑光标"。依据:
src/main/island.ts:1910-1930。
2.17 现状问题清单(重构动因,逐条对应 04 的改造项)
src/main/island.ts单文件 2445 行,窗口/命中/消息/课程/课件/外观全部耦合,难以按 ClassIsland 规格替换视觉与交互。- 几何与尺寸常量分散在主进程
BASE/SIZES、sharedISLAND_SIZE_DELTA、渲染进程targetSize()三处,主/渲染各算一次,改动时容易漂移。 - 经典与自定义是两套独立运行时,但共享消息面板、课程提醒、命中上报;重构需要抽出公共"岛壳 + 消息面板 + 课程条"层,避免两套 UI 各改一遍。
- 数据里没有版本号:
config.islandLayout无version,新增 ClassIsland 行/组件/外观字段时无法做确定性迁移与回滚。 - 现状行/组件缺少 ClassIsland 的行级外观(背景色/不透明度/圆角/字号覆盖)、行级隐藏规则、行级进度条等字段;只有节点级覆盖。
- 提醒只有"条状提醒 + 徽标",没有 ClassIsland 的两阶段气泡与强提醒 Topmost 特效;需要在视觉层新增但不得改变现有消息队列语义。
- 主进程状态机把"提醒优先级、上课守卫、预览、课件"混在一个
syncWindow里,重构需要拆成"状态投影 + 可见性决策 + 几何/命中"三层。 - 编辑器保存/校验逻辑在 Vue 侧,主进程只做
normalizeIslandLayouts;重构应把 schema 校验/迁移收口到 shared,编辑器和主进程共用。
2.18 验收标准(第 2 部分)
- 你能在不运行程序的情况下,仅凭本部分定位:岛窗口创建位置、消息分流规则、命中轮询、触摸模式、失焦宽限、收回动画、课程提醒、课件覆盖、设置页与编辑器的入口文件与行号。
- 你能列出重构前必须保留的 8 条硬约束与 8 条现状问题,并说明每条对应到 04 的哪个改造项。
- 你能复述现状数据流:服务端
ScheduleDto→bridge.ts→islandSetClassState→ 主进程状态机 →island:state→ 渲染进程;以及通知/作业/叫人三条投递链路的差异。 - 你能指出任何一处"主进程与渲染进程各算一次"的几何/布局逻辑(窗口包围盒、列表高度、命中矩形、细缝尺寸、字号推导),并确认 04 已把它们收口到单一事实来源。
- 本部分引用到的每个数值都能追到
packages/...:行号或packages/shared/...:行号;没有"凭印象"的数值。