feat: 完善课表档案、课件归档、提醒与客户端更新并发布 1.4.1

- 重写课表档案工作区并合并时间表、课表与完整档案导入导出
- 修复课前/上下课提醒遮罩与通知收回冲突
- 增加课件文件夹分类、教师归档路径和客户端本机归档
- 打通客户端下载/安装/完成状态上报并在管理端展示更新过程
- 修复侧栏折叠裁切、课时多选与上传控件样式
- 统一版本到 1.4.1
This commit is contained in:
LoveChengke committed 2026-10-18 17:39:54 +08:00
1 parent 2612a912ef
commit 6347d1e5aa
59 files changed
+2251 -584

No files matched your search

@@ -6,6 +6,7 @@ import { assertCanManageSchedule } from '../../lib/access.js';
import { ApiError } from '../../lib/http.js';
import type { TokenPayload } from '../../lib/jwt.js';
import { notifyScheduleChanged } from './class-plans.service.js';
import { getMaxTermWeeks } from '../../lib/term.js';
const record = z.record(z.string(), z.unknown());
function object(value: unknown): Record<string, unknown> {
const parsed = record.safeParse(value);
@@ -39,6 +40,7 @@ export async function importProfile(user: TokenPayload, classId: string, payload
if (Object.keys(plans).length > 500 || Object.keys(layouts).length > 100)
throw ApiError.badRequest('导入课表过多');
const cls = await prisma.class.findUniqueOrThrow({ where: { id: classId } });
const maxWeeks = await getMaxTermWeeks();
const warnings: string[] = [];
const stable = (kind: string, id: string) =>
`profile-${kind}-${createHash('sha256')
@@ -183,6 +185,14 @@ export async function importProfile(user: TokenPayload, classId: string, payload
)
throw ApiError.badRequest('导入启用规则不合法(支持1–9周轮换)');
const previous = await tx.classPlan.findUnique({ where: { id: stable('plan', id) } });
const range = z
.object({
start: z.number().int().min(1).max(maxWeeks),
end: z.number().int().min(1).max(maxWeeks),
})
.refine((r) => r.start <= r.end)
.safeParse({ start: p.WeekStart ?? 1, end: Math.min(Number(p.WeekEnd ?? maxWeeks), maxWeeks) });
if (!range.success) throw ApiError.badRequest('导入课表生效周次无效');
const data = {
classId,
name: String(p.Name || '导入课表').slice(0, 64),
@@ -191,8 +201,11 @@ export async function importProfile(user: TokenPayload, classId: string, payload
weekday,
weekCountDiv,
weekCountDivTotal,
weekStart: range.data.start,
weekEnd: range.data.end,
isEnabled: p.IsEnabled !== false,
isOverlay: p.IsOverlay === true,
overlayDate: typeof p.OverlayDate === 'string' && isDayKey(p.OverlayDate) ? p.OverlayDate : null,
};
const plan = await tx.classPlan.upsert({
where: { id: stable('plan', id) },