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

70 KiB
Raw Permalink Blame History

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 常量(必须照抄):

    { "Kind": 5, "StringParameter": "classisland.getStarted/welcome", "ActionSet": null }
    
    // 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,且后续动作照常执行。