feat(textbooks): 教材模块审计重构 — 跨模块解耦 + 权限 + i18n + 错误边界 + 纯函数抽取

P0 修复:
- 解耦跨模块 UI 依赖:knowledge-point-dialogs 不再直接 import questions,
  改为 renderQuestionCreator render prop 由页面注入
- 接入 usePermission Hook 替换 canEdit 硬编码
- 全模块 i18n 改造:新增 en/zh-CN 翻译文件,替换所有硬编码文案
- Server Action 资源归属校验:新增 verifyChapterBelongsToTextbook/
  verifyKnowledgePointBelongsToTextbook,在 reorder/update/delete/create 中校验

P1 改进:
- 补齐 Error Boundary:4 个 error.tsx + TextbookSectionErrorBoundary 区块包裹
- 抽取纯函数到 utils.ts/graph-layout.ts/constants.ts 并补单测(26 用例全通过)
- 消除重复组件:删除 knowledge-point-panel/create-knowledge-point-dialog
- 修复类型断言:chapter.children! → 守卫式访问
- 图谱 a11y:添加 role/aria-label/aria-pressed
- 统一删除确认:confirm() → AlertDialog
- 数据范围过滤:getTextbooksWithScope 支持学生端按年级过滤

P2 预留:
- TextbookAnalytics 埋点接口 + Provider + Hook

同步 005 架构数据 JSON:补充 getTextbooksWithScope/verify*/ChapterTreeNode 等
This commit is contained in:
SpecialX
2026-06-22 16:25:59 +08:00
parent 45ee1ae43c
commit 22d3f07fcf
35 changed files with 2043 additions and 792 deletions

View File

