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

62 KiB
Raw Blame History

13. 数据模型、存储、备份与迁移

13.1 存储总则(必须遵守)

  1. 本应用是纯本地单机应用,所有设置、档案、课表、组件、自动化、提醒历史、主题、插件配置必须存在本机,不得依赖任何服务器;断网、无账号、首次启动都必须完整可用。
  2. 存储根目录必须是 app.getPath('userData')(Windows 为 %APPDATA%\ClassIsland,macOS 为 ~/Library/Application Support/ClassIsland,Linux 为 ~/.config/ClassIsland)。不得把用户数据写进安装目录(安装目录可能只读、且升级会被整体替换)。依据:repos/ClassHelper-master/packages/desktop-client/src/main/config.ts:165-167(path.join(app.getPath('userData'), 'config.json'))与 repos/ClassIsland/ClassIsland.Core/CommonDirectories.cs:16-28(ClassIsland 的 AppRootFolderPath/Config/Logs 划分)。
  3. 所有持久化文件必须是 UTF-8 无 BOM、LF 换行、两空格缩进的 JSON;字段名大小写必须按第 13.3/13.4 节的约定(ClassIsland 兼容文件用 PascalCase,本应用运行期配置用 camelCase)。读入时必须同时容忍紧凑格式(无缩进、无换行)与带注释之外的一切合法 JSON;不得写注释(ClassIsland 的 examples/example.json 带注释只是示例,真实加载器不支持注释,见 repos/ClassIsland/examples/example.json:1-2)。
  4. 所有写入必须原子化:写 <file>.tmp → fsync → rename 覆盖目标;禁止直接 writeFile 覆盖正在被读的文件。依据:repos/ClassIsland/ClassIsland.Shared/Helpers/ConfigureFileHelper.cs:182-188(FileStream.Flush(true) 后关闭)。
  5. 每次成功读取 JSON 后必须把原文件复制为 <file>.bak;读取失败时自动回退加载 .bak,成功后把 .bak 复制回目标(对齐 ConfigureFileHelper.LoadConfigUnWrapped 的语义,见 ConfigureFileHelper.cs:63-96)。若 .bak 也失败,必须使用全默认值并把错误加入"配置错误列表"(ConfigError{ Path, Code, IsFallback, Exception },见 repos/ClassIsland/ClassIsland.Shared/Models/ConfigError.cs),在设置页"诊断/存储"中展示。
  6. 所有配置对象必须"缺失字段用默认值补齐、未知字段忽略、越界值夹紧"。禁止因为新增字段就让老配置文件加载失败。依据:repos/ClassIsland/ClassIsland/Services/SettingsService.cs:63-118(加载失败只记日志并继续)与 config.ts:198-233(逐字段归一化)。
  7. 所有写入必须经过单一写入入口(storage.ts 的 saveXxx()),禁止业务代码直接 fs.writeFile;该入口负责合并写、备份、事件通知。依据:config.ts:235-346。

13.2 目录与文件清单(必须逐项命名一致)

<userData>/                                  # app.getPath('userData')
├─ config.json                               # 运行期设置(camelCase;对应 ClassIsland Settings.json)
├─ Settings.json                             # ClassIsland 格式兼容文件(PascalCase;仅只读导入/导出,运行期不读写)
├─ data/
│  ├─ profiles/
│  │  ├─ Default.json                        # Profile 档案(PascalCase,与 ClassIsland 同构)
│  │  └─ <档案名>.json
│  ├─ component-layouts/
│  │  └─ Default.json                        # ComponentProfile(PascalCase)
│  ├─ automations/
│  │  └─ Default.json                        # Workflow[] 数组(PascalCase)
│  ├─ themes/
│  │  ├─ <主题Id>/                            # 主题包解压目录(manifest、样式、资源)
│  │  └─ EnabledThemes.json                  # string[],启用中的主题 Id
│  ├─ plugins/                               # 插件解压目录:<插件Id>/
│  ├─ plugin-data/                           # 插件私有存储:<插件Id>/storage.json
│  ├─ notification-history.json              # 通知历史(用于提醒历史视图)
│  └─ generated/
│     └─ default-subjects.json               # 首次启动写入的默认科目表
├─ backups/                                  # 自动/手动备份 zip
├─ logs/
│  └─ main.log                               # 超过 1MB 轮转为 main.log.1
├─ cache/
│  ├─ plugin-index/                          # 插件市场索引缓存
│  ├─ theme-packages/                        # 主题包下载缓存 <id>.zip
│  └─ update/                                # 更新清单/元数据缓存
└─ updates/                                  # 待安装更新包、policy.json、install-result.json

依据:repos/ClassIsland/ClassIsland/Services/PluginService.cs:35-44(插件三目录)、XamlThemeService.cs:47-49(主题两目录 + EnabledThemes.json)、AutomationService.cs:27-32(自动化目录 + <CurrentAutomationConfig>.json)、ComponentsService.cs:28-58(ComponentLayouts/<CurrentComponentConfig>.json)、ProfileService.cs:48-58(Profiles/ 与 <SelectedProfile>)、FileFolderService.cs:23-49(需要预创建的目录清单)、auto-update.ts:131-133(updates/)、logger.ts:32-39(logs/main.log)、config.ts:165-167(config.json)。

13.3 config.json 完整 schema(字段级,含默认值)

config.json 是 ClassIsland Settings.json 的超集;下表的"ClassIsland 对应字段"必须保留语义,默认值必须与源码一致。任何字段缺失都必须落到默认值,任何越界值都必须夹紧到合法区间。

Settings.json 兼容层(强制):运行期只读写 config.json;Settings.json 只作为「ClassIsland 格式导入/导出兼容层」存在——导入时只读解析(PascalCase → 本实现字段映射),导出时生成一份 ClassIsland 可识别的 Settings.json(PascalCase)。禁止双向双写(不得同时写 config.json 与 Settings.json,否则两份文件必然会分叉)。导入映射必须保留历史拼写,尤其是 IsIslandSeperated(ClassIsland 源码就是少一个 a,repos/ClassIsland/ClassIsland/Models/Settings.cs:1852)——本实现内部字段可写作 isIslandSeparated,但导入/导出映射表必须双向接受原拼写。【推断】

13.3.1 外观与主界面

字段 类型 默认值 合法区间/枚举 ClassIsland 依据
theme number 2 0 亮 / 1 暗 / 2 跟随系统 Settings.cs:32
primaryColor string #00BFFF #RRGGBBAA Settings.cs:35(DeepSkyBlue)
secondaryColor string #7FFFD4 同上 Settings.cs:36(Aquamarine)
isCustomBackgroundColorEnabled boolean false — Settings.cs:33
backgroundColor string #000000FF 同上 Settings.cs:34(Colors.Black)
isCustomForegroundColorEnabled boolean false — Settings.cs:202
customForegroundColor string #1E90FF 同上 Settings.cs:203(DodgerBlue)
opacity number 0.5 0.1–1 Settings.cs:64
scale number 1.0 0.75–2.5 Settings.cs:86
radiusX / radiusY number 8 / 8 0–20 Settings.cs:183-184
mainWindowFont string 跟随系统默认字体 合法字体族名 Settings.cs:95
mainWindowFontWeight number 500(Medium) 100–900 Settings.cs:101
mainWindowSecondaryFontSize number 14 8–96 Settings.cs:197
mainWindowBodyFontSize number 16 同上 Settings.cs:198
mainWindowEmphasizedFontSize number 18 同上 Settings.cs:199
mainWindowLargeFontSize number 20 同上 Settings.cs:200
mainWindowLineVerticalMargin number 5 0–150 Settings.cs:219
isIslandSeperated boolean false — Settings.cs:1852(拼写保留)
reduceProgressAccuracy boolean true — Settings.cs:1853
animationLevel number 1 0 关闭 / 1 标准 / 2 全部 Settings.cs:1851
isScreenRecordingModeEnabled boolean false — Settings.cs:1861
isWindowCaptureBlockingEnabled boolean false — Settings.cs:1862

