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:
1 parent
938285bb9c
commit
682818dd4a
41 files changed
+4349
-1095
No files matched your search
@@ -2224,6 +2224,114 @@ async function main() {
|
||||
`status=${studentLayoutList.status}`,
|
||||
);
|
||||
|
||||
// 8.2.1 时间点默认课程:随布局返回、无条目时回退、覆盖现有课程写入条目
|
||||
if (layoutId && courseId) {
|
||||
const defaultCourseName = courses.payload?.data?.[0]?.name ?? '';
|
||||
const savedLayout = await api(`/time-layouts/${layoutId}`, {
|
||||
method: 'PUT',
|
||||
token: adminToken,
|
||||
body: {
|
||||
name: '默认时间表',
|
||||
classId,
|
||||
points: [
|
||||
{
|
||||
name: '第 1 节',
|
||||
startTime: '08:00',
|
||||
endTime: '08:40',
|
||||
timeType: 'LESSON',
|
||||
defaultClassId: courseId,
|
||||
isHideDefault: false,
|
||||
breakName: '',
|
||||
actionSet: '{}',
|
||||
},
|
||||
{
|
||||
name: '课间',
|
||||
startTime: '08:40',
|
||||
endTime: '08:50',
|
||||
timeType: 'BREAK',
|
||||
breakName: '课间休息',
|
||||
actionSet: '{}',
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
const savedPoints = savedLayout.payload?.data?.points ?? [];
|
||||
const lessonPoint = savedPoints.find((point) => point.timeType === 'LESSON');
|
||||
record(
|
||||
'时间表保存默认课程并随布局返回',
|
||||
savedLayout.status === 200 &&
|
||||
lessonPoint?.defaultClassId === courseId &&
|
||||
lessonPoint?.defaultCourse?.name === defaultCourseName,
|
||||
`status=${savedLayout.status} default=${lessonPoint?.defaultCourse?.name ?? '-'}`,
|
||||
);
|
||||
|
||||
const defaultPlan = await api('/class-plans', {
|
||||
method: 'POST',
|
||||
token: teacherToken,
|
||||
body: {
|
||||
classId,
|
||||
name: '默认课程验证课表',
|
||||
timeLayoutId: layoutId,
|
||||
weekday: 0,
|
||||
weekCountDiv: 0,
|
||||
weekCountDivTotal: 2,
|
||||
weekStart: 1,
|
||||
weekEnd: 30,
|
||||
isEnabled: true,
|
||||
},
|
||||
});
|
||||
const defaultPlanId = defaultPlan.payload?.data?.id ?? null;
|
||||
|
||||
const activeDefault = await api(`/schedules/active-week?classId=${classId}&week=1`, {
|
||||
token: teacherToken,
|
||||
});
|
||||
const fallback = (activeDefault.payload?.data?.days ?? [])
|
||||
.flatMap((day) => day.schedules ?? [])
|
||||
.find((item) => item.course?.name === defaultCourseName && String(item.id).startsWith('default:'));
|
||||
record(
|
||||
'日课表没有单独条目时回退时间点默认课程',
|
||||
activeDefault.status === 200 && Boolean(fallback),
|
||||
`status=${activeDefault.status} fallback=${fallback ? fallback.course?.name : '-'}`,
|
||||
);
|
||||
|
||||
if (lessonPoint?.id) {
|
||||
const applied = await api(`/time-layouts/${layoutId}/points/${lessonPoint.id}/apply-default`, {
|
||||
method: 'POST',
|
||||
token: adminToken,
|
||||
});
|
||||
const plansAfterApply = await api(`/class-plans?classId=${classId}`, { token: teacherToken });
|
||||
const appliedPlan = (plansAfterApply.payload?.data ?? []).find((item) => item.id === defaultPlanId);
|
||||
const appliedEntry = appliedPlan?.entries?.find((entry) => entry.timeLayoutItemId === lessonPoint.id);
|
||||
record(
|
||||
'覆盖现有课程写入所有引用课表',
|
||||
applied.status === 200 &&
|
||||
(applied.payload?.data?.plans ?? 0) >= 1 &&
|
||||
appliedEntry?.courseId === courseId,
|
||||
`status=${applied.status} plans=${applied.payload?.data?.plans ?? 0}`,
|
||||
);
|
||||
}
|
||||
|
||||
if (defaultPlanId) {
|
||||
await api(`/class-plans/${defaultPlanId}`, { method: 'DELETE', token: teacherToken });
|
||||
}
|
||||
}
|
||||
|
||||
// 8.2.2 时间表可通过新路由删除(Web 时间表编辑器使用的接口)
|
||||
const removableLayout = await api('/time-layouts', {
|
||||
method: 'POST',
|
||||
token: adminToken,
|
||||
body: { name: '临时删除验证', classId, points: [] },
|
||||
});
|
||||
const removableId = removableLayout.payload?.data?.id ?? null;
|
||||
const removedLayout = removableId
|
||||
? await api(`/time-layouts/${removableId}`, { method: 'DELETE', token: adminToken })
|
||||
: { status: 0 };
|
||||
record(
|
||||
'时间表可通过新路由删除',
|
||||
removableLayout.status === 201 && removedLayout.status === 200,
|
||||
`创建=${removableLayout.status} 删除=${removedLayout.status}`,
|
||||
);
|
||||
|
||||
// 8.3 成绩表格导入(CSV 校验 + XLSX 新增/重复/更新 + 错误行提示)
|
||||
const gradesCsv = [
|
||||
'学号,姓名,考试名称,分数,总分,科目',
|
||||
|
||||
Reference in new issue
Block a user