fix(integrations,grades,clients,web,client,plugin): 课表同步改为按需(修「课表被反复删改」)+ 客户端页合并 + 成绩排名(1.3.1)
线上问题的根因 - ClassIsland 插件默认每 15 秒上报一次,每次都带全量课表,上报完还顺手把课表镜像回教室 (本机 `UploadSchedule` / `MirrorSchedule` 两个开关默认都是开)。于是教室的课表每 15 秒被重写、 服务端也每 15 秒 merge + 清理一次幽灵行 —— 老师看到的就是"课表被反复删改"。 - 现在两个方向都只由 Web 端的一次性待办触发,插件侧那两个开关整体删除; 定时上报只报**运行状态**(当前上什么课),不带课表。 课表同步(#3) - 新增 `IntegrationDevice.requestMirrorSchedule` + `POST /integrations/devices/:id/mirror-schedule` (「下发课表」),与既有的 `request-schedule`(「获取课表」)对称;拉取成功即清标记 - 插件 `ClassPlanWriter.Apply(..., force: true)`:停用同一天同一单双周相位下冲突的旧课表 —— **只停用不删除**,内容原样留在档案里,老师随时能重新启用 - 插件设置页删掉「自动上报课表」「镜像回 ClassIsland」两个开关,改为一句说明 「客户端」页(#4) - 把「ClassIsland 联动」与「客户端设备」合成一页 `ClientHubView.vue`:一个班一张卡片, 两条链路状态放一起,勾选可批量下发课表 / 更新,点卡片进详情做逐台操作 - 「下发提醒」也搬了过来;入口权限用 `classisland:notify`(默认 ADMIN + TEACHER), 页内按钮再各自按 device:manage / remote:control / client:update-push 显示或禁用 - 两个旧页面 `meta.hidden: true` **保留路由**(安装包缓存管理、联动步骤说明仍在), 从详情抽屉里给链接 —— 不删功能 成绩(#5/#6/#7) - 新增 `Exam`(班级 + 考试名 + `countInTotal`)与迁移;`GET/PUT /api/grades/exams`、 `GET /api/grades/ranking`(两条都**不挂权限键**,班级端的有效权限是空集,边界在 service 里) - 排名口径写在服务端:**同一学生同一科目只取最高分**(补录 / 重考会多行,不去重等于"总分多一科")、 并列同名次(1、2、2、4) - 「成绩录入」页加「考试口径」开关(按班级 + 考试名记住);客户端「成绩」页新增总分榜 + 单科榜 - `updateStudentGradeQuery` 改成只有教师 / 管理员能改 —— 原先班级端自己也能改 - 客户端的成绩查询开关改为只读(只显示服务端下发的状态) 其他(#2/#5/#8/#9) - 全校学期开始日期改成 `AppSetting` 的 `term.startDate`,管理员在「学期周次」页直接改、立刻重算周次 (原先这一级只能来自 `.env`) - 大模型助手发紧急通知补上「上课时段 3 秒全屏警告」:判据抽成 `composables/useClassInSession.ts`, 与手动发布页共用 - 客户端去掉「关怀模式」「自动更新」两个开关(教室机器是统一投放的终端) - 修掉「选中设置二级菜单后收起分组、高亮块仍留在原地」:底片只在路由 / 滚动 / 缩放时重测, 而收起分组动不了 `.aside` 的盒子 —— 补上"子项被 overflow:hidden 裁掉"的判据 + 观察分组 `<ul>` 高度 - `src/types/express.d.ts` 里 `req.device` 改成引用 `DeviceContext`:原先手抄了一份结构, 往接口加字段时两边就漂了,而报错会落在 `getDevice()` 的 `return device;` 上,看不出真正原因 验收:`lint` / `typecheck` / `check:icons` / `docs:check` 全绿; 后端端到端 **247/247**、联动链路 **44/44**、插件静态契约 **76/76**、 Web 真实点击 **34/34**(含新增的「客户端」页用例)、客户端冒烟 **90/90**。
This commit is contained in:
1 parent
3236d3ad83
commit
227117ccb3
49 files changed
+2025
-281
No files matched your search
@@ -14,6 +14,6 @@ description: |-
|
||||
4. 可选:把 ClassHelper上排好的课表镜像回 ClassIsland。
|
||||
entranceAssembly: 'ClassHelper.ClassIslandPlugin.dll'
|
||||
url: https://github.com/LoveChengke/ClassHelper
|
||||
version: 1.3.0.0
|
||||
version: 1.3.1.0
|
||||
apiVersion: 2.0.0.0
|
||||
author: ClassHelper
|
||||
@@ -31,6 +31,7 @@ const clientSource = read('src/Interop/ClassHelperClient.cs');
|
||||
const settingsPageSource = read('src/Views/BridgeSettingsPage.axaml.cs');
|
||||
const settingsPageXaml = read('src/Views/BridgeSettingsPage.axaml');
|
||||
const mapperSource = read('src/Services/ScheduleMapper.cs');
|
||||
const writerSource = read('src/Services/ClassPlanWriter.cs');
|
||||
const settingsSource = read('src/Models/PluginSettings.cs');
|
||||
const serverModule = readRepo('packages/server/src/modules/integrations/integrations.module.ts');
|
||||
const serverSchemas = readRepo('packages/server/src/modules/integrations/integrations.schemas.ts');
|
||||
@@ -255,15 +256,24 @@ check(
|
||||
);
|
||||
check('失败信息带上请求 URL(便于定位代理/网关拦截)', /request\.RequestUri/.test(clientSource));
|
||||
|
||||
// 3.10 课表同步方向(2026-10-06 反转):课表以 ClassHelper为准,**服务端 → 教室自动下发**;
|
||||
// 「把教室课表传上来」改成 Web 端的一次性人工请求。这几条盯着反转后的契约,别删。
|
||||
// 3.10 课表同步方向(2026-10-11 定稿):**两个方向都只由 Web 端的一次性动作触发**。
|
||||
// 2026-10-06 曾经把它做成"服务端 → 教室自动下发 + 本机两个开关",但默认 15 秒一轮的
|
||||
// 自动上传 + 自动镜像会让服务端与教室里的课表每 15 秒互相覆盖一次,老师看到的是
|
||||
// "课表被反复删改"(2026-10-11 实测)。现在本机没有任何"一直同步"的开关:
|
||||
// · 教室 → 服务端:`scheduleRequested` 一次性待办(Web 端「从教室机器获取课表」)
|
||||
// · 服务端 → 教室:`mirrorRequested` 一次性待办(Web 端「下发课表到教室」,强制替换)
|
||||
// 这几条盯着新契约,别删。
|
||||
check(
|
||||
'默认「上报课表到 ClassHelper」关闭、镜像到 ClassIsland 开启(新方向)',
|
||||
/_uploadSchedule\s*=\s*false/.test(settingsSource) && /_mirrorSchedule\s*=\s*true/.test(settingsSource),
|
||||
'课表两个方向都改为按需:本机不再有「自动回传 / 自动镜像」开关',
|
||||
!/_uploadSchedule/.test(settingsSource) && !/_mirrorSchedule/.test(settingsSource),
|
||||
);
|
||||
check(
|
||||
'响应 DTO 里有 scheduleRequested(服务端请求立刻上报一次课表)',
|
||||
/scheduleRequested/.test(clientSource),
|
||||
'定时上报只报运行状态,课表仅在服务端显式请求时上传',
|
||||
/var uploadSchedule = forceSchedule;/.test(bridgeSource),
|
||||
);
|
||||
check(
|
||||
'响应 DTO 里有 scheduleRequested / mirrorRequested(两个方向的一次性待办)',
|
||||
/scheduleRequested/.test(clientSource) && /mirrorRequested/.test(clientSource),
|
||||
);
|
||||
check(
|
||||
'收到 scheduleRequested 会补发一次带课表的上报(且不受本机自动上报开关限制)',
|
||||
@@ -271,8 +281,13 @@ check(
|
||||
/forceSchedule:\s*true/.test(bridgeSource),
|
||||
);
|
||||
check(
|
||||
'服务端请求课表不依赖「自动回传」开关(forceSchedule 或上开关二者取一)',
|
||||
/forceSchedule\s*\|\|\s*_settings\.UploadSchedule/.test(bridgeSource),
|
||||
'只有收到 mirrorRequested 才镜像(不再跟着每一次上报走)',
|
||||
/MirrorRequested\s*==\s*true/.test(bridgeSource),
|
||||
);
|
||||
check(
|
||||
'镜像写入是「强制替换」(停用冲突的旧课表,而不是只留一条警告)',
|
||||
/Apply\(profile, plan, force: true\)/.test(bridgeSource) &&
|
||||
/IsEnabled = false/.test(writerSource),
|
||||
);
|
||||
check(
|
||||
'补发课表发生在镜像之前(顺序反了会把刚拿到的课表盖掉)',
|
||||
|
||||
@@ -88,6 +88,15 @@ public class ReportSettingsDto
|
||||
/// 现在是默认关闭的;而这是一次显式的人工请求,看到就要照做。
|
||||
/// </summary>
|
||||
[JsonPropertyName("scheduleRequested")] public bool ScheduleRequested { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 服务端请求**下发一次课表到教室**(老师在 Web 端点了「下发课表到教室」)。
|
||||
///
|
||||
/// 与 <see cref="ScheduleRequested"/> 反方向,同样是一次性待办:看到就拉本班课表并
|
||||
/// **强制替换**写进 ClassIsland;拉取成功时由服务端清除标记。
|
||||
/// 没有"后台定时镜像"那条路径了 —— 那会让教室课表被反复重写(见 BridgeService 的说明)。
|
||||
/// </summary>
|
||||
[JsonPropertyName("mirrorRequested")] public bool MirrorRequested { get; set; }
|
||||
}
|
||||
|
||||
public class ReportResultDto
|
||||
|
||||
@@ -17,11 +17,11 @@ public class PluginSettings : ObservableObject
|
||||
private string _deviceKey = "";
|
||||
private bool _autoReport = true;
|
||||
private int _reportIntervalSeconds = 60;
|
||||
// 同步方向(2026-10-06 起):课表以 ClassHelper为准,**服务端 → 教室自动下发**;
|
||||
// 「把教室课表传上去」改为 Web 端的一次性人工动作(点之前会弹警告)。
|
||||
// 自动回传会悄悄盖掉老师刚手排的课,所以默认关闭。
|
||||
private bool _uploadSchedule = false;
|
||||
private bool _mirrorSchedule = true;
|
||||
// 课表同步(2026-10-11 起):**两个方向都只由 Web 端的一次性动作触发** ——
|
||||
// 「从教室机器获取课表」与「下发课表到教室」,都走服务端的一个待办标记。
|
||||
// 原先这里有两个开关(自动回传 / 自动镜像),默认 15 秒一轮,结果是服务端与教室里
|
||||
// 的课表每 15 秒互相覆盖一次,老师看到的就是"课表被反复删改"。
|
||||
// 定时上报现在**只报运行状态**(当前上什么课),不带课表。
|
||||
private bool _receiveNotifications = true;
|
||||
private int _notificationDurationSeconds = 8;
|
||||
private int _notificationPollSeconds = 10;
|
||||
@@ -76,33 +76,6 @@ public class PluginSettings : ObservableObject
|
||||
set => SetProperty(ref _reportIntervalSeconds, Math.Clamp(value, 15, 3600));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否把 ClassIsland 的课表**自动**同步到 ClassHelper。
|
||||
///
|
||||
/// **默认关闭**:课表以 ClassHelper为准,教室这边改完课表不会自己传上去
|
||||
/// (否则老师刚在 ClassHelper上排好的课会被教室的旧课表悄悄覆盖)。
|
||||
/// 要取教室的课表,请在 Web 端点「从教室机器获取课表」——那是一次显式的人工请求,
|
||||
/// 与本开关无关(见 <c>ReportSettingsDto.ScheduleRequested</c>)。
|
||||
/// 关闭后仍会上报"当前上什么课"的状态。
|
||||
/// </summary>
|
||||
public bool UploadSchedule
|
||||
{
|
||||
get => _uploadSchedule;
|
||||
set => SetProperty(ref _uploadSchedule, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否把 ClassHelper上排好的课表镜像回 ClassIsland
|
||||
/// (新建一份名为「ClassHelper-<班级名>」的档案课表,不覆盖老师原有的课表)。
|
||||
///
|
||||
/// **默认开启**:这是现在课表同步的主方向。
|
||||
/// </summary>
|
||||
public bool MirrorSchedule
|
||||
{
|
||||
get => _mirrorSchedule;
|
||||
set => SetProperty(ref _mirrorSchedule, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否接收老师在 ClassHelper上发布的提醒,并在 ClassIsland 上弹出。
|
||||
/// </summary>
|
||||
|
||||
@@ -206,10 +206,16 @@ public sealed class BridgeService : INotifyPropertyChanged, IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>发起一次上报(事件驱动:已有上报在跑时直接丢弃本次,避免事件风暴打出并发请求)。</summary>
|
||||
/// <summary>
|
||||
/// 发起一次**仅状态**的上报(定时器与课程事件都走这里)。
|
||||
///
|
||||
/// 刻意不带课表:课表只在服务端显式点「从教室机器获取课表」时上传(见 <see cref="ReportAsync"/>
|
||||
/// 的 <c>forceSchedule</c>)。原先每次上报都带全量课表、默认 15 秒一次,服务端于是每 15 秒
|
||||
/// merge + 清理一次幽灵行 —— 老师看到的就是"课表被反复删改"(2026-10-11 实测)。
|
||||
/// </summary>
|
||||
public void RequestReport(string reason)
|
||||
{
|
||||
_ = ReportAsync(reason);
|
||||
_ = ReportAsync(reason, includeSchedule: false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -251,17 +257,14 @@ public sealed class BridgeService : INotifyPropertyChanged, IDisposable
|
||||
|
||||
var entryCount = 0;
|
||||
var layoutCount = 0;
|
||||
// 开关关着时也要留下原因:否则日志只有"上报成功:仅状态",
|
||||
// 用户会以为"服务端拿不到 ClassIsland 课表"是插件坏了。
|
||||
// forceSchedule:服务端显式点了「从教室机器获取课表」——这是一次人工请求,
|
||||
// 不受本机「自动上报课表」开关影响(见下方 ReportSettingsDto.ScheduleRequested)
|
||||
var uploadSchedule = forceSchedule || _settings.UploadSchedule;
|
||||
// 课表**只在服务端显式请求时**上传:没有"本机自动回传"这条路径了
|
||||
// (原先由本机 `UploadSchedule` 开关决定,默认开 + 定时 15 秒 = 服务端课表被反复 merge)。
|
||||
// forceSchedule:服务端点了「从教室机器获取课表」,这是一次人工请求。
|
||||
var uploadSchedule = forceSchedule;
|
||||
|
||||
if (includeSchedule && !uploadSchedule)
|
||||
{
|
||||
_logger.LogInformation(
|
||||
"ClassHelper 联动:本机设置里「上报课表到 ClassHelper」是关闭的,本次只上报状态;" +
|
||||
"需要把教室课表同步到 ClassHelper请打开该开关,或在 Web 端点「从教室机器获取课表」");
|
||||
_logger.LogInformation("ClassHelper 联动:本次只上报运行状态;课表由 Web 端点「从教室机器获取课表」时才会拉取");
|
||||
}
|
||||
|
||||
if (includeSchedule && uploadSchedule && profile is not null)
|
||||
@@ -337,8 +340,12 @@ public sealed class BridgeService : INotifyPropertyChanged, IDisposable
|
||||
_ = ReportAsync("服务端请求课表", includeSchedule: true, forceSchedule: true);
|
||||
}
|
||||
|
||||
if (_settings.MirrorSchedule)
|
||||
// 只有服务端点了「下发课表到教室」才镜像。原先跟着**每一次上报**走(本机 MirrorSchedule
|
||||
// 开关 + 默认 15 秒周期),教室的课表被反复重写 —— 与服务端那边的 merge/清理叠在一起,
|
||||
// 就是老师反馈的"课表一直在变"。现在是一次性待办,拉取成功由服务端清标记。
|
||||
if (data.Settings?.MirrorRequested == true)
|
||||
{
|
||||
_logger.LogInformation("ClassHelper 联动:服务端请求下发课表,正在拉取并强制替换教室课表…");
|
||||
await MirrorScheduleAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
@@ -449,7 +456,7 @@ public sealed class BridgeService : INotifyPropertyChanged, IDisposable
|
||||
// **把整个档案写成半截 JSON**(老师的课表/科目全丢,只能靠 .bak 兜底)。
|
||||
var outcome = await Dispatcher.UIThread.InvokeAsync(() =>
|
||||
{
|
||||
var applied = ClassPlanWriter.Apply(profile, plan);
|
||||
var applied = ClassPlanWriter.Apply(profile, plan, force: true);
|
||||
if (!applied.Applied)
|
||||
{
|
||||
return applied;
|
||||
@@ -507,7 +514,7 @@ public sealed class BridgeService : INotifyPropertyChanged, IDisposable
|
||||
/* ---------------------------------------------------------------- 内部实现 */
|
||||
|
||||
/// <summary>插件版本(与 manifest.yml / csproj 保持一致)。</summary>
|
||||
private const string PluginVersion = "1.3.0.0";
|
||||
private const string PluginVersion = "1.3.1.0";
|
||||
|
||||
/// <summary>ClassIsland 版本读不到时不该让上报失败(开发版可能为 null)。</summary>
|
||||
private static string SafeAppVersion()
|
||||
|
||||
@@ -41,7 +41,13 @@ public static class ClassPlanWriter
|
||||
/// <summary>
|
||||
/// 应用服务端下发的课表。
|
||||
/// </summary>
|
||||
public static ApplyResult Apply(Profile profile, ClassPlanMirrorDto source)
|
||||
/// <param name="force">
|
||||
/// 「强制更换课表」:同一星期几 + 同一单双周相位下老师还开着别的课表时,**停用**它们,
|
||||
/// 让教室大屏立刻改成以 ClassHelper 下发的这份为准。
|
||||
/// 由 Web 端的「下发课表到教室」按钮触发(服务端那个一次性待办)。
|
||||
/// **只停用不删除** —— 课表内容原样留在档案里,老师随时能在 ClassIsland 里重新启用。
|
||||
/// </param>
|
||||
public static ApplyResult Apply(Profile profile, ClassPlanMirrorDto source, bool force = false)
|
||||
{
|
||||
var result = new ApplyResult();
|
||||
if (source.Entries.Count == 0 || source.TimeLayouts.Count == 0)
|
||||
@@ -132,7 +138,9 @@ public static class ClassPlanWriter
|
||||
profile.ClassPlans[planId] = plan;
|
||||
result.PlanNames.Add(planName);
|
||||
|
||||
// 冲突提示:同一星期几 + 同一单双周相位下老师还开着别的课表
|
||||
// 冲突处理:同一星期几 + 同一单双周相位下老师还开着别的课表。
|
||||
// ClassIsland 在同一个时间点上只会用一份**启用**的课表,所以"强制更换"就是把冲突的
|
||||
// 那份停用 —— 教室大屏上立刻显示成 ClassHelper 下发的这一份。
|
||||
var conflicts = profile.ClassPlans
|
||||
.Where(pair => pair.Key != planId
|
||||
&& pair.Value.IsEnabled
|
||||
@@ -140,13 +148,22 @@ public static class ClassPlanWriter
|
||||
&& pair.Value.TimeRule.WeekDay == group.WeekDay
|
||||
&& pair.Value.TimeRule.WeekCountDivTotal == group.WeekCountDivTotal
|
||||
&& pair.Value.TimeRule.WeekCountDiv == group.WeekCountDiv)
|
||||
.Select(pair => pair.Value.Name)
|
||||
.ToList();
|
||||
if (conflicts.Count > 0)
|
||||
{
|
||||
result.Warnings.Add(
|
||||
$"星期 {group.WeekDay} 已有启用的课表({string.Join("、", conflicts)})," +
|
||||
"两者重叠时以 ClassHelper同步的课表为准");
|
||||
var names = string.Join("、", conflicts.Select(pair => pair.Value.Name));
|
||||
if (force)
|
||||
{
|
||||
foreach (var pair in conflicts) pair.Value.IsEnabled = false;
|
||||
result.Warnings.Add(
|
||||
$"星期 {group.WeekDay} 原有的课表({names})已停用,教室显示改以 ClassHelper 下发的课表为准" +
|
||||
"(只停用、未删除,可在 ClassIsland 的课表面板里重新启用)");
|
||||
}
|
||||
else
|
||||
{
|
||||
result.Warnings.Add(
|
||||
$"星期 {group.WeekDay} 已有启用的课表({names}),两者重叠时以 ClassHelper 同步的课表为准");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -35,8 +35,7 @@
|
||||
<Button Content="立即上报" Command="{Binding ReportCommand}" IsEnabled="{Binding !Bridge.Busy}" />
|
||||
<Button Content="检查连接" Command="{Binding TestCommand}" IsEnabled="{Binding !Bridge.Busy}" />
|
||||
<Button Content="拉取待提醒" Command="{Binding PullCommand}" IsEnabled="{Binding !Bridge.Busy}" />
|
||||
<Button Content="重建档案课表" Command="{Binding MirrorCommand}"
|
||||
IsEnabled="{Binding Settings.MirrorSchedule}" />
|
||||
<Button Content="重建档案课表" Command="{Binding MirrorCommand}" IsEnabled="{Binding !Bridge.Busy}" />
|
||||
</StackPanel>
|
||||
|
||||
<!-- 状态 -->
|
||||
@@ -50,33 +49,13 @@
|
||||
</Border>
|
||||
|
||||
<!-- 同步 -->
|
||||
<TextBlock Margin="0 24 0 0" Text="同步内容"
|
||||
<TextBlock Margin="0 24 0 0" Text="课表同步"
|
||||
Theme="{StaticResource SubtitleTextBlockStyle}" />
|
||||
<TextBlock Margin="0 6 0 12" TextWrapping="Wrap"
|
||||
Text="课表以「合并」方式写入 ClassHelper:同一星期、同一时间的课目会被更新,不会产生重复行。" />
|
||||
|
||||
<fa:SettingsExpander Header="自动上报课表到 ClassHelper"
|
||||
Description="把 ClassIsland 里启用的课表**自动**同步给 ClassHelper。默认关闭:课表以 ClassHelper 为准,自动回传会在老师手排课之后被教室的旧课表悄悄覆盖。要取教室的课表,请在 Web 端点「从教室机器获取课表」——那是一次显式的人工请求,与本开关无关。">
|
||||
<fa:SettingsExpander.IconSource>
|
||||
<ci:FluentIconSource Glyph="" />
|
||||
</fa:SettingsExpander.IconSource>
|
||||
<fa:SettingsExpander.Footer>
|
||||
<ToggleSwitch IsChecked="{Binding Settings.UploadSchedule, Mode=TwoWay}" />
|
||||
</fa:SettingsExpander.Footer>
|
||||
</fa:SettingsExpander>
|
||||
|
||||
<fa:SettingsExpander Header="把班级课表镜像回 ClassIsland"
|
||||
Description="反方向同步:把 ClassHelper上排好的课表写回 ClassIsland,会新建一份以「ClassHelper-」开头的档案课表,不会修改老师原有的课表。">
|
||||
<fa:SettingsExpander.IconSource>
|
||||
<ci:FluentIconSource Glyph="" />
|
||||
</fa:SettingsExpander.IconSource>
|
||||
<fa:SettingsExpander.Footer>
|
||||
<ToggleSwitch IsChecked="{Binding Settings.MirrorSchedule, Mode=TwoWay}" />
|
||||
</fa:SettingsExpander.Footer>
|
||||
</fa:SettingsExpander>
|
||||
Text="课表的两个方向都是按需的,由 Web 端「ClassIsland 联动」页的两个按钮触发,本机不再提供「一直同步」的开关:「从教室机器获取课表」把 ClassIsland 里启用的课表合并进 ClassHelper;「下发课表到教室」把 ClassHelper 排好的课表写回 ClassIsland,并把同一天同一单双周相位下冲突的旧课表停用(只停用、不删除,可在 ClassIsland 的课表面板里重新启用)。" />
|
||||
|
||||
<fa:SettingsExpander Header="自动上报"
|
||||
Description="应用启动、课表变化以及每隔一段时间各上报一次;关闭后只能用上面的「立即上报」手动触发。">
|
||||
Description="应用启动、课程事件以及每隔一段时间各上报一次**运行状态**(现在上什么课、ClassIsland 版本等)。课表不在这条路径上 —— 它只由 Web 端「从教室机器获取课表」按需拉取。关闭后只能用上面的「立即上报」手动触发。">
|
||||
<fa:SettingsExpander.IconSource>
|
||||
<ci:FluentIconSource Glyph="" />
|
||||
</fa:SettingsExpander.IconSource>
|
||||
|
||||
Reference in new issue
Block a user