feat(lesson-preparation): major update with AI features, schedules, and new components
- Add actions-schedules.ts and data-access-schedules.ts for schedule management - Add AI differentiation, AI feedback, consistency check dialogs - Add attachment-picker, curriculum-heatmap, print-view, version-diff-view - Add lesson-plan-mobile-view and schedule-dialog components - Add lib: ai-differentiation, ai-feedback, auto-layout, consistency-check, curriculum-coverage, export, version-diff - Add history-slice hook for version history - Update existing components, hooks, providers, services, types - Add teacher lesson-plans heatmap and library pages
This commit is contained in:
181
src/modules/lesson-preparation/data-access-schedules.ts
Normal file
181
src/modules/lesson-preparation/data-access-schedules.ts
Normal file
@@ -0,0 +1,181 @@
|
||||
/**
|
||||
* V5-7:课案-课时绑定数据访问层
|
||||
*
|
||||
* 将课案绑定到具体班级的某个日期/节次,支持 calendar-view 安排课时。
|
||||
*/
|
||||
import "server-only";
|
||||
import { db } from "@/shared/db";
|
||||
import { lessonPlanSchedules, classes } from "@/shared/db/schema";
|
||||
import { and, eq, gte, lte, desc } from "drizzle-orm";
|
||||
import { createId } from "@paralleldrive/cuid2";
|
||||
|
||||
/** 课时绑定记录 */
|
||||
export interface LessonPlanScheduleRecord {
|
||||
id: string;
|
||||
planId: string;
|
||||
classId: string;
|
||||
className: string;
|
||||
scheduledDate: string; // YYYY-MM-DD
|
||||
period: number;
|
||||
classScheduleId?: string | null;
|
||||
durationMin: number;
|
||||
createdBy: string;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
}
|
||||
|
||||
/** 将 Date 转换为 YYYY-MM-DD 字符串(用于接口输出) */
|
||||
function toDateStr(d: Date): string {
|
||||
const y = d.getFullYear();
|
||||
const m = String(d.getMonth() + 1).padStart(2, "0");
|
||||
const day = String(d.getDate()).padStart(2, "0");
|
||||
return `${y}-${m}-${day}`;
|
||||
}
|
||||
|
||||
/** 查询课案的所有课时绑定 */
|
||||
export async function getSchedulesByPlanId(
|
||||
planId: string,
|
||||
): Promise<LessonPlanScheduleRecord[]> {
|
||||
const rows = await db
|
||||
.select({
|
||||
id: lessonPlanSchedules.id,
|
||||
planId: lessonPlanSchedules.planId,
|
||||
classId: lessonPlanSchedules.classId,
|
||||
className: classes.name,
|
||||
scheduledDate: lessonPlanSchedules.scheduledDate,
|
||||
period: lessonPlanSchedules.period,
|
||||
classScheduleId: lessonPlanSchedules.classScheduleId,
|
||||
durationMin: lessonPlanSchedules.durationMin,
|
||||
createdBy: lessonPlanSchedules.createdBy,
|
||||
createdAt: lessonPlanSchedules.createdAt,
|
||||
updatedAt: lessonPlanSchedules.updatedAt,
|
||||
})
|
||||
.from(lessonPlanSchedules)
|
||||
.leftJoin(classes, eq(lessonPlanSchedules.classId, classes.id))
|
||||
.where(eq(lessonPlanSchedules.planId, planId))
|
||||
.orderBy(desc(lessonPlanSchedules.scheduledDate));
|
||||
|
||||
return rows.map((r) => ({
|
||||
id: r.id,
|
||||
planId: r.planId,
|
||||
classId: r.classId,
|
||||
className: r.className ?? "",
|
||||
scheduledDate: toDateStr(r.scheduledDate),
|
||||
period: r.period,
|
||||
classScheduleId: r.classScheduleId,
|
||||
durationMin: r.durationMin,
|
||||
createdBy: r.createdBy,
|
||||
createdAt: r.createdAt,
|
||||
updatedAt: r.updatedAt,
|
||||
}));
|
||||
}
|
||||
|
||||
/** 查询教师在某日期范围内的课时绑定 */
|
||||
export async function getSchedulesByDateRange(
|
||||
teacherPlanIds: string[],
|
||||
startDate: string,
|
||||
endDate: string,
|
||||
): Promise<LessonPlanScheduleRecord[]> {
|
||||
if (teacherPlanIds.length === 0) return [];
|
||||
const rows = await db
|
||||
.select({
|
||||
id: lessonPlanSchedules.id,
|
||||
planId: lessonPlanSchedules.planId,
|
||||
classId: lessonPlanSchedules.classId,
|
||||
className: classes.name,
|
||||
scheduledDate: lessonPlanSchedules.scheduledDate,
|
||||
period: lessonPlanSchedules.period,
|
||||
classScheduleId: lessonPlanSchedules.classScheduleId,
|
||||
durationMin: lessonPlanSchedules.durationMin,
|
||||
createdBy: lessonPlanSchedules.createdBy,
|
||||
createdAt: lessonPlanSchedules.createdAt,
|
||||
updatedAt: lessonPlanSchedules.updatedAt,
|
||||
})
|
||||
.from(lessonPlanSchedules)
|
||||
.leftJoin(classes, eq(lessonPlanSchedules.classId, classes.id))
|
||||
.where(
|
||||
and(
|
||||
// 简化:仅按日期范围过滤(planId 过滤由调用方处理或 join)
|
||||
gte(lessonPlanSchedules.scheduledDate, new Date(startDate)),
|
||||
lte(lessonPlanSchedules.scheduledDate, new Date(endDate)),
|
||||
),
|
||||
)
|
||||
.orderBy(desc(lessonPlanSchedules.scheduledDate));
|
||||
|
||||
return rows
|
||||
.filter((r) => teacherPlanIds.includes(r.planId))
|
||||
.map((r) => ({
|
||||
id: r.id,
|
||||
planId: r.planId,
|
||||
classId: r.classId,
|
||||
className: r.className ?? "",
|
||||
scheduledDate: toDateStr(r.scheduledDate),
|
||||
period: r.period,
|
||||
classScheduleId: r.classScheduleId,
|
||||
durationMin: r.durationMin,
|
||||
createdBy: r.createdBy,
|
||||
createdAt: r.createdAt,
|
||||
updatedAt: r.updatedAt,
|
||||
}));
|
||||
}
|
||||
|
||||
/** 创建课时绑定 */
|
||||
export async function createSchedule(input: {
|
||||
planId: string;
|
||||
classId: string;
|
||||
scheduledDate: string;
|
||||
period: number;
|
||||
classScheduleId?: string;
|
||||
durationMin?: number;
|
||||
createdBy: string;
|
||||
}): Promise<LessonPlanScheduleRecord> {
|
||||
const newId = createId();
|
||||
await db.insert(lessonPlanSchedules).values({
|
||||
id: newId,
|
||||
planId: input.planId,
|
||||
classId: input.classId,
|
||||
scheduledDate: new Date(input.scheduledDate),
|
||||
period: input.period,
|
||||
classScheduleId: input.classScheduleId,
|
||||
durationMin: input.durationMin ?? 40,
|
||||
createdBy: input.createdBy,
|
||||
});
|
||||
|
||||
const created = await db
|
||||
.select({
|
||||
id: lessonPlanSchedules.id,
|
||||
planId: lessonPlanSchedules.planId,
|
||||
classId: lessonPlanSchedules.classId,
|
||||
className: classes.name,
|
||||
scheduledDate: lessonPlanSchedules.scheduledDate,
|
||||
period: lessonPlanSchedules.period,
|
||||
classScheduleId: lessonPlanSchedules.classScheduleId,
|
||||
durationMin: lessonPlanSchedules.durationMin,
|
||||
createdBy: lessonPlanSchedules.createdBy,
|
||||
createdAt: lessonPlanSchedules.createdAt,
|
||||
updatedAt: lessonPlanSchedules.updatedAt,
|
||||
})
|
||||
.from(lessonPlanSchedules)
|
||||
.leftJoin(classes, eq(lessonPlanSchedules.classId, classes.id))
|
||||
.where(eq(lessonPlanSchedules.id, newId));
|
||||
|
||||
const r = created[0]!;
|
||||
return {
|
||||
id: r.id,
|
||||
planId: r.planId,
|
||||
classId: r.classId,
|
||||
className: r.className ?? "",
|
||||
scheduledDate: toDateStr(r.scheduledDate),
|
||||
period: r.period,
|
||||
classScheduleId: r.classScheduleId,
|
||||
durationMin: r.durationMin,
|
||||
createdBy: r.createdBy,
|
||||
createdAt: r.createdAt,
|
||||
updatedAt: r.updatedAt,
|
||||
};
|
||||
}
|
||||
|
||||
/** 删除课时绑定 */
|
||||
export async function deleteSchedule(id: string): Promise<void> {
|
||||
await db.delete(lessonPlanSchedules).where(eq(lessonPlanSchedules.id, id));
|
||||
}
|
||||
Reference in New Issue
Block a user