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。
This commit is contained in:
1 parent
9c9ac69ac6
commit
3f2b135211
17 files changed
+1632
-588
No files matched your search
@@ -20,7 +20,7 @@
|
||||
| 1 | pnpm monorepo 脚手架、TypeScript / ESLint / Prettier / 环境变量 | ✅ 已完成 |
|
||||
| 2 | 后端:Prisma schema + 迁移 + 种子数据 + JWT 认证 + RBAC + 模块化 REST API + Socket.IO | ✅ 已完成 |
|
||||
| 3 | Web 管理端:登录、主布局、仪表盘、班级/学生/课表/作业/通知/成绩页面、Axios 封装、实时提示 | ✅ 已完成 |
|
||||
| 4 | EXE 客户端:Electron 主进程/preload/渲染进程、登录、课表/作业/通知/成绩/设置、实时推送、IndexedDB 离线缓存 | ✅ 已完成(冒烟验证 49/49) |
|
||||
| 4 | EXE 客户端:Electron 主进程/preload/渲染进程、登录、课表/作业/通知/成绩/设置、实时推送、IndexedDB 离线缓存 | ✅ 已完成(冒烟验证 53/53) |
|
||||
| 5 | 三端联调脚本、打包命令、完整 README | ✅ 已完成(三套安装包 + 两套 UI 回归测试) |
|
||||
| 6 | 测试账号与种子数据说明 | ✅ 已完成(见下文) |
|
||||
| 7 | 生产化:服务端安装程序(内置 Node)、Web 端 PWA 可安装、Docker + Nginx 部署、生产加固与运维文档 | ✅ 已完成(见 [`docs/production.md`](docs/production.md)) |
|
||||
@@ -179,7 +179,7 @@ pnpm verify:desktop # Electron 客户端(含灵动岛动画/收回/已读
|
||||
后端脚本验证**实时推送时延、作业完成、成绩下发、权限隔离、上课时段紧急通知拦截、导入与失败回滚、班级账号代全班操作、老师端导入不越权**等 133 项,
|
||||
实测通知 37ms、作业 26ms、成绩 25ms 到达(要求 < 5 秒),紧急通知 409 拦截与二次确认后发布均通过;
|
||||
客户端脚本验证 preload 桥接、渲染进程、IndexedDB 读写、断网回退、**灵动岛四种状态切换**与联网集成
|
||||
(**班级账号登录** + 四类数据 + Socket.IO),实测 49/49 通过;退出后再启动一次校验单实例锁与文件锁已释放(离线复跑 43/43)。
|
||||
(**班级账号登录** + 四类数据 + Socket.IO),实测 53/53 通过;退出后再启动一次校验单实例锁与文件锁已释放(离线复跑 47/47)。
|
||||
|
||||
## 默认账号与种子数据
|
||||
|
||||
@@ -314,8 +314,21 @@ pnpm verify:desktop # Electron 客户端(含灵动岛动画/收回/已读
|
||||
|
||||
### 灵动岛(桌面通知浮窗)
|
||||
|
||||
客户端有一个独立于主窗口的**灵动岛**:一个无边框、透明、置顶、不占任务栏的窄条窗口,
|
||||
常驻桌面**中上方**(屏幕工作区顶部居中,`y = workArea.y + 8`)。它是"消息到达"的第一现场:
|
||||
客户端有一个独立于主窗口的**灵动岛**:一个无边框、透明、置顶、不占任务栏的窄条窗口。它是"消息到达"的第一现场。
|
||||
|
||||
> **视觉与排版参考 [WinIsland](https://github.com/WinIslandProject/WinIsland)(Rust/Direct2D 的 Windows 动态岛)重写**:
|
||||
> 连续圆角(超椭圆 squircle)而不是 `border-radius`、纯黑/亚克力毛玻璃/主题色三种底、
|
||||
> Apple 系统色强调色、白字 alpha 分级、**所有文本 = 基础字号 × 排版系数**、
|
||||
> 胶囊 `h/2` 满圆角 + 展开卡 `min(48, w/2, h/2)`(WinIsland `expanded_island_radius`)、
|
||||
> 顶/底 × 左/中/右 **6 个停靠位**、可选"空闲细缝"(WinIsland `hidden_width`)。
|
||||
> 逐项从 WinIsland 源码提取的设计 token 见 **[docs/winisland-design-tokens.md](docs/winisland-design-tokens.md)**。
|
||||
>
|
||||
> 与 WinIsland 的两点**有意差异**(都写进了实现注释):
|
||||
>
|
||||
> 1. WinIsland 用"固定大窗口 + 画布内形变",我们沿用"按状态缓动窗口尺寸"——
|
||||
> 在 Electron 里前者需要常驻的点击穿透大窗口(容易吞掉桌面点击),后者已被 53 项断言覆盖(含 0.00px 不震动);
|
||||
> 2. Windows 对透明窗口有约 **36px 的最小高度**(实测请求 32px 得到 36px),
|
||||
> 因此"空闲细缝"的高度以 36px 为下限(WinIsland 因为窗口恒定所以不受此限)。
|
||||
|
||||
| 场景 | 灵动岛行为 |
|
||||
| -------------------------- | ----------------------------------------------------------------------------------------------------------------- |
|
||||
@@ -394,18 +407,20 @@ pnpm verify:desktop # Electron 客户端(含灵动岛动画/收回/已读
|
||||
|
||||
**设置 → 灵动岛 · 个性化**(改动即实时生效,保存后写入本地配置,重启仍生效):
|
||||
|
||||
| 设置项 | 区间/取值 | 说明 |
|
||||
| ---------- | --------------------------------- | ------------------------------------------------------------------------------ |
|
||||
| 灵动岛高度 | 36–72px | 胶囊高度;展开卡按同差值联动(`+264/+292/+308px`),内容用 flex + 溢出滚动承载 |
|
||||
| 灵动岛宽度 | 220–420px | 同上,展开卡按差值联动 |
|
||||
| 圆角 | 8–32px | 卡片圆角 |
|
||||
| 不透明度 | 40%–100% | 窗口整体不透明度 |
|
||||
| 字号 | 11–18px | 卡片基础字号 |
|
||||
| 动画速度 | 0.5×–2× | 形变/淡入时长倍率 |
|
||||
| 主题色 | 取色器 | 用于高亮、按钮、进度条(CSS 变量下发到灵动岛渲染进程) |
|
||||
| 显示位置 | 顶部居中 / 左上 / 右上 / 底部居中 | 按住 `workArea` 计算,多屏跟随主屏 |
|
||||
| 动画开关 | 开/关 | 关闭后展开/收起瞬时生效(不再逐帧插值) |
|
||||
| 始终置顶 | 开/关 | 对应 `setAlwaysOnTop` |
|
||||
| 设置项 | 区间/取值 | 说明 |
|
||||
| ------------ | --------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| 灵动岛高度 | 36–72px | 胶囊高度;展开卡按同差值联动(`+186/+202/+218px`),内容用 flex 承载且不溢出 |
|
||||
| 灵动岛宽度 | 220–420px | 同上,展开卡按差值联动(`+156/+172/+188px`) |
|
||||
| 圆角 | 8–32px | 胶囊取 `min(圆角, h/2)`(WinIsland 胶囊是满圆角);展开卡取 `min(48×圆角/20, w/2, h/2)` |
|
||||
| 不透明度 | 40%–100% | 窗口整体不透明度(所有显示路径都会应用,含"隐藏后再弹出胶囊") |
|
||||
| **字号** | **11–20px** | **基础字号**:岛内所有文本 = 基础字号 × 排版系数(标题 1.08 / 正文 0.95 / 次要 0.78 / 徽标与按钮 0.76 / 胶囊标题 0.92 / 胶囊副题 0.74),改一处整体等比缩放 |
|
||||
| 动画速度 | 0.5×–2× | 形变/淡入时长倍率 |
|
||||
| 主题色 | 取色器(默认 Apple 系统蓝 `#0a84ff`) | 用于高亮、按钮、进度条、主题色风格(CSS 变量下发到灵动岛渲染进程) |
|
||||
| **视觉风格** | **纯黑 / 毛玻璃(亚克力)/ 主题色渐变** | 纯黑 = WinIsland `default`;毛玻璃调用 `win.setBackgroundMaterial('acrylic')`(真·桌面模糊,等价 HostBackdropBrush) |
|
||||
| **显示位置** | **顶部/底部 × 左/中/右(6 个锚点)** | 按 `workArea` 计算;顶/底决定不动的是上边还是下边,左/中/右决定水平锚点 |
|
||||
| 动画开关 | 开/关 | 关闭后展开/收起瞬时生效(不再逐帧插值) |
|
||||
| 始终置顶 | 开/关 | 对应 `setAlwaysOnTop` |
|
||||
| **空闲细缝** | **开/关(默认关)** | 参考 WinIsland `hidden_width`:开启后没有消息时不再完全隐藏,而是留一条 6px 宽的竖条(受 Windows 最小窗口高度 36px 限制) |
|
||||
|
||||
- 越界值在**主进程**统一夹紧(`normalizeIslandAppearance`),用户填坏配置也不会让布局错乱。
|
||||
- 「预览效果」按钮会本地推一条通知,立刻看到当前尺寸/配色/动画。
|
||||
@@ -425,7 +440,7 @@ pnpm verify:desktop # Electron 客户端(含灵动岛动画/收回/已读
|
||||
3. **卡片宽度取偶数**:卡片在窗口内水平居中,偶宽保证卡片中心落在整数像素上。
|
||||
|
||||
冒烟实测(逐帧采样窗口矩形 + 卡片真实屏幕矩形):展开时**卡片中心波动 0.00px、上边缘波动 0.00px**,
|
||||
窗口最大 404×316(等于目标,不再过冲到 411×329),卡片最大 396×308(不再放大到 400.6px 再回落);
|
||||
窗口最大 424×230(等于目标),卡片最大 414×220(重写前是 411×329 / 400.6×311.6 的过冲);
|
||||
收回时同样 0.00px / 0.00px、最小窗口 268,窗口宽度序列 `356→318→286→274→270→268…` 单调收缩无回跳;
|
||||
连续 5 次快速开合后状态与尺寸稳定。
|
||||
|
||||
@@ -505,18 +520,21 @@ pnpm dist:win # nsis 安装包 + portable 单文件(需联网下载 NSIS
|
||||
| **紧急红晕不外溢(窗口边缘无红光)** | ✅ 截图最外圈(2px)偏红像素 = **0**(守住"卡片周围不出现光晕硬边") |
|
||||
| 联网集成(`ELECTRON_SMOKE_ONLINE=1`) | ✅ `login=高一(1)班 classSession=true` / 1 班级 / 30 节课 / Socket.IO 已连接 |
|
||||
| **个性化:尺寸联动不破坏布局** | ✅ 高 56 / 宽 300 / 圆角 26 / 字号 14 → 窗口 `300x56`,CSS 变量同步,展开态内容溢出 **0px** |
|
||||
| **开合不震动:展开** | ✅ 逐帧采样 55 帧:卡片中心波动 **0.00px**、上边缘波动 **0.00px**、最大窗口 `404x316`(=目标,无过冲)、单调扩张 |
|
||||
| **开合不震动:收回** | ✅ 逐帧采样 57 帧:卡片中心/上边缘波动 **0.00px**、最小窗口 `268`、宽度序列单调收缩无回跳 |
|
||||
| **开合不震动:展开** | ✅ 逐帧采样 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` |
|
||||
| **个性化:停靠位置(4 个锚点)** | ✅ 左上 `8,8` / 右上 `1164,8` / 底部居中 `586,800` / 顶部居中 `586,8` |
|
||||
| **个性化:停靠位置(6 个锚点)** | ✅ 左上 `8,8` / 右上 `1216,8` / 底部居中 `586,800` / 底部左 / 底部右 / 顶部居中 `586,8` |
|
||||
| **个性化:置顶开关** | ✅ 关闭后 `isAlwaysOnTop=false`,开启后 `true` |
|
||||
| **个性化:动画开关 / 速度** | ✅ 关闭动画后 60ms 内即为 `404x316`(无过渡);speed=0.5 收回 435ms vs speed=2 收回 128ms |
|
||||
| **个性化:持久化** | ✅ 写入客户端配置后重新读取完全一致(尺寸/圆角/透明度/主题色/字号/动画/速度/位置/置顶) |
|
||||
| **个性化:动画开关 / 速度** | ✅ 关闭动画后 60ms 内即到位(无过渡);speed=2 收回明显快于 speed=0.5 |
|
||||
| **个性化:持久化** | ✅ 写入客户端配置后重新读取完全一致(尺寸/圆角/透明度/主题色/字号/动画/速度/位置/置顶/风格/细缝) |
|
||||
| **退出清理:托盘 / 灵动岛 / 窗口** | ✅ `isTrayReady()=false`、`island.isReady()=false`、窗口全部销毁 |
|
||||
| **退出后无残留(文件锁 + 二次启动)** | ✅ 配置目录可删除(句柄已释放)→ 用同一配置目录二次启动成功并再次全绿 |
|
||||
|
||||
该验证对**开发产物与打包后的 EXE 都适用**(打包后用 `ELECTRON_SMOKE_RESULT=<file>` 写出 JSON 结果,
|
||||
已实测 `packaged: true`、49/49 通过、退出码 0)。灵动岛各状态的窗口截图会写到
|
||||
已实测 `packaged: true`、53/53 通过、退出码 0)。灵动岛各状态的窗口截图会写到
|
||||
`ISLAND_SHOTS_DIR`(默认 `.cache/island-shots/`,仓库内留档目录 `docs/screenshots/island/`),
|
||||
并对尺寸、宽高比、绘制内容(不透明像素/颜色种类)、紧急态红色像素与外溢红光做断言。
|
||||
冒烟进程使用独立 userData(`ELECTRON_SMOKE_PROFILE`),因此**用户开着客户端也能跑验证**。
|
||||
@@ -871,14 +889,14 @@ pnpm db:migrate && pnpm db:seed
|
||||
|
||||
当前实测:
|
||||
|
||||
| 验证 | 结果 |
|
||||
| -------------------------------------------------------------------------------------------------- | ----------------------------------- |
|
||||
| `pnpm verify:e2e`(开发环境与**安装后的生产实例**各跑一次) | **133/133** ✅ |
|
||||
| `pnpm verify:web`(Web 管理端真实点击 + 权限入口隐藏 + 手机适配 + 叫人 + 成绩/时间配置导入 + PWA) | **19/19** ✅ |
|
||||
| `pnpm verify:desktop`(客户端冒烟 + 开合不震动 + 个性化全参数 + 托盘与退出无残留) | **49/49** ✅ |
|
||||
| `pnpm typecheck` / `pnpm lint` / `pnpm format:check` | 全部通过 ✅ |
|
||||
| 安装程序完整生命周期(静默安装 → 启停脚本 → 卸载) | 通过 ✅ |
|
||||
| Docker / Nginx 部署样例 | 文件已提供,本机无 Docker 未实测 ⚠️ |
|
||||
| 验证 | 结果 |
|
||||
| --------------------------------------------------------------------------------------------------- | ----------------------------------- |
|
||||
| `pnpm verify:e2e`(开发环境与**安装后的生产实例**各跑一次) | **133/133** ✅ |
|
||||
| `pnpm verify:web`(Web 管理端真实点击 + 权限入口隐藏 + 手机适配 + 叫人 + 成绩/时间配置导入 + PWA) | **19/19** ✅ |
|
||||
| `pnpm verify:desktop`(客户端冒烟 + 开合不震动 + WinIsland 风格 + 个性化全参数 + 托盘与退出无残留) | **53/53** ✅ |
|
||||
| `pnpm typecheck` / `pnpm lint` / `pnpm format:check` | 全部通过 ✅ |
|
||||
| 安装程序完整生命周期(静默安装 → 启停脚本 → 卸载) | 通过 ✅ |
|
||||
| Docker / Nginx 部署样例 | 文件已提供,本机无 Docker 未实测 ⚠️ |
|
||||
|
||||
## 常见问题(本机环境已知坑)
|
||||
|
||||
@@ -990,6 +1008,15 @@ pnpm db:migrate && pnpm db:seed
|
||||
| `packages/web-admin/src/components/TimeLayoutImportDialog.vue` | ClassIsland 时间配置导入弹窗(粘贴/选文件、解析预览、覆盖/合并、已存列表) |
|
||||
| `packages/shared/src/types.ts` 的导入 DTO | `TableImportPreview` / `TableImportResult` / `TimeLayoutDto` 等前后端共用 |
|
||||
|
||||
阶段 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 一一对应 |
|
||||
|
||||
阶段 10(学生端班级账号)新增文件:
|
||||
|
||||
| 路径 | 说明 |
|
||||
|
||||
Reference in new issue
Block a user