feat: ClassIsland / ClassHelper 灵动岛提示词套件

- 重构版主提示词(14 parts,面向 ClassHelper 灵动岛重构)
- ClassIsland 全量规格提示词(20 parts,含分卷与逐章版本)
- 投喂指南、分卷、逐节文件
- 调研记录、一致性基线、生成与质检脚本
This commit is contained in:
ClassIsland Prompt Kit committed 2026-11-12 21:59:58 +08:00
commit 22c1784bab
117 files changed
+47759

No files matched your search

+351
View File
@@ -0,0 +1,351 @@
## 11. 其它窗口与向导(闪屏 / OOBE / 崩溃 / 恢复 / 更新 / 换课 / 导入导出 / 迁移 / 教程 / 授权 / 日志)
本节规定除主界面(岛)与设置窗口之外的全部用户可见窗口。实现技术栈为 ClassHelper desktop-client 的 Electron 多窗口模型:**每个窗口必须是一个独立 BrowserWindow + 独立渲染入口**(或同一个渲染进程内的独立路由 + 独立 BrowserWindow,二者择一但同一类窗口必须一致),窗口参数、模态关系、键盘行为、关闭行为必须完全按本节实现。所有窗口必须复用统一窗口外壳组件(对应 ClassIsland 的 `MyWindow`):Windows 11 21H2+ 且支持 WinUI Composition 时启用 Mica(背景透明 + `windowTransparencyLevel: 'mica'`),否则使用纯色背景 `SolidBackgroundFillColorBaseBrush`;开发版/开源水印以绝对定位浮层实现(`repos/ClassIsland/ClassIsland.Core/Controls/MyWindow.cs:215-229`、`:178-198`)。
### 11.1 通用窗口规则(所有窗口必须遵守)
1. **标题栏**。除显式声明「无边框/透明」的窗口外,一律使用自定义标题栏,高度 `48px`(与设置窗口一致;`SettingsWindowNew.axaml.cs:135`)。窗口标题必须写入窗口标题属性(用于任务栏与 Alt+Tab)。
2. **模态**。设置、欢迎向导、数据迁移、Excel 导入/导出、授权、日志等窗口必须作为「主窗口的模态/非模态子窗口」处理:非模态窗口(设置、日志、教学中心、档案编辑)关闭时为 `hide()`;模态窗口(欢迎向导、数据迁移、更新进度、任务对话框)关闭时真正 `destroy()`,并把结果回传调用方。
3. **默认快捷键(所有窗口)**:`F3` 循环切换渲染调试浮层(无 → FPS → FPS+布局/渲染耗时),`Shift+F3` 直接进入第二级;`F6` 切换触摸模式(`Shift+F6` 同时抑制自动触摸判定);`F7` 关闭当前窗口全部 Toast(`MyWindow.cs:115-166`)。实现者必须在每个窗口注册同样的开发者快捷键(生产构建可移除)。
4. **Toast/通知条**。所有窗口必须支持右上/底部 Toast 队列(标题、正文、级别:灾难/错误/警告/信息/成功、是否可关闭、是否自动关闭);Toast 必须以 Adorner 形式浮于窗口内容之上,不改变布局(`MyWindow.cs:185-188`)。
5. **对话框**。普通确认类对话框必须使用 Fluent 风格 ContentDialog:标题、正文、主按钮、次按钮、关闭按钮、默认按钮;内容宽度约 `320–548px`【推断:FluentAvalonia 默认】,居中于父窗口并带遮罩。需要展示 Markdown/复杂内容时使用 TaskDialog(带标题栏、按钮列表、可勾选扩展区)。
6. **文案常量集中**。全部窗口文案必须集中存放在常量文件(固定路径 `src/renderer/constants/texts.zh-CN.ts`,按窗口分节),组件内只允许引用常量键、不得散落中文字面量;文案内容以本节给出的中文原文为准,后续接入 i18n 时以该常量文件为唯一来源。
### 11.2 欢迎向导(OOBE)`WelcomeWindow`
源:`Views/WelcomeWindow.axaml:14-20`、`WelcomeWindow.axaml.cs:62-243`、`Views/WelcomePages/*`。
1. **窗口参数**:`800×600`,`CenterScreen`,`CanResize=false`,`ShowAsDialog=true`(模态体验),启用 Mica,标题「ClassIsland」,Linux 下窗口类型必须为 `Normal`(`:14-20`)。内容区必须只有一个导航 Frame,无标题栏文字、无额外 chrome。
2. **页面序列**(必须支持三种启动模式,并按条件裁剪页面;`WelcomeWindow.axaml.cs:62-90`):
- 完整 OOBE(首次启动,`IsOnboarding=true` 且非翻新):欢迎页 → 许可条款 → 翻新页 → 基本页 → 颜色主题页 → 外观页 → 系统页 → 完成页(共 8 页);
- 翻新模式(`isRefreshing=true`):保留翻新页,去掉欢迎页/许可页/系统页;
- 翻新完成回访(`isRefreshCompleted=true`):同时去掉翻新页;
- 非 OOBE 且非翻新:许可页/欢迎页/系统页/翻新页全部去掉,仅剩基本/颜色/外观/完成。
3. **导航命令**:前进/后退/完成必须是三个路由命令;前进时索引 `min(index+1, count-1)`,后退时 `max(index-1, 0)`(`:126-142`)。所有页面共享同一个 ViewModel(设置、快捷方式选项、翻新范围),页面切换时不得重建状态。
4. **欢迎页(WelcomePage)**:整页 `Grid` 三行(`*, Auto, *`)。第 0 行:应用图标 `80×80`,底部对齐、距文字 `8px`;第 1 行:「ClassIsland」11 个字母逐字水平排列,`font-size: 32px`、`font-weight: Medium`,字间初始 `4px` 间距;第 2 行:主按钮(图标 `E130`,`48×48`、圆角 `24px`、accent,tooltip「下一步」)居中,下方 `margin: 8px`;页面底部居中显示版本号(`opacity: 0.75`)+ 三个超链接「数据迁移」「故障排除/恢复备份」「加入管理」(超链接 `padding: 4px`)(`WelcomePages/WelcomePage.axaml:26-101`)。
5. **欢迎页开场动画(必须逐帧还原)**:页面加载后先播放 OOBE 片头动画(`OobeIntroAnimationControl`),动画结束后页面内容容器才以 `200ms` 淡入(透明度 0→1,`WelcomePage.axaml:11-25`、`.axaml.cs:41-44`)。片头动画规格(`Controls/OobeIntroAnimationControl.axaml:13-144`、`.axaml.cs:25-165`):
- 结构:11 个 `32×32` 方块(间距 `4px`、背景 `ControlStrongFillColorDefaultBrush`、初始 `opacity: 0`、`translateY(50px)`、`transform-origin: 0 12px`)与 11 个字母(`font-size: 32px`、Medium、`min-width: 32px`、初始 `opacity: 0`、3D 翻转 `rotateX`)叠放在同一中心位置;
- 方块入场:逐个出现,延迟按 `delay_i = sin((i+2)/(n+2) * π/2) * 500 / n` 毫秒(n=11)计算;每个方块动画时长 `delay*9*1.5+750ms`,关键帧:`0%` 透明且下移 `50px` → `delay*9 ms` 时完全不透明且归位(`cubic-bezier(0.25,1,0.5,1)`)→ 停留 `+750ms` → 随 `rotateX 0°→90°` 翻出并消失(`cubic-bezier(0.32,0,0.67,0)`);第 6 个方块(索引 6)必须改成「宽度放大到 `2.17` 且变 `#00bfff`」的特殊高亮动画(`:44-58`、`:65-124`);
- 字母入场:与方块交错,单个字母动画时长 `delay*9*2+750ms`,在方块翻出后由 `rotateX(-90°)` 翻入(`cubic-bezier(0.33,1,0.68,1)`);
- 收尾:全部字母出现后,字母容器的字间距从 `4px` 过渡到 `0`、每个字母 `min-width` 从 `32px` 过渡到 `0`,时长 `700ms`、曲线 `cubic-bezier(0.33,1,0.68,1)`,随后整页 `opacity 0→1`、`scale 1.25→1.0`(时长 `3.0s`,曲线 `cubic-bezier(0,0.83,0.17,0.90)`);
- 动画期间必须禁用「下一步」交互【推断:源码未显式禁用,但按钮在动画下层】。
6. **许可条款页(LicensePage)**:页面内容 `max-width: 600px`、居中;标题「同意许可条款」(Subtitle)+ 说明「要继续使用 ClassIsland,您必须阅读并同意以下许可条款。」;两个复选框,文案分别为「我已阅读并同意《ClassIsland 开源许可》。」「我已阅读并同意《ClassIsland 隐私政策》。」,书名号部分是超链接,点击打开许可/隐私对话框;主按钮(`48×48` 圆角 24 accent,「下一步」)**仅在两个复选框都勾选时可用**(`WelcomePages/LicensePage.axaml:9-43`)。
7. **翻新页(RefreshingPage)**:标题「翻新」+ 说明「选择要重置的内容。选中的内容将被重置至初始状态,重置前应用将创建一个备份,以便还原至重置前的状态。」;5 个复选框,均带图标与描述:档案(`E699`,「包含课表、时间表等信息。」)、课表(`E6B1`,缩进 `22px`,仅当「档案」未勾选时显示)、时间表(`F0EB`,缩进 `22px`,同上)、组件配置(`E06F`,「主界面显示的内容。」)、自动化(`EEF1`,「应用在特定情况下自动执行的内容。」)、插件(`E071`,「可以扩展应用的功能。」);「档案」未勾选时另显示超链接「选择要保留的课表和时间表…」;执行翻新期间所有复选框禁用,并显示「正在翻新,请稍后…」+ 不确定进度条(`WelcomePages/RefreshingPage.axaml:15-90`)。
8. **基本页(GeneralPage)**:`max-width: 600px`;标题「基本」+「配置 ClassIsland 的基本设置。」;两条设置:「学期开始时间」(日期选择器 `min-width: 120px`,绑定 `SingleWeekStartTime`)、「匿名使用情况分析」(开关,绑定 `IsSentryEnabled`,说明与隐私页一致);主按钮「下一步」(`WelcomePages/GeneralPage.axaml:11-49`)。
9. **颜色主题页(ColorThemePage)**:标题「颜色主题」+「设置 ClassIsland 的主题外观。」;横向 ListBox 三张主题预览卡(每项 `margin: 0 8px 0 4px`,预览图宽 `144px`、高约 `75px`):第一张为「跟随系统」(左半暗色 `ThemeDemo` 宽 `144` 裁剪到 `0..72`,右半亮色裁剪到 `72..144`),第二张「明亮」(整幅亮色),第三张「黑暗」(整幅暗色);每张卡下方有 RadioButton 文案「跟随系统/明亮/黑暗」,选中项对应 `Theme`=0/1/2;底部「上一步」「下一步」两个 `48×48` 圆角按钮(`WelcomePages/ColorThemePage.axaml:22-79`)。
10. **外观页(AppearancePage)**:标题「外观」+「调整 ClassIsland 主界面的外观。」;三条设置:界面缩放(滑块 `0.75–2.5`,步进 `0.05`,宽 `180px`,`F2`)、背景不透明度(`0–1`,步进 `0.05`,`F2`)、圆角半径(`0–20`,步进 `0.5`,`F1`);底部「上一步」「下一步」(`WelcomePages/AppearancePage.axaml:12-79`)。
11. **系统页(SystemPage)**:标题「系统」+「设置ClassIsland的系统集成功能,这些功能可以优化您使用本软件的体验。」;复选框(`padding: 8px 0`,含图标与描述):开机自启(`E342`,macOS 隐藏)、创建桌面快捷方式(`E62F`,macOS 隐藏)、创建开始菜单快捷方式(`E064`,deb 包与 macOS 隐藏)、注册 Url 协议(`EAB0`,仅 Windows)、「在桌面创建换课快捷方式」(`E15F`,仅 Windows,缩进 `22px`,且仅在勾选「注册 Url 协议」时可用);底部「上一步」「下一步」(`WelcomePages/SystemPage.axaml:16-100`)。
12. **完成页(FinishPage)**:`Carousel` 宽 `600px`,三页:① 「点击任务栏托盘图标可以打开应用主菜单。」+ 截图 `Demo_MainMenu.png`;② 「要编辑课表和时间点,请在应用主菜单中点击【编辑档案】。」+ 截图 `Demo_OpenProfileSettingsWindow.png`;③ 85×85 贴纸(`白厄_再见.png`,回退 `E31A`)+「完成」+「恭喜!应用的基本设置已完成,感谢您选用 ClassIsland。点击【完成图标】以结束设置向导。」。Carousel 页面切换必须使用「水平滑动 `300ms` `cubic-bezier(0.76,0,0.24,1)` + 交叉淡入 `300ms`」的复合过渡;底部按钮:非最后一页显示「上一步」「下一步」,最后一页显示「上一步」+「完成」(图标 `E423`,`48×48` 圆角 24 accent)(`WelcomePages/FinishPage.axaml:14-98`)。
13. **完成行为**:点击完成必须依次执行:置 `CanClose=true`、`IsWizardCompleted=true`;若为 OOBE:在 Windows 创建开机启动/开始菜单/桌面快捷方式(按系统页勾选)并注册 Url 协议,Linux 写 `~/.config/autostart/cn.classisland.app.desktop`、`~/.local/share/applications/cn.classisland.app.desktop`、桌面 `ClassIsland.desktop` 并复制图标;重置教程完成状态;关闭 `RefreshingToastIsOnboardingGuide`;若为翻新:清零剩余通知次数;最后关闭窗口(`WelcomeWindow.axaml.cs:186-218`)。
14. **关闭保护**:OOBE 模式下用户点关闭必须弹出模态对话框:标题「退出 ClassIsland」,正文「您需要完成设置才能开始使用本应用。关闭此窗口将直接退出应用。」,主按钮「退出」,次按钮「取消」;确认后关闭窗口并退出应用(`.axaml.cs:220-243`)。
15. **欢迎页底部三个入口**:「数据迁移」→ 把向导页面序列替换为 [欢迎页, 许可页, 数据迁移页] 并前进;「故障排除/恢复备份」→ 以参数 `-m -r` 重启到恢复模式;「加入管理」→ 打开 `JoinManagementDialog`(`WelcomePage.axaml.cs:46-69`)。
### 11.3 启动闪屏 `SplashWindow`
源:`Views/SplashWindow.axaml:1-100`、`.axaml.cs:41-130`、`Services/SplashService.cs`。
1. **窗口参数**:`400×100`,`CenterScreen`,`Topmost`,`SystemDecorations=None`,背景 `Transparent`,`ShowInTaskbar=false`,`ShowActivated=false`,`TransparencyLevelHint=AcrylicBlur,Transparent`,强制暗色主题;窗口圆角约 `8px`【推断:Fluent 默认】,内容容器 `padding: 17px`、背景 `#88111111`、`border: 1px` 描边(`ControlStrokeColorOnAccentDefaultBrush`)。
2. **布局**:4 行 `*, 8, *, Auto`。第 0 行:Logo(`height: 24px`,`margin-right: 8px`)+「ClassIsland」文字(`20px` Medium,仅当未自定义 Logo 时显示;自定义 Logo 时只显示图片);第 2 行:`ProgressRing` `24×24` + 状态文本(`16px` Regular,绑定 `SplashStatus`);第 2 行左下角叠加版本号(`9px`,`opacity: 0.5`,`margin: -16px 0 0 -14px`);第 3 行:`400×4` 白色进度条(`margin-bottom: -17px`)。
3. **入场动画**:Logo 与状态行分别做「从右侧 `32px` 滑入 + 淡入」:位移 `300ms` `ExponentialEaseInOut`(`cubic-bezier(0.87,0,0.13,1)` 近似【推断】),透明度 `300ms` `CubicEaseOut`;状态行延迟 `150ms`,Logo 延迟 `0ms`(`.axaml.cs:53-54`、`:101-121`)。
4. **进度条动画**:进度值 `p`(0–100)变化时,以合成动画把白色填充条的 `X` 偏移从当前值补间到 `-400*(1-p/100)`;时长 = `max((p - 上次p)/8, 0.5)` 秒;结束(`EndSplash`)时以 `150ms` 补间到 100%(`.axaml.cs:64-82`、`:123-130`);缓动 `CubicEaseOut`。
5. **文案**:默认「正在启动…」;用户自定义 `SplashCustomText` 非空时必须用它替换;仅当 `ShowDetailedStatusOnSplash=true` 时才允许用详细启动阶段文案覆盖(`SplashService.cs:27-33`、`:89-92`)。集控策略 `DisableSplashCustomize` 为真时必须清空自定义 Logo/文字/详细状态。
6. **启用条件**:仅当 `Settings.IsSplashEnabled=true`(默认 `false`,`Models/Settings.cs:115`)时显示;该窗口不是主窗口,必须由主进程在应用初始化最早阶段创建,并在主窗口显示后销毁。
### 11.4 崩溃窗口 `CrashWindow`
源:`Views/CrashWindow.axaml:1-99`、`.cs:16-86`。
1. **窗口参数**:`800×450`,`CenterScreen`,启用 Mica,标题「ClassIsland 崩溃报告」,图标 `AppLogo.ico`。
2. **布局**(`Grid margin: 12px`,6 行 2 列):
- 头部:`48×48` 贴纸(`光辉矢愿_不可以看.png`,回退 `E809`),`margin: 0 -8px 0 0`;标题 `TitleTextBlockStyle`「ClassIsland 发生错误 ヽ(*。>Д<)o゜」(禁用彩蛋时改为「ClassIsland 发生错误」),`margin: 8px 0 16px 16px`;
- 正文(`14px`,可换行):「ClassIsland 碰到了严重错误而无法正常运行。您可以保存下方的错误信息并向他人寻求帮助。如果您认为这是由软件本身的错误所致,请点击下方“反馈问题”按钮。」
- 错误信息:只读多行文本框,`font-size: 13px`、无边框、`padding: 0`、顶部对齐(`TextBoxCrashInfo`);
- 当异常不可忽略时显示 Error InfoBar:「这个异常无法被忽略,您需要重启或退出应用。很抱歉对您的使用造成不便。」;
- 分隔线(`margin: -12px 8px`);
- 底部工具条:背景 `LayerOnMicaBaseAltFillColorDefaultBrush`,`margin: -12px 0 -12px -12px`,左右 `padding: 12px`。左侧按钮:调试(`E2C8`,仅 critical 时显示,点击 `Debugger.Launch()`)、忽略(`E671`,仅在允许忽略时显示,点击关闭窗口)、退出应用(`E0DF`)、重启应用(`E0BD`,accent);右侧按钮:复制(`E58B`,全选并复制错误信息)、反馈问题(`ED11`)。
3. **反馈问题**:必须打开浏览器到 `https://github.com/ClassIsland/ClassIsland/issues/new`,并带查询参数 `template=BugReport.yml&stacktrace=<URL 编码的崩溃信息>&app_version=<版本>&os_version=<系统版本>`(`.cs:66-77`)。
4. **退出语义**:critical 时「退出应用」必须调用 `Environment.Exit(1)`;非 critical 时走正常退出流程(`.cs:42-52`)。
### 11.5 恢复模式 `RecoveryWindow` 与恢复页
源:`Views/RecoveryWindow.axaml:12-21`、`Views/RecoveryPages/HomePage.axaml:1-85`、`RecoverBackupPage.axaml:1-45`。
1. **进入方式**:以命令参数 `-m -r` 重启(等价于「重启到恢复模式」);恢复模式窗口为主窗口:`450×600`,`CenterScreen`,标题「恢复」,图标 `AppLogo.png`,内容区为导航 Frame(`CacheSize=0`)。
2. **主页(HomePage)**:`margin: 8px`,纵向间距 `6px`;标题「选择一个选项」+「选择一个操作以继续。」(`margin: 0 2px 10px`)。三组「卡片组」(子项间距 `1px`、首尾圆角 `4px`),全部为可点击行(`SettingsExpanderItem`),右侧统一 `E44E` 箭头图标:
- 第 1 组:继续使用 ClassIsland(`E131`);以安全模式继续使用 ClassIsland(`E131`,描述「禁用所有插件和自动化功能。」);以诊断模式继续使用 ClassIsland(`E131`,描述「在日志中记录详细的信息,并启用控制台输出。」);打开日志目录(`E889`,`EC2E`);
- 第 2 组:恢复备份(`E195`,描述「从先前的备份恢复处于正确状态的配置文件。」);回滚(`E135`,描述「回滚到之前的 ClassIsland 版本」,默认禁用);
- 第 3 组:清除临时文件(`E623`,描述「删除所有缓存和临时文件。」);重置应用设置(`E0BD`,描述「重置应用的设置信息,不影响档案、组件、自动化、插件等的配置。」);重置全部配置(`E6B9`,描述「重置应用的所有配置,包括应用设置、组件配置、自动化配置、插件设置等,不影响档案配置。」);重置全部信息(`E12F`,描述「重置应用的所有信息,恢复到安装前的状态。」)。
3. **恢复备份页(RecoverBackupPage)**:三行布局:顶部 `margin: 8px` 一行:返回按钮(图标 `E109`,tooltip「返回」,`margin-right: 16px`,working 时禁用)+ 标题「选择要恢复的备份」;中部为备份列表(`IsEnabled` 随 working 反转),working 时列表顶部叠加不确定进度条;底部(`margin: 8px`):两个单选按钮「增量恢复:替换备份中已有的文件,保留新添加的文件」(默认,值 0)与「完整恢复:完整恢复到选定备份,不保留新添加的文件」(值 1,`margin-top: 2px`);右下 accent 按钮「恢复到选定备份」(图标 `E195`),未选择备份时禁用。
4. **安全/诊断模式标识**:以安全模式启动时,主界面与标题必须标明当前处于安全模式;诊断模式必须写出更详细日志并附带控制台输出【推断:由启动参数控制,UI 标识在源码中未直接可见】。
### 11.6 更新相关窗口与流程
1. **更新进度窗口 `UpdateProgressWindow`**(`Views/UpdateProgressWindow.xaml:8-38`):`300×100`,`WindowStartupLocation=Manual`、初始位置 `left: 50, top: 50`(主进程可再调整到屏幕角落),标题「正在更新 ClassIsland…」,`ResizeMode=NoResize`,背景 `SolidBackgroundFillColorBaseBrush`,字体 `HarmonyOS Sans` 14px。内容 `margin: 16px`,两行:第一行两行文本「正在更新 ClassIsland……」+ 动态进度文本(`ProgressText`);第二行不确定进度条(`margin-top: 8px`)。关闭行为必须被拦截(`Closing` 事件取消关闭),防止更新中途被关掉。
2. **自动更新模式**(`Settings.UpdateMode`,默认 `3`,`Models/Settings.cs:77`):
- `0`:只允许手动检查;
- `1`:启动时自动检查,发现更新只提示;
- `2`:自动检查并自动下载;
- `3`:自动检查、下载并安装(下次启动生效)。
对应设置页见 part 10.4.13。
3. **更新状态机**(`UpdateStatus`):已是最新 / 有更新 / 已下载 / 已部署;按钮与文案切换规则见 part 10.4.13。更新包校验必须使用 `UpdateArtifactHash`(默认空,`Models/Settings.cs:157`),并按通道元数据公钥校验签名【推断:由 `UpdateService` 实现,源码见 `Services/UpdateService.cs`】。
4. **部署后提示**:部署完成必须通过 InfoBar 提示「更新将在重启后应用」,并允许「重启并应用更新」;`AutoInstallUpdateNextStartup` 默认 `true`(`Models/Settings.cs:78`),即下次启动自动安装。
### 11.7 换课窗口 `ClassChangingWindow`
源:`Views/ClassChangingWindow.axaml:1-273`、`.cs:53-192`。
1. **窗口参数**:`600×250`,`MinWidth=730`、`MinHeight=175`,`SizeToContent=WidthAndHeight`,`CenterScreen`,启用 Mica,标题「换课」,图标 `AppLogo.png`。根 Grid `MinWidth=730`、`MinHeight=175`。
2. **顶部标题条(AppBar)**:`padding: 8px`,内含「换课」标题(`TitleTextBlockStyle`,Medium,`margin-right: 16px`)+ 目标课表按钮(图标 `E6B1` + 课表名,tooltip「要换课的课表」)+ 箭头图标 `E131` + 操作提示文本。提示文本随步骤切换:第 0 步「选择要调换的课程。」;第 1 步「选择要与刚刚选择的课程调换的课程。」(`:219-256`)。
3. **主体 Carousel**(两页):过渡必须为「水平滑动 `300ms` `cubic-bezier(0.76,0,0.24,1)` + 交叉淡入 `300ms`」(`:52-60`)。
- 第 0 页:`LessonsListBox` 居中(当前课表全部课程),`margin: 0 8px`,`SelectionChanged` 后若合法值则自动进入第 1 步;
- 第 1 页:`margin: 8px`,三列 `Auto, Auto, *`:左列为课程卡片(`LessonControlExpanded`,`height: 40px`,展示当前选中的课程/时间点/科目);中列为「替换 / 交换」TabStrip(`compact toggle`;选中「替换」= `IsSwapMode=false`,选中「交换」= `IsSwapMode=true`);右列为二级 Carousel(垂直滑动 `300ms` 同曲线 + 交叉淡入):「替换」页 = 字段「换课成…」+ 科目下拉(`min-width: 100px`);「交换」页 = 第二个课程选择列表(`:61-140`)。
4. **底部工具条**:背景 `LayerOnMicaBaseAltFillColorDefaultBrush`,左右 `margin: 6px`。左侧:取消(图标 `E671`,`IsCancel`,tooltip「取消换课操作。」)、上一步(`E109`,仅第 1 步可见)、超链接「高级换课...」(tooltip「打开调课界面,支持跨天的换课选项和更复杂的调课方案。」→ 打开档案编辑窗口的 `classisland://app/profile/adjustment` 并关闭本窗口)。右侧:下一步(`E131`,accent,仅在第 0 步且已选中课程时显示)、复选框「永久换课」(`margin: 4px -8px`;tooltip「如果选择,将不会在换课时创建临时层课表,而会直接应用到原课表上。」;仅当课表不是临时层时显示;受 `DisableProfileTimeLayoutEditing`/`DisableProfileEditing` 策略禁用)、「确认换课」按钮(`E424`,accent,`margin-left: 12px`)(`:146-216`)。
5. **交互与数据逻辑**:
- 选择课程时必须把行索引换算成「上课时间点索引」(仅统计 `TimeType==0` 的上课项),换算失败必须 Toast 警告「选择的课程无效。」(`.cs:45-73`);
- 「交换」模式:互换两节课的 `SubjectId`;「替换」模式:把第一节课的 `SubjectId` 改为目标科目,目标科目为空则直接返回(`.cs:136-157`);
- **临时层语义**:未勾选「永久换课」且当前课表不是临时层时,必须先检查是否已存在临时层课表:若存在,弹出确认对话框(标题「覆盖当前的临时层」,正文「当前已经存在一个临时层课表,如果继续换课,那么该临时层将被覆盖。是否继续?」,主按钮「继续」、次按钮「取消」,默认主按钮);确认后清除临时层,再为新换课创建临时层(`.cs:99-119`);
- 换课完成后必须保存档案,并在集控受管时上报审计事件 `ClassChangeCompleted`(字段:ChangeMode、ClassPlanId、SourceClassIndex、SourceClassSubjectId、TargetClassIndex、TargetClassSubjectId、WriteToSourceClassPlan)(`.cs:159-172`);
- `Esc` 必须等价于取消(取消按钮 `IsCancel=true`);
- 窗口 `Unloaded` 时必须清空 `DataContext`(`.cs:183-186`)。
### 11.8 课表详细信息 `ClassPlanDetailsWindow`
> 本窗口**必须实现(P1)**,不得省略:它是课表/时间点附加设置的唯一可视化诊断入口。
源:`Views/ClassPlanDetailsWindow.axaml:1-199`。
1. **窗口参数**:`800×500`,启用 Mica,标题「课表详细信息」,图标默认。
2. **布局**:两行(`Auto, *`)、三列(`1*` 最小 `300` 最大 `500`、`5px` 分隔、`2*`),内容区背景 `LayerOnMicaBaseAltFillColorDefaultBrush` 铺满三列。
3. **左列**:只读 DataGrid(水平网格线),列:「时间」(起止时间 `HH:mm:ss-HH:mm:ss`,`margin: 4px`)、「科目」(科目名);`font-size: 14px`;选中行绑定 `SelectedLesson`。
4. **中列**:`GridSplitter`,方向 Columns。
5. **右列**(选中课程后):「附加设置结果」页签:
- 顶部一行:字段「要查看的附加设置」+ 下拉(`min-width: 150px`,每项「图标 + 名称」,选项来自全部已注册附加设置控件)+「刷新」按钮(`E0B5`);
- 说明文本(`margin: 4px`):「以下的附加设置将按从上到下的顺序使用。」+ 汇总文案;
- 列表:每项高 `36px`,左侧 `4px` 宽状态条(启用 = 主色渐变半透明块 + 主色竖条;被覆盖 = 警告色竖条),右侧渲染该附加设置的只读展示控件;状态条所在行必须有 `opacity: 0.25` 的高亮背景层(`:101-173`)。
6. **标题条**:「课表详细信息」(`font-size: 24px`,Medium,`margin: 8px`);右上「刷新」按钮(`E0B5`)。
### 11.9 Excel 导入向导 `ExcelImportWindow`
> **实现定性(必读)**:本实现**必须**把「从表格导入课表」做成**可用功能,优先级 P1**,不得以「遗留/可选」为由跳过。事实背景:ClassIsland 源码中该窗口的 XAML 仍在仓库(`Views/ExcelImportWindow.xaml`,992 行),但代码隐藏与 DI 注册均已停用(`Views/ExcelImportWindow.xaml.cs:1` 与 `Views/ExcelExportWindow.xaml.cs:1` 以 `#if false` 包裹整份代码,`App.Services.xaml.cs:204` 的 `services.AddTransient<ExcelImportWindow>()` 被注释)。**ClassIsland 源码中该窗口已停用,本实现以源码布局为参考重建**。技术约束:读写 `.xlsx` 必须使用纯 JavaScript 的 `xlsx` 库(SheetJS)或等价实现,**不得**引入 ReoGrid、WPF、`System.Windows.Forms` 或任何 .NET 互操作依赖;表格预览/编辑网格必须用 Vue 组件重建(虚拟滚动表格或 canvas 网格),交互与本文一致。
源:`Views/ExcelImportWindow.xaml:1-992`、`.cs:320-420`、`:746-822`(源码已停用,本实现以 XAML 布局与流程为规格依据)。
1. **窗口参数与入口**:默认 `WindowState=Maximized`(设计值 `450×800`),标题「从表格导入课表」,允许拖放文件,关闭前需保存清理;主容器为 `DialogHost` 等价的全窗遮罩容器。必须提供可到达的 UI 入口(建议放在档案编辑窗口的课表列表工具栏「从表格导入…」;入口的归属与位置归 part 06,但本窗口不得成为不可达的死功能)。
2. **三区域布局**(`Grid` 列定义随状态切换,`:182-221`):
- 未选择文件时:左列宽 `0`、中列宽 `0`、右列占满(引导卡居中);
- 已选择文件后:左列 `*`(表格)、中列 `5px`(分隔条实际宽 `10px`)、右列宽 `500px`(`min-width: 350`、`max-width: 800`);
- 行定义:第 0 行 Auto(页头)、第 1 行 `*`。
3. **页头**:普通状态为 `ColorZone`(PrimaryMid,`padding: 16px`,Elevation Dp4,`margin-bottom: 4px`)内「从表格导入课表」(Headline6);选区模式时页头变为「在下方表格中选择区域」+ 右侧当前选区(等宽字体,Headline6)+「确定」按钮(`Check` 图标),并叠加呼吸动画:选区模式下页头底部高亮条在 `0.1 → 0.25 → 0.1` 之间循环,单次 `1s`、`QuadraticEase EaseOut`、无限重复(`:64-82`、`:238-272`)。
4. **表格区**:必须用 Vue 网格组件重建,实现源码中 ReoGrid 的以下能力:可切换只读、显示并切换工作表标签(标签宽度 `500`)、文本默认黑色、支持单元格区域选择与合并/取消合并;单元格右键菜单固定 6 项:复制(`Ctrl+C`)、粘贴(`Ctrl+V`)、剪切(`Ctrl+X`)、清除内容(`Del`)、合并单元格、取消单元格合并(源码对应 `:274-308`;**不得引入 ReoGrid 依赖**)。
5. **右列向导容器**:`ScrollViewer`(`margin: 8px`)+ `Transitioner`(页面切换用 `CircleWipe` 圆形擦除过渡),共 **17 个页面(索引 0–16)**,页面标题一律 `MaterialDesignHeadline5TextBlock`、`margin-bottom: 12px`,正文 `TextBlock` 自动换行、`margin-bottom: 4px`:
- 0 「选择文件」:居中 `FileTableOutline` 75×75 图标 + 「从表格导入课表」(20px Bold)+「要开始导入,请将表格文件拖入到本窗口」+「或者」+「浏览文件…」按钮(`FolderOutline`);文件选择过滤器必须为「表格文件(*.xlsx)|*.xlsx」,标题「选择要导入的表格文件」(`:336-357`、`.cs:746-759`);
- 1 「加载中」:81×81 `帕姆_点赞` 贴纸 + 「正在加载你的表格……」+「坐和放宽,滚回功率,加载马上就好!」(13px,删除线);
- 2 「欢迎」:说明 +「开始」按钮(`ArrowRight`;若以「仅导入时间表」模式打开则直接跳到 5);
- 3 「询问界面形式」:「您希望本向导如何引导您?」+ 两个选项「详细」(对每个选项和流程做出详细解释,适用于刚接触本软件的新手)/「简洁」(简化流程和语言说明,提供简洁的体验,适用于熟悉本软件的用户)+「上一步」;
- 4 「从哪里导入时间表?」:三个选项:「我的时间表在这个文件中」/「我的时间表另一个文件中」/「我想手动创建时间表」+「上一步」;
- 5 「选择时间安排区域」:说明「点击下方文本框以从表格中选择包含时间点安排信息的区域。选区只能为单列或单行选区。Tips:若选区遇到合并的单元格,可以在单元格上右键取消单元格合并。」+ 区域选择文本框(水印「选择时间安排区域」)+ 无效时红色警告「选择区域无效。」+「下一步」(仅在区域合法且非选区模式时可用);合法性规则:选区必须为单行或单列(`.cs:761-777`);
- 6 「手动创建时间表」:共用「选择时间表」说明 + 时间表下拉(按 `Key` 选中)+「打开档案编辑窗口」按钮(`FileDocumentEditOutline`)+「下一步」(未选择时间表时禁用);
- 7 「时间表导入结果」:时间表名称输入框(Hint「时间表名称」)+「刷新」「编辑」按钮 + 只读时间线预览(`TimeLineListControl`,`Scale=3`,支持拖动平移)+ 导航按钮 +「跳过课表导入」(仅非「仅时间表」模式);
- 8 「选择课程区域」:区域选择文本框(水印「选择课程区域」)+ 无效警告 + 导航按钮(区域合法时可用);
- 9 「选择行列对应关系」:「点击下方高亮圆形按钮以选择读取课表的方向。」+ 切换按钮(未选中显示 `ArrowRight`、选中显示 `ArrowDown`)+ 文案「每竖列为一个课表。」/「每横行为一个课表。」+ 导航按钮;
- 10 「如何导入时间对应关系?」:两个选项「自动识别」「手动指定」;
- 11 「时间对应关系」:左右两个多选列表:左侧为识别区域(每行复选框内容为 `{值}`),右侧为该表格时间点(`HH:mm:ss-HH:mm:ss`);说明「设置每行对应的时间点。在识别课表时,仅会识别左侧选中的区域。在右侧选择该表格中包含的时间点,选中的时间点在识别时会按照顺序匹配左侧选择的区域。」+「下一步」;
- 12 「选择创建课表的区域」:「将从选择的区域创建课表。」+ 课表来源列表(选中项左边显示 Check 图标)+ 导航按钮(未选择来源时禁用);
- 13 「课表导入结果」:「已识别选中区域上的课程。请检查识别结果是否正确,并修改不正确的课程。」+ 课表名称输入框(Hint「课表名称」)+ DataGrid(行头宽 `150`;列「时间」只读、列「科目」为可编辑科目下拉)+ 导航按钮;
- 14 「课表设置」:「调整刚刚导入的课表的触发条件等设置。」+ 名称输入框 + 时间表下拉(禁用)+ 复选框「自动启用」+ 说明「当课表设置为自动启用时,将会根据触发规则自动启用该课表。否则只能通过临时课表或临时层手动启用该课表。」+ 启用时显示时间规则编辑控件;
- 15 「已导入的课表」:列表(`FileChartOutline` + 名称)+ 三个可点击项:「继续导入课表。」(`ArrowRight`)、「使用新的时间表继续导入课表。」(`UndoVariant`)、「完成导入。」(`Check`);
- 16 「完成」:`光辉矢愿_回眸` 贴纸(63×63)+「恭喜!您已经完成了课表导入!现在您可以关闭本页面了。」+「完成」按钮。
页面跳转映射与返回映射必须按源码实现(`.cs:345-420`),例如:`TimeLayoutSource→4`、`ImportTimeLayoutFromThisFile→5`、`CreateTimeLayoutManually→6`、`TimePointImportResult→7`、`SelectSubjectsPosition→8`(仅时间表模式为 16)、`SelectVerticalMode→9`、`RowClassesTimeRelationshipImportAuto/Man→11`、`SelectClassPlanArea→12`、`PreviewClassPlan→13`、`ClassPlanDetails→14`、`AllClassPlansView→15`、`Finish→16`;返回时 `3/4→2`、`5/6→4`、`7→5`、`8→7`(时间表来源=0)或 `8→9`(来源=2)、`9→6`、`10→8`、`11→10`、`12→11`、`13→12`、`14→13`、`15→14`、`16→15`。
6. **拖放**:拖入文件时整窗显示遮罩(`SolidBackgroundFillColorBaseBrush`,`opacity: 0.8`)+ 居中 `FileImport` 64×64 图标 +「将表格文件拖入到此处,松手即可开始导入。」;仅当拖入数据包含文件且尚未选择文件时显示(`:972-986`、`.cs:289-291`)。
7. **错误对话框**(全窗遮罩对话框):「无法打开文件」(标题「无法打开文件」,正文包含文件名与异常信息,按钮「确定」);「无法以兼容模式打开」;「兼容性警告」(「无法打开文件“…”因为本文件中有一些不兼容的格式和字体。您可以尝试以兼容模式打开。若选择以兼容模式打开,应用将为此文件创建一个临时副本,并移除副本中不兼容的格式和字体。」,按钮「取消」「在兼容模式中继续」)。用 `xlsx` 库实现时,「兼容模式」等价于:以 `cellStyles:false`/忽略样式与公式的方式二次解析同一文件,失败才进入「无法以兼容模式打开」(源码对应 `:85-164`)。
8. **关闭行为**:关闭窗口时必须取消选中时间表的激活状态(`SelectedTimeLayout.IsActivated=false`、`IsActivatedManually=false`),避免导入中断留下脏数据(`.cs:801-805`)。
### 11.10 Excel 导出窗口 `ExcelExportWindow`
> **实现定性(必读)**:本实现**必须**把「导出到 Excel 表格」做成**可用功能,优先级 P1**。事实背景:源码中该窗口 XAML 保留、代码隐藏位于 `#if false`(`Views/ExcelExportWindow.xaml.cs:1`)、未在 DI 中注册(`App.Services.xaml.cs:205` 被注释)。**ClassIsland 源码中该窗口已停用,本实现以源码布局为参考重建**。技术约束:必须使用 `xlsx`(SheetJS)或等价纯 JS 库生成/读取 `.xlsx`,不得依赖 ReoGrid/WPF;表格网格必须用 Vue 组件重建,并支持本文列出的单元格样式(字体、字号、粗斜体、下划线、水平对齐、合并/取消合并、字体色、背景色、框线、行高列宽)。
源:`Views/ExcelExportWindow.xaml:1-301`。
1. **窗口参数**:`1400×700`,默认 `WindowState=Maximized`,标题「导出到 Excel 表格」,背景 `SolidBackgroundFillColorBaseBrush`,字体 HarmonyOS Sans 14px。
2. **布局**:三列 `*`、`Auto`、`360px`(最小 `300`、最大 `400`);三行 `Auto`、`Auto`、`*`。第 0 行为 Snackbar 层,第 1 行为工具栏(跨三列),第 2 行为表格 + 右侧设置面板。
3. **表格区**:必须用 Vue 网格组件重建(不得引入 ReoGrid),实现可编辑单元格、工作表标签(标签宽度 `500`)、区域选择、合并/取消合并,以及复制/粘贴/剪切/清除内容(带快捷键提示)右键菜单。
4. **工具栏(普通模式)**:保存…(`ContentSaveOutline`)、保存并在默认 Excel 文件编辑器中打开(`MicrosoftExcel`)、分隔线、撤销(`Undo`,随 `CanUndo` 禁用)、重做(`Redo`)、分隔线、字体下拉(宽 `150px`,可编辑,每项用自身字体渲染)、字号数值框(宽 `75px`,后缀 `px`)、减小字号、增大字号、加粗/斜体/下划线(多选切换)、分隔线、水平对齐(无/左/居中/右/两端)、分隔线、合并单元格、取消合并。
5. **工具栏(选区模式)**:「完成选择」按钮(`Check`)+ 分隔线 +「当前选区:」+ 选区坐标(等宽字体 `16px`)。
6. **右侧面板**:`TabControl`,页签位于右侧(`TabStripPlacement=Right`),每个页签 `min-width: 42px`、`height: 42px`、内边距 `4px`:
- 「课表」页(图标 `FileChartOutline`):标题「课表」+「基准位置」选择框(进入选区模式)+ 课表多选列表(CheckBox + 课表名);
- 「样式」页(图标 `StyleOutline`):标题「样式」+ 复选框「包含教师名称」+ 下拉「水平对齐方式」(无/左/居中/右/两端)+ 下拉「垂直对齐方式」(默认/顶部/居中/低端)+「字体颜色」颜色选择器 +「背景颜色」颜色选择器 + 下拉「单元格框线」(无/全框线/全粗框线)+ 并排数值框「行高」「列宽」(后缀 `px`)+「刷新内容」按钮;
- 「保存」页(图标 `ContentSaveOutline`):标题「保存」+「保存到 Excel 表格」+ 说明 +「保存并在默认 Excel 文件编辑器中打开」+ 说明。
7. **Snackbar**:所有保存/错误提示必须以底部/顶部 Snackbar 显示(MaterialDesign Snackbar),不得使用阻塞对话框。
### 11.11 数据迁移 `DataTransferWindow` / `DataTransferPage`
源:`Views/DataTransferWindow.axaml:1-20`、`.cs:13-57`、`Views/DataTransferPage.axaml:1-273`、`.axaml.cs:54-120`。
1. **窗口参数**:`800×600`,`CenterScreen`,`ShowAsDialog=true`,`CanResize=false`,`ShowInTaskbar=true`,启用 Mica,标题「数据迁移」,图标 `AppLogo.png`;内容整体替换为 `DataTransferPage`。
2. **页面**:`ScrollViewer` 内 `Grid max-width: 600px` 居中 + `Carousel` 宽 `600px`(5 页,页面过渡为水平滑动 `300ms` `cubic-bezier(0.76,0,0.24,1)` + 交叉淡入 `300ms`):
- 页 0「数据迁移」:标题 +「向 ClassIsland 导入或从 ClassIsland 导出数据。」+ TabControl:
- 导入页签三个可点击卡片:ClassIsland 2.x(应用图标)、ClassIsland 1.7.0.x(应用图标)、Class Widgets 1.2(`E06F`);
- 导出页签:InfoBar 说明「此处的数据导出功能主要用于在不同 ClassIsland 实例间转移数据。若需导出诊断信息,请前往【应用设置】->【更多选项…】->【导出诊断数据】;若需备份应用数据,请使用【应用设置】->【通用】->【存储】中的备份功能。」+ 卡片「ClassIsland 2.x」;
- 页 1「选择要导入的数据 / 选择要导出的数据」:显示来源描述文本 +「选择要导入的信息 / 选择要导出的信息」+ 3 个复选框:课表配置(`E699`,「包括课表内容、时间安排、科目定义等信息。」)、应用设置(`EF27`,「包括应用的各个首选项,如隐藏设置、颜色设置、提醒/铃声设置、天气信息等。」)、其它配置(`E06F`,「组件、自动化等配置文件。」)+「上一步」「下一步」(`48×48` 圆角 24);
- 页 2「导入来源 / 导出目标」:「选择要导入的数据来源。 / 选择要导出到的目标。」+ 卡片「浏览…」(`E88D`,说明「手动浏览导入位置 / 手动浏览导出位置」)+「上一步」;
- 页 3「正在导入 / 正在导出」:「请坐和放宽,导入很快就能完成。」+ `ProgressRing` `48×48`(不确定);
- 页 4「导入完成 / 导出完成」:「已成功完成导入。/ 已成功完成导出。」+ 完成按钮(`E423`,`48×48` 圆角 24 accent)。
所有文案必须随「导入/导出」模式动态切换(通过容器 `.export` 类实现,`:80-88` 等)。
3. **导入来源语义**:
- ClassIsland 1.x:说明「支持从 1.x 版本的 ClassIsland 导入课表、组件配置、自动化配置、应用设置、部分插件和主题等数据。」;浏览时选择旧实例的 `ClassIsland.exe`,以该可执行文件所在目录为根(`.cs:77-114`);
- ClassIsland 2.x:说明「支持从 ClassIsland 2.x 数据文件导入课表、组件配置、自动化配置、应用设置、部分插件和主题等数据。」;
- 导出:说明「支持导出为可以导入到其它 ClassIsland 2 实例的文件格式。」;
- Class Widgets 1:说明「支持从 Class Widgets 1 导入全部课表信息和大部分配置。」;
- 导入前若需要重启(如替换正在使用的配置),必须弹出模态确认「重启以继续」(`.cs:116-120`)。
4. **外部调用**:应用必须支持以 `-i` 等参数在启动时执行迁移并把结果(`ImportStage`、`ImportName`)传入页面;`ImportComplete` 时必须直接把页面切到索引 4(`.cs:52-56`)。
### 11.12 授权窗口 `AuthorizeWindow` 与集控加入对话框
1. **AuthorizeWindow**(`Views/AuthorizeWindow.axaml:14-152`):宽 `500`,`SizeToContent=Height`,`CanResize=false`,`MinHeight=300`、`MaxHeight=700`,`CenterScreen`,`Topmost=true`,`ShowAsDialog=true`,`CanMaximize=false`,标题「ClassIsland」。内容 `margin: 16px`,三行两列:
- 头部(普通模式):「验证您的身份」(Subtitle)+「要继续操作,请通过下列的方式验证您的身份。」;编辑模式:「编辑认证项目」+「编辑进行此操作需要的认证方式。」;
- 编辑模式工具行:「添加认证方式」(`E00D`)+ 认证方式下拉(`min-width: 100px`,每项「图标 + 名称」);
- 认证方式列表:每项两行:第一行 `20×20` 图标(主色)+「用{名称}继续」(14px Medium)+ 右侧(选中且编辑模式时显示)拖拽把手与删除按钮(`E671`,`IndianRed`,点击弹出 Flyout:「要删除此认证方式吗?」「此操作无法撤销。」+「删除」accent 按钮);第二行渲染该认证方式的输入控件(如密码框);列表项可垂直拖拽排序(水平阈值禁用、垂直阈值 `3px`);
- 底部:两列等宽按钮「取消」(`IsCancel`)与「确定」(accent,仅编辑模式显示);
- 认证完成必须通过 `CompleteAuthorizeCommand` 路由命令触发,成功后关闭并返回结果。
2. **密码认证控件**(`Controls/AuthorizeProvider/PasswordAuthorizeProvider.axaml:15-66`):密码输入框(`PasswordChar=●`,水印「密码」,`margin-top: 8px`,按 Enter 直接提交)+(编辑模式下)确认密码框(水印「确认密码」,失焦/输入时校验)+ 右侧「确定」圆形按钮(`32×32`、圆角 `16px`、accent,tooltip「确定」);错误提示行(红色):密码不正确。/ 确认密码与密码不一致。/ 请输入密码。;已设置密码时显示「密码已设定。」+「更改密码」按钮(`E7C9`)。
3. **JoinManagementDialog**(`Controls/JoinManagementDialog.axaml:12-64`):宽 `400`,`SizeToContent=Height`,`CanResize=false`,标题「加入管理」。内容 `margin: 16px`:标题「加入管理」+ 说明「将当前ClassIsland实例连接到管理组织。」+ 配置文件行(左「配置文件:{路径}」单行截断 + 右侧文件选择按钮,文件类型为集控配置文件)+ 字段「ID(可选)」+ 错误文本(红色,按需显示)+ 不确定进度条(工作中)+ 底部两列按钮「取消」(`IsCancel`)「连接」(默认按钮,accent,仅当配置已加载时可用)。
### 11.13 教学(教程)窗口组
1. **教学中心 `TutorialCenterWindow`**(`Views/TutorialCenterWindow.axaml:14-248`、`.cs:31-84`):`1240×550`,启用 Mica,标题「教学中心」,图标 `AppLogo.ico`。结构:
- 顶部 TabStrip(`navigation` 样式,`font-size: 20px`)按「教程组」切换;右上「停止当前教程」按钮(`F087`,仅教程运行中显示);
- 内容区背景 `LayerOnMicaBaseAltFillColorDefaultBrush`;`TabControl`(`navigation`)承载当前教程组的 StickyScrollViewer;
- 头部大图:最大高度 `200px`,横幅 `DefaultTutorialGroupBanner.webp` 右对齐;背景为 `#2bbdbc` 叠加 60° 旋转的 `#19ffffff` 棋盘格纹理(`DestinationRect 64×64`,格子 32×32);头部底部标题行(`margin: 12px 8px 18px`):组标题(TitleTextBlockStyle)+ 组描述(14px 次级);头部随滚动折叠,折叠高度等于标题行高度;
- 教程卡片网格:WrapPanel 自动换列(目标宽度 `328px`),卡片:圆角 `4px`、裁剪;横幅图高 `100px`(`UniformToFill`)+ 内容区(`padding: 12px 8px 12px`,间距 `6px`):标题 `16px` Medium、描述 `12px` 次级、底部进度条(高度紧贴底部,`max = 段落数`,`value = 已完成段落数`);
- 点击卡片打开右侧详情 Pane:`SplitView` 右置、`DisplayMode=Inline`、`OpenPaneLength=328px`、`CompactPaneLength=0`;Pane 内 `padding: 12px`:教程标题 `20px` Medium、描述、进度文本「已完成 X/Y 个段落。」、「播放教程」accent 按钮(教程运行中禁用)+「或者,选择下方的段落播放:」+ 段落列表(已完成段落标题加删除线、斜体、`opacity: 0.75`,并在标题后显示主色「已完成」;每项右侧悬停/选中时显示「播放」按钮 `EDB9`);
- 关闭窗口时若因用户操作则 `hide()`(保持单例),应用退出时正常销毁(`.cs:74-84`)。
2. **教程播放控制条 `TutorialControllerWindow`**(`Views/TutorialControllerWindow.axaml:1-46`):无系统装饰条(`SystemDecorations=BorderOnly`)、透明背景 + `AcrylicBlur`、`SizeToContent=WidthAndHeight`、`Topmost`、`ShowInTaskbar=false`、`CanResize=false`、标题「教程控制窗口」。内容:半透明底色 `SolidBackgroundFillColorBaseBrush`(`opacity: 0.5`)+ 横向排列(`margin: 12px 4px`,间距 `4px`):「教学进行中…」(`E959`,`opacity: 0.75`)+「跳过」按钮(`E444`,透明样式);跳过按钮点击弹出 Flyout(`max-width: 328px`,间距 `8px`):「跳过教程」(粗体)+「是否确定跳过本段落教程?您稍后可以在【主菜单】->【教学…】中重看本段教程。」+「跳过」accent 按钮。该窗口位置必须由主进程放在主界面附近(右下角、距屏幕边缘 `16px`【推断】),并在教程结束时销毁。
3. **教程编辑器 `TutorialEditorWindow`**(`Views/TutorialEditorWindow.axaml:15-377`):`WindowState=Maximized`,标题「教程编辑器」。结构:
- 菜单栏(跨 3 列):「文件」含 打开…(`Ctrl+O`)、保存…(`Ctrl+S`)、另存为…(`Ctrl+Shift+S`)、分隔线、教程章节信息…(`Ctrl+I`);「运行」含 运行当前教程(`F5`)、从当前段落开始运行(`Ctrl+F5`)、分隔线、停止运行(`Shift+F5`);
- 列定义 `220px, Auto, *, Auto, 328px`,行定义 `Auto, Auto, *, Auto`;
- 第 1 列:教程列表(顶部「添加/复制/删除」按钮,图标 `E00D/E58B/E61D`,删除为 `IndianRed`;列表项「图标 + 标题」,可垂直拖拽排序);
- 第 3 列:段落列表(同上按钮);第 5 列:语句 DataGrid(列:标题 `*`、内容 `3*`、目标选择器 `2*`;`RowHeaderWidth=24`,行头为拖拽把手;支持行拖拽排序);
- 最右侧 `328px`:选中对象的属性面板;教程组属性:ID/标题/描述;教程属性:ID/标题/描述/Banner(水印「(默认)」);段落属性:基本页(ID/标题/描述/TopLevel 类名/复选框「启用屏障」)+「进入事件」页;语句属性:基本页(标题/内容/头图/图标/目标控件选择器/教学提示摆放模式 14 种(Auto/Top/Bottom/Left/Right/TopRight/TopLeft/BottomRight/BottomLeft/LeftTop/LeftBottom/RightTop/RightBottom/Center)/指向目标/高亮目标/使目标具有模态/可等待被外部推动/Tag/使用 LightDismiss 代替右侧按钮/右侧按钮/左侧按钮)+「进入事件」「左按钮事件」「右按钮事件」三个行动编辑页 +「脚本」页(启用脚本开关 +「编辑脚本…」按钮打开脚本编辑器)。
- 关闭窗口时必须拦截并处理未保存修改(`Closing` 事件),窗口失焦时不得自动关闭。
4. **教程脚本编辑器 `TutorialScriptEditWindow`**(`Views/TutorialScriptEditWindow.axaml:1-29`):`800×520`,`MinWidth=480`、`MinHeight=320`,标题「编辑教程脚本」。顶部一行(`margin: 8px`,间距 `6px`):「Lua 教程脚本」(`E943`)+ 复选框「启用脚本」;主体为代码编辑器:显示行号、深色主题(背景 `#1E1E1E`、前景 `#D4D4D4`)、等宽字体 `JetBrains Mono, Cascadia Code, Consolas, Menlo, Monospace`、`font-size: 14px`、`margin: 0 8px 8px`。脚本语言为 Lua;失焦/输入时不得丢失内容,关闭时保存。
5. **教程行动类型(TutorialActionKind)映射表(必须逐值实现,JSON 数字不可改)**:
枚举定义:`ClassIsland.Core/Enums/Tutorial/TutorialActionKind.cs:6-44`,共 **9 项(0–8)**;模型字段:`TutorialAction.Kind`(`ClassIsland.Core/Models/Tutorial/TutorialAction.cs:18`)、`StringParameter`(同文件 `:23`,字符串参数)、`ActionSet`(同文件 `:29`,仅 `InvokeActionSet` 使用,序列化时空值省略)。ClassIsland 的 JSON 由 `System.Text.Json` 默认选项写出(`ClassIsland.Shared/Helpers/ConfigureFileHelper.cs:30-36` 只设置了 `WriteIndented`,未注册字符串枚举转换器),因此 `Kind` 在 JSON 中是**数字**;本实现读写教程 JSON 时必须使用 `Kind`(PascalCase)与数字值,**不得**改成 `kind` 或字符串。
| 数值 | 枚举名 | 中文语义(编辑器文案) | 触发来源 / 控制条入口 | 执行语义与边界(源码证据) |
|---|---|---|---|---|
| 0 | `None` | 无 | 任意动作数组 | 什么都不做(`TutorialService.cs:622-623`)。 |
| 1 | `NextSentence` | 下一句 | 语句右按钮 / LightDismiss / TeachingTip 关闭 / 聚光灯点击(均以参数 1 调用 `PostSentenceWorks`) | 若当前段还有下一句则进入下一句;否则尝试进入下一段;已是最后一段则停止教学(`TutorialService.cs:624-626`、`:678-691`;按钮参数注入 `:404-416`;`PostSentenceWorks` `:586-600`;聚光灯 `:503-506`)。**初始化动作中不执行**(`isInit` 守卫)。 |
| 2 | `PreviousSentence` | 上一句 | 语句左按钮(以参数 0 调用 `PostSentenceWorks`) | 若存在上一句则回退到上一句;否则尝试回退到上一段;再失败则停止教学(`TutorialService.cs:635-647`)。默认左按钮文字为空、默认动作为 `PreviousSentence`(`TutorialSentence.cs:84-94`)。初始化动作中**允许执行**(源码未加 `!isInit` 守卫)。 |
| 3 | `NextParagraph` | 下一段 | 任意动作数组 | 进入下一段并先把当前段落标记为已完成;若已是最后一段则停止教学(`TutorialService.cs:627-634`、`:693-707`)。**初始化动作中不执行**。 |
| 4 | `PreviousParagraph` | 上一段 | 任意动作数组 | 回退一段;已到第一段则停止教学(`TutorialService.cs:648-655`、`:709-724`)。**初始化动作中不执行**。 |
| 5 | `JumpParagraph` | 跳转到段落 | 任意动作数组 + `StringParameter` | 参数为段落路径:含 `/` 时按 `tutorialId/paragraphId` 全局查找,只有段落 id 时在当前教程内查找;查不到时**静默忽略**(`TutorialService.cs:656-663`、`ParseParagraphPath` `:519-553`)。编辑器仅在选中该项时显示参数输入框(`Controls/TutorialEditor/TutorialActionsEditControl.axaml:64-79`)。**初始化动作中不执行**。 |
| 6 | `Stop` | 停止 | 任意动作数组 | 立即 `StopTutorial()`:关闭控制条、清理教学提示、标记当前段落完成、清空当前教程/段落/句子(`TutorialService.cs:664-665`、`:565-575`)。初始化动作中**也执行**。 |
| 7 | `InvokeActionSet` | 执行行动 | 任意动作数组 + `ActionSet` | 调用自动化服务执行动作集;`ActionSet == null` 时忽略(`TutorialService.cs:667-668`)。编辑器在切换到该项时必须初始化 `ActionSet`(`TutorialActionsEditControl.axaml.cs:62-65`)。初始化动作中**也执行**。 |
| 8 | `InvokeUri` | 执行 Uri | 任意动作数组 + `StringParameter` | 把参数作为 Uri 交给 Uri 导航服务(支持 `classisland://` 内部路由);参数非法时源码会抛异常(`TutorialService.cs:670-671`)——本实现必须捕获、写 warning 日志并继续,不得崩溃。初始化动作中**也执行**。 |
| ≥9 | 未知值 | — | — | 源码 `default:` 抛 `ArgumentOutOfRangeException`(`TutorialService.cs:673-674`);**本实现必须改为:忽略该动作并写 warning 日志(含 Kind 数值与动作索引),继续执行后续动作**。 |
执行上下文与顺序(必须一致):
- 动作数组共 4 组:段落 `InitializeActions`(段落开始时执行,`TutorialService.cs:244-251`)、语句 `InitializeActions`(语句开始时执行,`:362-379`)、语句 `LeftButtonActions`、语句 `RightButtonActions`;每组内按数组顺序**同步串行**执行(`:608-616`),执行期间 `_isInvokingActions=true`,此时外部推进(`Next` 命令/LightDismiss)必须被忽略(`:202-212`)。
- 「初始化动作」等价于 `isInit=true`:`NextSentence`/`NextParagraph`/`PreviousParagraph`/`JumpParagraph` 被跳过,其余照常执行;`LeftButtonActions`/`RightButtonActions` 等价于 `isInit=false`。
- 控制条(`TutorialControllerWindow`)的「跳过」按钮**不经过 Kind 枚举**:它等价于 `SkipTutorial()`(把当前段落标记为已完成并停止教学,`TutorialControllerWindow.axaml.cs:54-57`、`TutorialService.cs:577-584`)。
- Lua 脚本不是 Kind 的执行者,但可在 5 个钩子里干预:`onInitializing` 返回 `false` 会中止该语句初始化(**不执行** InitializeActions)、`onLeftButtonClicked` 返回 `false` 会阻止左按钮动作、其余钩子失败只记错误;脚本全局变量固定为 `service`(教程服务,可直接调 `service:PostSentenceWorks(0|1)`、`service:JumpToParagraph("tutorialId/paragraphId")`、`service:InvokeActions(...)`)与 `context`(`TutorialService.cs:454-500`、`:586-616`)。脚本异常必须弹出错误 Toast 并写 error 日志,不得中断整个教程。
JSON 兼容性与 TypeScript 常量(必须照抄):
```json
{ "Kind": 5, "StringParameter": "classisland.getStarted/welcome", "ActionSet": null }
```
```ts
// src/shared/tutorial-action-kind.ts —— 数字必须与 ClassIsland 的 TutorialActionKind 完全一致
export const TutorialActionKind = {
None: 0,
NextSentence: 1,
PreviousSentence: 2,
NextParagraph: 3,
PreviousParagraph: 4,
JumpParagraph: 5,
Stop: 6,
InvokeActionSet: 7,
InvokeUri: 8,
} as const;
export type TutorialActionKindValue = (typeof TutorialActionKind)[keyof typeof TutorialActionKind];
// 读取时兼容三种来源:数字(ClassIsland 原生)、枚举名字符串(插件手写)、未知值
export function normalizeTutorialActionKind(raw: unknown): TutorialActionKindValue | null;
```
读取规则:字段名必须以 `Kind`/`StringParameter`/`ActionSet` 精确匹配(.NET 默认 `PropertyNameCaseInsensitive = false`,写错大小写会读不到);`Kind` 既接受 `0–8` 数字,也接受枚举名字符串(大小写敏感)作为容错;**写出时必须写数字**,以保证导回 ClassIsland 时兼容。未知值/未知名字符串:`normalizeTutorialActionKind` 返回 `null`,调用方跳过该动作并在日志中记录 `unknown TutorialActionKind: <raw>`(warning 级别),**禁止崩溃或静默改变 Kind**。
### 11.14 日志窗口 `AppLogsWindow`
源:`Views/AppLogsWindow.axaml:1-178`、`.cs`。
1. **窗口参数**:`1200×600`,启用 Mica,标题「日志」,单例(关闭即 `hide()`,重复打开时 `show()`+`focus()`)。
2. **布局**:三行(`Auto, *, Auto`):
- 顶部左侧为筛选 Chip 列表(多选可切换):灾难(`E84E`)、错误(`E808`)、警告(`F430`)、信息(`E9E4`)、调试(`E2C7`)、追踪(`E50F`);默认选中级别必须与日志级别配置一致【推断:默认全部开启】;
- 顶部右侧搜索框(水印「筛选日志…」,`min-width: 200px`),输入即时过滤;
- 中部 DataGrid(只读、水平网格线、背景 `LayerOnMicaBaseAltFillColorDefaultBrush`,`font-size: 13px`):列「时间」(Auto,`margin: 2px 9px 0`)、「等级」(Auto:胶囊样式——灾难/错误使用 `SystemFillColorCriticalBackgroundBrush` + 粗体,警告使用 `SystemFillColorCautionBackgroundBrush` + 粗体,调试/追踪 `opacity: 0.75`,均配图标与中文级别名)、「区域」(Auto,`max-width: 250px`,可换行可选择)、「内容」(`*`,可换行可选择);
- 底部右对齐按钮:清空日志(`E61D`)、复制选中(`E58B`),均为透明按钮。
3. **行为**:日志必须来自主进程统一日志总线(`IPC` 推送),行数上限固定为 `10000` 条并在超出时裁掉最旧条目【推断:上限值由工程约定给出,源码未硬编码】;关闭时必须取消订阅、停止追加,避免内存泄漏。
### 11.15 其余窗口与对话框(逐个规格)
1. **配置文件错误窗口 `ConfigErrorsWindow`**(`Views/ConfigErrorsWindow.xaml:1-106`):`1200×600`,标题「配置文件错误」,纯色背景 + HarmonyOS Sans 14。结构:第 0 行 ToolBar(`ToolBarTray.IsLocked=true`)含「恢复备份」按钮(`Restore` 图标);第 1 行说明文本「下方的配置文件因意外断电等原因已损坏且无法加载,现已自动加载备份或使用默认值。您可以重启到恢复模式并从备份中恢复配置文件。」;第 2 行 DataGrid(只读、像素滚动、禁止重排列)四列:状态图标(警告=黄色 `WarningOutline`,错误=红色 `ErrorOutline`)、「路径」、状态文本(警告「已恢复至上次正确的配置」/错误「已重置为默认配置」)、「操作」(复制详细信息按钮)。打开方式:设置→存储页 InfoBar 的「详细信息…」或 URI `classisland://app/config-errors`。
2. **实例已存在窗口 `InstanceExistedWindow`**(`Views/InstanceExistedWindow.xaml:1-54`):`800×450`,`CenterScreen`,`ResizeMode=NoResize`,标题「ClassIsland 正在运行」。三行:说明文本(两段:已在运行请通过托盘图标操作;若看不到主界面可能是选择了隐藏主界面、规则隐藏或自动化修改)、演示图(`OpenTaskBarMenuDemo`,指引用户点托盘)、右下按钮「重启应用」(图标 `Restart`)与「我知道了」(图标 `Close`,纯色背景按钮样式)。
3. **窗口选择器 `WindowsPicker`**(`Views/WindowsPicker.xaml:1-149`):`1300×600`,标题「选择壁纸窗口」,用于选择壁纸层窗口类名。结构:第 0 行 Material ColorZone 标题栏(PrimaryMid,Dp4,`padding: 16px`):「选择窗口」(Headline6)+ 右侧「刷新」按钮(`Refresh`,24px)+「更多选项…」弹出框(含复选框「仅显示全屏窗口」);第 1 行窗口缩略图网格(WrapPanel):每项含截图(`height: 150px`,`Uniform`)、左下角 `32×32` 应用图标(圆角 4)、右下角选中标记(`40×40` 圆形主色 + 白色 Check,带 `BlurRadius=6, ShadowDepth=0` 阴影)、下方四行文本:描述(18px Bold)、类名、窗口标题、进程名(均 13px);第 2 行分隔线;第 3 行底栏(`margin: 8px`):`WindowMaximize` 图标 + 文本框(Hint「窗口类名」)+ 右侧「取消」「确定」按钮(`Cancel`/`Check` 图标);加载中显示全屏遮罩 + `StarRailLoadingControl`。
4. **壁纸预览窗口 `WallpaperPreviewWindow`**(`Views/WallpaperPreviewWindow.xaml:1-74`):`800×450`,`CenterOwner`,标题「壁纸提取结果预览」。内容:整窗壁纸图(`Uniform`、高质量缩放);底部悬浮操作条(半透明底 `opacity: 0.8`,`margin: 8px`):「刷新」按钮(左,工作中禁用)、「关闭」按钮(右,`IsCancel`);工作中底部显示不确定进度条;数据未就绪时整窗显示加载遮罩(`TeyvatLoadingControl` 右下角)。
5. **顶层特效窗口 `TopmostEffectWindow`**(`Views/TopmostEffectWindow.axaml:1-35`):`1×1`、`Topmost`、无边框(`SystemDecorations=None`)、背景透明,标题「顶层效果窗口」。它在运行时会由主进程调整为覆盖**整个屏幕**(含任务栏)的透明置顶窗口,用于全屏提醒特效;内容为 `ItemsControl`(面板为 `Grid`,所有特效控件叠放并拉伸),每个特效控件外层包 `Border` 拉伸。关闭行为必须拦截(除应用退出外不关闭)。
6. **清除通知按钮窗口 `ClearNotificationButtonWindow`(不实现)**:该窗口在 ClassIsland 2.1 中由 `#if false` 包裹、内容仅为空按钮,属于遗留未完成代码。本实现**不得**照搬该窗口;清除提醒必须在主界面(岛)的右键菜单中提供「清除全部提醒」菜单项(该菜单的完整交互归 part 04 / part 07)。源码仅作反例记录:`Views/ClearNotificationButtonWindow.xaml:1-15`、`Views/ClearNotificationButtonWindow.xaml.cs`。
7. **翻新范围对话框 `RefreshingScopesConfigDialog`**(`Views/RefreshingScopesConfigDialog.axaml:1-60`):`400×600`,`CenterOwner`,`ShowAsDialog=true`,`CanResize=false`,标题「编辑翻新范围」。三行(`Auto * Auto`,`margin: 12px`):头部「选择翻新时要保留的内容」(Subtitle)+「以下内容在翻新时不会被删除。」(次级);TabControl(compact)两个页签「课表」「时间表」,各为多选列表(每项 CheckBox + 名称,`SelectionMode=Multiple,Toggle`);底部(`margin: -12px 0 -12px -12px`,背景 `LayerOnMicaBaseAltFillColorDefaultBrush`)右侧「确定」accent 按钮(`IsCancel=true`)。
8. **窗口规则调试工具 `WindowRuleDebugWindow`**(`Views/WindowRuleDebugWindow.axaml:1-30`):`400` 宽、`SizeToContent=Height`、`Topmost`、启用 Mica,标题「窗口规则调试工具」。内容为可选择文本的多行信息:窗口句柄、窗口类名、窗口标题、窗口状态、窗口进程名(每行「标签:值」)。窗口加载后必须持续监听前台窗口变化并刷新。
9. **截图辅助窗口 `ScreenshotHelperWindow`**(`Views/ScreenshotHelperWindow.axaml:1-37`,仅开发/内部):标题「截图」。四个字段:窗口下拉(每项「标题 (Id=…) [Type]」)、保存文件夹文本框、缩放数值框、底部「刷新」「截图」按钮(截图为主色)。实现可选,但不得在正式版暴露给普通用户。
10. **功能调试窗口 `FeatureDebugWindow`**(`Views/FeatureDebugWindow.xaml:1-246`,仅开发):`800×450`,标题「FeatureDebugWindow」,TabControl 多页(WeatherIcon、ScheduleCalendar、MarkdownTest、SecureDesktop、AuthorizeWindow 等),用于内部测试;正式构建必须移除入口。
11. **开发者门户 `DevPortalWindow`**(`Views/DevPortalWindow.axaml:1-269`,仅开发):`1200×700`,`CenterScreen`,启用 Mica,标题「DevPortal」。顶部 TabControl(`navigation` 样式,覆盖整窗,距顶 `48px` 的底色层),页签:首页(调试时间偏移、测试选择器解析、测试教程、贴纸高亮演示、StartSplash/EndSplash)、控件(ChipListBox、Toast、Shimmer、平行四边形/斜切遮罩动画、贴纸、TutorialBarrier 等)、提醒、LDV、OOBE_Intro、Markdown(左编辑器右预览)、调课日历、WeatherIcon、StickyScrollViewer、竖直抽屉、SDG。正式构建必须不可达。
12. **通知中心**:ClassIsland **没有**独立的通知中心窗口;提醒以「提醒队列 + 主界面提醒气泡 + 全屏强调特效」形式呈现(见 part 07)。实现者不得创建通知中心窗口,以免与主界面提醒语义冲突;若要提供历史通知列表,必须作为可选扩展并明确不属于 ClassIsland 原版功能。
### 11.16 全局快捷键与关闭行为总表
| 场景 | 快捷键 | 行为 |
|---|---|---|
| 任意窗口 | `F3` / `Shift+F3` | 切换/升级渲染调试浮层(FPS → FPS+布局/渲染耗时) |
| 任意窗口 | `F6` / `Shift+F6` | 切换触摸模式 / 同时抑制自动触摸判定 |
| 任意窗口 | `F7` | 关闭当前窗口所有 Toast |
| 设置窗口 | `Esc` | 关闭最上层抽屉/菜单,不关闭窗口 |
| 换课窗口 | `Esc` | 取消并关闭 |
| 授权窗口 | `Enter`(密码框内) | 提交认证 |
| 数据迁移/欢迎向导 | `Enter`(默认按钮) | 执行默认操作 |
| 教程编辑器 | `Ctrl+O`/`Ctrl+S`/`Ctrl+Shift+S`/`Ctrl+I` | 打开/保存/另存为/章节信息 |
| 教程编辑器 | `F5`/`Ctrl+F5`/`Shift+F5` | 运行当前教程/从当前段落运行/停止运行 |
| Excel 导入导出表格 | `Ctrl+C`/`Ctrl+V`/`Ctrl+X`/`Del` | 复制/粘贴/剪切/清除内容 |
| 日志窗口 | 无 | 通过筛选与搜索操作 |
关闭行为必须满足:
1. 非模态单例窗口(设置、日志、教学中心):关闭 = 隐藏;应用退出时统一销毁。
2. 模态窗口(欢迎向导、数据迁移、Excel 导入/导出、更新进度、授权、翻新范围):关闭 = 销毁并回传结果;更新进度窗口在更新过程中必须拒绝关闭。
3. 换课窗口:`Esc`/取消 = 直接关闭且不写入数据;确认后关闭并持久化。
4. 顶层特效窗口:仅由提醒系统控制生命周期,用户不可关闭。
5. 崩溃窗口:必须允许用户选择「忽略」继续(非 critical)或「退出/重启」。
### 11.17 验收标准
实现完成后必须逐项验证(全部通过才算合格):
1. 首次启动进入 OOBE:窗口 `800×600` 居中、不可缩放;片头字母/方块动画时长与顺序符合 11.2 第 5 条;欢迎页「下一步」可前进;OOBE 关闭时弹出「退出 ClassIsland」确认框,取消后仍停留在向导内。
2. 许可页未勾选两个复选框时「下一步」不可用;勾选后可用;系统页「在桌面创建换课快捷方式」在未勾选「注册 Url 协议」时不可用。
3. 完成页 Carousel 有 3 页,过渡为水平滑动+交叉淡入 `300ms`;最后一页按钮变为「完成」,点击后关闭窗口并按勾选项创建快捷方式。
4. 开启「显示启动加载界面」后启动:闪屏为 `400×100` 顶部居中、无边框、半透明、`17px` 内边距;Logo 与状态行分别延迟 `0/150ms` 滑入;进度条从左侧填充至 100% 后闪屏关闭。
5. 制造一次未处理异常:崩溃窗口 `800×450`,错误文本可选择复制;「反馈问题」打开 GitHub issue 页面且 URL 携带 stacktrace/app_version/os_version;critical 异常时「退出应用」使进程以退出码 1 结束。
6. 以恢复模式启动:恢复窗口 `450×600`,主页 10 个操作项齐全;进入「恢复备份」后列表、两种恢复模式单选、右下按钮布局与禁用规则符合 11.5 第 3 条。
7. 打开换课窗口:`MinWidth=730`;选中课程后自动进入第 2 步;「替换/交换」切换时右侧内容垂直滑动 `300ms`;存在临时层时确认「覆盖当前的临时层」;勾选「永久换课」后直接改写原课表;取消后课表未变化。
8. 打开 Excel 导入:拖入 `.xlsx` 后进入页面 2(欢迎),按导航映射可完整走到页面 16「完成」;选择非单行/单列区域时出现红色「选择区域无效。」且不可下一步;选区模式页头有呼吸高亮动画;导入结果写入档案后可在课表中看到对应课程;实现中不得出现 ReoGrid/WPF/`System.Windows.Forms` 依赖。
9. 打开 Excel 导出:窗口最大化,右侧三个页签在右边缘;「样式」页可改字体颜色/背景/框线并「刷新内容」;保存后的 `.xlsx` 在 Excel/WPS 中可正常打开且课表内容、合并单元格、边框与列宽行高与界面一致。
10. 打开数据迁移:窗口 `800×600` 不可缩放且为模态;导入/导出两组文案随模式切换;ClassIsland 1.x 导入需选择旧版 `ClassIsland.exe`。
11. 打开日志窗口:五类筛选与搜索同时生效;灾难/错误/警告行有不同背景色;「复制选中」只复制选中行;关闭后再次打开仍为同一窗口实例且日志继续累积。
12. 打开教学中心:`1240×550`;教程卡片按 `328px` 目标宽度自动换列;点击卡片右侧滑出 `328px` 详情栏;「播放教程」启动后出现顶部「停止当前教程」按钮与置顶「教学进行中…」控制条。
13. 所有窗口在 Windows 11 上呈现 Mica 背景、无系统标题栏;在不支持 Mica 的环境退化为纯色背景且不出现黑边或透视异常。
14. 所有模态窗口在有父窗口时居中于父窗口;所有窗口的 `Esc`、`Enter`、`F3/F6/F7` 行为符合 11.16 表。
15. 教程行动类型:构造覆盖 `Kind = 0–8` 全部 9 个值的测试教程 JSON(含 `JumpParagraph` 的两种路径形式、`InvokeUri` 的 `classisland://` 路由、`InvokeActionSet` 的空/非空 `ActionSet`、以及段落与语句两处 `InitializeActions` 中的 `NextSentence/NextParagraph/PreviousParagraph/JumpParagraph` 应被跳过),逐值验证行为与 11.13 第 5 条映射表一致;再注入 `Kind = 9`、`Kind = -1`、`Kind = "NotAKind"` 与 `kind`(小写键)等异常数据,要求:不崩溃、不卡死、未知动作被跳过、日志中出现 `unknown TutorialActionKind` warning,且后续动作照常执行。