diff --git a/README.md b/README.md index fb4d13d..1452588 100644 --- a/README.md +++ b/README.md @@ -422,7 +422,7 @@ pnpm verify:desktop # Electron 客户端(含灵动岛动画/收回/已读 | **显示位置** | **顶部/底部 × 左/中/右(6 个锚点)** | 按 `workArea` 计算;顶/底决定不动的是上边还是下边,左/中/右决定水平锚点 | | 动画开关 | 开/关 | 关闭后展开/收起瞬时生效(不再逐帧插值) | | 始终置顶 | 开/关 | 对应 `setAlwaysOnTop` | -| **空闲细缝** | **开/关(默认关)** | 参考 WinIsland `hidden_width`:开启后没有消息时不再完全隐藏,而是留一条 6px 宽的竖条(受 Windows 最小窗口高度 36px 限制) | +| **空闲细缝** | **开/关(默认关)** | 参考 WinIsland `hidden_width`:开启后没有消息时不再完全隐藏,而是留一条 6px 宽的竖条(窗口是固定包围盒,不受 Windows 最小窗口高度限制) | - 越界值在**主进程**统一夹紧(`normalizeIslandAppearance`),用户填坏配置也不会让布局错乱。 - 「预览效果」按钮会本地推一条通知,立刻看到当前尺寸/配色/动画。 @@ -498,42 +498,44 @@ pnpm dist:win # nsis 安装包 + portable 单文件(需联网下载 NSIS `pnpm verify:desktop` 以 `ELECTRON_SMOKE_TEST=1` 启动应用(不弹窗),主进程依次校验后自动退出: -| 校验项 | 实测结果 | -| -------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| 渲染进程挂载 + 窗口标题 | ✅ `children=1` / `title=班级小助手` | -| 登录页渲染(三个输入框) | ✅ 服务器地址 / **班级码** / 班级密码 | -| preload contextBridge 注入 | ✅ 9 个方法(含灵动岛 3 个) | -| IPC 往返(getAppInfo/saveConfig) | ✅ Electron 44.3.0 | -| 配置文件写入用户目录 | ✅ `%APPDATA%\@classhelper\desktop-client\config.json` | -| IndexedDB 缓存读写 | ✅ 读写/统计/删除 + 6 个 store | -| 断网时回退本地缓存 | ✅ `fromCache=true`(请求不可达端口) | -| 灵动岛窗口创建(置顶/透明/不占任务栏) | ✅ `ready=true` | -| 上课期间普通通知自动隐藏 | ✅ `mode=hidden queued=1 visible=false`(窗口真的隐藏,不是只改状态) | -| 上课期间点击不展开(严格不显示) | ✅ `mode=hidden visible=false` | -| 下课后自动弹出暂存通知详情 | ✅ `mode=expanded reason=after-class` | -| 上课期间紧急通知立即展开(无需点击) | ✅ `mode=expanded reason=urgent` | -| **紧急通知带展开动画** | ✅ 16ms 采样 34 帧,出现 7 个中间尺寸,最终 `424x344`(窗口从胶囊尺寸单调展开,无过冲) | -| 上课期间收起不回缩成胶囊 | ✅ `mode=hidden visible=false` | -| **普通通知直接显示胶囊(无入场动画)** | ✅ 推送前已隐藏,60ms 内窗口即为 `268x44`、`mode=pill`(无"上岛"动画) | -| 胶囊 → 点击 → 展开详情 | ✅ `新消息 · 共 2 条` → `expanded`(含真实 DOM 断言与截图) | -| **点击卡片空白处回缩为胶囊** | ✅ 真实点击 `.body` 空白区域 → `mode=pill` | -| **点击右上角收起按钮回缩为胶囊** | ✅ 真实点击 `.icon-btn` → `mode=pill` | -| **灵动岛截图留档 + 像素级校验** | ✅ 4 张(808×632 / 848×688 / 536×88 / 808×632);宽高比与状态机配置偏差 **0.000**;形态尺寸递增 pill < expanded < urgent;紧急态偏红 **20584 px**(卡片内部光晕) | -| **紧急红晕不外溢(窗口边缘无红光)** | ✅ 截图最外圈(2px)偏红像素 = **0**(守住"卡片周围不出现光晕硬边") | -| 联网集成(`ELECTRON_SMOKE_ONLINE=1`) | ✅ `login=高一(1)班 classSession=true` / 1 班级 / 30 节课 / Socket.IO 已连接 | -| **个性化:尺寸联动不破坏布局** | ✅ 高 56 / 宽 300 / 圆角 26 / 字号 14 → 窗口 `300x56`,CSS 变量同步,展开态内容溢出 **0px** | -| **开合不震动:展开** | ✅ 逐帧采样 57 帧:卡片中心波动 **0.00px**、上边缘波动 **0.00px**、最大窗口 `424x230`(=目标,无过冲)、单调扩张 | -| **开合不震动:收回** | ✅ 逐帧采样 56 帧:卡片中心/上边缘波动 **0.00px**、最小窗口 `268`、宽度序列单调收缩无回跳 | -| **WinIsland 排版:字号即时生效** | ✅ 13px → 19px:胶囊标题 `11.96→17.48`、胶囊副题 `9.62→14.06`、标题 `14.04→20.52`、正文 `12.35→18.05`、按钮 `9.88→14.44`,**倍率全部 1.462 = 19/13** | -| **WinIsland 风格:三种底即时切换** | ✅ 纯黑 `fill=rgb(0,0,0)`/材质 `none`;毛玻璃 `fill=rgba(10,10,14,.59)`/材质 **`acrylic`**;主题色 `fill=rgb(11,11,15)` | -| **WinIsland 空闲细缝** | ✅ 空闲窗口收成 `16x36`(含 Windows 36px 最小高度下限)→ 来消息自动回到 `268x44`;关闭细缝后空闲 `isVisible=false` | -| **个性化:透明度 / 主题色** | ✅ `setOpacity` 实际 **0.62**(设置 0.62);`--island-accent` = `#ff7043` | -| **个性化:停靠位置(6 个锚点)** | ✅ 左上 `8,8` / 右上 `1216,8` / 底部居中 `586,800` / 底部左 / 底部右 / 顶部居中 `586,8` | -| **个性化:置顶开关** | ✅ 关闭后 `isAlwaysOnTop=false`,开启后 `true` | -| **个性化:动画开关 / 速度** | ✅ 关闭动画后 60ms 内即到位(无过渡);speed=2 收回明显快于 speed=0.5 | -| **个性化:持久化** | ✅ 写入客户端配置后重新读取完全一致(尺寸/圆角/透明度/主题色/字号/动画/速度/位置/置顶/风格/细缝) | -| **退出清理:托盘 / 灵动岛 / 窗口** | ✅ `isTrayReady()=false`、`island.isReady()=false`、窗口全部销毁 | -| **退出后无残留(文件锁 + 二次启动)** | ✅ 配置目录可删除(句柄已释放)→ 用同一配置目录二次启动成功并再次全绿 | +| 校验项 | 实测结果 | +| --------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| 渲染进程挂载 + 窗口标题 | ✅ `children=1` / `title=班级小助手` | +| 登录页渲染(三个输入框) | ✅ 服务器地址 / **班级码** / 班级密码 | +| preload contextBridge 注入 | ✅ 9 个方法(含灵动岛 3 个) | +| IPC 往返(getAppInfo/saveConfig) | ✅ Electron 44.3.0 | +| 配置文件写入用户目录 | ✅ `%APPDATA%\@classhelper\desktop-client\config.json` | +| IndexedDB 缓存读写 | ✅ 读写/统计/删除 + 6 个 store | +| 断网时回退本地缓存 | ✅ `fromCache=true`(请求不可达端口) | +| 灵动岛窗口创建(置顶/透明/不占任务栏) | ✅ `ready=true` | +| 上课期间普通通知自动隐藏 | ✅ `mode=hidden queued=1 visible=false`(窗口真的隐藏,不是只改状态) | +| 上课期间点击不展开(严格不显示) | ✅ `mode=hidden visible=false` | +| 下课后自动弹出暂存通知详情 | ✅ `mode=expanded reason=after-class` | +| 上课期间紧急通知立即展开(无需点击) | ✅ `mode=expanded reason=urgent` | +| **紧急通知带展开动画** | ✅ 16ms 采样 34 帧,出现 7 个中间尺寸,最终 `424x344`(窗口从胶囊尺寸单调展开,无过冲) | +| 上课期间收起不回缩成胶囊 | ✅ `mode=hidden visible=false` | +| **普通通知直接显示胶囊(无入场动画)** | ✅ 推送前已隐藏,60ms 内窗口即为 `268x44`、`mode=pill`(无"上岛"动画) | +| 胶囊 → 点击 → 展开详情 | ✅ `新消息 · 共 2 条` → `expanded`(含真实 DOM 断言与截图) | +| **点击卡片空白处回缩为胶囊** | ✅ 真实点击 `.body` 空白区域 → `mode=pill` | +| **点击右上角收起按钮回缩为胶囊** | ✅ 真实点击 `.icon-btn` → `mode=pill` | +| **灵动岛截图留档 + 像素级校验** | ✅ 4 张(808×632 / 848×688 / 536×88 / 808×632);宽高比与状态机配置偏差 **0.000**;形态尺寸递增 pill < expanded < urgent;紧急态偏红 **20584 px**(卡片内部光晕) | +| **紧急红晕不外溢(窗口边缘无红光)** | ✅ 截图最外圈(2px)偏红像素 = **0**(守住"卡片周围不出现光晕硬边") | +| 联网集成(`ELECTRON_SMOKE_ONLINE=1`) | ✅ `login=高一(1)班 classSession=true` / 1 班级 / 30 节课 / Socket.IO 已连接 | +| **个性化:尺寸联动不破坏布局** | ✅ 高 56 / 宽 300 / 圆角 26 / 字号 14 → 窗口 `300x56`,CSS 变量同步,展开态内容溢出 **0px** | +| **开合不震动:展开** | ✅ 逐帧采样 57 帧:岛中心波动 **0.00px**、上边缘 **0.00px**;**窗口冻结**(全程 `476x282@482,-2`);岛最大 `440x246` = 目标无过冲 | +| **开合不震动:收回** | ✅ 逐帧采样 56 帧:岛中心/上边缘波动 **0.00px**;窗口全程冻结;岛 `386x230→…→268x44` 单调收缩无回跳 | +| **WinIsland 架构:窗口恒定 + 形状正确** | ✅ 逐帧窗口签名恒为 `476x282@482,-2`(只有 1 种);SVG 形状 bbox 与卡片框一致(±2px,可抓出 viewBox 错位/残留角) | +| **岛外鼠标穿透** | ✅ 指针在岛内 `interactive=true`、在岛外 `interactive=false`(固定大包围盒窗口不吞桌面点击) | +| **WinIsland 排版:字号即时生效** | ✅ 13px → 19px:胶囊标题 `11.96→17.48`、胶囊副题 `9.62→14.06`、标题 `14.04→20.52`、正文 `12.35→18.05`、按钮 `9.88→14.44`,**倍率全部 1.462 = 19/13** | +| **WinIsland 风格:三种底即时切换** | ✅ 纯黑 `fill=rgb(0,0,0)`/材质 `none`;毛玻璃 `fill=rgba(10,10,14,.59)`/材质 **`acrylic`**;主题色 `fill=rgb(11,11,15)` | +| **WinIsland 空闲细缝** | ✅ 空闲时岛收成 `6x22`(窗口固定,不再受 Windows 36px 最小窗口高度限制)→ 来消息自动回到 `268x44`;关闭细缝后空闲 `isVisible=false` | +| **个性化:透明度 / 主题色** | ✅ `setOpacity` 实际 **0.62**(设置 0.62);`--island-accent` = `#ff7043` | +| **个性化:停靠位置(6 个锚点)** | ✅ 左上 `8,8` / 右上 `1216,8` / 底部居中 `586,800` / 底部左 / 底部右 / 顶部居中 `586,8` | +| **个性化:置顶开关** | ✅ 关闭后 `isAlwaysOnTop=false`,开启后 `true` | +| **个性化:动画开关 / 速度** | ✅ 关闭动画后 60ms 内即到位(无过渡);speed=2 收回明显快于 speed=0.5 | +| **个性化:持久化** | ✅ 写入客户端配置后重新读取完全一致(尺寸/圆角/透明度/主题色/字号/动画/速度/位置/置顶/风格/细缝) | +| **退出清理:托盘 / 灵动岛 / 窗口** | ✅ `isTrayReady()=false`、`island.isReady()=false`、窗口全部销毁 | +| **退出后无残留(文件锁 + 二次启动)** | ✅ 配置目录可删除(句柄已释放)→ 用同一配置目录二次启动成功并再次全绿 | 该验证对**开发产物与打包后的 EXE 都适用**(打包后用 `ELECTRON_SMOKE_RESULT=` 写出 JSON 结果, 已实测 `packaged: true`、54/54 通过、退出码 0)。灵动岛各状态的窗口截图会写到 @@ -1012,12 +1014,12 @@ pnpm db:migrate && pnpm db:seed 阶段 11(参考 WinIsland 重写灵动岛)新增文件: -| 路径 | 说明 | -| -------------------------------------------------- | ---------------------------------------------------------------------------------------- | -| `docs/winisland-design-tokens.md` | 从 WinIsland 源码逐条提取的设计 token(几何/配色/排版/层级/布局/图标 + 动效与设置模型) | -| `packages/desktop-client/src/island/squircle.ts` | 连续圆角(超椭圆角)路径生成,对齐 WinIsland `utils/shape.rs` 的 continuous rounded rect | -| `packages/desktop-client/src/island/IslandApp.vue` | 灵动岛 UI 全量重写:squircle + 三种底 + Apple 系统色 + 字号系数驱动的排版 + 无回弹过渡 | -| `packages/desktop-client/src/main/island.ts` | 6 锚点、空闲细缝、亚克力材质、Windows 最小高度下限;尺寸常量与渲染进程 EXTRA 一一对应 | +| 路径 | 说明 | +| -------------------------------------------------- | -------------------------------------------------------------------------------------------- | +| `docs/winisland-design-tokens.md` | 从 WinIsland 源码逐条提取的设计 token(几何/配色/排版/层级/布局/图标 + 动效与设置模型) | +| `packages/desktop-client/src/island/squircle.ts` | 连续圆角(超椭圆角)路径生成,对齐 WinIsland `utils/shape.rs` 的 continuous rounded rect | +| `packages/desktop-client/src/island/IslandApp.vue` | 灵动岛 UI 全量重写:squircle + 三种底 + Apple 系统色 + 字号系数驱动的排版 + 无回弹过渡 | +| `packages/desktop-client/src/main/island.ts` | 固定包围盒窗口、6 锚点、空闲细缝、亚克力材质、鼠标命中开关;岛尺寸增量与渲染层共用同一常量表 | 阶段 10(学生端班级账号)新增文件: diff --git a/docs/winisland-design-tokens.md b/docs/winisland-design-tokens.md index f96b67f..064ae4f 100644 --- a/docs/winisland-design-tokens.md +++ b/docs/winisland-design-tokens.md @@ -561,3 +561,22 @@ VISUALIZER_TITLE_OFFSET = 4.0 // 视觉条中心 y = 曲名基线 - 4 - 阴影:仅展开 `0 2px σ rgba(0,0,0,.11)`(σ ≈ 3px) - 文字:`-apple-system / "Microsoft YaHei" / "Segoe UI"`,白 + alpha 分层(.65 / .72 / .9 / .6 / .5 / .58 / .62) - 图标:内联 SVG,`fill: currentColor`,播放/暂停按 640 坐标系、尺寸 ≈ 34.5×37 + +--- + +## 附:班级小助手实际采纳了哪些(本次重写对照表) + +| WinIsland 做法 | 出处 | 我们的落地 | 验证 | +| -------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------- | +| **窗口一次建大包围盒,形变发生在窗口内** | `window/app/layout.rs` `required_window_size()` + `PADDING=80` | 窗口固定为「最大形态 + `ISLAND_SHADOW_PAD(10)`」,开合期间**一帧都不 setBounds** | 逐帧窗口签名恒为 `476x282@482,-2`(只有 1 种) | +| 尺寸/圆角由弹簧驱动 | `utils/physics.rs` + `app.rs` `IslandSprings`(w/h/r = `0.10/0.68`) | `src/island/spring.ts` 同参数同更新式(dt 以帧计、`damping^dt`、换向保留 35% 动量 + 20% 距离上限),并加**零过冲钳制** | 岛中心/边缘波动 0.00px、无过冲 | +| 连续圆角(超椭圆) | `utils/shape.rs` `continuous_rounded_rect_path` / `expanded_island_radius = min(48*scale, w/2, h/2)` | `src/island/squircle.ts` 超椭圆角路径;展开半径 `min(48×圆角/20, w/2, h/2)` | 形状 bbox 与卡片框一致(±2px) | +| 内容交叉淡入 `expanded=progress²`、`mini=1-1.5×progress` | `core/render.rs`(`MINI_FADE_RATE=1.5`) | 两层常驻、透明度按同一公式 | 截图与 DOM 断言 | +| 描边 1px 白 alpha 30(纯色)/ 40(glass) | `core/render.rs` `BORDER_*_ALPHA` | `--wn-border` 取同值(`.118` / `.157`) | 计算样式断言 | +| 展开投影 `rgba(0,0,0,.11)`、y+2、σ=3 | `draw_expanded_shadow` | `drop-shadow(0 2px 3px rgba(0,0,0,.11))` | 截图 | +| 纯黑底 / 亚克力(HostBackdropBrush) | `background.rs` + `win32.rs` `DWMWA_USE_HOSTBACKDROPBRUSH` | 纯黑底 / `win.setBackgroundMaterial('acrylic')` | 计算样式 + `getBackgroundMaterial()` | +| `set_cursor_hittest` 命中控制 | `frame.rs`(`set_cursor_hittest`) | 默认 `setIgnoreMouseEvents(true,{forward:true})`,渲染层命中测试后切换 | 岛内 true / 岛外 false | +| 空闲细缝 `hidden_width = 5` | `core/config.rs` | 6px 宽竖条(窗口固定,不再受 Windows 最小窗口高度限制) | 空闲 `6x22` → 来消息 `268x44` | +| 6 个停靠位 `DockPosition` | `core/config.rs` | 顶/底 × 左/中/右 6 锚点,岛(而非窗口)精确落在锚点 | 6 锚点坐标断言 | +| `font_size` = 基础字号 × 各文本系数 | `core/render/mini.rs`、`ui/expanded/music_view.rs` | 基础字号 11–20px × 系数表(标题 1.08/正文 0.95/次要 0.78/徽标与按钮 0.76/胶囊 0.92、0.74) | 13→19px 全部文本倍率 1.462 | +| 未采纳:音乐/歌词/小组件/音量浮窗 | `ui/expanded/**`、`ui/widget/**` | 业务不同(我们做通知/作业/叫人/紧急) | — |