## 15. 资源清单(图标 / 字体 / 音效 / 天气图标 / 教程 / 本地化) 本节要求实现者**按清单准备并放置资源**,不得缺项、不得自行替换为网络图片。所有路径均以 ClassIsland 仓库为参照;实现时统一放到 `packages/desktop-client/src/renderer/assets/`(渲染进程资源)与 `packages/desktop-client/resources/`(打包资源)两处,并通过构建脚本生成多尺寸产物(见 15.9)。 ### 15.1 应用图标 #### 15.1.1 主图标(多尺寸 PNG) | 文件 | 尺寸 | 用途 | 证据 | |---|---|---|---| | `Assets/AppLogo.png` | 192×192 | 应用内窗口图标、关于页、欢迎页 Logo | `ClassIsland/Assets/AppLogo.png`;`App.axaml:29-31`;`WelcomePage.axaml:29-33` | | `Assets/AppLogo@48w.png` | 48×48 | 小尺寸(任务栏/列表) | 实测文件头 | | `Assets/AppLogo@384w.png` | 384×384 | 中尺寸(设置页/关于页展示) | 实测文件头 | | `Assets/AppLogo@768w.png` | 768×768 | 大尺寸(高分屏) | 实测文件头 | | `Assets/AppLogo_Fade.png` | 192×193 | 淡入用的半透明 Logo(启动/遮罩) | 实测文件头 | | `Assets/AppLogo_Monochrome.png` | 2715×2003 | 单色 Logo(水印/背景纹样) | 实测文件头 | | `Assets/AppLogo_AppLogo.svg` | 矢量 | 可缩放图标源 | 文件名 | | `Assets/AppLogo.ico` | 多尺寸 ICO:16/24/32/48/64/256×2 | Windows 可执行文件图标 | 解析 ICO 头得 7 个条目 | | `Assets/FreedesktopIcons/AppLogo@128w.png` | 128×128 | Linux Freedesktop 图标 | 实测文件头 | | `AppLogo@32w.png`(`ClassIsland.Desktop/Assets/`) | 32×32 | Windows 任务栏/托盘小图标 | 实测文件头 | #### 15.1.2 macOS 图标集(`ClassIsland.Desktop/Assets/AppLogo.xcassets/AppIcon.appiconset/`) 必须提供:16×16(1x/2x)、32×32(1x/2x)、128×128(1x/2x)、256×256(1x/2x)、512×512(1x/2x)、以及无后缀的 192/384/768 三档。实测目录内共 16 个 PNG,命名遵循 `AppLogo_<尺寸>_<倍率>.png` 与 `AppLogo@<宽>w.png` 两种规则,实现时按 `electron-builder` 的 `icon.icns` 要求重新生成即可,但**必须保留这两个命名族**以免资源引用失效。 #### 15.1.3 Windows 打包瓦片(`ClassIsland.Packaging/Images/`) | 资源 | 尺寸(100%/200%/400%) | 用途 | |---|---|---| | `Square44x44Logo` | 44 / 88 / 176 | 开始菜单小图标 | | `Square44x44Logo.targetsize-16/24/48/256` | 16/24/48/256 | 任务栏/Alt-Tab 变体 | | `Square150x150Logo` | 150 / 300 / 600 | 开始菜单中等图标 | | `Wide310x150Logo` | 310×150 / 620×300 / 1240×600 | 宽幅磁贴 | | `LargeTile` | 310 / 620 / 1240 | 大磁贴 | | `SmallTile` | 71 / 142 / 284 | 小磁贴 | | `StoreLogo` | 50 / 100 / 200 | 商店图标 | | `SplashScreen` | 620×300 / 1240×600 / 2480×1200 | 系统级启动图(与自绘闪屏无关) | | `LockScreenLogo` | 48×48 | 锁屏 | (以上均为实测文件头尺寸;`ClassIsland/Images/` 下另有一份 `.scale-200` 版本,实现时只保留打包用的那份即可。) #### 15.1.4 其他图标 | 资源 | 尺寸 | 用途 | |---|---|---| | `Assets/Marketplace/Logo.png` | 512×512 | 插件/主题市场 Logo | | `Assets/Marketplace/Logo@256w/128w/64w.png` | 256/128/64 | 市场图标多尺寸 | | `Assets/Marketplace/Logo.svg` | 矢量 | 市场矢量源 | | `ClassIsland.ExamplePlugin/icon.png` | 510×510 | 示例插件图标(插件必须自带 510×510 图标) | | `ClassIsland.Launcher/Assets/AppLogo.ico` | 18 条目 ICO(16–256) | 启动器图标 | 验收要求:所有图标必须**本地打包**、离线可用;PNG 必须由同一矢量源派生,不得出现「不同尺寸风格不一致」;ICO 必须内嵌 16/24/32/48/64/256 六档。 ### 15.2 字体 #### 15.2.1 正文/界面字体:HarmonyOS Sans SC | 文件 | 字重 | 用途 | 证据 | |---|---|---|---| | `Assets/Fonts/HarmonyOS_Sans_SC_Light.ttf` | 300 | 轻量标题/水印 | 文件名 | | `Assets/Fonts/HarmonyOS_Sans_SC_Regular.ttf` | 400 | 正文 | 文件名 | | `Assets/Fonts/HarmonyOS_Sans_SC_Medium.ttf` | 500 | 默认字重(岛内主文字) | `Settings.cs:101` | | `Assets/Fonts/HarmonyOS_Sans_SC_Bold.ttf` | 700 | 标题/强调 | 文件名 | - 字体族注册名:`HarmonyOS Sans SC`;全局资源键 `AppFont`,字体栈见 part 03 §3.2.1(`Assets/Fonts/Fonts.axaml:3`;`Assets/Fonts/Fonts.xaml:3`)。 - **许可**:`Assets/Fonts/LICENSE.txt` 为「HarmonyOS Sans Fonts License Agreement」,版权归华为;分发时必须**原样保留**该许可文件,且不得单独售卖字体。应用本体为 GPL-3.0(`LICENSE.txt`),字体许可与代码许可分离。 - 实现要求:把 4 个 ttf 放进 Electron 打包资源,`@font-face` 声明 4 个 `font-weight`,`font-display: swap`;**禁止**声明 `font-display: block`(会白屏到字体加载完)。 #### 15.2.2 图标字体(三套,必须全部内置) | 字体 | 文件 | 大小 | 附带映射 | 用途 | 证据 | |---|---|---|---|---|---| | Fluent System Icons | `ClassIsland.Core/Assets/Fonts/FluentSystemIcons-Resizable.ttf` | 1,472,748 B | `FluentSystemIcons-Resizable.json`(262,830 B) | 全局 UI 图标(Segoe Fluent 风格) | `AppBase.cs:142`;`ThemeBase.axaml:46` | | Lucide | `ClassIsland.Core/Assets/Fonts/lucide.ttf` | 715,192 B | `lucide.json`(45,030 B) | 天气图标(默认)、部分 UI 图标 | `AppBase.cs:147`;`ThemeBase.axaml:47` | | SF Symbols | `ClassIsland.Core/Assets/Fonts/SF-Symbols.ttf` | 174,616 B | — | 天气图标(Apple 风格) | `AppBase.cs:152` | - 字体族名分别为 `FluentSystemIcons-Resizable`、`lucide`、`SF-Symbols`;Avalonia 资源 URI 形如 `avares://ClassIsland.Core/Assets/Fonts/#lucide`,Web 实现改为 `@font-face { font-family:'lucide'; src:url('lucide.ttf') }` 并保留同名 `font-family`。 - JSON 映射文件必须作为图标选择器数据源(Codepoint/名称表),不得只带 ttf 不带 json——图标选择器需要按名称搜索。 - 许可:应用整体 GPL-3.0;Fluent System Icons 与 Lucide 通常为 MIT/ISC【推断:仓库内未放独立许可文件,按上游项目许可执行】;SF Symbols 字体来自 Apple 符号集,**仅允许在 Apple 平台或已获授权场景使用**,跨平台分发时须评估并在「关于/许可」页声明。 #### 15.2.3 字体相关的硬性要求 1. 字体必须在应用启动**首帧前**注册完成(`document.fonts.ready` 前不得渲染最终布局,可用 `font-display: swap` + 骨架屏规避跳动)。 2. 不得使用系统缺失字体作为唯一来源;`HarmonyOS Sans SC` 必须随包分发。 3. 用户自定义字体(`MainWindowFont`)只允许从**系统已安装字体**中选择(源码 `AppearanceSettingsPage.axaml:409-446` 枚举系统字体),实现时用 `queryLocalFonts()`(需权限)或主进程枚举字体列表,二者都不可用时至少提供内置 4 字重 + 3 图标字体。 4. 字体文件不得被 CSP 阻止:`font-src 'self'` 必须放行本地资源。 ### 15.3 音效 | 资源 | 大小 | 用途 | 默认设置 | 证据 | |---|---|---|---|---| | `Assets/Media/Notification/1.wav` | 130,012 B | 提醒音效(唯一内置音效) | `IsNotificationSoundEnabled=true`;`NotificationSoundPath=""`(表示使用内置音效);`NotificationSoundVolume=1.0` | 实测文件;`Settings.cs:144-145,1584-1593` | | 自定义音效 | 用户文件 | 用户可指定本地音频路径 | 支持 wav/mp3 等系统可解码格式【推断】 | `Settings.cs:1496-1505` | | 语音合成 | 系统/Edge TTS | 提醒朗读 | `IsSpeechEnabled=true`;`SpeechVolume=1.0`;`SpeechSource=0`;Edge TTS 默认音色 `zh-CN-XiaoxiaoNeural` | `Settings.cs:135-139,1406-1428,1452-1461` | 实现要求: - 音频必须用 `HTMLAudioElement` 或 Web Audio 播放,**不得**在主进程用系统播放器;播放前需用户手势解锁(Electron 可用 `webPreferences.autoplayPolicy: 'no-user-gesture-required'`,但必须在配置中显式声明)。 - 音量 = `NotificationSoundVolume × 系统音量`,范围 0–1;语音音量独立(`SpeechVolume`)。 - 同一条提醒**只播一次**音效(源码用 `MaskSession.HasSoundsPlayed` 去重,`MainWindowLine.cs:746-751`);提醒队列切换时不得重复触发。 - 语音提供方必须可插拔:系统语音(Windows SAPI)、Edge TTS、GPT-SoVITS 三选一(`App.Services.xaml.cs:281-285` 注册三个 provider),默认走系统语音或 Edge TTS,且必须支持「无网络时降级到系统语音」。 - 内置音效 1.wav 必须打包进 `resources/audio/`,不得 CDN 引用。 ### 15.4 天气图标(4 套模板) #### 15.4.1 模板注册表 | 模板 id | 显示名 | 默认 | 实现方式 | 尺寸参数 | 证据 | |---|---|---|---|---|---| | `classisland.weatherIcons.lucide` | Lucide(默认) | ✅ `WeatherIconId="classisland.weatherIcons.lucide"` | `Viewbox` + `LucideIcon` | 16×16,FontSize 16,Margin 1 | `App.Services.xaml.cs:287`;`Settings.cs:2106`;`LucideWeatherIconTemplate.axaml` | | `classisland.weatherIcons.fluentDesign` | Fluent Design | — | `Viewbox` + `FluentIcon` | 20×20,FontSize 24,Margin `0 1.2 0 -1.2` | `App.Services.xaml.cs:288`;`FluentDesignWeatherIconTemplate.axaml` | | `classisland.weatherIcons.sfSymbols` | SF Symbols | — | `Viewbox` + `SFSymbolsIcon` | 16×16 | `App.Services.xaml.cs:289`;`SFSymbolsWeatherIconTemplate.axaml` | | `classisland.weatherIcons.simpleText` | 纯文本 | — | 文本 + 分隔符 `~` | 文本随字号 | `App.Services.xaml.cs:290`;`SimpleTextWeatherIconTemplate.axaml` | 实现要求:模板必须可插拔(插件可注册新模板),模板注册信息至少包含 `{id, name, renderer}`;切换模板立即生效且不需要重启(源码 `WeatherService.LoadWeatherIconTemplate()` 在设置变化时重新取模板,`Services/WeatherService.cs:84-90`)。 #### 15.4.2 天气代码 → 名称 → 图标(必须完整映射,共 40 个代码) 数据源:小米天气状态表 `Assets/XiaomiWeather/xiaomi_weather_status.json`;图标映射:`ClassIsland/Converters/WeatherConverters.cs`。下表前端三列(Fluent/Lucide/SF)为源码私有区码位,后端实现时请**原样保留码位**,Web 端用 `data-icon` 名称 + 本地字体渲染。 | code | 天气名 | Fluent 码位 | Lucide 码位 | SF 码位 | |---|---|---|---|---| | 0 | 晴 | `\uf465` | `\ue2b1` | `\uFE42` | | 1 | 多云 | `\uf44f` | `\ue216` | `\uFDC9` | | 2 | 阴 | `\uf43d` | `\ue217` | `\uFDF1` | | 3 | 阵雨 | `\uf455` | `\ue2fb` | `\uFDE9` | | 4 | 雷阵雨 | `\uf46b` | `\ue090` | `\uFDCD` | | 5 | 雷阵雨并伴有冰雹 | `\uf46b` | `\ue08f` | `\uFDDD` | | 6 | 雨夹雪 | `\uf459` | `\ue094` | `\uFDD4` | | 7 | 小雨 | `\uf43f` | `\ue08e` | `\uFDED` | | 8 | 中雨 | `\uf453` | `\ue092` | `\uFDE9` | | 9 | 大雨 | `\uf453` | `\ue092` | `\uFDE5` | | 10 | 暴雨 | `\uf453` | `\ue093` | `\uFDE4` | | 11 | 大暴雨 | `\uf453` | `\ue093` | `\uFDE4` | | 12 | 特大暴雨 | `\uf453` | `\ue093` | `\uFDE4` | | 13 | 阵雪 | `\uf45d` | `\ue376` | `\uFDD9` | | 14 | 小雪 | `\uf45b` | `\ue094` | `\uFDD9` | | 15 | 中雪 | `\uf45b` | `\ue094` | `\uFDD9` | | 16 | 大雪 | `\uf45b` | `\ue094` | `\uFDD9` | | 17 | 暴雪 | `\uf45b` | `\ue094` | `\uFDD9` | | 18 | 雾 | `\uf443` | `\ue214` | `\uFDE1` | | 19 | 冻雨 | `\uf461` | `\ue08f` | `\uFDD5` | | 20 | 沙尘暴 | `\uf441` | `\ue218` | `\uFDB1` | | 21 | 小雨-中雨 | `\uf453` | `\ue092` | `\uFDED` | | 22 | 中雨-大雨 | `\uf453` | `\ue092` | `\uFDE9` | | 23 | 大雨-暴雨 | `\uf453` | `\ue093` | `\uFDE5` | | 24 | 暴雨-大暴雨 | `\uf453` | `\ue093` | `\uFDE5` | | 25 | 大暴雨-特大暴雨 | `\uf453` | `\ue093` | `\uFDE5` | | 26 | 小雪-中雪 | `\uf45b` | `\ue094` | `\uFDD9` | | 27 | 中雪-大雪 | `\uf45b` | `\ue094` | `\uFDD9` | | 28 | 大雪-暴雪 | `\uf45b` | `\ue094` | `\uFDD9` | | 29 | 浮尘 | `\uf449` | `\ue0f4` | `\uFE2E` | | 30 | 扬沙 | `\uf441` | `\ue0f4` | `\uFE2E` | | 31 | 强沙尘暴 | `\uf441` | `\ue218` | `\uFE2E` | | 32 | 飑 | `\uf463` | `\ue093` | `\uFDAD` | | 33 | 龙卷风 | `\uf463` | `\ue218` | `\uFDA3` | | 34 | 弱高吹雪 | `\uf45b` | `\ue094` | `\uFDD9` | | 35 | 轻雾 | `\uf443` | `\ue214` | `\uFDE1` | | 53 | 霾 | `\uf449` | `\ue0f4` | `\uFDE1` | | 301 | 雨 | `\uf453` | `\ue092` | `\uFDE9` | | 302 | 雪 | `\uf45b` | `\ue094` | `\uFDD9` | | 99 及未知 | 未知 | `\uee31` | `\ue091` | `\uFD8C` | 实现硬要求: - 未知代码必须回落到「未知」图标,**不得**抛异常或显示空白。 - 纯文本模板必须实现「今天~明天」的 `~` 分隔符逻辑(模板内 `Separator` 仅在 `To` 场景可见,`SimpleTextWeatherIconTemplate.axaml`),且文本颜色跟随 `--ci-text-*`。 - 天气图标颜色:晴天/温度等常规图标用前景色;雨天图标可选用 `--ci-weather-rain`(亮 `#0000FF` / 暗 `#00BFFF`),见 part 03。 ### 15.5 贴纸资源(HoYoStickers) `Assets/HoYoStickers/` 内共 **22** 个 PNG,按「角色_动作」命名,用于教程/提醒的图标表达式 `sticker(名字)`(`App.axaml.cs:253` 注册 `IconExpressionHelper` 的 `sticker` 处理器)。实现时必须支持 `sticker(帕姆_嗨)` 这种写法并渲染对应 PNG。 | 角色 | 尺寸 | 文件(动作) | |---|---|---| | 光辉矢愿 | 162×162(6 个) | `不可以看`、`回眸`、`小喇叭`、`想我了吗`、`瞄准`、`遨游` | | 帕姆 | 510×510(5 个)/450×450(1 个) | `不可以`、`哭哭`、`嗨`(450)、`注意`、`点赞`、`震惊` | | 昔涟 | 510×510(4 个) | `回眸`、`守护`、`收到`、`爱` | | 白厄 | 510×510(6 个) | `再见`、`我吗`、`战斗`、`掉线`、`没事` | 实现要求:贴纸必须按**原始比例**缩放(`object-fit: contain`),不得拉伸;建议在 UI 中统一缩放到 96×96 或 128×128,并保留 `image-rendering: -webkit-optimize-contrast` 以求清晰。图标表达式解析器必须支持 `fluent(码位)`、`lucide(码位)`、`sfsymbols(码位)`、`bitmap(路径)`、`sticker(名字)` 五种前缀(`App.axaml.cs:250-260`),未知前缀回落为文本。 ### 15.6 教程资源 #### 15.6.1 教程定义文件(JSON) 内置教程组:`Assets/Tutorials/classisland.getStarted.json`(另有 `classisland.sp.json`、`classisland.test.json` 仅作开发样例,**不要**在正式版启用)。数据结构(逐字段实现): | 层级 | 字段 | 类型 | 说明 | |---|---|---|---| | 顶层 | `Id` | string | 教程组 id,如 `classisland.getStarted` | | 顶层 | `Title` / `Description` | string | 组标题/描述(中文) | | 顶层 | `Tutorials` | array | 教程条目列表 | | 教程 | `Id` / `Title` / `Description` | string | 教程 id/标题/描述 | | 教程 | `ActualBanner` / `Banner` | string | Banner 资源 URI(`avares://`) | | 教程 | `Ordinary` | int | 是否为普通教程(0/1) | | 教程 | `Paragraphs` | array | 段落列表 | | 段落 | `Id` / `Title` / `Description` | string | 段落标识/标题/描述 | | 段落 | `TopLevelClassName` | string | 目标窗口类名(如 `ClassIsland.MainWindow`) | | 段落 | `IsBarrierEnabled` | bool | 是否启用教程遮罩屏障 | | 段落 | `InitializeActions` | array | 进入段落时执行的行动 | | 段落 | `Content` | array | 每屏内容 | | 内容 | `Title` / `Content` | string | 标题/正文(中文) | | 内容 | `IconExpression` | string | 图标表达式(如 `sticker(帕姆_嗨)`) | | 内容 | `HeroImage` | string | 大图 URI(可空) | | 内容 | `TargetSelector` / `PointToTarget` / `ModalTarget` | string/bool | 指向的目标与是否模态 | | 内容 | `RightButtonText` / `LeftButtonText` | string | 按钮文案(可空) | | 内容 | `RightButtonActions` / `LeftButtonActions` / `InitializeActions` | array | 行动列表,元素为 `{Kind:int, StringParameter:string}` | | 内容 | `WaitForNextCommand` / `UseLightDismiss` | bool | 交互策略 | | 内容 | `Tag` / `Script` / `Xmlns` | string/object | 扩展位 | (来源:`Assets/Tutorials/classisland.getStarted.json:1-200+`。行动 `Kind` 的具体枚举含义在源码 `TutorialActionKind` 中定义,实现时必须对照该枚举逐值映射,不得自行编号;本节不臆造枚举值。) #### 15.6.2 教程图片 | 组/条目 | 文件 | 大小 | |---|---|---| | welcome | `banner.webp` | 42,664 B | | welcome | `mainMenu.webp` / `openAppSettings.webp` / `openEditMode.webp` / `openProfileEditor.webp` / `openTutorial.webp` | 约 25–27 KB/张 | | componentsEditing | `banner.webp` / `ruleset.webp` | 28,292 / 48,738 B | | profileEditing | `banner.webp` / `时间表.webp` / `科目.webp` / `课表.webp` / `课表与时间表关系.webp` | 25,900–109,912 B | 另有默认图:`Assets/DefaultTutorialBanner.webp`(10,416 B)、`Assets/DefaultTutorialGroupBanner.webp`(46,540 B),用作无 Banner 时的兜底。 要求:图片统一用 WebP(保留 `.webp` 扩展名),懒加载;教程播放时按窗口宽度自适应,不得变形。 ### 15.7 本地化 #### 15.7.1 现状(必须如实实现,不得虚构更多语言) ClassIsland v2 的本地化是**局部**的,只覆盖两个命名空间(`Assets/Localization/`): | 命名空间 | 资源文件 | 语言 | |---|---|---| | `SettingsPage`(设置页) | `Localization.SettingsPage.resx`(默认/中文)、`.zh-Hans.resx`、`.zh-Hant.resx`、`.en.resx` | 简体中文、繁体中文、英文 | | `SettingsPage/General`(常规设置子页) | 同上 4 个 | 同上 | | `TrayMenu`(托盘菜单) | 同上 4 个 | 同上 | - 默认语言为中文(`resx` 无文化后缀文件即默认),并强制 `Thread.CurrentThread.CurrentUICulture = zh-CN`(`ClassIsland/App.axaml.cs:512-513`)。 - 其余界面文案**硬编码中文**,没有资源化。因此「支持多语言」不是现状能力。 #### 15.7.2 实现要求(在 Web 栈中的等价方案) 1. 必须建立 `i18n` 层(推荐 `vue-i18n`),语言键命名与 resx 的 `Key` 保持一致,便于数据迁移。 2. 首期必须提供 **zh-Hans(默认)、zh-Hant、en** 三份文案;其余界面文案随实现逐步资源化,**不得**伪造「已支持 N 种语言」。 3. 语言切换必须持久化到配置,并立即刷新界面(无需重启);日期/时间格式必须跟随语言(`Intl.DateTimeFormat`)。 4. 本地化文件不得包含 HTML 标签;带占位符的文案必须使用命名参数(`{name}`),禁止字符串拼接。 5. 字体必须覆盖三语:英文用 HarmonyOS Sans SC 的拉丁字形即可;繁体中文若依赖系统字体兜底,必须在 fallback 栈中显式列出(`PingFang TC`、`Microsoft JhengHei`)。 ### 15.8 其他资源与文档 | 资源 | 用途 | 要求 | 证据 | |---|---|---|---| | `Assets/Documents/ChangeLog.md` | 更新日志(关于页展示) | 必须随版本更新,按版本倒序 | 文件名 | | `Assets/Documents/Privacy_.md` | 隐私政策(关于页展示) | 必须原样保留;含 Sentry/上报说明 | 文件名 | | `LICENSE.txt`(根、各项目、Assets) | GPL-3.0 许可证 | 必须随安装包分发 | `LICENSE.txt` | | `Assets/TrustedPublicKeys/ClassIsland.MetadataPublisher.asc` | 更新/市场元数据签名公钥 | 必须内置,用于校验更新包 | 文件名 | | `Assets/ShortcutTemplates/ClassSwap.url`、`cn.classisland.app.desktop` | 换课/桌面快捷方式模板 | Windows `.url` 与 Linux `.desktop` 两套 | 文件名 | | `Assets/Highlighting/Lua.xshd` | 自动化脚本 Lua 语法高亮定义 | 编辑器必须使用该配色 | 文件名 | | `Assets/default-subjects.json` | 默认科目表(23 个科目,含固定 GUID:语文/数学/英语/历史/政治/物理/化学/生物/地理/信息技术/体育/自习/通用技术/音乐/美术/选修课/社团/心理/早读/班会/周测 等) | 必须原样内置,GUID 不得重新生成(迁移依赖) | `Assets/default-subjects.json` | | `Assets/AsciiLogo.txt` | ASCII 艺术 Logo(控制台/崩溃页) | 6 行,宽 63 字符 | 文件内容 | | `Assets/Tellings.txt` | 关于页/加载页随机「一句话」(含彩蛋) | 至少 20 条,需保留「关于页连续点击图标 10 次开调试」等提示 | 文件内容 | | `Assets/dependencies.g.json` | NuGet 依赖许可清单(供「许可」页展示) | Web 栈需生成 `licenses.json` 等价物(npm 依赖 + 字体许可) | `Models/NugetLicenseInfo.cs`、`Views/WelcomePages/LicensePage.axaml` | | `Assets/XiaomiWeather/xiaomi_weather_status.json` | 天气代码→中文名映射(40 条) | 与 15.4.2 表一致 | 文件内容 | | `Assets/BannerMain_App2.png` | 2031×600 | 关于页/更新页主视觉 | 实测文件头 | | `Assets/Demo_MainMenu.png`、`Demo_OpenProfileSettingsWindow.png` | 1608×878 | 教程/引导演示图 | 实测文件头 | | `Assets/ClassIsland.ai` | 矢量源文件 | 设计源文件,可不打包 | 文件名 | | `Assets/XamlThemePreviews/classisland.fluent.png`、`classisland.classic.png` | 1920×350 | 主题市场预览图(见 part 09) | 实测文件头 | ### 15.9 ClassHelper 侧资源落点 | 资源类别 | 落点 | 生成/校验方式 | |---|---|---| | 应用图标 | `build/classhelper.png`(源)+ 设计导出的 `.ico` | 用 `pnpm icons` 派生其余尺寸(与 ClassHelper 现有约定一致:图标源是 `build/classhelper.png` 与设计导出的 `.ico`,`pnpm icons` 派生其余资源) | | Web 品牌图 | `packages/web-admin/public/logo.png` 或站点 `/logo.png` | Web 用 `/logo.png`,客户端用 `BRAND_LOGO_URL` 常量(避免 `file://` 绝对路径问题) | | 字体 | `packages/desktop-client/resources/fonts/` | 4 个 HarmonyOS TTF + 3 个图标 TTF + 2 个 JSON 映射;构建时校验文件存在与哈希 | | 音效 | `packages/desktop-client/resources/audio/1.wav` | 校验大小 130,012 B(或替换为等价资源但保持单文件、可循环) | | 贴纸 | `packages/desktop-client/resources/stickers/` | 22 个 PNG,文件名保持中文原名(打包后可用 URL 编码访问) | | 教程 | `packages/desktop-client/resources/tutorials/` | JSON + WebP;启动时校验 id 唯一 | | 主题预览 | `packages/desktop-client/resources/theme-previews/` | 1920×350 PNG 两张 | | 许可清单 | `packages/desktop-client/resources/licenses.json` | 构建时从 `package.json`/`node_modules` 生成;必须包含字体与图标字体的许可条目 | ### 15.10 资源命名、加载与缓存规范 1. **命名**:图片用 `kebab-case` 或保持中文原名(贴纸、教程图必须保留原名,因为教程 JSON 以名字引用);字体文件名保持上游原名(`HarmonyOS_Sans_SC_Medium.ttf`),不得改成 `medium.ttf` 之类丢失来源信息的名字;音效用 `1.wav` 这类稳定短名,便于设置里持久化路径。 2. **目录约定**:`renderer/assets/{icons,fonts,audio,stickers,tutorials,theme-previews}/`,每类一个目录;禁止在组件目录里散落资源文件。 3. **加载方式**: - 图标字体、正文 TTF 在应用启动时通过 `@font-face` 预加载(`crossorigin` 同源),并用 `document.fonts.load()` 显式触发; - 贴纸/教程图/主题预览图走懒加载(`loading="lazy"` + IntersectionObserver),未加载时显示 `Shimmer` 骨架(part 14 D9); - 音效在首次提醒时按需加载并缓存 `AudioBuffer`,不得每次播放都重新 `fetch`。 4. **缓存**:所有资源 URL 必须带内容哈希(构建期生成 `asset-manifest.json`),Electron 生产环境用 `file://` + 哈希避免陈旧缓存;开发环境禁用强缓存。 5. **失败降级**:图片加载失败必须显示「图裂了」占位(ClassIsland 的行为:`SystemFillColorCriticalBackgroundBrush` 底 + 提示「喔唷,图裂啦」,`StylesBase.axaml:214-223`),不得留白或抛错;字体加载失败必须回落系统字体并保持布局不跳变(用 `font-display: swap` + 固定行高)。 6. **安全**:禁止加载 `http(s)` 远程图片作为 UI 资源;所有资源必须走打包路径,CSP 中 `img-src 'self' data:`、`font-src 'self'`、`media-src 'self'`。 7. **引用完整性**:构建时扫描 CSS/TS/JSON 中的资源引用,所有引用必须命中 `asset-manifest.json`;反向扫描资源目录,未被引用的资源要么删除要么登记为「运行时动态加载」白名单。 ### 15.11 资源版本与许可核对 - 「关于」页必须提供「许可」入口,展示:应用许可(GPL-3.0)、字体许可(HarmonyOS Sans License)、图标字体许可(Fluent/Lucide/SF)、贴纸素材来源、第三方依赖(npm 依赖清单)。ClassIsland 用生成文件 `dependencies.g.json` + `LicensePage.axaml` 展示 NuGet 依赖,Web 实现必须生成等价的 `licenses.json`。 - 每次发版必须核对:新增/替换的资源是否引入新的许可义务;贴纸、Banner、Demo 图是否为项目自有或有授权(HoYoStickers 为《崩坏:星穹铁道》同人素材,**仅可作为内置彩蛋、不得用于商业宣传**)。 - 版本号变更时必须同步「关于」页的 `AppVersionLong` 展示(源码用编译期常量 `AppBase.AppVersionLong`,Web 端改为读 `package.json.version`)。 补充说明:ClassIsland 的「关于」页还会展示更新日志、隐私政策与致谢名单,这些内容都是随包分发的 Markdown 文档。实现时不得改为在线拉取——教室环境常常断网,关于页必须在离线时完整可读。更新日志与隐私政策的内容以 `Assets/Documents/` 下的文件为准,发版时同步更新,不允许只改版本号不改日志。 ### 15.12 验收标准 1. **完整性**:本节列出的资源 100% 存在于仓库;用脚本比对清单与文件,缺一项即失败(允许同一资源用等价格式替换,但必须在清单中登记变更)。 2. **图标**:`electron-builder` 打包后 Windows exe/installer、Linux AppImage、macOS icns 三端图标均正确;ICO 内嵌 6 档尺寸;任务栏/开始菜单/关于页三处图标一致。 3. **字体**:断网启动,界面仍显示 HarmonyOS Sans SC(非回退字体);图标字体全部可渲染(抽查 Fluent/Lucide/SF 各 3 个码位);`document.fonts.check()` 对 4 个正文字重与 3 个图标族返回 true。 4. **字体许可**:安装包内包含 `fonts/LICENSE.txt`(HarmonyOS 许可)与 `licenses.json`;「关于 → 许可」页可查看并可滚动到底。 5. **音效**:默认配置下提醒有声音;把音量调到 0 静音;自定义音效路径失效时回落内置 1.wav 且不崩溃;同一条提醒只响一次。 6. **天气图标**:4 套模板均可切换且立即生效;40 个天气代码逐个渲染正确(截图对比名称与图标);未知代码 99 显示「未知」图标。 7. **贴纸**:`sticker(帕姆_嗨)` 在教程中正确显示;22 张贴纸全部可加载(无 404);比例不变形。 8. **教程**:`classisland.getStarted` 三个教程组可完整播放到结束;跳过后再次进入从第一步开始;教程图片无拉伸、无破图。 9. **本地化**:切换 en/zh-Hans/zh-Hant 后设置页与托盘菜单文案切换且立即生效;日期格式随之变化;其余页面若无翻译必须回落中文而不是显示 key。 10. **离线**:在完全断网、无缓存的干净环境中启动,所有资源(字体/图标/音效/贴纸/教程图)均本地可用,控制台无对外请求(除用户主动触发的天气/更新功能)。 11. **体积**:字体 + 图标 + 音效 + 贴纸 + 教程图的总增量不超过 30MB 打包后体积;超过时必须说明取舍(例如贴纸按需下载,但 ClassIsland 的语义是离线可用,默认应全量内置)。