13.3.2 窗口、停靠与隐藏

字段 类型 默认值 说明 依据
windowDockingLocation number 1 0–5:左上/中上/右上/左下/中下/右下 Settings.cs:48
windowDockingOffsetX/Y number 0/0 停靠偏移(px) Settings.cs:49-50
windowDockingMonitorIndex number 0 多显示器索引 Settings.cs:51
windowLayer number 1 窗口层级(普通/置顶/桌面) Settings.cs:52
isMouseClickingEnabled boolean false 鼠标穿透(true = 点击穿透) Settings.cs:53
hideOnClass boolean false 上课自动隐藏 Settings.cs:45
hideOnMaxWindow boolean false 有最大化窗口时隐藏 Settings.cs:63
hideOnFullscreen boolean false 全屏时隐藏 Settings.cs:54
excludedFullscreenWindow string[] [] 全屏检测排除的进程名 Settings.cs:56-59
hideMode number 0 0 基本(上课/最大化/全屏);1 高级(规则集) Settings.cs:185
hideRules Ruleset 空规则集 见 13.5.3 Settings.cs:186
useRawInput boolean false 原始输入(兼容某些输入法) Settings.cs:192
isMouseInFadingEnabled boolean true 鼠标进入时淡化 Settings.cs:193
isMouseInFadingReversed boolean false 反向淡化 Settings.cs:194
touchInFadingDurationMs number 0 触摸淡化时长 Settings.cs:195
isCompatibleWindowTransparentEnabled boolean false 兼容透明(老显卡) Settings.cs:196
windowTopmostRecheckMode number 0 置顶复查策略 Settings.cs:1860
showCurrentLessonOnlyOnClass boolean false — Settings.cs:209
isSwapMode boolean true 换课模式 Settings.cs:210

坐标口径(强制):windowDockingOffsetX/Y、scale、radiusX/Y、所有字号与组件尺寸/间距字段一律以 DIP(CSS px) 存储与解释;窗口 bounds 也按 DIP 交给 Electron。禁止把 devicePixelRatio 乘进这些字段;只有截图/像素级视觉验收时才用 devicePixelRatio 把 DIP 换算成物理像素(与 04 部分同一口径)。

13.3.3 课程与时间

字段 类型 默认值 依据
selectedProfile string "Default.json" Settings.cs:66
singleWeekStartTime string(ISO) 首次启动时写入当天 00:00 Settings.cs:60(与其相关的周次计算见 ProfileService)
multiWeekRotationOffset number[5] [-1,-1,0,0,0] Settings.cs:60
multiWeekRotationMaxCycle number 4 Settings.cs:61
classPrepareNotifySeconds number 60 Settings.cs:43
isClassPrepareNotificationEnabled boolean true Settings.cs:47
isClassChangingNotificationEnabled boolean true Settings.cs:46
isClassOffNotificationEnabled boolean true Settings.cs:55
showDate boolean true Settings.cs:44
showExtraInfoOnTimePoint boolean true Settings.cs:103
extraInfoType number 0 Settings.cs:104(0 持续时间/1 已过/2 剩余/3 已过百分比/4 剩余时间/5 倒计时)
isCountdownEnabled boolean true Settings.cs:105
countdownSeconds number 60 Settings.cs:106
extraInfo4ShowSecondsSeconds number 300 Settings.cs:107
defaultOnClassTimePointMinutes number 40 Settings.cs:108
defaultBreakingTimePointMinutes number 10 Settings.cs:109
isNonExactCountdownEnabled boolean false Settings.cs:221

13.3.4 提醒、语音与时间同步

字段 类型 默认值 依据
isNotificationEnabled boolean true Settings.cs:91
notificationProvidersEnableStates map<string,bool> {} Settings.cs:88
notificationProvidersPriority string[] [] Settings.cs:89
notificationProvidersSettings map<string,any> {} Settings.cs:90
notificationProvidersNotifySettings map<string,NotificationSettings> {} Settings.cs:143
notificationChannelsNotifySettings map<string,NotificationSettings> {} Settings.cs:1846
isNotificationEffectEnabled boolean true Settings.cs:142
isNotificationTopmostEnabled boolean true Settings.cs:149
notificationEffectRenderingScale number 1.0 Settings.cs:150
isNotificationEffectRenderingScaleAutoSet boolean false Settings.cs:151
isNotificationSoundEnabled boolean true Settings.cs:144
notificationSoundPath string "" Settings.cs:145
notificationSoundVolume number 1.0 Settings.cs:182
allowNotificationSpeech/Effect/Sound/Topmost boolean false/true/false/true Settings.cs:153-156
notificationUseStandaloneEffectUiThread boolean true Settings.cs:1837
isSpeechEnabled boolean true Settings.cs:135
speechVolume number 1.0 Settings.cs:136
speechSource number 0 Settings.cs:137
selectedSpeechProvider string "classisland.speech.edgeTts" Settings.cs:1847
edgeTtsVoiceName string "zh-CN-XiaoxiaoNeural" Settings.cs:138
isSystemSpeechSystemExist boolean false(运行时探测) Settings.cs:133
exactTimeServer string "ntp.aliyun.com" Settings.cs:139
isExactTimeEnabled boolean true Settings.cs:140
timeOffsetSeconds number 0 Settings.cs:141
isTimeAutoAdjustEnabled boolean false Settings.cs:146
timeAutoAdjustSeconds number 0 Settings.cs:147
lastTimeAdjustDateTime string(ISO) 当前时间 Settings.cs:148

13.3.5 组件、自动化、主题

字段 类型 默认值 依据
currentComponentConfig string "Default" Settings.cs:159
isAutomationEnabled boolean false Settings.cs:160
currentAutomationConfig string "Default" Settings.cs:162
isAutomationWarningVisible boolean true Settings.cs:161
selectedPlatte string #1E90FFFF Settings.cs:80(DodgerBlue)
selectedPlatteIndex number 0 Settings.cs:81
wallpaperColorPlatte string[] 5 个 DodgerBlue Settings.cs:82
colorSource number 2 Settings.cs:83(0 无/1 壁纸/2 手动)
wallpaperClassName string "" Settings.cs:84
targetLightValue number 0.6 Settings.cs:85
isWallpaperAutoUpdateEnabled boolean false Settings.cs:92
wallpaperAutoUpdateIntervalSeconds number 60 Settings.cs:93
weatherIconId string "classisland.weatherIcons.lucide" Settings.cs:1849
isRollingComponentWarningVisible boolean true Settings.cs:1850
isThemeWarningVisible boolean true Settings.cs:1848
isThemeSeparateInfoVisible boolean true Settings.cs:1875

13.3.6 天气

字段 类型 默认值 依据
cityId string "weathercn:101010100" Settings.cs:99
cityName string "北京 (北京, 中国)" Settings.cs:100
lastWeatherInfo object {} Settings.cs:98
weatherLatitude / weatherLongitude number 0/0 Settings.cs:1838-1839
weatherLocationSource number 0 Settings.cs:1840
autoRefreshWeatherLocation boolean false Settings.cs:1841
noTLSWeatherRequests boolean false Settings.cs:1842
excludedWeatherAlerts string[] [] Settings.cs:158
selectedMiniInfoProvider string? 内置天气提供者 Guid Settings.cs:97
miniInfoProviderSettings map<string,any> {} Settings.cs:96

13.3.7 更新、插件、备份、诊断

