按 ARCHITECTURE.md 与 admin-NeedTodo.md 要求补齐所有管理页面缺失功能: - users/roles/permissions:权限矩阵搜索/折叠、zod 校验、value 字段 - audit-logs:行内详情对话框、分页页码、ChartCardShell - school:CRUD 对话框、GradeOverviewCards、academic-year 侧栏 - announcements/invitation-codes/ai-settings:发布按钮、分页、zod 校验 - course-plans/elective:Select 导入、undefined 处理 - error-book/scheduling/questions/lesson-plans/attendance:统计卡片 验证:typecheck 0 错误、arch:scan 已更新
126 lines
3.4 KiB
TypeScript
126 lines
3.4 KiB
TypeScript
/**
|
||
* Admin Lesson Plans 数据变换工具(ARCHITECTURE.md §11.3 DoD - 纯函数单测)
|
||
*
|
||
* 所有格式化/映射函数均为纯函数,便于 vitest 单测。
|
||
* 关联:ARCHITECTURE.md §11.3 DoD "数据变换/权限判断等纯函数有 vitest 单测"
|
||
*/
|
||
|
||
import type { AdminLessonPlanListItem } from "@/lib/api/admin-p5";
|
||
|
||
/** 教案状态中文标签映射 */
|
||
export const LESSON_PLAN_STATUS_LABEL: Record<string, string> = {
|
||
DRAFT: "草稿",
|
||
PUBLISHED: "已发布",
|
||
ARCHIVED: "已归档",
|
||
SUBMITTED: "已提交",
|
||
};
|
||
|
||
/**
|
||
* 将教案状态枚举值映射为中文标签。
|
||
* 未知状态回退为原始值。
|
||
*/
|
||
export function formatLessonPlanStatus(status: string): string {
|
||
return LESSON_PLAN_STATUS_LABEL[status] ?? status;
|
||
}
|
||
|
||
/**
|
||
* 格式化 ISO 日期字符串为本地化展示(zh-CN,含年月日时分)。
|
||
* 输入无效(null/undefined/空/非法)时返回占位符 "--"。
|
||
*/
|
||
export function formatLessonPlanDate(
|
||
isoDate: string | null | undefined,
|
||
): string {
|
||
if (!isoDate) return "--";
|
||
const d = new Date(isoDate);
|
||
if (Number.isNaN(d.getTime())) return "--";
|
||
return d.toLocaleString("zh-CN", {
|
||
year: "numeric",
|
||
month: "2-digit",
|
||
day: "2-digit",
|
||
hour: "2-digit",
|
||
minute: "2-digit",
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 判断教案是否可编辑(草稿与已发布状态允许编辑,归档后不可编辑)。
|
||
*/
|
||
export function isLessonPlanEditable(status: string): boolean {
|
||
return status === "DRAFT" || status === "PUBLISHED";
|
||
}
|
||
|
||
/**
|
||
* 判断教案是否已发布(PUBLISHED)。
|
||
*/
|
||
export function isLessonPlanPublished(status: string): boolean {
|
||
return status === "PUBLISHED";
|
||
}
|
||
|
||
/**
|
||
* 判断教案是否已归档(ARCHIVED)。
|
||
*/
|
||
export function isLessonPlanArchived(status: string): boolean {
|
||
return status === "ARCHIVED";
|
||
}
|
||
|
||
/**
|
||
* 从教案详情中提取列表项视图模型(裁剪字段)。
|
||
*
|
||
* 用于详情→列表裁剪场景;详情字段(textbookId/chapterId/content 等)被剥离。
|
||
*/
|
||
export function toAdminLessonPlanListItem(detail: {
|
||
id: string;
|
||
title: string;
|
||
subjectId: string;
|
||
subjectName: string;
|
||
teacherId: string;
|
||
teacherName: string;
|
||
classId: string;
|
||
className: string;
|
||
status: string;
|
||
createdAt: string;
|
||
updatedAt: string;
|
||
}): AdminLessonPlanListItem {
|
||
return {
|
||
id: detail.id,
|
||
title: detail.title,
|
||
subjectId: detail.subjectId,
|
||
subjectName: detail.subjectName,
|
||
teacherId: detail.teacherId,
|
||
teacherName: detail.teacherName,
|
||
classId: detail.classId,
|
||
className: detail.className,
|
||
status: detail.status,
|
||
createdAt: detail.createdAt,
|
||
updatedAt: detail.updatedAt,
|
||
};
|
||
}
|
||
|
||
/**
|
||
* 格式化数量为展示字符串。
|
||
* 输入无效(null/undefined/NaN/负数)返回 "0"。
|
||
*/
|
||
export function formatCount(count: number | null | undefined): string {
|
||
if (count === null || count === undefined) return "0";
|
||
if (!Number.isFinite(count) || count < 0) return "0";
|
||
return `${count}`;
|
||
}
|
||
|
||
/**
|
||
* 根据教案状态返回 Tailwind 徽章语义类名。
|
||
*/
|
||
export function lessonPlanStatusToBadgeClass(status: string): string {
|
||
switch (status) {
|
||
case "DRAFT":
|
||
return "bg-muted text-muted-foreground";
|
||
case "PUBLISHED":
|
||
return "bg-primary/10 text-primary";
|
||
case "ARCHIVED":
|
||
return "bg-muted text-muted-foreground";
|
||
case "SUBMITTED":
|
||
return "bg-amber-500/10 text-amber-600 dark:text-amber-400";
|
||
default:
|
||
return "bg-muted text-muted-foreground";
|
||
}
|
||
}
|