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

352 lines
70 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
## 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,且后续动作照常执行。