feat: 重做灵动岛编辑器与课表档案时间表编辑

灵动岛
- 重构自定义方案编辑器:组件库常驻、拖拽添加与排序、方案切换/新建/复制/删除、编辑模式预览与缩放,容器在编辑时展开全部子组件。
- 新增天气简报组件:主进程固定请求 Open-Meteo,只接受数值经纬度,失败退避并保留本次运行缓存;冒烟模式不访问外网。
- 倒计时支持目标名称与日期;课程表组件新增「今日时间点」,按时间点类型折叠/展开,课中不再常驻倒计时。
- preload 暴露 getIslandWeather 与时间点订阅,契约同步 desktop.d.ts。

课表档案
- 时间点默认课程:布局返回 defaultCourse,日课表没有条目时回退;新增 apply-default 覆盖所有引用课表;全校时间表不携带默认课程。
- 新增 ClassIsland 式时间表编辑器(列表/时间轴视图、拖动把柄、按类型添加、默认时长与缩放)与「课表信息」表单。
- 课表页默认进入单表编辑,底部保留列表/周/月视图;时间表支持新路由删除。
- 「课间」统一改称「课间休息」。

验证
- pnpm typecheck、pnpm lint 通过。
- verify:e2e 隔离验收 248/248 项通过(含默认课程回退、覆盖写入与时间表删除)。
- ui-smoke 覆盖单表编辑、课表信息与时间轴视图,并更新回归截图。
This commit is contained in:
LoveChengke committed 2026-10-25 16:41:29 +08:00
1 parent 938285bb9c
commit 682818dd4a
41 files changed
+4349 -1095

No files matched your search

@@ -1,5 +1,7 @@
import type {
IslandLayoutSettings as ContractIslandLayoutSettings,
IslandTimePointDto as ContractIslandTimePointDto,
IslandWeather,
ScheduleDto as ContractScheduleDto,
} from '@classhelper/shared';
import { contextBridge, ipcRenderer } from 'electron';
@@ -10,6 +12,7 @@ import type { IslandAppearance, IslandState } from '@classhelper/shared';
* 只暴露「订阅状态/外观」与「发送用户操作」两件事,渲染进程不接触任何 Node 能力。
*/
export interface IslandBridge {
getIslandWeather(coordinates: { latitude: number; longitude: number }): Promise<IslandWeather>;
getLayout(): Promise<ContractIslandLayoutSettings>;
saveLayout(value: ContractIslandLayoutSettings): Promise<ContractIslandLayoutSettings>;
onLayout(handler: (value: ContractIslandLayoutSettings) => void): void;
@@ -24,6 +27,8 @@ export interface IslandBridge {
onViewport(handler: (value: { width: number; height: number }) => void): () => void;
getSchedules(): Promise<ContractScheduleDto[]>;
onSchedules(handler: (value: ContractScheduleDto[]) => void): void;
getTimePoints(): Promise<ContractIslandTimePointDto[]>;
onTimePoints(handler: (value: ContractIslandTimePointDto[]) => void): void;
onState(handler: (state: IslandState) => void): void;
sendAction(
@@ -57,6 +62,7 @@ export interface IslandBridge {
}
const bridge: IslandBridge = {
getIslandWeather: (coordinates) => ipcRenderer.invoke('island:weather', coordinates),
getLayout: () => ipcRenderer.invoke('island:get-layout'),
saveLayout: (value) => ipcRenderer.invoke('island:save-layout', value),
onLayout: (handler) => {
@@ -84,6 +90,10 @@ const bridge: IslandBridge = {
onSchedules: (handler) => {
ipcRenderer.on('island:schedules', (_event, value) => handler(value));
},
getTimePoints: () => ipcRenderer.invoke('island:get-time-points'),
onTimePoints: (handler) => {
ipcRenderer.on('island:time-points', (_event, value) => handler(value));
},
onState: (handler) => {
ipcRenderer.on('island:state', (_event, state: IslandState) => handler(state));