@@ -0,0 +1,186 @@
{
"list": {
"title": "Textbooks",
"subtitle": "Manage your digital curriculum resources and chapters.",
"add": "Add Textbook",
"empty": {
"withFilters": "No textbooks match your filters",
"withoutFilters": "No textbooks yet",
"withFiltersDesc": "Try clearing filters or adjusting keywords.",
"withoutFiltersDesc": "Create your first textbook to start organizing chapters."
},
"clearFilters": "Clear filters",
"chapters": "Chapters"
},
"student": {
"list": {
"title": "Textbooks",
"subtitle": "Browse your course textbooks.",
"empty": {
"withFilters": "No textbooks match your filters",
"withoutFilters": "No textbooks yet",
"withFiltersDesc": "Try clearing filters or adjusting keywords.",
"withoutFiltersDesc": "No textbooks are available right now."
}
},
"noUser": "No user found",
"noUserDesc": "Create a student user to see textbooks."
},
"reader": {
"back": "Back to textbooks",
"tabs": {
"chapters": "Chapters",
"knowledge": "Knowledge Points",
"graph": "Graph"
},
"selectChapter": "Please select a chapter to start reading.",
"selectChapterKnowledge": "Please select a chapter to view knowledge points.",
"selectChapterGraph": "Please select a chapter to view the knowledge graph.",
"emptyKnowledge": "No knowledge points in this chapter yet.",
"emptyContent": "No content yet",
"editContent": "Edit Content",
"cancel": "Cancel",
"save": "Save",
"saving": "Saving...",
"addKnowledgePoint": "Add Knowledge Point",
"clickToViewKp": "Click to view knowledge point details",
"noChapters": "No chapters",
"noChaptersDesc": "This textbook has no chapters yet."
},
"dialog": {
"create": {
"title": "Add New Textbook",
"description": "Create a new digital textbook. Click save when you're done.",
"submit": "Save changes",
"saving": "Saving..."
},
"settings": {
"title": "Textbook Settings",
"description": "Update textbook details or delete this textbook.",
"delete": "Delete Textbook",
"deleteConfirmTitle": "Delete Textbook?",
"deleteConfirmDesc": "This action cannot be undone. This will permanently delete the textbook and all its chapters and knowledge points.",
"save": "Save Changes",
"processing": "Processing...",
"trigger": "Settings"
},
"chapter": {
"createTitle": "Add New Chapter",
"createDesc": "Create a new chapter or section.",
"submit": "Create Chapter",
"creating": "Creating...",
"deleteTitle": "Delete Chapter?",
"deleteDesc": "This will permanently delete {title}. This action cannot be undone.",
"delete": "Delete",
"deleting": "Deleting...",
"cannotDeleteWithSubchapters": "Cannot delete chapter with subchapters",
"addSubchapter": "Add Subchapter",
"titlePlaceholder": "e.g. Chapter 1: Introduction"
},
"knowledge": {
"createTitle": "Add Knowledge Point",
"createDesc": "Create a knowledge point from the selected text.",
"editTitle": "Edit Knowledge Point",
"editDesc": "Modify the knowledge point name and description.",
"name": "Name",
"description": "Description (optional)",
"descriptionPlaceholder": "Enter description...",
"displayName": "Display Name",
"anchorText": "Advanced: Anchor Text (affects highlighting)",
"anchorTextHint": "Changing this field will change the text highlighted in the content. Usually keep it consistent with the original.",
"create": "Create",
"creating": "Creating...",
"save": "Save",
"saving": "Saving...",
"cancel": "Cancel",
"deleteTitle": "Confirm Delete",
"deleteDesc": "Are you sure you want to delete this knowledge point? This action cannot be undone.",
"delete": "Delete",
"createQuestion": "Create Related Question",
"editKp": "Edit Knowledge Point",
"deleteKp": "Delete Knowledge Point"
}
},
"field": {
"title": "Title",
"subject": "Subject",
"grade": "Grade",
"publisher": "Publisher",
"titlePlaceholder": "e.g. Advanced Calculus",
"publisherPlaceholder": "e.g. Next Education",
"subjectPlaceholder": "Select subject",
"gradePlaceholder": "Select grade"
},
"filters": {
"searchPlaceholder": "Search by title, publisher...",
"allSubjects": "All Subjects",
"allGrades": "All Grades"
},
"card": {
"chapters": "Chapters",
"updated": "Updated",
"gradeNA": "Grade N/A",
"publisherNA": "Publisher N/A",
"editContent": "Edit Content",
"delete": "Delete",
"moreOptions": "More options"
},
"panel": {
"knowledgePoints": "Knowledge Points",
"noPointsYet": "No points yet",
"noPointsDesc": "Add knowledge points to tag content in this chapter.",
"selectChapter": "Select a chapter to manage knowledge points",
"level": "Lv."
},
"subject": {
"mathematics": "Mathematics",
"physics": "Physics",
"chemistry": "Chemistry",
"biology": "Biology",
"english": "English",
"history": "History",
"geography": "Geography"
},
"grade": {
"grade7": "Grade 7",
"grade8": "Grade 8",
"grade9": "Grade 9",
"grade10": "Grade 10",
"grade11": "Grade 11",
"grade12": "Grade 12"
},
"error": {
"loadFailed": "Failed to load textbook",
"loadFailedDesc": "An error occurred while loading the textbook content. Please try again.",
"retry": "Retry"
},
"action": {
"createSuccess": "Textbook created successfully.",
"createFailed": "Failed to create textbook.",
"updateSuccess": "Textbook updated successfully.",
"updateFailed": "Failed to update textbook.",
"deleteSuccess": "Textbook deleted successfully.",
"deleteFailed": "Failed to delete textbook.",
"chapterCreateSuccess": "Chapter created successfully",
"chapterCreateFailed": "Failed to create chapter",
"chapterDeleteSuccess": "Chapter deleted successfully",
"chapterDeleteFailed": "Failed to delete chapter",
"contentUpdateSuccess": "Content updated successfully",
"contentUpdateFailed": "Failed to update content",
"kpCreateSuccess": "Knowledge point created successfully",
"kpCreateFailed": "Failed to create knowledge point",
"kpUpdateSuccess": "Knowledge point updated successfully",
"kpUpdateFailed": "Failed to update knowledge point",
"kpDeleteSuccess": "Knowledge point deleted successfully",
"kpDeleteFailed": "Failed to delete knowledge point",
"reorderSuccess": "Order updated",
"reorderFailed": "Failed to reorder chapters",
"fillRequired": "Please fill in all required fields.",
"titleRequired": "Title is required",
"nameRequired": "Name is required",
"invalidContent": "Invalid chapter content data",
"errorOccurred": "An error occurred",
"deleteFailed": "Deletion failed",
"updateFailedGeneric": "Update failed"
}
}

View File

