refactor(lesson-preparation): V2 审计深度修复 — Server Actions i18n + 错误码模式 + 类型断言清零 + a11y 深度修复 + Tracker 埋点接入

V2-1: 12 个 Server Action 通过 getTranslations 翻译错误消息;Service/DataAccess 层抛出错误码异常(PublishServiceError/LessonPlanDataError),Actions 层通过 PUBLISH_ERROR_KEY_MAP 翻译为 i18n 消息
V2-2: SYSTEM_TEMPLATES name/title 改为 i18n 键,createLessonPlan 接受 translateTitle 函数在服务端翻译后存储到 DB
V2-3: 8 处 as unknown as 断言替换为显式类型映射函数(mapRowToLessonPlan/mapRowToListItem/mapRowToTemplate/mapRowToVersion)+ 类型守卫(isLessonPlanStatus/isTemplateType/isTemplateScope)
V2-4: MiniMap nodeColor 复用 lib/node-summary.ts 的 getNodeColor
V2-5: a11y 深度修复 — lesson-plan-filters/exercise-block/inline-question-editor 的 select 添加 label htmlFor 关联;exercise-block 题目列表改为 ul/li;node-editor 画布添加 role=application + 键盘导航配置
V2-6: Tracker 埋点接入 — 新增 useLessonPlanTrackerSafe hook,在 create/save/publish/revert/duplicate/archive 6 处调用 tracker.track

同步更新架构图 004 和 005 文档
This commit is contained in:
SpecialX
2026-06-22 18:45:35 +08:00
parent 1fe30984b6
commit 97e59b95a1
23 changed files with 668 additions and 135 deletions

View File

@@ -19,6 +19,7 @@ import "@xyflow/react/dist/style.css";
import { useLessonPlanEditor } from "../hooks/use-lesson-plan-editor";
import { LessonNode } from "./nodes/lesson-node";
import { toRfNodes, toRfEdges } from "../lib/rf-mappers";
import { getNodeColor } from "../lib/node-summary";
import type { LessonPlanNode } from "../types";
const nodeTypes = { lesson: LessonNode };
@@ -87,7 +88,7 @@ export function NodeEditor({}: Props) {
);
return (
<div className="w-full h-full relative">
<div className="w-full h-full relative" role="application" aria-label={t("editor.canvasLabel")}>
{doc.nodes.length === 0 && (
<div className="absolute inset-0 flex items-center justify-center pointer-events-none z-10">
<div className="text-center text-on-surface-variant">
@@ -107,6 +108,12 @@ export function NodeEditor({}: Props) {
onPaneClick={() => selectNode(null)}
fitView
fitViewOptions={{ padding: 0.2, maxZoom: 1.2 }}
nodesFocusable
nodesDraggable
edgesFocusable
elementsSelectable
deleteKeyCode={["Backspace", "Delete"]}
multiSelectionKeyCode={["Shift", "Meta", "Control"]}
defaultEdgeOptions={{
animated: true,
style: { stroke: "#1976d2", strokeWidth: 2 },
@@ -126,21 +133,7 @@ export function NodeEditor({}: Props) {
nodeColor={(n) => {
const nodeData = (n.data as { node?: LessonPlanNode }).node;
if (!nodeData) return "#9e9e9e";
const colors: Record<string, string> = {
objective: "#4caf50",
key_point: "#f44336",
import: "#2196f3",
new_teaching: "#9c27b0",
consolidation: "#ff9800",
summary: "#607d8b",
homework: "#795548",
blackboard: "#009688",
text_study: "#3f51b5",
exercise: "#e91e63",
rich_text: "#9e9e9e",
reflection: "#cddc39",
};
return colors[nodeData.type] ?? "#9e9e9e";
return getNodeColor(nodeData.type);
}}
/>
</ReactFlow>