feat(lesson-preparation): add readonly view, anchor node selector, and type guards

- Add lesson-plan-readonly-view for viewing published plans

- Add anchor-node-selector and textbook-segments for canvas anchor positioning

- Add i18n-errors and type-guards lib utilities

- Add lesson-plan-provider-setup for provider initialization

- Update actions, data-access (knowledge, versions, main), publish-service

- Update blocks (blackboard, exercise, homework, import, key-point, objective, reflection)

- Update editor, node-editor, node-edit-panel, pickers, and providers
This commit is contained in:
SpecialX
2026-06-24 12:02:42 +08:00
parent a48e7d0e27
commit 6bc113eaff
39 changed files with 2129 additions and 571 deletions

View File

@@ -2,17 +2,33 @@
import {
getLessonPlansAction,
getLessonPlanByIdAction,
updateLessonPlanAction,
saveLessonPlanVersionAction,
getLessonPlanVersionsAction,
revertLessonPlanVersionAction,
duplicateLessonPlanAction,
deleteLessonPlanAction,
publishLessonPlanAction,
unpublishLessonPlanAction,
createLessonPlanAction,
getTextbooksForPickerAction,
getChaptersForPickerAction,
getLessonPlanTemplatesAction,
} from "../actions";
import { getKnowledgePointOptionsAction } from "../actions-kp";
import { publishLessonPlanHomeworkAction } from "../actions-publish";
import { getQuestionsAction } from "@/modules/questions/actions";
import type { LessonPlanDataService } from "../providers/lesson-plan-provider";
/**
* 默认数据服务实现:包装现有 Server Actions。
* 通过 LessonPlanProvider 注入,组件不直接 import actions。
* 测试时可替换为 mock 实现。
*
* V3 扩展:新增 picker/dialog 组件所需方法createLessonPlan / getTextbooksForPicker /
* getChaptersForPicker / getLessonPlanTemplates / getKnowledgePointOptions /
* publishLessonPlanHomework / getQuestions
*/
export function createDefaultDataService(): LessonPlanDataService {
return {
@@ -24,6 +40,27 @@ export function createDefaultDataService(): LessonPlanDataService {
return { success: false, message: res.message };
},
async getLessonPlanById(planId) {
const res = await getLessonPlanByIdAction(planId);
if (res.success && res.data) {
return { success: true, data: { plan: res.data.plan } };
}
return { success: false, message: res.message };
},
async updateLessonPlan(input) {
const res = await updateLessonPlanAction(input);
return { success: res.success, message: res.message };
},
async saveLessonPlanVersion(input) {
const res = await saveLessonPlanVersionAction(input);
if (res.success && res.data) {
return { success: true, data: { versionNo: res.data.versionNo } };
}
return { success: false, message: res.message };
},
async getLessonPlanVersions(planId) {
const res = await getLessonPlanVersionsAction(planId);
if (res.success && res.data) {
@@ -46,5 +83,83 @@ export function createDefaultDataService(): LessonPlanDataService {
const res = await deleteLessonPlanAction(planId);
return { success: res.success, message: res.message };
},
async publishLessonPlan(planId) {
const res = await publishLessonPlanAction(planId);
return { success: res.success, message: res.message };
},
async unpublishLessonPlan(planId) {
const res = await unpublishLessonPlanAction(planId);
return { success: res.success, message: res.message };
},
// ---- V3 新增picker/dialog 组件所需方法 ----
async createLessonPlan(prevState, formData) {
const res = await createLessonPlanAction(prevState, formData);
if (res.success && res.data) {
return { success: true, data: { planId: res.data.planId } };
}
return { success: false, message: res.message, errors: res.errors };
},
async getTextbooksForPicker() {
const res = await getTextbooksForPickerAction();
if (res.success && res.data) {
return { success: true, data: { textbooks: res.data.textbooks } };
}
return { success: false, message: res.message };
},
async getChaptersForPicker(textbookId) {
const res = await getChaptersForPickerAction(textbookId);
if (res.success && res.data) {
return { success: true, data: { chapters: res.data.chapters } };
}
return { success: false, message: res.message };
},
async getLessonPlanTemplates() {
const res = await getLessonPlanTemplatesAction();
if (res.success && res.data) {
return { success: true, data: { templates: res.data.templates } };
}
return { success: false, message: res.message };
},
async getKnowledgePointOptions(input) {
const res = await getKnowledgePointOptionsAction(input);
if (res.success && res.data) {
return { success: true, data: { options: res.data.options } };
}
return { success: false, message: res.message, errors: res.errors };
},
async publishLessonPlanHomework(input) {
const res = await publishLessonPlanHomeworkAction(input);
if (res.success && res.data) {
return {
success: true,
data: { examId: res.data.examId, assignmentId: res.data.assignmentId },
};
}
return { success: false, message: res.message, errors: res.errors };
},
async getQuestions(params) {
const res = await getQuestionsAction(params);
if (res.success && res.data) {
// 从 questions 模块的返回结构中提取 picker 所需字段
const items = res.data.data.map((q) => ({
id: q.id,
type: q.type,
difficulty: q.difficulty,
content: q.content,
}));
return { success: true, data: { data: items } };
}
return { success: false, message: res.message };
},
};
}