LoveChengke
|
7a4668cb1b
|
feat(island): 彻底照搬 WinIsland 架构重写灵动岛(固定包围盒窗口 + 窗口内弹簧形变)
背景:上一版仍沿用"每帧移动/缩放透明窗口"的做法,实机上会出现形变抖动与残影
(用户反馈"越改越烂、开合震动还在")。本次按 WinIsland 的真实做法重做架构。
主进程(packages/desktop-client/src/main/island.ts):
- 窗口一次创建为**固定包围盒**(最大形态 + ISLAND_SHADOW_PAD 阴影留白),
开合过程中不再调用 setBounds;只在"尺寸/位置设置变化"时重排一次窗口
- 新增 setInteractive():默认 setIgnoreMouseEvents(true,{forward:true}) 整块穿透,
由渲染层命中测试回调切换(照搬 WinIsland set_cursor_hittest),保证不吞桌面点击
- 移除逐帧几何代码(animateBounds/boundsFor/computeBounds/resolveAnchor 用于窗口定位的简化版保留)
- 空闲细缝不再受 Windows 36px 最小窗口高度限制(窗口固定,岛可真正缩到 6px 宽)
渲染层(IslandApp.vue + 新增 spring.ts):
- 岛是**单一元素**,宽/高/圆角由 WinIsland 弹簧逐帧逼近:
force=(target-value)*0.10*dt → velocity=(velocity+force)*0.68^dt → value+=velocity*dt
(dt 以帧计;换向保留 35% 动量、单帧位移 ≤ 剩余距离 20%),并加零过冲钳制
- 胶囊层与展开层常驻并交叉淡入(展开 progress²、胶囊 1-1.5×progress),形变全程无缩放回弹
- 形状层改为显式像素宽高 + preserveAspectRatio="none"(避免 viewBox 缩放导致形状错位/残留角)
- 修两个实机可见缺陷:① 展开层漏了 flex-direction: column(展开卡内容横排、元素散落);
② 隐藏且无细缝时不再把岛缩成细缝(否则下次弹出会"从细缝长大")
- 尺寸增量 ISLAND_SIZE_DELTA / 阴影留白移入 @classhelper/shared,主/渲染两端共用一份
验证(verify:desktop 53 → 54 项,全绿):
- 新增「开合过程中窗口全程恒定」(逐帧窗口签名恒为 476x282@482,-2,岛 386x230→…→268x44 单调收缩,
SVG 形状 bbox 与卡片框一致 ±2px)
- 新增「岛外鼠标穿透」(岛内 interactive=true / 岛外 false)
- 开合不震动改为按"岛"判定:窗口冻结 + 岛锚点 0.00px + 无过冲;截图按岛矩形裁剪并统计岛外红像素=0
- 其余能力(通知/作业/叫人/紧急、标为已读、上课隐藏、6 锚点、字号可调、设置立即生效、空闲细缝)全部保留
|
2026-09-12 18:46:20 +08:00 |
|
LoveChengke
|
93447922dc
|
fix(island): 失焦收起加 500ms 宽限期 + 冒烟不再改写班级码(打包产物验证稳定)
- 灵动岛窗口 blur 处理增加 BLUR_GRACE_MS = 500:激活后 500ms 内的失焦一律忽略。
实机/打包产物上偶发"刚展开就被系统换前台造成的 blur 立刻收起",
表现为"下课后自动弹出通知详情"变成 pill(截图比例也随之异常);
宽限期后该用例稳定,且"点击屏幕任意位置收起"用例(展开后等 700ms 再触发 blur)仍正常。
- 桌面冒烟准备班级账号时**永不修改班级码**(只按需重置密码):
班级码是给学生用的凭据,不应被测试工具改写;实测产物验证不再依赖固定班级码。
验证: verify:desktop 53/53(首轮)与 47/47(二次启动)全绿
|
2026-09-12 17:48:32 +08:00 |
|
LoveChengke
|
3f2b135211
|
feat(island): 参考 WinIsland 重写灵动岛(squircle + 三种底 + 字号系数排版 + 空闲细缝)
参考: 工作目录 WinIsland-master.zip(Rust/Direct2D 的 Windows 动态岛),
逐项提取的设计 token 归档在 docs/winisland-design-tokens.md。
视觉与造型:
- 连续圆角 squircle(新增 island/squircle.ts,超椭圆角,对齐 WinIsland utils/shape.rs),
胶囊半径 = min(圆角, h/2)(满圆角),展开卡 = min(48*圆角/20, w/2, h/2)(WinIsland expanded_island_radius)
- 三种底: 纯黑(WinIsland default)/ 毛玻璃(win.setBackgroundMaterial('acrylic'),等价 HostBackdropBrush)/
主题色渐变;描边 1px 白 alpha .118(纯黑)/.157(玻璃),展开投影 0 2px 3px rgba(0,0,0,.11)
- Apple 系统色: 强调蓝 #0a84ff、警告橙 #ff9f0a、危险红 #ff453a;白字 alpha 分级 .92/.72/.58
- 状态色只做"内层微染 + 描边 + 徽标",不外溢(保持外圈偏红 = 0)
排版(本次重点):
- 基础字号 11–20px,岛内**所有文本** = 基础字号 × 排版系数
(标题 1.08 / 正文 0.95 / 次要 0.78 / 徽标与按钮 0.76 / 胶囊标题 0.92 / 胶囊副题 0.74),
与 WinIsland 的 font_size 模型一致;实测 13→19px 全部文本倍率 1.462(=19/13)
- 图标、按钮高度、间距也由字号推导,改字号不会溢出
形态与设置:
- 尺寸基准对齐 WinIsland 比例(胶囊 216x34 基准、展开 +156/+186 等),卡片 = 窗口 - 2*5px 且水平居中
- 6 个停靠位(顶/底 × 左/中/右,对齐 WinIsland DockPosition)
- 新增"空闲细缝"(WinIsland hidden_width):空闲留一条 6px 宽竖条,来消息自动展开
- 所有设置依旧"改即生效";顺带修掉 hide() 用 skipWindow 绕过 syncWindow 导致细缝不生效的问题
验证(verify:desktop 49 → 53 项,全绿):
- 字号即时生效: 13px→19px 五项文本倍率全部 1.462
- 风格即时生效: 纯黑 fill=rgb(0,0,0)/材质 none;毛玻璃 fill=rgba(10,10,14,.59)/材质 acrylic;主题色 fill=rgb(11,11,15)
- 空闲细缝: 16x36 → 来消息 268x44;关闭后完全隐藏
- 6 锚点、开合不震动(0.00px / 无过冲)、截图 6 张(比例偏差 0.000、外圈偏红 0)全部保持
- verify:e2e 133/133、verify:web 19/19 不受影响;截图归档到 docs/screenshots/island/
工程发现: Windows 对透明窗口有约 36px 最小高度(请求 32px 实际 36px),
空闲细缝高度据此设下限并写入注释与 README。
|
2026-09-12 17:41:05 +08:00 |
|
LoveChengke
|
9c9ac69ac6
|
fix(island): 开合不再震动——去掉弹簧过冲 + 锚点整数化
问题(用户反馈"灵动岛开合的时候不要震动"):
之前虽然有「收回不抖动」断言,但它只看窗口宽度单调收缩,漏掉了两处真实回弹。
逐帧采样卡片真实屏幕矩形后复现:
- 展开时窗口冲到 411x329(目标 404x316)→ 主进程用了 easeOutBack 弹簧缓动,尺寸超过目标再弹回
- 卡片被放大到 400.6x311.6(目标 396x310)→ 卡片入场用 cubic-bezier(0.34,1.56,0.64,1) 缩放,先超过 1 再回落
- 卡片中心左右波动 1.00px、上边缘波动 2.88px → 逐帧 Math.round 在 0.5 边界跳变 + transform 位移
修复:
1. 缓动单调化: 删除 easeOutBack,展开/收回统一 easeOutCubic(窗口与卡片都不再过冲)
2. 锚点整数化且与尺寸解耦: 新增 resolveAnchor(),停靠位置决定不动的那条边/中心,动画前算一次
3. 卡片宽度取偶数 + boundsFor 统一推导窗口矩形(消除逐帧 0.5px 抖动)
4. 卡片入场去掉 scale/translateY,只做 180ms 淡入,形变完全交给窗口尺寸
5. 顺带修掉新探针暴露的真实缺陷: 隐藏后再弹出胶囊时透明度被硬编码为 1,忽略了个性化设置
验证(verify:desktop 49/49,修复前后对比):
- 展开: 卡片中心波动 0.00px / 上边缘 0.00px,最大窗口 404x316(=目标)、最大卡片 396x308,单调=true
- 收回: 0.00px / 0.00px,最小窗口 268,宽度序列单调无回跳
- 透明度 0.62 生效;四个停靠位置、置顶、动画开关/速度、持久化全部通过
- verify:e2e 133/133、verify:web 19/19 不受影响
文档: README 形变动画章节更新为「单调不过冲 + 整数锚点」,并记录修复前后实测数据
|
2026-09-12 16:41:01 +08:00 |
|
LoveChengke
|
0dcab6f369
|
feat(client): 灵动岛个性化设置(需求 2)、形变抖动修复(需求 4)、关闭到系统托盘与退出清理(需求 5)
## 需求 2:设置页个性化(实时生效 + 持久化)
- 新增 `IslandAppearance`(shared 类型)+ `DEFAULT_ISLAND_APPEARANCE` / `ISLAND_APPEARANCE_RANGES` /
`ISLAND_POSITIONS`:高度 36–72、宽度 220–420、圆角 8–32、不透明度 0.4–1、字号 11–18、
动画速度 0.5–2、主题色、显示位置(顶部居中/左上/右上/底部居中)、动画开关、始终置顶。
- 客户端 `config.json` 增加 `island` 字段(`normalizeIslandAppearance` 夹紧越界值;
旧配置无该字段时自动补默认值,向后兼容);`SettingsView` 新增「灵动岛 · 个性化」面板:
滑块/取色器/开关/位置选择 + 「保存设置 / 预览效果 / 恢复默认」,拖动即实时预览。
- 主进程 `island.setAppearance()`:窗口尺寸按外观联动(展开卡按差值同步放大,内容用 flex + 溢出滚动承载)、
透明度、置顶、动画开关与速度、停靠位置实时生效,并把外观通过 `island:appearance` 下发给灵动岛渲染进程;
渲染进程用 CSS 变量(`--island-w/-h/-radius/-font/-accent`)驱动卡片尺寸、圆角、字号与强调色。
## 需求 4:形变抖动修复
- 窗口尺寸动画改为**单条帧循环**(优先 `requestAnimationFrame`,主进程无该全局时退化为 ~60fps 定时器);
新动画取消上一条并作废令牌;结束对齐最终尺寸(消除 1px 累积误差);**展开动画结束后才取焦点**。
- 卡片保持固定尺寸 + 顶部居中锚定(此前已修),只让窗口露出/裁切,形变时内容不参与重排。
- 冒烟新增断言:收回过程窗口宽度**单调收缩**(实测 362→330→305→288→274→270→268…)、
卡片上边缘波动 **0px**、卡片布局宽度只取 `396/260` 两个固定值;连续 5 次快速开合后状态与尺寸稳定。
## 需求 5:关闭到系统托盘 + 退出清理
- 新增 `main/tray.ts`:托盘图标(`public/tray.png`,`pnpm icons` 会自动同步)、右键菜单
「显示主窗口 / 隐藏到托盘 / 退出班级小助手」、左键单击/双击恢复窗口。
- `main/index.ts`:`close` → `preventDefault + hide`(关闭窗口不退出,后台继续接收通知,日志留痕);
`window-all-closed` 不再退出;`quitApp()` 与 `before-quit`/`will-quit` 统一走
`shutdownResources()`:销毁托盘 → 销毁灵动岛(帧循环/定时器)→ 销毁全部窗口
(渲染进程随之释放 Socket.IO 连接与 IndexedDB 句柄)→ `app.quit()` + `app.exit(0)` 兜底。
- 冒烟新增断言:关闭后 `visible=false / destroyed=false / 进程存活=true`;清理后 `托盘=false / 灵动岛=false`。
## 验证
- `pnpm verify:desktop` **40/40** ✅(新增 6 项:个性化外观生效、调整尺寸后内容不溢出、收回不抖动、
快速开合稳定、关闭到托盘、退出前资源清理)
- `pnpm verify:e2e` **82/82** ✅、`pnpm verify:web` **17/17** ✅(回归无破坏)
- `pnpm lint` / `format:check` / `typecheck` 全绿
- 顺带修复:托盘/关闭处理器此前挂在冒烟验证之后,导致冒烟模式关闭窗口会真的销毁窗口
(现已调整为在冒烟前挂载);`smoke.mjs` 配置目录按进程号隔离,避免上次异常退出的目录占用导致启动失败。
|
2026-09-12 15:19:13 +08:00 |
|
LoveChengke
|
219f6ae83a
|
feat(desktop+web+server): ClassIsland 风格 UI、灵动岛动画/交互修复、作业上岛与"叫人"功能
## 灵动岛修复与动画优化(用户反馈)
- **收回不再出现"方框"闪烁**:卡片改为**固定逻辑尺寸 + 顶部居中锚定**,
窗口只负责露出/裁切透明区域。此前卡片随窗口拉伸,形变过程中会看到被拉扁的圆角方块,
胶囊里的铃铛/文字还会被垂直居中而"从上跳到下";两条问题同源,现已消除。
冒烟用 15 次采样断言卡片尺寸恒为固定值(`396x308` / `260x38`),一旦出现中间值即失败。
- **点击屏幕任意位置收回**:展开态临时 `setFocusable(true)` 并取焦,点到别处即失焦 → 自动回缩为胶囊;
收起后立即释放可聚焦性。断言覆盖 `focusable=true → 失焦后 mode=pill、focusable=false`。
- 动画继续打磨:危险/叫人形态改为**内部光晕**(卡片四周再留 4px 透明边,彻底避免窗口边界裁切出硬边)、
尖角与圆角统一、形态尺寸各异(胶囊 268×44 / 详情 404×316 / 紧急 424×344 / 叫人 440×360)。
## 灵动岛"标为已读"真正同步通知中心
- 新增链路:灵动岛 `mark-read` → 主进程 `setMarkReadHandler` → 主窗口渲染进程 →
`notificationApi.markRead` + 刷新 store(未读红点即时减少)。
- 真机链路断言:点击"标为已读"后 `read=false → true`、未读数 `1 → 0`(此前只从灵动岛队列移除)。
## 作业发布也上岛
- `homework:new` → 灵动岛 `kind=homework`:胶囊显示"新作业"(青蓝描边 + 书本图标),
展开显示作业说明与"截止时间:…"。
## 新增"叫人"功能
- 服务端:新模块 `modules/calls`(`POST /api/calls`)——校验班级可写 + 学生属于该班,
落库一条 HIGH 级通知(进通知中心、可用已读状态),广播 `notification:new` 到班级房间,
并定向 `call:new` 给被叫学生。
- 共享层:`CALL_QUICK_PHRASES`(8 条快捷短语)、`buildCallTitle()`("请 XXX 同学找 XXX 老师",老师名已含"老师"不重复)、
`ServerToClientEvents['call:new']`、`CreateCallRequest`。
- Web 管理端:学生管理列表新增 **叫人** 按钮 → 弹窗显示对象、快捷短语(点选/取消)、自定义消息(优先),发送即通知。
- 客户端:`call:new` → 灵动岛 `kind=call`,**上课时段也立即展开**(老师正在等这名学生),
琥珀金卡片 +"叫人"徽标 +"收到"按钮,90 秒后自动收起。
## 客户端界面改为 ClassIsland 同款 Fluent 风格
- 主题令牌:Mica 浅底 `#f3f3f3`、亚克力卡片 `rgba(255,255,255,.7)` + `backdrop-filter`、
强调色 `#0F6CBD`、半透明描边、卡片 8px / 控件 4px 圆角、极轻阴影。
- Element Plus 变量整体对齐(`--el-color-primary`、圆角、表头/行分隔、滚动条等)。
- 侧边导航改为 Fluent NavigationView 观感:白色半透明底、选中项强调色淡底 + 左侧 3px 强调条;
品牌标改为强调色渐变圆角方块。附客户端五个页面实拍(`docs/screenshots/client/`)。
## 验证
- `pnpm verify:e2e` **68/68**(新增 6 项叫人:201 + 标题模板、自定义消息优先、上课时段允许、学生 403、通知中心可见、清理)
- `pnpm verify:desktop` **33/33**(新增:卡片固定尺寸、点击任意处收回、标为已读同步、作业上岛、叫人上课中立即展开、真实通知链路)
- `pnpm verify:web` **16/16**(新增叫人入口:弹窗、8 条快捷短语、自定义输入、短语可选中)
- `pnpm lint` / `format:check` / `typecheck` 全部通过;服务端安装包已重打并安装验证
|
2026-09-12 13:57:46 +08:00 |
|
LoveChengke
|
38c7660dd9
|
fix(desktop-client): 灵动岛反馈修正——红晕改为卡片内部 inset(窗口边缘零外溢)、紧急通知补展开动画、普通通知去掉上岛入场动画、点空白处/右上角按钮回缩为胶囊、上课时段严格不显示;冒烟用独立 userData 以免与运行中的客户端抢单实例锁
|
2026-09-12 09:39:33 +08:00 |
|
LoveChengke
|
3a9f8812fc
|
feat: 客户端灵动岛 + 上课时段紧急通知二次确认,并修复打包/校验脚本在 Electron 宿主下的多个缺陷
## 客户端灵动岛(动态岛)
- 新增独立透明置顶窗口:桌面中上方常驻、不占任务栏、不抢焦点(268x44 胶囊 / 404x316 详情 / 424x344 紧急)
- 三种形态与动画:隐藏 ↔ 胶囊 ↔ 详情,尺寸用 easeOutCubic 260ms 逐帧缓动、透明度单独淡入淡出
(尺寸与淡入使用各自动画令牌,避免互相打断导致窗口卡在胶囊尺寸)
- 行为:普通通知变「新消息 · 共 N 条」胶囊,点击展开详情;上课时段自动隐藏并进入待发队列,
下课后自动弹出;紧急通知无论是否上课立即展开(红色呼吸光晕 + 紧急角标,无需点击)
- 上课状态来自 GET /api/schedules/current(10s 心跳 + 下课时长定时器),
新增 src/main/island.ts(控制器/IPC)、src/preload/island.ts、src/island/IslandApp.vue、
src/renderer/island/bridge.ts、island.html 与多页 Vite 构建
## 上课时段发布紧急通知的二次确认
- 服务端:priority=URGENT 且当前处于上课时间段且未带 confirmDuringClass 时返回
409 URGENT_DURING_CLASS(携带 current/next/week/serverTime),绕过界面调接口同样被拦截
- 新增 GET /api/schedules/current(inClass/current/next,at 参数用于诊断与测试)
- Web 管理端:全屏警告遮罩(UrgentClassWarning.vue),显示正在上的课时段与待发布标题,
确认按钮带 3 秒倒计时(圆环进度,倒计时结束前禁用),取消即不发;并发命中 409 同样兜底弹窗
## 打包与验证脚本修复(Electron 宿主环境)
- scripts/lib/node-runtime.mjs:定位真正的 node.exe。此前经 pnpm 运行时 process.execPath 指向
Electron,dist:server 会把 Electron 复制成安装包内的 node.exe,dist:win 会让 electron-builder 报
Unknown argument: .../cli.js(改为显式使用真实 Node)
- scripts/lib/electron-env.mjs:启动 Electron 前清理继承的 ELECTRON_RUN_AS_NODE(否则 Electron 退化为
纯 Node,require('electron') 直接 MODULE_NOT_FOUND),并从 path.txt 定位 electron.exe
(process.versions.electron 存在时 import 'electron' 返回的是 API 对象而非路径)
- scripts/dist-server.mjs:新增构建产物新鲜度校验(曾出现 server 已重建但打进安装包的 shared/dist 是旧的,
安装后服务启动即报 does not provide an export named MAX_TERM_WEEK)
- scripts/ui-smoke:新增 live-probe.mjs 真实制造/清理上课时段,Electron 驱动真实点击断言全屏警告文案、
按钮禁用与 3 秒倒计时时长(实测 2966ms),测试后自动删除探针课表与测试通知
## 验证结果
- pnpm verify:e2e 62/62(新增上课时段拦截、二次确认后发布、普通通知不受限等 8 项)
- pnpm verify:web 8/8(新增上课时段紧急通知全屏警告 + 倒计时)
- pnpm verify:desktop 18/18(新增灵动岛窗口/上课隐藏/下课弹出/紧急立即展开/胶囊点击展开 + 截图)
- 打包后 EXE 冒烟 15/15(packaged: true,退出码 0)
- 服务端安装程序升级安装:.env 与数据库保持不变,/api/schedules/current 正常
- pnpm lint / format:check / typecheck 全部通过
|
2026-09-12 08:41:08 +08:00 |
|