feat(modules): add leave-requests, invitation-codes, and standards modules
- Add leave-requests module for staff and student leave request management - Add invitation-codes module for class invitation code generation and redemption - Add standards module for curriculum standards management
This commit is contained in:
222
src/modules/standards/actions.ts
Normal file
222
src/modules/standards/actions.ts
Normal file
@@ -0,0 +1,222 @@
|
||||
/**
|
||||
* M1 课标(Standards)模块 - Server Actions
|
||||
*
|
||||
* 所有 Action 必须调用 requirePermission() 进行权限校验。
|
||||
* 返回值统一采用 ActionState<T> 类型。
|
||||
*/
|
||||
"use server";
|
||||
|
||||
import { getTranslations } from "next-intl/server";
|
||||
import { revalidatePath } from "next/cache";
|
||||
import {
|
||||
createStandardSchema,
|
||||
updateStandardSchema,
|
||||
getStandardsParamsSchema,
|
||||
linkPlanToStandardSchema,
|
||||
} from "./schema";
|
||||
import {
|
||||
getStandards,
|
||||
getStandardsTree,
|
||||
getStandardById,
|
||||
createStandard,
|
||||
updateStandard,
|
||||
deactivateStandard,
|
||||
searchStandards,
|
||||
getStandardsByPlanId,
|
||||
linkPlanToStandard,
|
||||
unlinkPlanFromStandard,
|
||||
} from "./data-access";
|
||||
import type { Standard, StandardTreeNode, LessonPlanStandardLink } from "./types";
|
||||
import type { ActionState } from "@/shared/types/action-state";
|
||||
import { Permissions } from "@/shared/types/permissions";
|
||||
import { getAuthContext, requirePermission } from "@/shared/lib/auth-guard";
|
||||
import { handleActionError } from "@/shared/lib/action-utils";
|
||||
import { safeParseWithI18n, translateFieldErrors } from "@/modules/lesson-preparation/lib/i18n-errors";
|
||||
|
||||
/**
|
||||
* 查询课标列表
|
||||
*/
|
||||
export async function getStandardsAction(
|
||||
params?: Record<string, unknown>,
|
||||
): Promise<ActionState<{ items: Standard[]; tree?: StandardTreeNode[] }>> {
|
||||
try {
|
||||
await requirePermission(Permissions.STANDARD_READ);
|
||||
const t = await getTranslations("standards");
|
||||
const parseResult = getStandardsParamsSchema.safeParse(params ?? {});
|
||||
if (!parseResult.success) {
|
||||
return {
|
||||
success: false,
|
||||
message: t("error.invalidParams"),
|
||||
errors: await translateFieldErrors(parseResult.error.flatten().fieldErrors),
|
||||
};
|
||||
}
|
||||
|
||||
const { asTree, ...queryParams } = parseResult.data;
|
||||
if (asTree) {
|
||||
const tree = await getStandardsTree(queryParams);
|
||||
return { success: true, data: { items: [], tree } };
|
||||
}
|
||||
const items = await getStandards(queryParams);
|
||||
return { success: true, data: { items } };
|
||||
} catch (e) {
|
||||
return handleActionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取单个课标
|
||||
*/
|
||||
export async function getStandardByIdAction(
|
||||
id: string,
|
||||
): Promise<ActionState<{ standard: Standard }>> {
|
||||
try {
|
||||
await requirePermission(Permissions.STANDARD_READ);
|
||||
const standard = await getStandardById(id);
|
||||
if (!standard) {
|
||||
const t = await getTranslations("standards");
|
||||
return { success: false, message: t("error.notFound") };
|
||||
}
|
||||
return { success: true, data: { standard } };
|
||||
} catch (e) {
|
||||
return handleActionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建课标
|
||||
*/
|
||||
export async function createStandardAction(
|
||||
input: Record<string, unknown>,
|
||||
): Promise<ActionState<{ standard: Standard }>> {
|
||||
try {
|
||||
await requirePermission(Permissions.STANDARD_MANAGE);
|
||||
const t = await getTranslations("standards");
|
||||
const parseResult = await safeParseWithI18n(createStandardSchema, input);
|
||||
if (!parseResult.success) return parseResult;
|
||||
|
||||
const auth = await getAuthContext();
|
||||
if (!auth.userId) {
|
||||
return { success: false, message: t("error.unauthorized") };
|
||||
}
|
||||
const standard = await createStandard(parseResult.data, auth.userId);
|
||||
revalidatePath("/admin/standards");
|
||||
return { success: true, data: { standard } };
|
||||
} catch (e) {
|
||||
return handleActionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新课标
|
||||
*/
|
||||
export async function updateStandardAction(
|
||||
input: Record<string, unknown>,
|
||||
): Promise<ActionState<{ standard: Standard }>> {
|
||||
try {
|
||||
await requirePermission(Permissions.STANDARD_MANAGE);
|
||||
const t = await getTranslations("standards");
|
||||
const parseResult = await safeParseWithI18n(updateStandardSchema, input);
|
||||
if (!parseResult.success) return parseResult;
|
||||
|
||||
const { id, ...patch } = parseResult.data;
|
||||
const updated = await updateStandard(id, patch);
|
||||
if (!updated) {
|
||||
return { success: false, message: t("error.notFound") };
|
||||
}
|
||||
revalidatePath("/admin/standards");
|
||||
return { success: true, data: { standard: updated } };
|
||||
} catch (e) {
|
||||
return handleActionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 停用课标(软删除)
|
||||
*/
|
||||
export async function deactivateStandardAction(id: string): Promise<ActionState<null>> {
|
||||
try {
|
||||
await requirePermission(Permissions.STANDARD_MANAGE);
|
||||
await deactivateStandard(id);
|
||||
revalidatePath("/admin/standards");
|
||||
return { success: true, data: null };
|
||||
} catch (e) {
|
||||
return handleActionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 搜索课标
|
||||
*/
|
||||
export async function searchStandardsAction(
|
||||
keyword: string,
|
||||
limit = 50,
|
||||
): Promise<ActionState<{ items: Standard[] }>> {
|
||||
try {
|
||||
await requirePermission(Permissions.STANDARD_READ);
|
||||
const items = await searchStandards(keyword, limit);
|
||||
return { success: true, data: { items } };
|
||||
} catch (e) {
|
||||
return handleActionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询课案关联的课标
|
||||
*/
|
||||
export async function getPlanStandardsAction(
|
||||
planId: string,
|
||||
): Promise<ActionState<{ links: LessonPlanStandardLink[] }>> {
|
||||
try {
|
||||
await requirePermission(Permissions.STANDARD_READ);
|
||||
const links = await getStandardsByPlanId(planId);
|
||||
return { success: true, data: { links } };
|
||||
} catch (e) {
|
||||
return handleActionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 关联课案与课标
|
||||
*/
|
||||
export async function linkPlanToStandardAction(
|
||||
input: Record<string, unknown>,
|
||||
): Promise<ActionState<{ link: LessonPlanStandardLink }>> {
|
||||
try {
|
||||
await requirePermission(Permissions.STANDARD_LINK);
|
||||
const t = await getTranslations("standards");
|
||||
const parseResult = await safeParseWithI18n(linkPlanToStandardSchema, input);
|
||||
if (!parseResult.success) return parseResult;
|
||||
|
||||
const auth = await getAuthContext();
|
||||
if (!auth.userId) {
|
||||
return { success: false, message: t("error.unauthorized") };
|
||||
}
|
||||
const link = await linkPlanToStandard(
|
||||
parseResult.data.planId,
|
||||
parseResult.data.standardId,
|
||||
parseResult.data.relationType,
|
||||
auth.userId,
|
||||
);
|
||||
revalidatePath(`/teacher/lesson-plans/${parseResult.data.planId}/edit`);
|
||||
return { success: true, data: { link } };
|
||||
} catch (e) {
|
||||
return handleActionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消课案与课标的关联
|
||||
*/
|
||||
export async function unlinkPlanFromStandardAction(
|
||||
planId: string,
|
||||
standardId: string,
|
||||
): Promise<ActionState<null>> {
|
||||
try {
|
||||
await requirePermission(Permissions.STANDARD_LINK);
|
||||
await unlinkPlanFromStandard(planId, standardId);
|
||||
revalidatePath(`/teacher/lesson-plans/${planId}/edit`);
|
||||
return { success: true, data: null };
|
||||
} catch (e) {
|
||||
return handleActionError(e);
|
||||
}
|
||||
}
|
||||
265
src/modules/standards/data-access.ts
Normal file
265
src/modules/standards/data-access.ts
Normal file
@@ -0,0 +1,265 @@
|
||||
/**
|
||||
* M1 课标(Standards)模块 - 数据访问层
|
||||
*
|
||||
* 严格遵守三层架构:本文件只被 modules/standards/actions.ts 和 app/ 路由调用,
|
||||
* 不直接被其他业务模块引用。其他模块如需查询课标,应通过本文件导出的函数。
|
||||
*/
|
||||
import "server-only";
|
||||
import { db } from "@/shared/db";
|
||||
import { standards, lessonPlanStandards } from "@/shared/db/schema";
|
||||
import { and, eq, asc, like } from "drizzle-orm";
|
||||
import type {
|
||||
Standard,
|
||||
StandardTreeNode,
|
||||
LessonPlanStandardLink,
|
||||
GetStandardsParams,
|
||||
} from "./types";
|
||||
import type { StandardLevel } from "../lesson-preparation/lib/type-guards";
|
||||
|
||||
/**
|
||||
* 查询课标列表(可按层级/学科/年级过滤,可选返回树形结构)
|
||||
*/
|
||||
export async function getStandards(
|
||||
params: GetStandardsParams = {},
|
||||
): Promise<Standard[]> {
|
||||
const conditions = [];
|
||||
if (params.level) conditions.push(eq(standards.level, params.level));
|
||||
if (params.parentId) conditions.push(eq(standards.parentId, params.parentId));
|
||||
if (params.subjectId) conditions.push(eq(standards.subjectId, params.subjectId));
|
||||
if (params.gradeId) conditions.push(eq(standards.gradeId, params.gradeId));
|
||||
if (params.stage) conditions.push(eq(standards.stage, params.stage));
|
||||
if (!params.includeInactive) conditions.push(eq(standards.isActive, true));
|
||||
|
||||
const rows = await db
|
||||
.select()
|
||||
.from(standards)
|
||||
.where(conditions.length > 0 ? and(...conditions) : undefined)
|
||||
.orderBy(asc(standards.sortOrder), asc(standards.code));
|
||||
|
||||
return rows.map(mapRowToStandard);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询课标树形结构
|
||||
*/
|
||||
export async function getStandardsTree(
|
||||
params: Omit<GetStandardsParams, "asTree" | "parentId"> = {},
|
||||
): Promise<StandardTreeNode[]> {
|
||||
const allStandards = await getStandards(params);
|
||||
return buildStandardsTree(allStandards);
|
||||
}
|
||||
|
||||
/**
|
||||
* 按 ID 获取单个课标
|
||||
*/
|
||||
export async function getStandardById(id: string): Promise<Standard | null> {
|
||||
const rows = await db.select().from(standards).where(eq(standards.id, id)).limit(1);
|
||||
return rows.length === 0 ? null : mapRowToStandard(rows[0]!);
|
||||
}
|
||||
|
||||
/**
|
||||
* 按 code 获取单个课标
|
||||
*/
|
||||
export async function getStandardByCode(code: string): Promise<Standard | null> {
|
||||
const rows = await db.select().from(standards).where(eq(standards.code, code)).limit(1);
|
||||
return rows.length === 0 ? null : mapRowToStandard(rows[0]!);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建课标
|
||||
*/
|
||||
export async function createStandard(
|
||||
input: {
|
||||
code: string;
|
||||
title: string;
|
||||
description?: string;
|
||||
level: StandardLevel;
|
||||
parentId?: string;
|
||||
subjectId?: string;
|
||||
gradeId?: string;
|
||||
stage?: string;
|
||||
sortOrder?: number;
|
||||
},
|
||||
createdBy: string,
|
||||
): Promise<Standard> {
|
||||
const [row] = await db.insert(standards).values({
|
||||
code: input.code,
|
||||
title: input.title,
|
||||
description: input.description,
|
||||
level: input.level,
|
||||
parentId: input.parentId,
|
||||
subjectId: input.subjectId,
|
||||
gradeId: input.gradeId,
|
||||
stage: input.stage,
|
||||
sortOrder: input.sortOrder ?? 0,
|
||||
isActive: true,
|
||||
createdBy,
|
||||
});
|
||||
const insertedId = row.insertId;
|
||||
const created = await getStandardById(String(insertedId));
|
||||
if (!created) throw new Error("STANDARD_CREATE_FAILED");
|
||||
return created;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新课标
|
||||
*/
|
||||
export async function updateStandard(
|
||||
id: string,
|
||||
patch: {
|
||||
title?: string;
|
||||
description?: string;
|
||||
parentId?: string;
|
||||
sortOrder?: number;
|
||||
isActive?: boolean;
|
||||
},
|
||||
): Promise<Standard | null> {
|
||||
await db
|
||||
.update(standards)
|
||||
.set({
|
||||
...(patch.title !== undefined ? { title: patch.title } : {}),
|
||||
...(patch.description !== undefined ? { description: patch.description } : {}),
|
||||
...(patch.parentId !== undefined ? { parentId: patch.parentId } : {}),
|
||||
...(patch.sortOrder !== undefined ? { sortOrder: patch.sortOrder } : {}),
|
||||
...(patch.isActive !== undefined ? { isActive: patch.isActive } : {}),
|
||||
})
|
||||
.where(eq(standards.id, id));
|
||||
return getStandardById(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 软删除课标(标记 isActive=false,保留关联数据完整性)
|
||||
*/
|
||||
export async function deactivateStandard(id: string): Promise<void> {
|
||||
await db.update(standards).set({ isActive: false }).where(eq(standards.id, id));
|
||||
}
|
||||
|
||||
/**
|
||||
* 按关键词搜索课标
|
||||
*/
|
||||
export async function searchStandards(
|
||||
keyword: string,
|
||||
limit = 50,
|
||||
): Promise<Standard[]> {
|
||||
const rows = await db
|
||||
.select()
|
||||
.from(standards)
|
||||
.where(like(standards.title, `%${keyword}%`))
|
||||
.limit(limit)
|
||||
.orderBy(asc(standards.sortOrder));
|
||||
return rows.map(mapRowToStandard);
|
||||
}
|
||||
|
||||
// ---- 课案 ↔ 课标 关联 ----
|
||||
|
||||
/**
|
||||
* 查询课案关联的所有课标
|
||||
*/
|
||||
export async function getStandardsByPlanId(
|
||||
planId: string,
|
||||
): Promise<LessonPlanStandardLink[]> {
|
||||
const rows = await db
|
||||
.select({
|
||||
link: lessonPlanStandards,
|
||||
standard: standards,
|
||||
})
|
||||
.from(lessonPlanStandards)
|
||||
.innerJoin(standards, eq(lessonPlanStandards.standardId, standards.id))
|
||||
.where(eq(lessonPlanStandards.planId, planId))
|
||||
.orderBy(asc(lessonPlanStandards.createdAt));
|
||||
|
||||
return rows.map((r) => ({
|
||||
id: r.link.id,
|
||||
planId: r.link.planId,
|
||||
standardId: r.link.standardId,
|
||||
relationType: r.link.relationType as "primary" | "related",
|
||||
createdBy: r.link.createdBy,
|
||||
createdAt: r.link.createdAt,
|
||||
standard: mapRowToStandard(r.standard),
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* 关联课案与课标
|
||||
*/
|
||||
export async function linkPlanToStandard(
|
||||
planId: string,
|
||||
standardId: string,
|
||||
relationType: "primary" | "related",
|
||||
createdBy: string,
|
||||
): Promise<LessonPlanStandardLink> {
|
||||
const [row] = await db.insert(lessonPlanStandards).values({
|
||||
planId,
|
||||
standardId,
|
||||
relationType,
|
||||
createdBy,
|
||||
});
|
||||
const insertedId = row.insertId;
|
||||
const links = await getStandardsByPlanId(planId);
|
||||
const created = links.find((l) => l.id === String(insertedId));
|
||||
if (!created) throw new Error("STANDARD_LINK_CREATE_FAILED");
|
||||
return created;
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消课案与课标的关联
|
||||
*/
|
||||
export async function unlinkPlanFromStandard(
|
||||
planId: string,
|
||||
standardId: string,
|
||||
): Promise<void> {
|
||||
await db
|
||||
.delete(lessonPlanStandards)
|
||||
.where(
|
||||
and(
|
||||
eq(lessonPlanStandards.planId, planId),
|
||||
eq(lessonPlanStandards.standardId, standardId),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// ---- 辅助函数 ----
|
||||
|
||||
function mapRowToStandard(row: typeof standards.$inferSelect): Standard {
|
||||
return {
|
||||
id: row.id,
|
||||
code: row.code,
|
||||
title: row.title,
|
||||
description: row.description ?? undefined,
|
||||
level: row.level as StandardLevel,
|
||||
parentId: row.parentId ?? undefined,
|
||||
subjectId: row.subjectId ?? undefined,
|
||||
gradeId: row.gradeId ?? undefined,
|
||||
stage: row.stage ?? undefined,
|
||||
sortOrder: row.sortOrder,
|
||||
isActive: row.isActive,
|
||||
createdBy: row.createdBy ?? undefined,
|
||||
createdAt: row.createdAt,
|
||||
updatedAt: row.updatedAt,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 将扁平列表构建为树形结构
|
||||
*/
|
||||
function buildStandardsTree(items: Standard[]): StandardTreeNode[] {
|
||||
const map = new Map<string, StandardTreeNode>();
|
||||
const roots: StandardTreeNode[] = [];
|
||||
|
||||
// 第一遍:建立 id → node 映射
|
||||
for (const item of items) {
|
||||
map.set(item.id, { ...item, children: [] });
|
||||
}
|
||||
|
||||
// 第二遍:构建父子关系
|
||||
for (const item of items) {
|
||||
const node = map.get(item.id)!;
|
||||
if (item.parentId && map.has(item.parentId)) {
|
||||
map.get(item.parentId)!.children.push(node);
|
||||
} else {
|
||||
roots.push(node);
|
||||
}
|
||||
}
|
||||
|
||||
return roots;
|
||||
}
|
||||
68
src/modules/standards/schema.ts
Normal file
68
src/modules/standards/schema.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
/**
|
||||
* M1 课标(Standards)模块 - Zod 验证 schema
|
||||
*
|
||||
* 所有错误消息使用 i18n 键,由 actions 层通过 translateFieldErrors() 翻译。
|
||||
*/
|
||||
import { z } from "zod";
|
||||
|
||||
export const createStandardSchema = z.object({
|
||||
code: z.string().min(1, "error.codeRequired").max(100, "error.codeTooLong"),
|
||||
title: z.string().min(1, "error.titleRequired").max(255, "error.titleTooLong"),
|
||||
description: z.string().max(2000, "error.descriptionTooLong").optional(),
|
||||
level: z.enum(["national", "curriculum", "custom"]),
|
||||
parentId: z.string().optional(),
|
||||
subjectId: z.string().optional(),
|
||||
gradeId: z.string().optional(),
|
||||
stage: z.enum(["primary", "junior_high", "senior_high"]).optional(),
|
||||
sortOrder: z.number().int().min(0).default(0),
|
||||
});
|
||||
|
||||
export const updateStandardSchema = z.object({
|
||||
id: z.string().min(1, "error.idRequired"),
|
||||
title: z.string().min(1, "error.titleRequired").max(255, "error.titleTooLong").optional(),
|
||||
description: z.string().max(2000, "error.descriptionTooLong").optional(),
|
||||
parentId: z.string().optional(),
|
||||
sortOrder: z.number().int().min(0).optional(),
|
||||
isActive: z.boolean().optional(),
|
||||
});
|
||||
|
||||
export const getStandardsParamsSchema = z.object({
|
||||
level: z.enum(["national", "curriculum", "custom"]).optional(),
|
||||
parentId: z.string().optional(),
|
||||
subjectId: z.string().optional(),
|
||||
gradeId: z.string().optional(),
|
||||
stage: z.enum(["primary", "junior_high", "senior_high"]).optional(),
|
||||
includeInactive: z.boolean().optional(),
|
||||
asTree: z.boolean().optional(),
|
||||
});
|
||||
|
||||
export const importStandardsSchema = z.object({
|
||||
standards: z
|
||||
.array(
|
||||
z.object({
|
||||
code: z.string().min(1, "error.codeRequired"),
|
||||
title: z.string().min(1, "error.titleRequired"),
|
||||
description: z.string().optional(),
|
||||
level: z.enum(["national", "curriculum", "custom"]),
|
||||
parentId: z.string().optional(),
|
||||
subjectId: z.string().optional(),
|
||||
gradeId: z.string().optional(),
|
||||
stage: z.enum(["primary", "junior_high", "senior_high"]).optional(),
|
||||
sortOrder: z.number().int().min(0).optional(),
|
||||
}),
|
||||
)
|
||||
.min(1, "error.atLeastOneStandard"),
|
||||
conflictStrategy: z.enum(["skip", "update", "fail"]).default("skip"),
|
||||
});
|
||||
|
||||
export const linkPlanToStandardSchema = z.object({
|
||||
planId: z.string().min(1, "error.planIdRequired"),
|
||||
standardId: z.string().min(1, "error.standardIdRequired"),
|
||||
relationType: z.enum(["primary", "related"]).default("primary"),
|
||||
});
|
||||
|
||||
export type CreateStandardInput = z.infer<typeof createStandardSchema>;
|
||||
export type UpdateStandardInput = z.infer<typeof updateStandardSchema>;
|
||||
export type GetStandardsParamsInput = z.infer<typeof getStandardsParamsSchema>;
|
||||
export type ImportStandardsInputZod = z.infer<typeof importStandardsSchema>;
|
||||
export type LinkPlanToStandardInput = z.infer<typeof linkPlanToStandardSchema>;
|
||||
83
src/modules/standards/types.ts
Normal file
83
src/modules/standards/types.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
/**
|
||||
* M1 课标(Standards)模块 - 类型定义
|
||||
*
|
||||
* 支持国家标准 / 课标 / 自定义三层级课标库。
|
||||
* 课案通过 lessonPlanStandards 关联表实现多对多关系。
|
||||
*/
|
||||
|
||||
import type { StandardLevel } from "../lesson-preparation/lib/type-guards";
|
||||
|
||||
/** 课标节点 */
|
||||
export interface Standard {
|
||||
id: string;
|
||||
code: string;
|
||||
title: string;
|
||||
description?: string;
|
||||
level: StandardLevel;
|
||||
parentId?: string;
|
||||
subjectId?: string;
|
||||
gradeId?: string;
|
||||
/** 学段:primary / junior_high / senior_high */
|
||||
stage?: string;
|
||||
sortOrder: number;
|
||||
isActive: boolean;
|
||||
createdBy?: string;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
}
|
||||
|
||||
/** 课标树节点(带子节点) */
|
||||
export interface StandardTreeNode extends Standard {
|
||||
children: StandardTreeNode[];
|
||||
}
|
||||
|
||||
/** 课案 ↔ 课标 关联 */
|
||||
export interface LessonPlanStandardLink {
|
||||
id: string;
|
||||
planId: string;
|
||||
standardId: string;
|
||||
/** 关联类型:primary(主要对标)/ related(相关对标) */
|
||||
relationType: "primary" | "related";
|
||||
createdBy: string;
|
||||
createdAt: Date;
|
||||
/** 关联的课标详情(join 查询时填充) */
|
||||
standard?: Standard;
|
||||
}
|
||||
|
||||
/** 课案课标覆盖度统计 */
|
||||
export interface LessonPlanStandardsCoverage {
|
||||
planId: string;
|
||||
totalStandards: number;
|
||||
primaryCount: number;
|
||||
relatedCount: number;
|
||||
coveragePercent: number;
|
||||
}
|
||||
|
||||
/** 课标查询参数 */
|
||||
export interface GetStandardsParams {
|
||||
level?: StandardLevel;
|
||||
parentId?: string;
|
||||
subjectId?: string;
|
||||
gradeId?: string;
|
||||
stage?: string;
|
||||
includeInactive?: boolean;
|
||||
/** 是否返回树形结构 */
|
||||
asTree?: boolean;
|
||||
}
|
||||
|
||||
/** 课标导入参数 */
|
||||
export interface ImportStandardsInput {
|
||||
standards: Array<{
|
||||
code: string;
|
||||
title: string;
|
||||
description?: string;
|
||||
level: StandardLevel;
|
||||
parentId?: string;
|
||||
subjectId?: string;
|
||||
gradeId?: string;
|
||||
stage?: string;
|
||||
sortOrder?: number;
|
||||
}>;
|
||||
/** 导入策略:skip(跳过已存在)/ update(更新已存在)/ fail(重复则失败) */
|
||||
conflictStrategy?: "skip" | "update" | "fail";
|
||||
}
|
||||
Reference in New Issue
Block a user