fix(island,classisland,server,desktop,plugin): 灵动岛去掉整窗背景材质(修卡片外圈白底面板)+ 左右边距只在左/右停靠生效 + 镜像课表补课间

- 灵动岛:移除 win.setBackgroundMaterial() 与 applyBackgroundMaterial/getBackgroundMaterial。它把系统材质刷在整个窗口矩形上(岛是"固定大包围盒窗口",卡片外还有留白),且明暗跟随应用的浅色主题 → 卡片周围出现一圈浅色面板;更糟的是调用过一次(含 none)后窗口就回不到逐像素透明,从 glass 切到 black/tinted 会永久留一圈纯白。glass 因此改为纯 CSS 半透明深色卡(WinIsland 拿不到 host backdrop 时的降级色 rgba(32,32,36,.804))
- 回归用例:新增「个性设置:卡片外圈透出桌面(三种风格都没有白底面板)」——整屏截图取样,并在岛底下垫一块已知颜色底板,让"环 = 底板色"可被精确断言(webContents.capturePage() 拍不到窗口之后的材质,是当初漏掉这个 bug 的原因)
- 边距:左右边距只在"左/右停靠"时生效(默认顶部居中时水平锚点就是屏幕中线),设置页把该滑块置灰并说明,避免被当成"调了不生效"
- 课间:镜像回 ClassIsland 的课表改为每两节之间补一段课间、最后一节之后不补(放学);服务端 synthesizeTimeLayout 同口径,不再依赖导入的时间表里有没有课间点
- 文档:README / AGENTS.md / docs/winisland-design-tokens.md / 插件 README 同步

门禁:pnpm lint 0 error、pnpm typecheck 四包通过。未跑:verify:desktop(需先 build:desktop)、verify:e2e / verify:classisland(需后端启动)。
This commit is contained in:
LoveChengke committed 2026-09-27 18:08:51 +08:00
1 parent 7536abfc95
commit 5c27586af9
12 files changed
+678 -141

No files matched your search

@@ -462,24 +462,26 @@ public sealed class BridgeService : INotifyPropertyChanged, IDisposable
}
/// <summary>
/// 优先用"当前正在生效"的课表对应的时间表;取不到再退回档案里的第一个。
/// 有多个时间表时选错会把节次时间报错,所以先按当前课表找。
/// 选一份"最有代表性"的时间表上报给班级小助手:**上课点最多的那一份**。
///
/// 为什么不按"当前课表"选:镜像会给每个(星期 + 单双周)建一份时间表,
/// 于是"今天"那一份可能只有一两节课 —— 按当前课表选就会把只有一节的时间表报上去,
/// 服务端那边的节次时间表就退化了。取上课点最多的那份,得到的是完整作息。
/// 并列时优先"每周"型(单双周的两份内容一致,每周型更通用)。
/// </summary>
private static ClassIsland.Shared.Models.Profile.TimeLayout? ResolvePrimaryTimeLayout(
ClassIsland.Shared.Models.Profile.Profile profile)
{
if (profile.TimeLayouts.Count == 0) return null;
var current = profile.SelectedClassPlanGroupId != Guid.Empty
? CurrentClassPlan(profile)
: null;
if (current is not null)
{
var layout = ScheduleMapper.ResolveTimeLayout(current, profile);
if (layout is not null) return layout;
}
var best = profile.TimeLayouts
.Select(pair => (pair.Value, ClassPoints: pair.Value.Layouts.Count(item => item.TimeType == 0)))
.Where(item => item.ClassPoints > 0)
.OrderByDescending(item => item.ClassPoints)
.ToList();
if (best.Count > 0) return best[0].Value;
// 退而求其次:档案里被激活的时间表,最后才是任意一个
// 兜底:档案里被激活的时间表,最后才是任意一个
return profile.TimeLayouts.Values.FirstOrDefault(item => item.IsActivated)
?? profile.TimeLayouts.Values.First();
}
@@ -51,11 +51,6 @@ public static class ClassPlanWriter
}
var layout = source.TimeLayouts[0];
// 课间 / 分割线 / 行动点:按起始时间索引,供"每条课目后接上对应课间"使用
var breakPoints = layout.Layouts
.Where(item => item.TimeType != 0)
.GroupBy(item => item.StartTime)
.ToDictionary(group => group.Key, group => group.First());
// 1) 补齐科目(同名的复用,避免每次镜像堆出新的 Subject)
EnsureSubjects(profile, source);
@@ -86,7 +81,7 @@ public static class ClassPlanWriter
group.WeekCountDivTotal, groups.Count);
var planId = FindPlanId(profile, planName);
var (timeLayoutId, timeLayout) = BuildTimeLayout(
profile, planName, group.Entries, breakPoints);
profile, planName, group.Entries);
var plan = profile.ClassPlans.TryGetValue(planId, out var existing)
? existing
@@ -172,38 +167,45 @@ public static class ClassPlanWriter
}
/// <summary>
/// 按分组内的条目重建时间表:每条课目前后按需插入服务端的课间点。
/// 返回写入的时间表 GUID。
/// 按分组内的条目重建时间表:**每节课之间补一个课间**(最后一节之后不补)。
///
/// 课间 = 「上一节下课」到「下一节上课」之间的那段空隙,直接由两条课目的时间算出来,
/// 不依赖服务端有没有给课间点 —— 这样无论班级小助手那边是否维护节次时间表,
/// ClassIsland 档案里的课表都会有一致的课间(需求:每节课的间隔都是课间,放学后除外)。
/// </summary>
private static (Guid Id, TimeLayout Layout) BuildTimeLayout(
Profile profile,
string planName,
IReadOnlyList<ClassPlanEntryMirrorDto> entries,
IReadOnlyDictionary<string, TimeLayoutItemMirrorDto> breakPoints)
IReadOnlyList<ClassPlanEntryMirrorDto> entries)
{
var layoutName = planName + TimeLayoutNameSuffix;
var existing = profile.TimeLayouts.FirstOrDefault(pair => pair.Value.Name == layoutName);
var id = existing.Key != Guid.Empty ? existing.Key : Guid.NewGuid();
var layout = new TimeLayout { Name = layoutName, IsActivated = true };
foreach (var entry in entries)
for (var index = 0; index < entries.Count; index++)
{
var entry = entries[index];
layout.Layouts.Add(new TimeLayoutItem
{
TimeType = 0, // 上课
StartTime = ParseTime(entry.StartTime),
EndTime = ParseTime(entry.EndTime),
});
// 该课目结束后紧接着的课间(服务端时间表里有同起始时刻的课间点才插入)
if (breakPoints.TryGetValue(entry.EndTime, out var brk))
// 课间:下一节开始晚于本节结束才有空隙;最后一节之后(放学)不插
if (index + 1 >= entries.Count) continue;
var next = entries[index + 1];
var breakStart = ParseTime(entry.EndTime);
var breakEnd = ParseTime(next.StartTime);
if (breakEnd <= breakStart) continue;
layout.Layouts.Add(new TimeLayoutItem
{
layout.Layouts.Add(new TimeLayoutItem
{
TimeType = brk.TimeType,
StartTime = ParseTime(brk.StartTime),
EndTime = ParseTime(brk.EndTime),
});
}
TimeType = 1, // 课间
StartTime = breakStart,
EndTime = breakEnd,
});
}
profile.TimeLayouts[id] = layout;