@@ -0,0 +1,186 @@
{
"list": {
"title": "教材",
"subtitle": "管理数字课程资源与章节。",
"add": "新建教材",
"empty": {
"withFilters": "没有匹配的教材",
"withoutFilters": "暂无教材",
"withFiltersDesc": "请尝试清除筛选或调整关键词。",
"withoutFiltersDesc": "创建你的第一本教材以开始组织章节。"
},
"clearFilters": "清除筛选",
"chapters": "章节"
},
"student": {
"list": {
"title": "教材",
"subtitle": "浏览你的课程教材。",
"empty": {
"withFilters": "没有匹配的教材",
"withoutFilters": "暂无教材",
"withFiltersDesc": "请尝试清除筛选或调整关键词。",
"withoutFiltersDesc": "暂时没有可用的教材。"
}
},
"noUser": "未找到用户",
"noUserDesc": "请创建学生用户以查看教材。"
},
"reader": {
"back": "返回教材列表",
"tabs": {
"chapters": "章节目录",
"knowledge": "知识点",
"graph": "图谱"
},
"selectChapter": "请选择一个章节开始阅读。",
"selectChapterKnowledge": "请选择一个章节查看知识点。",
"selectChapterGraph": "请选择一个章节查看知识图谱。",
"emptyKnowledge": "该章节暂无知识点。",
"emptyContent": "暂无内容",
"editContent": "编辑内容",
"cancel": "取消",
"save": "保存",
"saving": "保存中...",
"addKnowledgePoint": "添加知识点",
"clickToViewKp": "点击查看知识点详情",
"noChapters": "暂无章节",
"noChaptersDesc": "这本教材还没有章节。"
},
"dialog": {
"create": {
"title": "新建教材",
"description": "创建一本新的数字教材。完成后点击保存。",
"submit": "保存",
"saving": "保存中..."
},
"settings": {
"title": "教材设置",
"description": "更新教材信息或删除此教材。",
"delete": "删除教材",
"deleteConfirmTitle": "确认删除教材?",
"deleteConfirmDesc": "此操作无法撤销。将永久删除该教材及其所有章节和知识点。",
"save": "保存修改",
"processing": "处理中...",
"trigger": "设置"
},
"chapter": {
"createTitle": "新建章节",
"createDesc": "创建一个新章节或小节。",
"submit": "创建章节",
"creating": "创建中...",
"deleteTitle": "删除章节?",
"deleteDesc": "将永久删除 {title}。此操作无法撤销。",
"delete": "删除",
"deleting": "删除中...",
"cannotDeleteWithSubchapters": "无法删除含有子章节的章节",
"addSubchapter": "添加子章节",
"titlePlaceholder": "例如:第一章:入门"
},
"knowledge": {
"createTitle": "添加知识点",
"createDesc": "从选中的文本创建知识点。",
"editTitle": "编辑知识点",
"editDesc": "修改知识点的名称和描述。",
"name": "名称",
"description": "描述(可选)",
"descriptionPlaceholder": "请输入描述...",
"displayName": "显示名称",
"anchorText": "高级:关联文本(影响文中高亮)",
"anchorTextHint": "修改此字段会改变文中被高亮匹配的文字。通常保持与原文一致。",
"create": "创建",
"creating": "创建中...",
"save": "保存",
"saving": "保存中...",
"cancel": "取消",
"deleteTitle": "确认删除",
"deleteDesc": "确定要删除这个知识点吗?此操作无法撤销。",
"delete": "删除",
"createQuestion": "创建相关题目",
"editKp": "编辑知识点",
"deleteKp": "删除知识点"
}
},
"field": {
"title": "标题",
"subject": "学科",
"grade": "年级",
"publisher": "出版社",
"titlePlaceholder": "例如:高等数学",
"publisherPlaceholder": "例如:人教社",
"subjectPlaceholder": "选择学科",
"gradePlaceholder": "选择年级"
},
"filters": {
"searchPlaceholder": "按标题、出版社搜索...",
"allSubjects": "全部学科",
"allGrades": "全部年级"
},
"card": {
"chapters": "章节",
"updated": "更新于",
"gradeNA": "暂无年级",
"publisherNA": "暂无出版社",
"editContent": "编辑内容",
"delete": "删除",
"moreOptions": "更多操作"
},
"panel": {
"knowledgePoints": "知识点",
"noPointsYet": "暂无知识点",
"noPointsDesc": "添加知识点以标记本章内容。",
"selectChapter": "选择一个章节以管理知识点",
"level": "等级"
},
"subject": {
"mathematics": "数学",
"physics": "物理",
"chemistry": "化学",
"biology": "生物",
"english": "英语",
"history": "历史",
"geography": "地理"
},
"grade": {
"grade7": "七年级",
"grade8": "八年级",
"grade9": "九年级",
"grade10": "高一",
"grade11": "高二",
"grade12": "高三"
},
"error": {
"loadFailed": "教材加载失败",
"loadFailedDesc": "加载教材内容时发生错误,请重试。",
"retry": "重试"
},
"action": {
"createSuccess": "教材创建成功。",
"createFailed": "创建教材失败。",
"updateSuccess": "教材更新成功。",
"updateFailed": "更新教材失败。",
"deleteSuccess": "教材删除成功。",
"deleteFailed": "删除教材失败。",
"chapterCreateSuccess": "章节创建成功",
"chapterCreateFailed": "创建章节失败",
"chapterDeleteSuccess": "章节删除成功",
"chapterDeleteFailed": "删除章节失败",
"contentUpdateSuccess": "内容更新成功",
"contentUpdateFailed": "更新内容失败",
"kpCreateSuccess": "知识点创建成功",
"kpCreateFailed": "创建知识点失败",
"kpUpdateSuccess": "知识点更新成功",
"kpUpdateFailed": "更新知识点失败",
"kpDeleteSuccess": "知识点删除成功",
"kpDeleteFailed": "删除知识点失败",
"reorderSuccess": "排序已更新",
"reorderFailed": "章节排序失败",
"fillRequired": "请填写所有必填字段。",
"titleRequired": "标题为必填项",
"nameRequired": "名称为必填项",
"invalidContent": "章节内容数据无效",
"errorOccurred": "发生错误",
"deleteFailed": "删除失败",
"updateFailedGeneric": "更新失败"
}
}