feat(schedules,classisland,server,plugin,shared): 课表行加来源字段,插件上报可真正同步删除
课表行新增 Schedule.source(manual / classisland):插件每次全量上报时,服务端 只清理「source=classisland 且本次未再上报」的行。原先 merge 只做 upsert,老师在 ClassIsland 里删掉或挪走一节,服务端会永远留着那行幽灵课;现在能真正同步下来, 而老师在小助手这边手排的课(manual)一律不删。 配套改动: - 插件上报时带回 machineKey,服务端回填到设备记录(避开 [classId, deviceKey] 唯一约束); - 新增迁移 20260930120000_add_schedule_source,存量数据保守地按 manual 处理; - verify:classisland 补「幽灵行清理」「手排课不被误删」用例。 另:.gitignore 补上 .zcode/ 与 .zcodeignore(AI 会话产物,不属于产品代码)。
This commit is contained in:
1 parent
9c678b3730
commit
7ea0e0f7c9
17 files changed
+620
-44
No files matched your search
@@ -40,7 +40,10 @@ public class PluginSettings : ObservableObject
|
||||
public string DeviceToken
|
||||
{
|
||||
get => _deviceToken;
|
||||
set => SetProperty(ref _deviceToken, value);
|
||||
// 在 setter 里就去掉首尾空白:粘贴令牌时带前导空格非常常见,
|
||||
// 而 Normalize() 只在启动加载时跑一次 —— 运行期粘进来的脏值会让 IsConfigured
|
||||
// 一直为 false,用户看着明明填好了却查不出原因。
|
||||
set => SetProperty(ref _deviceToken, (value ?? "").Trim());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -154,5 +157,20 @@ public class PluginSettings : ObservableObject
|
||||
|
||||
/// <summary>是否已经配置好(地址 + 令牌都填了)。</summary>
|
||||
public bool IsConfigured =>
|
||||
Uri.TryCreate(ServerUrl, UriKind.Absolute, out _) && DeviceToken.StartsWith("chci_", StringComparison.Ordinal);
|
||||
TryGetServerUri(out _) && DeviceToken.StartsWith("chci_", StringComparison.Ordinal);
|
||||
|
||||
/// <summary>
|
||||
/// 解析服务器地址,**只接受 http/https 绝对地址**。
|
||||
///
|
||||
/// 不用裸的 `Uri.TryCreate(..., UriKind.Absolute)`:它会接受 `file://` / `ftp://`,
|
||||
/// 那种地址会在发请求时抛一句难以理解的 NotSupportedException,用户根本猜不到是地址填错了。
|
||||
/// </summary>
|
||||
public bool TryGetServerUri(out Uri uri)
|
||||
{
|
||||
uri = null!;
|
||||
if (!Uri.TryCreate((ServerUrl ?? "").Trim(), UriKind.Absolute, out var parsed)) return false;
|
||||
if (parsed.Scheme != Uri.UriSchemeHttp && parsed.Scheme != Uri.UriSchemeHttps) return false;
|
||||
uri = parsed;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user