字段 类型 默认值 依据
updateMode number 3 Settings.cs:77
autoInstallUpdateNextStartup boolean true Settings.cs:78
selectedUpdateChannelV2 string "stable" Settings.cs:217
selectedUpdateMirrorV2 string "main" Settings.cs:216
selectedUpdateChannelV3 string(Guid) Guid.Empty Settings.cs:1854
lastCheckUpdateTime string(ISO) DateTime.MinValue Settings.cs:75
lastUpdateStatus number 0(UpToDate) Settings.cs:76
updateArtifactHash string "" Settings.cs:157
ignoredUpdateVersion string "" config.ts:76(本应用字段,兼容旧行为)
userPluginIndexes string[] [] Settings.cs:167
additionalPluginIndexes map<string,string> {} Settings.cs:168
pluginIndexes object[] [] Settings.cs:169
pluginIndexSelectedMirrors map<string,string> {} Settings.cs:166
officialSelectedMirror string "github" Settings.cs:170
officialIndexMirrors map<string,string> {github: https://github.com, ghproxy: …, moeyy: …} Settings.cs:171-176
ignoreSslForPluginMirrors boolean false Settings.cs:177
isPluginMarketWarningVisible boolean true Settings.cs:204
isPluginsAutoUpdateEnabled boolean true Settings.cs:1858
isPluginsUpdateNotificationEnabled boolean true Settings.cs:1859
autoDisableCorruptPlugins boolean true Settings.cs:1844
corruptPluginsDisabledLastSession boolean false Settings.cs:1845
lastRefreshPluginSourceTime string(ISO) MinValue Settings.cs:180
isAutoBackupEnabled boolean true Settings.cs:187
lastAutoBackupTime string(ISO) 当前时间 Settings.cs:188
autoBackupLimit number 16 Settings.cs:190
autoBackupIntervalDays number 7 Settings.cs:191
backupFilesSize string "计算中..." Settings.cs:189
firstLaunchTime string(ISO) 当前时间 Settings.cs:127
diagnosticStartupCount number 0 Settings.cs:128
diagnosticCrashCount number 0 Settings.cs:129
diagnosticLastCrashTime string(ISO) MinValue Settings.cs:130
diagnosticMemoryKillCount number 0 Settings.cs:131
diagnosticLastMemoryKillTime string(ISO) 当前时间 Settings.cs:132
developerMode boolean false config.ts:79(本应用字段,决定是否显示开发者页)
careMode boolean false config.ts:80(关怀模式)
isDebugEnabled boolean false Settings.cs:65
isDebugOptionsEnabled boolean false Settings.cs:79
isDebugConsoleEnabled boolean false Settings.cs:118
debugAnimationScale number 1.0 Settings.cs:110
debugTimeSpeed number 1.0 Settings.cs:111
debugTimeOffsetSeconds number 0 Settings.cs:112
isSplashEnabled boolean false Settings.cs:115
splashCustomText string "" Settings.cs:116
showDetailedStatusOnSplash boolean false Settings.cs:222
isWelcomeWindowShowed boolean false Settings.cs:68
onboardingVersion number 0 config.ts:41
isReportingEnabled boolean true Settings.cs:69
isFallbackModeEnabled boolean true Settings.cs:94
isCriticalSafeMode boolean false Settings.cs:207
criticalSafeModeMethod number 0 Settings.cs:1836
trustedProfileIds string(Guid)[] [] Settings.cs:220
taskBarIconClickBehavior number Windows 0,其它 4 Settings.cs:102
settingsPagesCachePolicy number 0 Settings.cs:213
isRefreshingToastEnabled boolean true Settings.cs:1865
refreshingToastThresholdDays number 20 Settings.cs:1866
maxRefreshingToastCounts number 5 Settings.cs:1873

字段级兼容规则(必须实现):

  1. 新增字段一律有默认值,缺失即用默认值(SettingsService.LoadSettingsAsync 缺失文件时直接返回 new Settings(),见 SettingsService.cs:73-77)。
  2. 字段类型不匹配时用默认值覆盖并记 warn,不得整体抛错(对齐 config.ts:198-233 每个字段逐个 typeof 判断)。
  3. 枚举越界值回落到默认值(如 theme 非 0/1/2 → 2)。
  4. 语义变更或废弃的字段必须保留读取(忽略)一个版本,禁止删除后导致老配置迁移失败。不得删除 isIslandSeperated 这类历史拼写字段(Settings.cs:1852)。
  5. deviceKey 类"机器身份"字段一旦生成必须永不变;如由外部传入不同值必须拒绝并记 warn(对齐 config.ts:501-505)。

13.4 档案(Profile)schema — 与 ClassIsland 完全同构

档案文件:data/profiles/<SelectedProfile>,默认 Default.json。字段名必须保留 PascalCase,以便直接导入/导出 ClassIsland 档案。

13.4.1 Profile 根对象

字段 类型 默认值 说明 依据
Name string "" 档案名称 Profile.cs:227-236
Id string(Guid) 新建 Guid 档案 ID(用于"信任档案"判断) Profile.cs:452-463
TimeLayouts map<Guid,TimeLayout> {} 时间表字典,键为 Guid Profile.cs:241-250
ClassPlans map<Guid,ClassPlan> {} 课表字典 Profile.cs:255-270
Subjects map<Guid,Subject> 首次启动写入默认科目表 科目字典 Profile.cs:284-293
EditingSubjects Subject[] [JsonIgnore] 不落盘 编辑期临时集合 Profile.cs:298-308
IsOverlayClassPlanEnabled boolean false 是否启用临时层课表 Profile.cs:313-322
OverlayClassPlanId string(Guid)? null 当前临时层课表 ID Profile.cs:327-337
TempClassPlanId string(Guid)? null 临时课表 ID Profile.cs:342-351
TempClassPlanSetupTime string(ISO) 当前时间 临时课表设置时间 Profile.cs:356-365
ClassPlanGroups map<Guid,ClassPlanGroup> 必含默认群 + 全局群 课表群 Profile.cs:370-379
SelectedClassPlanGroupId string(Guid) 默认群 Guid ACAF4EF0-E261-4262-B941-34EA93CB4369 当前选中群 Profile.cs:384-393、ClassPlanGroup.cs:47-52
TempClassPlanGroupId string(Guid)? null 临时课表群 Profile.cs:398-407
TempClassPlanGroupExpireTime string(ISO) 当前时间 临时群失效时间 Profile.cs:412-421
IsTempClassPlanGroupEnabled boolean false — Profile.cs:426-435
TempClassPlanGroupType number Inherit Inherit/Override 等(枚举) Profile.cs:440-449
OrderedSchedules map<DateTime,OrderedSchedule> {} 预定课表(日期→课表 ID) Profile.cs:468-477

初始化规则:新档案必须自动加入两个群:默认群(ACAF4EF0-E261-4262-B941-34EA93CB4369,名称"默认")与全局群(Guid.Empty,名称"全局课表群",IsGlobal=true)。依据:Profile.cs:44-59。

13.4.2 TimeLayout(时间表)

字段 类型 默认值 依据
Name string "新时间表" TimeLayout.cs:17
Layouts TimeLayoutItem[] [] TimeLayout.cs:254-267
IsOverlay boolean false TimeLayout.cs:26-35
OverlaySourceId string(Guid)? null TimeLayout.cs:40-49
IsActivated / IsActivatedManually boolean [JsonIgnore] 不落盘 TimeLayout.cs:272-297

13.4.3 TimeLayoutItem(时间点)

字段 类型 默认值 说明 依据
StartTime TimeSpan 字符串 "00:00:00" 开始时间(当天内的偏移) TimeLayoutItem.cs:62-77
EndTime TimeSpan 字符串 "00:00:00" 结束时间;TimeType 为 2/3 时强制等于 StartTime TimeLayoutItem.cs:82-93、:70-73
TimeType number 0 0 上课 / 1 课间 / 2 分割线 / 3 行动 TimeLayoutItem.cs:128-138
IsHideDefault boolean false 默认隐藏(仅上课类型有效) TimeLayoutItem.cs:143-153
DefaultClassId string(Guid) Guid.Empty(空字符串也解析为 Empty) 默认科目 TimeLayoutItem.cs:158-169、GuidEmptyFallbackConverter
BreakName string "" 自定义课间名;展示用 BreakNameText(空则"课间休息") TimeLayoutItem.cs:180-191,174-175
ActionSet ActionSet? null 仅 TimeType=3 时有意义 TimeLayoutItem.cs:196-205
StartSecond / EndSecond string [Obsolete] 旧版字段,读取时忽略、写出时不得生成 TimeLayoutItem.cs:26-57
attachedObjects map<Guid, object> {}(导入时接受 AttachedObjects 原拼写) 一等公民的附加设置字典(课级提醒/天气/课程显示等),见 13.4.8 repos/ClassIsland/ClassIsland.Shared/AttachableSettingsObject.cs:10-15

校验规则:StartTime > EndTime 必须拒绝(对齐 TimeLayoutItem.EnsureTime,TimeLayoutItem.cs:95-101);同一时间表内时间点必须按 StartTime 升序,重叠允许但必须保留排序;分割线/行动点不占课程位(见 13.4.4 的"有效时间点")。

13.4.4 ClassPlan(课表)

字段 类型 默认值 说明 依据
Name string "新课表" — ClassPlan.cs:440-449
TimeLayoutId string(Guid) Guid.Empty 引用时间表 ClassPlan.cs:396-407
TimeRule TimeRule {WeekDay:0, WeekCountDiv:0, WeekCountDivTotal:2} 周次规则 ClassPlan.cs:412-421、TimeRule.cs:13-15
Classes ClassInfo[] [] 课程列表,长度 = 时间表中上课类型点数 ClassPlan.cs:426-435,451-491
IsActivated boolean [JsonIgnore] 不落盘 当前激活 ClassPlan.cs:527-537
IsOverlay boolean false 临时层课表 ClassPlan.cs:542-551
OverlaySourceId string(Guid)? null 临时层源课表 ClassPlan.cs:556-565
OverlaySetupTime string(ISO) 当前时间 — ClassPlan.cs:570-579
IsEnabled boolean true 是否默认启用 ClassPlan.cs:584-593
AssociatedGroup string(Guid) 默认群 Guid 所属课表群 ClassPlan.cs:598-607

有效时间点规则(必须实现 ValidTimeLayoutItems):只取 TimeType ∈ {0,1,2};从前后两个方向扫描 ClassInfo.IsEnabled,把被禁用课程"夹住"的时间点全部隐藏;TimeType=3 的行动点永不显示。依据:ClassPlan.cs:54-98。

13.4.5 ClassInfo(一节课)

字段 类型 默认值 依据
SubjectId string(Guid) Guid.Empty(空串也解析为 Empty) ClassInfo.cs:70-80
IsChangedClass boolean false ClassInfo.cs:85-94
IsEnabled boolean true ClassInfo.cs:99-108
Index / CurrentTimeLayout / IsEmpty — [JsonIgnore] 不落盘 ClassInfo.cs:21-64,110-121

13.4.6 Subject(科目)

字段 类型 默认值 依据
Name string "" Subject.cs:19-30
Initial string ""(Name 变化且 Initial 为空时自动取首字) Subject.cs:35-44
TeacherName string "" Subject.cs:49-58
IsOutDoor boolean false Subject.cs:63-72

内置常量必须实现:Fallback = {Name:"???", Initial:"?"}、Empty = {Name:"", Initial:""}、Breaking = {Name:"课间休息", Initial:"休"}。依据:Subject.cs:79-101。教师姓氏提取必须支持复姓表(万俟/司马/上官/欧阳……)与英文姓名取最后一个单词。依据:Subject.cs:14,107-140。

13.4.7 课表群与预定

  • ClassPlanGroup { Name: "新课表群", IsGlobal: false };默认群 Guid ACAF4EF0-E261-4262-B941-34EA93CB4369;全局群 Guid Guid.Empty;解散群时课表移到默认群,删除群时群内课表一并删除。依据:ClassPlanGroup.cs、Profile.cs:104-137。
  • OrderedSchedule { ClassPlanId: Guid };OrderedSchedules 的键是日期(本地日期,序列化为 "2026-11-12T00:00:00")。
  • 临时层规则:每天启动/跨天时必须 CleanExpiredTempClassPlan():删除今天以前的 OrderedSchedules;删除未被任何预定引用的临时课表;删除未被引用的临时时间表。依据:ProfileService.cs:295-328。

13.4.8 附加设置(attachedObjects)——一等公民

定位:附加设置不是 1.7 遗留字段,而是 ClassIsland 2.x 仍在使用的核心机制:可附着对象(AttachableSettingsObject)自带 Dictionary<Guid, object?> AttachedObjects,键是"附加设置提供者的 Guid",值是设置对象;界面通过 AttachedSettingsControlPresenter 读写它(repos/ClassIsland/ClassIsland.Shared/AttachableSettingsObject.cs:10-15、repos/ClassIsland/ClassIsland.Core/Controls/AttachedSettingsControlPresenter.axaml.cs:241-253)。本实现必须把它升级为一等公民字段,供 06/07 部分的"课级提醒/课级附加行动/天气提醒"直接引用。

承载对象(均必须有 attachedObjects 字段):

对象 目标枚举 说明
Profile(档案级) — 本实现扩展的档案级附加设置;ClassIsland 2.x 的可附着基类未覆盖档案,档案级为本实现新增【推断】
ClassPlan(课表级) ClassPlan 整张课表的附加设置
ClassInfo(课级) Lesson 单节课的附加设置(最常用,例如这节课用自定义提醒文案)
TimeLayout(时间表级) TimeLayout 整张时间表的附加设置
TimeLayoutItem(时间点级) TimePoint 单个时间点的附加设置
Subject(科目级) Subject 科目的附加设置

字段定义:attachedObjects: map<GuidString, object>,默认 {};序列化字段名 attachedObjects(camelCase);导入 ClassIsland 档案时接受原拼写 AttachedObjects(PascalCase),导出/写出统一 camelCase。值必须是普通 JSON 对象(结构化克隆可序列化),不得存函数或类实例。依据:AttachableSettingsObject.cs:12-15(字典键为 Guid);AttachedSettingsControlPresenter.axaml.cs:245-253(目标对象 → AttachedSettingsTargets 的映射)。

已知附加设置 Guid 与设置对象类型(必须内置映射):

Guid 名称 设置对象类型 允许附着目标 依据
58e5b69a-764a-472b-bcf7-003b6a8c7fdf 课程显示设置 LessonControlAttachedSettings ClassPlan / TimeLayout / Lesson / Subject / TimePoint LessonControlAttachedSettingsControl.axaml.cs:10-14、AttachedSettingsTargets.cs
08f0d9c3-c770-4093-a3d0-02f3d90c24bc 上课提醒设置 ClassNotificationAttachedSettings ClassPlan / TimeLayout / Lesson / Subject / TimePoint ClassNotificationAttachedSettingsControl.axaml.cs:11-15
8fbc3a26-6d20-44dd-b895-b9411e3ddc51 放学提醒设置 AfterSchoolNotificationAttachedSettings ClassPlan / TimeLayout AfterSchoolNotificationAttachedSettingsControl.axaml.cs:10-12
7625de96-38aa-4b71-b478-3f156dd9458d 天气提醒设置 WeatherNotificationAttachedSettings TimePoint WeatherNotificationAttachedSettingsControl.axaml.cs:10-12

设置对象字段(默认值必须逐项一致):

  • LessonControlAttachedSettings:IsAttachSettingsEnabled=false、ShowExtraInfoOnTimePoint=true、ExtraInfoType=0、IsCountdownEnabled=true、CountdownSeconds=60、ExtraInfo4ShowSecondsSeconds=0、ScheduleSpacing=1、ShowCurrentLessonOnlyOnClass=false、IsNonExactCountdownEnabled=false。依据:repos/ClassIsland/ClassIsland.Core/Models/AttachedSettings/LessonControlAttachedSettings.cs:7-17。
  • ClassNotificationAttachedSettings:IsClassOnNotificationEnabled=true、IsClassOnPreparingNotificationEnabled=true、IsClassOffNotificationEnabled=true、ClassPreparingDeltaTime=60、ClassOnPreparingText="准备上课,请回到座位并保持安静,做好上课准备。"、OutdoorClassOnPreparingText="下节课程为户外课程,请合理规划时间,做好上课准备。"、ClassOnPreparingMaskText="即将上课"、OutdoorClassOnPreparingMaskText="即将上课"、ClassOnMaskText="上课"、ClassOffMaskText="课间休息"、ClassOffOverlayText=""、IsAttachSettingsEnabled=false。依据:repos/ClassIsland/ClassIsland/Models/AttachedSettings/ClassNotificationAttachedSettings.cs:7-24。
  • AfterSchoolNotificationAttachedSettings:IsEnabled=true、NotificationMsg="今天的课程已结束,请同学们有序离开。"、IsAttachSettingsEnabled=false。依据:repos/ClassIsland/ClassIsland/Models/AttachedSettings/AfterSchoolNotificationAttachedSettings.cs:7-14。
  • WeatherNotificationAttachedSettings:AlertShowMode=Default、ForecastShowMode=Default、IsAttachSettingsEnabled=true。依据:repos/ClassIsland/ClassIsland/Models/AttachedSettings/WeatherNotificationAttachedSettings.cs:7-17。
  • 所有设置对象都带 IsAttachSettingsEnabled:只有它为 true 时附加设置才生效;为 false 时按空对象处理并允许回落到全局设置。

读写与兼容规则(强制):

  1. 读取时必须"按目标对象类型过滤 Guid":若某 Guid 允许的目标不包含当前对象类型,保留数据但不得生效(不能因为档案来自别处就崩溃)。
  2. 未知 Guid 必须原样保留(不删除、不报错),以便插件卸载后重装仍能恢复设置;UI 显示为"未知附加设置(可删除)"。
  3. 值里出现 null 时按"未设置"处理;导出的 JSON 必须省略值为 null 的键(对齐 object? 语义)。
  4. 导入 ClassIsland 档案的 AttachedObjects 时:键 Guid 统一小写、值按上表类型反序列化;未知 Guid 的 JsonElement 原样保留;不得丢弃。
  5. 导出为 ClassIsland 档案时字段名写 AttachedObjects(PascalCase),以保持双向可导入。
  6. 附加设置必须参与 ProfileService.SaveProfile 的保存触发(对象属性变化 → 档案保存),并在 UI 中提供"清除该对象全部附加设置"的入口。

13.5 组件布局(ComponentProfile)schema

文件:data/component-layouts/<CurrentComponentConfig>.json,默认 Default.json。依据:ComponentsService.cs:28-58,122-166。

13.5.1 顶层

字段 类型 默认值 说明
Lines MainWindowLineSettings[] 1 行 + 2 个默认组件 主界面行集合

默认组件配置的固定内容:一行,含两个组件,Id 分别为 df3f8295-21f6-482e-bada-fa0e5f14bb66(日期)与 1db2017d-e374-4bc6-9d57-0b4adf03a6b8(时间)。依据:ComponentsService.cs:33-52。这两个 GUID 必须保留(导入 ClassIsland 组件布局时按 GUID 匹配)。

13.5.2 MainWindowLineSettings(主界面行)

字段 类型 默认值 依据
Children ComponentSettings[] [] MainWindowLineSettings.cs
IsMainLine boolean false 同上
IsNotificationEnabled boolean true 同上
IsResourceOverridingEnabled boolean false 同上
MainWindowSecondaryFontSize / Body / Emphasized / Large number 14/16/18/20 同上
IsCustomForegroundColorEnabled / ForegroundColor boolean / string false / #1E90FFFF 同上
BackgroundOpacity / IsCustomBackgroundOpacityEnabled number / boolean 0.5 / false 同上
BackgroundColor / IsCustomBackgroundColorEnabled string / boolean #000000FF / false 同上
CustomCornerRadius / IsCustomCornerRadiusEnabled number / boolean 0 / false 同上
Opacity number 1.0 同上
IsVisible boolean true 同上
HideOnRule boolean false 同上
HidingRules Ruleset 空规则集 同上
IslandSeparationMode number 0 同上(0 默认 / 1 禁用 / 2 启用)

13.5.3 ComponentSettings(单个组件)

字段 类型 默认值 依据
Id string 组件 GUID(小写) ComponentSettings.cs:56-66
NameCache string ""(组件未加载时显示) :71-80
Settings object? 组件专属设置对象 :85-95
HideOnRule boolean false :100-109
HidingRules Ruleset 空规则集 :114-123
IsResourceOverridingEnabled boolean false :149-158
MainWindowSecondaryFontSize / Body / Emphasized / Large number 14/16/18/20 :161-180 起
ForegroundColor / IsCustomForegroundColorEnabled string / boolean #1E90FFFF / false :28-29
IsMinWidthEnabled / MinWidth boolean / number false / 100 :30-31
IsMaxWidthEnabled / MaxWidth boolean / number false / 300 :32-33
IsFixedWidthEnabled / FixedWidth boolean / number false / 200 :34-35
HorizontalAlignment enum Stretch :36(Left/Center/Right/Stretch)
RelativeLineNumber number 0 :37
IsVisible boolean true :38
BackgroundOpacity / IsCustomBackgroundOpacityEnabled number / boolean 0.5 / false :40-41
BackgroundColor / IsCustomBackgroundColorEnabled string / boolean #000000FF / false :42-43
CustomCornerRadius / IsCustomCornerRadiusEnabled number / boolean 8 / false :44-45
Opacity number 1.0 :46
IsCustomMarginEnabled + MarginLeft/Top/Right/Bottom boolean + number×4 false + 0 :47-51

组件专属设置对象必须在 data/component-layouts/ 同一文件内序列化,其字段名按组件设置模型:ClockComponentSettings、CountDownComponentSettings、LessonControlSettings、RollingComponentSettings、SlideComponentSettings、StackComponentSettings、GroupComponentSettings、TextComponentSettings、WeatherComponentSettings。依据:repos/ClassIsland/ClassIsland/Models/ComponentSettings/*.cs。组件设置的字段名与默认值必须逐项对齐这 9 个模型(具体见第 5 部分组件规格)。

组件迁移规则:加载组件布局时,若组件 Id 命中 ComponentRegistryService.MigrationPairs,必须把 Id 改成新 GUID、IsMigrated=true、MigrationSource=旧 GUID(两者均 [JsonIgnore] 不落盘),迁移后立即保存。依据:ComponentsService.cs:140-161、ComponentSettings.cs:125-128。

13.6 自动化配置 schema

文件:data/automations/<CurrentAutomationConfig>.json,默认 Default.json;顶层类型是 Workflow 数组。依据:AutomationService.cs:27-32,173-208,441。

层级 字段 类型 默认值 依据
Workflow Triggers TriggerSettings[] [] Workflow.cs:12-14
Workflow IsConditionEnabled boolean false Workflow.cs:17-18
Workflow Ruleset Ruleset 空规则集 Workflow.cs:21-22
Workflow ActionSet ActionSet 新行动组 Workflow.cs:25-26
TriggerSettings Id string "" TriggerSettings.cs:14-30
TriggerSettings Settings object? null TriggerSettings.cs:33-45
ActionSet Name string "新行动组" ActionSet.cs:74-77
ActionSet Actions ActionItem[] [] ActionSet.cs:79-83(序列化名必须为 Actions)
ActionSet IsEnabled boolean true ActionSet.cs:88
ActionSet IsRevertEnabled boolean false ActionSet.cs:93
ActionSet Status number Normal ActionSet.cs:104-105
ActionSet Guid string(Guid) 新建 Guid ActionSet.cs:115
ActionItem Id string "" ActionItem.cs:43
ActionItem Settings object? null(null 时不写该字段) ActionItem.cs:50-51

规则集(Ruleset)嵌套结构(必须精确实现):

{
  "Mode": 0,
  "IsReversed": false,
  "Groups": [
    {
      "Rules": [{ "IsReversed": false, "Id": "classisland.lessons.currentTimeStateChanged", "Settings": {} }],
      "Mode": 1,
      "IsReversed": false,
      "IsEnabled": true
    }
  ]
}
  • Ruleset.Mode 默认 Or(Ruleset.cs:14)、RuleGroup.Mode 默认 And(RuleGroup.cs:14);两者均支持 IsReversed;RuleGroup.IsEnabled 默认 true;Ruleset.Groups 默认含一个"一个空规则"的组(Ruleset.cs:16)。State 是运行期字段([JsonIgnore]),不得落盘。
  • 触发器 ID 必须使用以下注册表 ID(导入 ClassIsland 自动化时按此匹配):
触发器 ID 依据
上课时 classisland.lessons.onClass OnClassTrigger.cs:8
课间休息时 classisland.lessons.onBreakingTime OnBreakingTimeTrigger.cs:8
放学时 classisland.lessons.onAfterSchool OnAfterSchoolTrigger.cs:8
当前时间状态变化 classisland.lessons.currentTimeStateChanged CurrentTimeStateChangedTrigger.cs:8
特定时间点前 classisland.lessons.preTimePoint PreTimePointTrigger.cs:13
应用启动时 classisland.lifetime.startup AppStartupTrigger.cs:10
应用退出时 classisland.lifetime.stopping AppStoppingTrigger.cs:8
cron classisland.cron CronTrigger.cs:15
收到信号时 classisland.signal SignalTrigger.cs:8
调用 Uri 时 classisland.uri UriTrigger.cs:8
从托盘菜单运行 classisland.trayMenu TrayMenuTrigger.cs:11
规则集更新时 classisland.ruleSet.rulesetChanged RulesetChangedTrigger.cs:8

行动 ID:classisland.showNotification(显示提醒)、classisland.notification.weather(天气提醒)、classisland.os.run(运行)、classisland.action.sleep(等待)、classisland.app.quit(退出)、classisland.app.restart(重启)、classisland.settings(应用设置)。依据:repos/ClassIsland/ClassIsland/Services/Automation/Actions/*.cs 的 ActionInfo 特性。

13.7 提醒数据

  1. 运行期队列不落盘(重启丢失可接受);通知历史必须落盘到 data/notification-history.json,结构为:
{
  "schemaVersion": 1,
  "items": [
    {
      "id": "uuid",
      "providerId": "classisland.notification.class",
      "title": "上课提醒",
      "content": "语文课 08:00-08:45",
      "priority": 3,
      "channelId": "classisland.channel.desktop",
      "createdAt": "2026-11-12T07:59:00",
      "read": false,
      "speech": "准备上课",
      "overlayDurationMs": 5000,
      "maskDurationMs": 5000
    }
  ]
}
  1. 历史保留条数由设置项 notificationHistoryLimit 控制:默认 500,合法区间 100–5000;超限按最旧优先滚动删除(不得一次性清空)。"清空通知"必须二次确认并写审计日志。落盘必须批量节流:累积 ≥ 5 条或距上次写入 ≥ 5 秒才落盘一次,退出时强制 flush;不得每来一条通知就写一次盘。【推断】(数量上限为工程决策,ClassIsland 不落盘历史。)
  2. 提醒设置对象 NotificationSettings 的字段与默认值必须为:IsNotificationEnabled=true、IsSpeechEnabled=false、IsNotificationEffectEnabled=true、IsNotificationSoundEnabled=false、NotificationSoundPath=""、IsSettingsEnabled=false、IsNotificationTopmostEnabled=false。依据:repos/ClassIsland/ClassIsland.Shared/Models/Notification/NotificationSettings.cs:11-17。
  3. 提醒播放会话字段:OverlayContent、MaskContent、OverlayDuration(默认 5s)、MaskDuration(默认 5s)、TargetOverlayEndTime、TargetMaskEndTime、OverlaySpeechContent、MaskSpeechContent、IsSpeechEnabled(默认 true)、RequestNotificationSettings、ChannelId、State、LeftProgress(默认 1.0)。依据:repos/ClassIsland/ClassIsland.Core/Models/Notification/NotificationRequest.cs:14-160、repos/ClassIsland/ClassIsland.Shared/Models/Notification/NotificationRequest.cs:16-20。
  4. 优先级排序必须与 ClassIsland 一致:IsPriorityOverride 最高(插队);否则先比 Priority(数值小优先),再比入队 Index。依据:NotificationPriority.CompareTo(repos/ClassIsland/ClassIsland.Shared/Models/Notification/NotificationPriority.cs)。

13.8 主题数据

  1. 主题目录:data/themes/<主题Id>/(解压后),启用列表 data/themes/EnabledThemes.json(string[]),主题包缓存 cache/theme-packages/<主题Id>.zip。依据:XamlThemeService.cs:47-49,392-440。
  2. 主题清单 ThemeManifest 字段与默认值:Id=""、Name=""、Author=""、Url=""、Description=""、Version="0.0.0.0"、Banner="banner.png"、VerticalSafeAreaPx=0。依据:repos/ClassIsland/ClassIsland.Core/Models/XamlTheme/ThemeManifest.cs。
  3. 启用/禁用主题后必须重建样式表并持久化;禁用全部主题时必须回落到内置主题,不得白屏。
  4. 主题包安装必须:校验 zip 路径安全 → 校验清单必填字段 → 解压到 data/themes/<Id>/ → 若已存在则保留用户自定义值(按字段合并,不整目录覆盖)。
  5. 默认离线(强制):主题市场与插件市场同一策略——安装包不内置在线官方索引,只内置离线示例索引;用户必须手动添加索引 URL 并二次确认后才联网;网络失败不得影响本机已安装主题与 EnabledThemes.json。依据:与 12.3 第 7 条同一产品决策【推断】。

13.9 其他存储

  1. 机器级全局存储:Global.json(键值对字符串),ClassIsland 放在 %LOCALAPPDATA%\ClassIsland\Global\Global.json;本实现放 <userData>/global.json,用于"上次运行版本、是否首次启动、Sentry 开关、MLE 标记"等不适合放进用户配置的信息。依据:repos/ClassIsland/ClassIsland.Core/Services/GlobalStorageService.cs。
  2. 设置叠层:自动化/插件临时覆盖设置时必须记录"原值 + 覆盖者 Guid",移除覆盖者后按栈顶恢复;不得直接改配置后忘记恢复。依据:repos/ClassIsland/ClassIsland/Services/SettingsService.cs:191-259。
  3. 信任档案列表:trustedProfileIds 默认 [];首次加载未信任档案时必须提示"该档案来自外部,是否信任";不允许自动化在未信任档案下运行(对齐 AutomationService 对 IsCurrentProfileTrusted 的检查)。依据:ProfileService.cs:166-176,398-422。
  4. 缓存目录可安全清空:cache/ 下任何内容删除后不得影响功能(下次自动重建);设置页"清理缓存"不得删除 data/、backups/、config.json。

13.10 备份、恢复与迁移(必须实现)

加载时备份:

  1. 每次读取 JSON 成功 → 复制为 .bak;失败 → 用 .bak 恢复并记入错误列表。依据:ConfigureFileHelper.cs:63-96。
  2. .bak 只保留一份(覆盖式),不做多版本滚动;多版本由 backups/ 承担。

整包备份:

  1. 备份内容固定为:config.json、Settings.json(若存在)、data/ 全目录、data/themes/EnabledThemes.json;不得包含 cache/、logs/、updates/、插件包缓存。依据:repos/ClassIsland/ClassIsland/Services/FileFolderService.cs:115-168(备份 Config/ + Profiles/ + Settings.json,排除缓存/日志)。
  2. 备份文件名:自动备份 Auto_Backup_yy-MMM-dd_HH-mm-ss.zip,手动备份 Backup_yy-MMM-dd_HH-mm-ss.zip。依据:FileFolderService.cs:128-132。
  3. 自动备份策略:isAutoBackupEnabled=true 时,距上次备份 ≥ autoBackupIntervalDays(默认 7 天)才执行;只保留最近 autoBackupLimit(默认 16)份 Auto_*,按文件修改时间倒序删除多余。依据:FileFolderService.cs:83-113。
  4. 备份写入必须"先写 .tmp 再 rename";备份失败不得影响启动与当前数据(只记日志并在设置页提示)。
  5. 恢复流程:选择备份 → 先自动创建一份"恢复前快照" → 校验 zip 结构(必须含 config.json 或 Settings.json + data/profiles/Default.json)→ 解压到临时目录 → 原子替换 → 提示"重启后生效" → 重启应用。恢复失败必须回滚到快照。
  6. 备份必须可在资源管理器里直接解压查看(不得加密、不得用私有格式)。

版本迁移:

  1. 必须在 config.json 写入 schemaVersion(当前 1),并在 data/profiles/*.json 内写 SchemaVersion(当前 1,允许缺省视为 1)。这是本实现的工程必需字段(ClassIsland 没有,但迁移流水线需要显式版本号)。【推断】
  2. 迁移执行顺序:读取文件 → 记录 fromVersion → 逐级执行 migrations[fromVersion → fromVersion+1] → 写回 → 备份迁移前文件到 backups/migrate-<from>-to-<to>-<时间>.zip。任一步失败必须中止并保留原文件,提示"迁移失败,已保留原配置"。
  3. ClassIsland 形迁移规则必须实现(依据 repos/ClassIsland/ClassIsland/Services/SettingsService.cs:134-168):
    • 旧版本 LastAppVersion < 2.0.0.2:重新登记 URL 协议(classisland://)。
    • 旧版本 LastAppVersion < 2.0.3.0:重置教程初始化标记(WillMigrateInitTutorial)。
    • 旧版本 LastAppVersion < 2.0.3.3:重写开机自启快捷方式(先禁用再启用)。
    • 迁移完成后必须把 LastAppVersion 更新为当前版本(SettingsService.cs:112)。
  4. 从 ClassIsland 导入必须支持两种入口:--importV1 <dir>(1.x)、--importV2 <dir>(2.x);导入前必须自动备份当前数据;导入后必须展示"导入结果清单"(成功项/跳过项/失败项及原因),不得静默覆盖。依据:repos/ClassIsland/ClassIsland/Program.cs:60-65。
  5. 组件迁移:ComponentRegistryService.MigrationPairs 必须内置一份"旧 GUID → 新 GUID"映射;导入旧组件布局时按映射改写并保存。依据:ComponentsService.cs:140-161。
  6. 空 GUID 兼容:所有 Guid 字段读到 "" 必须解析为 Guid.Empty;写出时 Guid.Empty 写成 ""(对齐 GuidEmptyFallbackConverter 的双向规则)。依据:repos/ClassIsland/ClassIsland.Shared/JsonConverters/GuidEmptyFallbackConverter.cs。
  7. 颜色兼容:读入支持 #RRGGBB、#RRGGBBAA、#AARRGGBB 三种写法(ClassIsland 写出为 #RRGGBBAA,Alpha 在尾部);写出统一 #RRGGBBAA 大写。依据:repos/ClassIsland/ClassIsland.Core/Converters/ColorHexJsonConverter.cs(AlphaComponentPosition.Trailing)。
  8. 时间兼容:DateTime 写出为 ISO yyyy-MM-ddTHH:mm:ss(无时区,本地时间);TimeSpan 写出为 hh:mm:ss(超过 24h 用 d.hh:mm:ss);读入必须同时接受带时区与不带时区的 ISO 字符串,并按本地时区归一。依据:repos/ClassIsland/examples/example.json:10-11 与 System.Text.Json 默认 TimeSpan 格式。
  9. 枚举兼容:所有枚举按数字落盘(如 TimeType、UpdateStatus、RulesetLogicalMode),不得写出名字;读到未知数字必须夹紧到合法范围并记 warn。依据:ClassIsland 使用默认 System.Text.Json 序列化(ConfigureFileHelper.SerializerOptions = new(),ConfigureFileHelper.cs:30),枚举默认输出数字。
  10. 迁移必须幂等:同一版本重复执行不得产生二次修改(用 schemaVersion 与字段存在性双重判断)。

Excel 导入导出(P1 必须实现,xlsx 重建):

  1. 必须用 xlsx(SheetJS)在主进程/utilityProcess 中重建 ClassIsland 的 Excel 导入导出能力;ClassIsland 的 unvell.ReoGrid + WPF 窗口实现(repos/ClassIsland/ClassIsland/Views/ExcelImportWindow.xaml.cs:29-44、ExcelExportWindow.xaml.cs:23-43)只作字段与流程参考,不得照搬 UI。
  2. 导入最低能力:读取 .xlsx → 选择工作表 → 识别"行 = 节次 / 列 = 星期"的网格 → 生成 ClassPlan + 对应 TimeLayout;必须支持合并单元格、空节次、科目名去重、教师名写入 Subject.TeacherName;解析失败必须给出行列级错误提示,且不得覆盖现有档案(先写入临时档案,用户确认后替换)。
  3. 导出最低能力:把当前档案/课表写成 .xlsx(一个工作表一张课表,首行星期、首列节次与起止时间),首行冻结;导出文件默认落 下载/ClassIsland-课表-<日期>.xlsx,由用户选择路径。
  4. 导入导出必须遵守 P1 数据兼容:导出的 xlsx 再导入必须得到等价档案(科目/时间表/课表/周次规则不丢);单文件上限 8MB(对齐 ClassHelper 服务端表格上限,见 repos/ClassHelper-master/AGENTS.md §2);导入前自动备份当前档案。

13.11 读写一致性与错误处理

  1. 所有加载函数必须返回"数据 + 警告列表",不得只返回数据;设置页与诊断页必须能展示"哪些文件回退了备份、哪些字段被重置"。
  2. 阻止"写入正在读取的文件":每个文件维护内存版本号,写入前比较版本;发现外部修改(用户在资源管理器里改了 JSON)必须提示"配置已被外部修改,是否重新加载",禁止静默覆盖用户改动。【推断】
  3. 任何写入失败必须:记 error(含路径、错误码、可用磁盘空间)→ 提示用户 → 保留旧文件不变(不得留下半截文件)。
  4. 磁盘空间不足(< 100MB)时禁止自动备份并提示"清理磁盘";备份与导出前必须检查可用空间。
  5. 读取时必须限制单文件大小(配置文件 ≤ 8MB、档案 ≤ 32MB、备份 zip ≤ 512MB),超限拒绝并提示,防止恶意文件把内存打爆。【推断】

13.12 天气数据 schema(与 07 天气服务对齐)

  1. 缓存位置与字段:最后一份天气必须持久化在 config.json 的 lastWeatherInfo(与 ClassIsland 把 Settings.LastWeatherInfo 当缓存一致,见 repos/ClassIsland/ClassIsland/Services/WeatherService.cs:48-51,387),并附加 lastWeatherInfoUpdatedAt(ISO 本地时间)与 lastWeatherInfoStale(boolean)。缓存不得因查询失败被清空:失败时保留旧值并标记 stale,由 UI 展示"天气数据可能已过期"。
  2. 刷新节奏:天气服务必须每 5 分钟检查一次(WeatherService.cs:41-44)、启动后立即查询一次(:71);查询失败按 30s/60s/120s 退避重试 3 次后停到下个周期。请求默认使用 https://;仅当用户显式开启 noTLSWeatherRequests 时才允许 http://(对齐 WeatherService.cs:34)。
  3. 来源抽象:不得把第三方接口写死在渲染层。必须定义 WeatherProvider 接口(id、name、requiresNetwork、fetch()),内置"小米天气"参考实现并允许替换:城市信息 GET {base}/location/city/info?locationKey=<cityId>&locale=zh_cn、全量天气 GET {base}/weather/all?latitude=<lat>&longitude=<lon>&locationKey=<cityId>&days=15&appKey=<key>&sign=<sign>&isGlobal=false&locale=zh_cn(依据 WeatherService.cs:327,358-360)。base/appKey/sign 必须可配置、不得出现在渲染进程产物中;默认天气提示"需联网"。
  4. 预警清洗:查询结果必须按 excludedWeatherAlerts(默认 [])过滤——Title 或 Detail 包含排除词即剔除(WeatherService.cs:363-374);同一 Type 只保留 PubTime 最新一条(:376-385);alerts 按 pubTime 倒序展示。
  5. 图标与码表:weatherIconId 默认 classisland.weatherIcons.lucide(Settings.cs:1849);天气码到文案的映射必须来自内置资源 Assets/XiaomiWeather/xiaomi_weather_status.json(WeatherService.cs:228-233),并提供 getWeatherTextByCode(code) 等价函数。
  6. 数据结构(字段名必须与 07 一致,JSON 键为 camelCase;ClassIsland 原始模型用 [JsonPropertyName] 指定小写键,可直接对齐):
类型 字段 类型 默认值
WeatherInfo current CurrentWeather 空对象
alerts WeatherAlert[] []
updateTime number(Unix 毫秒) 0
forecastDaily ForecastDaily 空对象
forecastHourly ForecastHourly 空对象
minutely Minutely 空对象
aqi AqiInfo 空对象
CurrentWeather feelsLike / humidity / pressure / temperature / visibility ValueUnitPair {value:string, unit:string} {"",""}
weather string(天气码) "99"
pubTime string(ISO) 查询时间
wind WindInfo {direction:ValueUnitPair, speed:ValueUnitPair} 空对象
WeatherAlert locationKey / alertId / title / type / level / detail string ""
pubTime string(ISO) 当前时间
images map<string,string> {}(至少含 icon)
ForecastDaily precipitationProbability StatusValueBase<string[]> {value:[]}
temperature / weather / sunRiseSet StatusValueBase<RangedValue[]>(RangedValue {from,to}) {value:[]}
ForecastHourly temperature / weather StatusValueBase<number[]> {value:[]}
Minutely precipitation MinutelyPrecipitation {value:number[]} {value:[]}
AqiInfo aqi string "0.0"
WeatherQueryResult isSuccess / isPrecisionDegraded boolean false
errorMessage string? null
degradedPrecision number 0
  1. 派生字段(不得落盘,读取时计算):WeatherInfo.updateTime → 本地时间;MinutelyPrecipitation.rainRemainingMinutes(首值为 0 时返回"接下来多少分钟开始下雨",首值 >0 时返回负的"还有多少分钟停雨",算法见 repos/ClassIsland/ClassIsland.Core/Models/Weather/MinutelyPrecipitation.cs);AqiInfo.aqiLevel(1–6,按 50/100/150/200/300 分档,见 AqiInfo.cs)。
  2. 与 07 的契约:07 天气服务必须复用本节字段名与派生字段;规则集 handler ID 必须为 classisland.weather.currentWeather、classisland.weather.tomorrowWeather、classisland.weather.hasWeatherAlert、classisland.weather.rainTime、classisland.weather.sunRiseSet(WeatherService.cs:64-68);提醒文案与图标渲染由 07 定义,但读取的数据结构只能是本节 schema。

13.13 验收标准(第 13 部分)

  1. 首次启动能在 <userData>/ 下生成完整的目录结构与默认 config.json、Default.json 档案、Default.json 组件布局、默认科目表;目录清单与 13.2 完全一致。
  2. 删除 config.json 后应用能正常启动并重建默认配置;删除 data/ 后能重建默认档案与组件布局,不白屏、不崩溃。
  3. 故意把 config.json 改成非法 JSON 后启动:应用回退到 .bak;若 .bak 也非法则使用默认值并进入"配置错误"提示,不得启动失败。
  4. Settings.json/档案的字段名、类型、默认值与 13.3/13.4 表一致;用 ClassIsland 的真实档案 JSON 导入后,时间表、课表、科目、周次规则全部正确解析(含 TimeType=3 与 IsHideDefault、AttachedObjects)。并且:运行期只写 config.json,Settings.json 只读导入/单独导出、不双向双写;IsIslandSeperated 原拼写能被导入与导出映射。
  5. 写入过程中断电(模拟 rename 前杀进程)不会损坏原文件;.tmp 残留有清理机制。
  6. 自动备份:把 lastAutoBackupTime 改为 8 天前,启动后生成 Auto_Backup_*.zip;备份超过 16 份时最旧的 Auto_* 被删除,手动 Backup_* 不受影响。
  7. 备份 zip 内必须含 config.json、data/profiles/Default.json、data/component-layouts/Default.json、data/automations/Default.json,且不含 cache/、logs/、updates/。
  8. 恢复备份:选择备份 → 自动生成恢复前快照 → 恢复 → 重启后数据与备份一致;中途失败能回滚。
  9. 迁移流水线:把 schemaVersion 改为 0 的旧结构文件,启动后能迁移到 1 并保留原字段;迁移失败时原文件不被破坏。
  10. 组件布局导入旧 GUID 后按 MigrationPairs 改写,且迁移后立即保存、不重复迁移。
  11. 空 GUID、#RRGGBB、带时区 ISO、数字枚举四类兼容用例全部通过(读写往返不丢信息)。
  12. 档案切换/删除后 config.selectedProfile 指向有效文件;指向不存在文件时回落到 Default.json 并提示。
  13. 自动化配置按 Workflow[] 落盘;触发器/行动 ID 与 13.6 表一致;规则集 Mode/IsReversed/IsEnabled 往返不丢。
  14. 通知历史落盘、默认上限 500(可配 100–5000)、超限滚动删除、写入节流(≥5 条或 ≥5s)、清空需二次确认;历史文件损坏时回退为空列表并记警告,不影响提醒播放。
  15. 所有写入路径都能在世界可读的 JSON 中直接看到内容(无私有封装、无加密),且 UTF-8 无 BOM、LF 换行。
  16. 附加设置:Profile/ClassPlan/ClassInfo/TimeLayout/TimeLayoutItem/Subject 都能读写 attachedObjects;4 个已知 Guid 的设置对象默认值正确;导入 AttachedObjects(PascalCase)后可按目标生效;未知 Guid 不丢失;导出写回 AttachedObjects。
  17. 天气:lastWeatherInfo 缓存可持久化、失败时保留旧值并标 stale;WeatherInfo/WeatherAlert/ForecastDaily/ForecastHourly/Minutely/AqiInfo/WeatherQueryResult 字段与 13.12 完全一致;预警按排除词过滤并按 Type 保留最新;5 个天气规则 ID 可被 07 使用。
  18. Excel:能从 .xlsx 导入生成课表(先临时档案、确认后替换、导入前备份),能导出 .xlsx 再导入得到等价档案;8MB 上限;所有解析在 utilityProcess 或 worker 中执行,不阻塞主进程。
  19. 坐标:config.json 中所有几何字段(windowDockingOffsetX/Y、scale、radiusX/Y、字号、间距)均为 DIP 数值;把 scale 由 1 改为 1.5 后窗口与岛按 CSS px 等比放大,不出现 devicePixelRatio 二次换算。
  20. 主题市场默认离线:无内置在线官方索引;手动添加 URL 需二次确认;断网时本机已安装主题与 EnabledThemes.json 正常工作。