refactor(dashboard): V2 审计重构 — i18n 补齐 + 共享抽象 + 单测 + a11y
V2 审计报告(docs/architecture/audit/dashboard-audit-report-v2.md)发现并修复: - P0 i18n:10 个子组件硬编码字符串全部接入 next-intl(teacher-quick-actions / teacher-classes-card / teacher-homework-card / teacher-schedule / recent-submissions / teacher-grade-trends / student-grades-card / student-today-schedule-card / student-upcoming-assignments-card / admin-dashboard),新增 ~50 个翻译键 - P1 共享抽象:新增 DashboardGreetingHeader 组件,消除 teacher/student 头部 90% 重复代码,两个 Header 改为薄包装 - P2 单测:为 6 个纯函数添加 31 个单元测试 (tests/integration/dashboard/dashboard-utils.test.ts) - P2 a11y:admin 表格 caption、teacher/student 视图语义化标签 (header / section aria-label / aside aria-label) - 同步架构图 004/005
This commit is contained in:
@@ -6055,6 +6055,11 @@
|
||||
"name": "RecentSubmissions",
|
||||
"file": "teacher-dashboard/RecentSubmissions",
|
||||
"purpose": "最近提交"
|
||||
},
|
||||
{
|
||||
"name": "DashboardGreetingHeader",
|
||||
"file": "dashboard-greeting-header",
|
||||
"purpose": "共享问候头部组件(V2 抽象,消除 teacher/student 头部 90% 重复代码,接收 userName 和可选 actions slot)"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -8329,6 +8334,80 @@
|
||||
]
|
||||
}
|
||||
],
|
||||
"statsService": [
|
||||
{
|
||||
"name": "computeGradeStats",
|
||||
"signature": "(rows: RawScoreRow[]) => GradeStats | null",
|
||||
"file": "stats-service.ts",
|
||||
"purpose": "从原始成绩行计算班级统计(均分、中位数、标准差、及格率、优秀率、最高分、最低分、参考人数)(P1-1 新增:从 data-access.getClassGradeStats 抽取为纯函数)",
|
||||
"usedBy": [
|
||||
"data-access.getClassGradeStats"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "computeAverageScore",
|
||||
"signature": "(scores: number[]) => number",
|
||||
"file": "stats-service.ts",
|
||||
"purpose": "计算分数平均值(空数组返回 0)(P1-1 新增:从 data-access.getStudentGradeSummary 抽取为纯函数)",
|
||||
"usedBy": [
|
||||
"data-access.getStudentGradeSummary"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "buildGradeTrendPoints",
|
||||
"signature": "(rows: RawScoreRow[]) => GradeTrendPoint[]",
|
||||
"file": "stats-service.ts",
|
||||
"purpose": "构建成绩趋势数据点(按考试标题分组,归一化分数 0-100)(P1-1 新增:从 data-access-analytics.getGradeTrend 抽取为纯函数)",
|
||||
"usedBy": [
|
||||
"data-access-analytics.getGradeTrend"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "computeTrendAverage",
|
||||
"signature": "(points: GradeTrendPoint[]) => number",
|
||||
"file": "stats-service.ts",
|
||||
"purpose": "计算趋势数据点的平均分(P1-1 新增:从 data-access-analytics.getGradeTrend 抽取为纯函数)",
|
||||
"usedBy": [
|
||||
"data-access-analytics.getGradeTrend"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "computeClassComparisonStats",
|
||||
"signature": "(rows: RawScoreRow[]) => Pick<ClassComparisonItem, 'averageScore' | 'passRate' | 'excellentRate' | 'studentCount'>",
|
||||
"file": "stats-service.ts",
|
||||
"purpose": "计算班级对比统计(均分、及格率、优秀率、参考人数)(P1-1 新增:从 data-access-analytics.getClassComparison 抽取为纯函数)",
|
||||
"usedBy": [
|
||||
"data-access-analytics.getClassComparison"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "computeSubjectComparisonStats",
|
||||
"signature": "(scores: number[]) => Pick<SubjectComparisonItem, 'averageScore' | 'passRate' | 'excellentRate' | 'maxScore' | 'minScore'>",
|
||||
"file": "stats-service.ts",
|
||||
"purpose": "计算科目对比统计(均分、及格率、优秀率、最高分、最低分)(P1-1 新增:从 data-access-analytics.getSubjectComparison 抽取为纯函数)",
|
||||
"usedBy": [
|
||||
"data-access-analytics.getSubjectComparison"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "computeGradeDistribution",
|
||||
"signature": "(rows: RawScoreRow[]) => GradeDistributionResult",
|
||||
"file": "stats-service.ts",
|
||||
"purpose": "计算分数分布(90-100/80-89/70-79/60-69/<60 五个区间)(P1-1 新增:从 data-access-analytics.getGradeDistribution 抽取为纯函数)",
|
||||
"usedBy": [
|
||||
"data-access-analytics.getGradeDistribution"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "buildRankingTrendPoints",
|
||||
"signature": "(byTitle: Map<string, RankingTrendEntry>, targetStudentId: string) => RankingTrendPoint[]",
|
||||
"file": "stats-service.ts",
|
||||
"purpose": "构建排名趋势数据点(按考试标题排序、计算每次考试学生排名)(P1-1 新增:从 data-access-ranking.getRankingTrend 抽取为纯函数)",
|
||||
"usedBy": [
|
||||
"data-access-ranking.getRankingTrend"
|
||||
]
|
||||
}
|
||||
],
|
||||
"components": [
|
||||
{
|
||||
"name": "GradeRecordForm",
|
||||
@@ -8433,6 +8512,15 @@
|
||||
"usedBy": [
|
||||
"teacher/grades/stats/page.tsx"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "WidgetBoundary",
|
||||
"file": "components/widget-boundary.tsx",
|
||||
"purpose": "通用 Widget 边界组件(Error Boundary + Suspense + Skeleton 组合,含 a11y 属性 role=alert/aria-live/aria-label)(P1-5 新增)",
|
||||
"deps": [
|
||||
"shared/components/ui/skeleton",
|
||||
"shared/components/ui/button"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -9109,19 +9197,20 @@
|
||||
},
|
||||
"messaging": {
|
||||
"path": "src/modules/messaging",
|
||||
"description": "站内消息系统:用户间私信收发(支持回复链)、站内通知(多态类型:message/announcement/homework/grade),SiteHeader 通知下拉菜单展示未读数",
|
||||
"description": "站内私信系统:用户间私信收发(支持回复链)。通知相关 UI 组件和 CRUD Action 已迁移至 notifications 模块(P1-4 修复)",
|
||||
"exports": {
|
||||
"actions": [
|
||||
{
|
||||
"name": "sendMessageAction",
|
||||
"permission": "MESSAGE_SEND",
|
||||
"signature": "(prevState: ActionState<string> | null, formData: FormData) => Promise<ActionState<string>>",
|
||||
"purpose": "发送消息(同时通过 notifications dispatcher 为收件人创建多渠道通知;支持 parentMessageId 回复)",
|
||||
"purpose": "发送消息(同时通过 notifications dispatcher 为收件人创建多渠道通知;支持 parentMessageId 回复;P2-11 新增 trackEvent 埋点)",
|
||||
"deps": [
|
||||
"requirePermission",
|
||||
"shared/db",
|
||||
"data-access.createMessage",
|
||||
"notifications.dispatcher.sendNotification",
|
||||
"trackEvent",
|
||||
"revalidatePath"
|
||||
],
|
||||
"usedBy": [
|
||||
@@ -9132,11 +9221,12 @@
|
||||
"name": "markMessageAsReadAction",
|
||||
"permission": "MESSAGE_READ",
|
||||
"signature": "(id: string) => Promise<ActionState<void>>",
|
||||
"purpose": "标记消息已读(设置 readAt)",
|
||||
"purpose": "标记消息已读(设置 readAt;P2-11 新增 trackEvent 埋点)",
|
||||
"deps": [
|
||||
"requirePermission",
|
||||
"schema.MessageIdSchema",
|
||||
"data-access.markMessageAsRead",
|
||||
"trackEvent",
|
||||
"revalidatePath"
|
||||
],
|
||||
"usedBy": [
|
||||
@@ -9148,11 +9238,12 @@
|
||||
"name": "deleteMessageAction",
|
||||
"permission": "MESSAGE_DELETE",
|
||||
"signature": "(id: string) => Promise<ActionState<void>>",
|
||||
"purpose": "删除消息(仅发送者或接收者可删)",
|
||||
"purpose": "删除消息(仅发送者或接收者可删;P2-11 新增 trackEvent 埋点)",
|
||||
"deps": [
|
||||
"requirePermission",
|
||||
"schema.MessageIdSchema",
|
||||
"data-access.deleteMessage",
|
||||
"trackEvent",
|
||||
"revalidatePath"
|
||||
],
|
||||
"usedBy": [
|
||||
@@ -9162,14 +9253,14 @@
|
||||
{
|
||||
"name": "getMessagesAction",
|
||||
"permission": "MESSAGE_READ",
|
||||
"signature": "(params?: { type?, page?, pageSize? }) => Promise<ActionState<PaginatedResult<MessageListItem>>>",
|
||||
"purpose": "获取消息列表(收件箱/已发送,分页)",
|
||||
"signature": "(params?: { type?, page?, pageSize?, keyword? }) => Promise<ActionState<PaginatedResult<MessageListItem>>>",
|
||||
"purpose": "获取消息列表(收件箱/已发送,分页,关键词搜索;客户端通过 useMessageSearch hook 调用)",
|
||||
"deps": [
|
||||
"requirePermission",
|
||||
"data-access.getMessages"
|
||||
],
|
||||
"usedBy": [
|
||||
"message-list.tsx"
|
||||
"message-list.tsx (via useMessageSearch hook)"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -9201,47 +9292,16 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "getNotificationsAction",
|
||||
"name": "getUnreadMessageCountAction",
|
||||
"permission": "MESSAGE_READ",
|
||||
"signature": "(params?: { page?, pageSize? }) => Promise<ActionState<PaginatedResult<NotificationListItem>>>",
|
||||
"purpose": "获取当前用户通知列表(分页)",
|
||||
"signature": "() => Promise<ActionState<number>>",
|
||||
"purpose": "获取当前用户未读私信计数(unread-message-badge 组件每 60 秒轮询)",
|
||||
"deps": [
|
||||
"requirePermission",
|
||||
"data-access.getNotifications"
|
||||
"data-access.getUnreadMessageCount"
|
||||
],
|
||||
"usedBy": [
|
||||
"notification-dropdown.tsx",
|
||||
"notification-list.tsx"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "markNotificationAsReadAction",
|
||||
"permission": "MESSAGE_READ",
|
||||
"signature": "(id: string) => Promise<ActionState<void>>",
|
||||
"purpose": "标记单条通知已读",
|
||||
"deps": [
|
||||
"requirePermission",
|
||||
"data-access.markNotificationAsRead",
|
||||
"revalidatePath"
|
||||
],
|
||||
"usedBy": [
|
||||
"notification-dropdown.tsx",
|
||||
"notification-list.tsx"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "markAllNotificationsAsReadAction",
|
||||
"permission": "MESSAGE_READ",
|
||||
"signature": "() => Promise<ActionState<void>>",
|
||||
"purpose": "标记所有通知已读",
|
||||
"deps": [
|
||||
"requirePermission",
|
||||
"data-access.markAllNotificationsAsRead",
|
||||
"revalidatePath"
|
||||
],
|
||||
"usedBy": [
|
||||
"notification-dropdown.tsx",
|
||||
"notification-list.tsx"
|
||||
"unread-message-badge.tsx"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -9251,7 +9311,7 @@
|
||||
"purpose": "获取当前用户的通知偏好设置(首次访问自动创建默认记录)",
|
||||
"deps": [
|
||||
"requirePermission",
|
||||
"notification-preferences.getNotificationPreferences"
|
||||
"notifications.preferences.getNotificationPreferences"
|
||||
],
|
||||
"usedBy": [
|
||||
"settings/page.tsx",
|
||||
@@ -9266,7 +9326,7 @@
|
||||
"deps": [
|
||||
"requirePermission",
|
||||
"schema.UpdateNotificationPreferencesSchema",
|
||||
"notification-preferences.upsertNotificationPreferences",
|
||||
"notifications.preferences.upsertNotificationPreferences",
|
||||
"revalidatePath"
|
||||
],
|
||||
"usedBy": [
|
||||
@@ -9362,70 +9422,23 @@
|
||||
"shared.db.schema.messages"
|
||||
],
|
||||
"usedBy": [
|
||||
"待扩展"
|
||||
"getUnreadMessageCountAction",
|
||||
"unread-message-badge.tsx"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "getNotifications",
|
||||
"signature": "(userId: string, params?: { page?, pageSize? }) => Promise<PaginatedResult<NotificationListItem>>",
|
||||
"name": "getMessagesPageData",
|
||||
"signature": "(userId: string) => Promise<{ messages: PaginatedResult<Message>, notifications: PaginatedResult<Notification> }>",
|
||||
"file": "data-access.ts",
|
||||
"purpose": "re-export shim(实际逻辑在 notifications/data-access.ts,P0-4 / P1-5 修复后迁移)",
|
||||
"purpose": "P1-5 新增:消息首页编排函数,一次性获取消息列表和通知列表(通知通过动态 import notifications/data-access)",
|
||||
"deps": [
|
||||
"data-access.getMessages",
|
||||
"notifications.data-access.getNotifications"
|
||||
],
|
||||
"usedBy": [
|
||||
"getNotificationsAction",
|
||||
"messages/page.tsx"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "createNotification",
|
||||
"signature": "(input: CreateNotificationInput) => Promise<string>",
|
||||
"file": "data-access.ts",
|
||||
"purpose": "re-export shim(实际逻辑在 notifications/data-access.ts,P0-4 / P1-5 修复后迁移)",
|
||||
"deps": [
|
||||
"notifications.data-access.createNotification"
|
||||
],
|
||||
"usedBy": [
|
||||
"notifications.dispatcher (via in-app-channel)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "markNotificationAsRead",
|
||||
"signature": "(id: string, userId: string) => Promise<void>",
|
||||
"file": "data-access.ts",
|
||||
"purpose": "re-export shim(实际逻辑在 notifications/data-access.ts,P0-4 / P1-5 修复后迁移)",
|
||||
"deps": [
|
||||
"notifications.data-access.markNotificationAsRead"
|
||||
],
|
||||
"usedBy": [
|
||||
"markNotificationAsReadAction"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "markAllNotificationsAsRead",
|
||||
"signature": "(userId: string) => Promise<void>",
|
||||
"file": "data-access.ts",
|
||||
"purpose": "re-export shim(实际逻辑在 notifications/data-access.ts,P0-4 / P1-5 修复后迁移)",
|
||||
"deps": [
|
||||
"notifications.data-access.markAllNotificationsAsRead"
|
||||
],
|
||||
"usedBy": [
|
||||
"markAllNotificationsAsReadAction"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "getUnreadNotificationCount",
|
||||
"signature": "(userId: string) => Promise<number>",
|
||||
"file": "data-access.ts",
|
||||
"purpose": "re-export shim(实际逻辑在 notifications/data-access.ts,P0-4 / P1-5 修复后迁移)",
|
||||
"deps": [
|
||||
"notifications.data-access.getUnreadNotificationCount"
|
||||
],
|
||||
"usedBy": [
|
||||
"待扩展"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "getRecipients",
|
||||
"signature": "(ctx: AuthContext) => Promise<RecipientOption[]>",
|
||||
@@ -9629,21 +9642,26 @@
|
||||
"purpose": "写消息表单(收件人 Select、主题 Input、内容 Textarea,支持回复模式)"
|
||||
},
|
||||
{
|
||||
"name": "NotificationDropdown",
|
||||
"file": "components/notification-dropdown.tsx",
|
||||
"purpose": "SiteHeader 通知下拉菜单(Bell 图标 + 未读数 Badge,滚动列表,标记已读,查看全部链接)"
|
||||
},
|
||||
"name": "UnreadMessageBadge",
|
||||
"file": "components/unread-message-badge.tsx",
|
||||
"purpose": "未读消息计数徽章(侧边栏 Messages 导航项旁显示,每 60 秒轮询 getUnreadMessageCountAction)"
|
||||
}
|
||||
],
|
||||
"hooks": [
|
||||
{
|
||||
"name": "NotificationList",
|
||||
"file": "components/notification-list.tsx",
|
||||
"purpose": "通知完整列表(全部标记已读、单条标记已读、查看链接)"
|
||||
"name": "useMessageSearch",
|
||||
"file": "hooks/use-message-search.ts",
|
||||
"purpose": "P1-7 新增:消息搜索 hook(400ms 防抖 + 请求竞态取消,通过 requestIdRef 匹配最新请求)",
|
||||
"usedBy": [
|
||||
"message-list.tsx"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"notifications": {
|
||||
"path": "src/modules/notifications",
|
||||
"description": "通知渠道集成层:基于用户通知偏好(notification_preferences)将通知分发到站内消息/SMS/微信公众号/邮件多渠道。所有渠道实现统一 NotificationChannelSender 接口,dispatcher 按偏好并行发送。支持 Mock 模式(开发环境无需外部服务)。",
|
||||
"description": "通知渠道集成层:基于用户通知偏好(notification_preferences)将通知分发到站内消息/SMS/微信公众号/邮件多渠道。所有渠道实现统一 NotificationChannelSender 接口,dispatcher 按偏好并行发送。支持 Mock 模式(开发环境无需外部服务)。P1-4 修复后新增通知 UI 组件和通知 CRUD Server Action。",
|
||||
"exports": {
|
||||
"actions": [
|
||||
{
|
||||
@@ -9673,6 +9691,66 @@
|
||||
"usedBy": [
|
||||
"待扩展"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "getNotificationsAction",
|
||||
"permission": "MESSAGE_READ",
|
||||
"signature": "(params?: { page?, pageSize?, unreadOnly? }) => Promise<ActionState<PaginatedResult<Notification>>>",
|
||||
"purpose": "P1-4 新增(从 messaging 迁移):获取当前用户通知列表(分页)",
|
||||
"deps": [
|
||||
"requirePermission",
|
||||
"data-access.getNotifications"
|
||||
],
|
||||
"usedBy": [
|
||||
"notification-dropdown.tsx",
|
||||
"notification-list.tsx"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "getUnreadNotificationCountAction",
|
||||
"permission": "MESSAGE_READ",
|
||||
"signature": "() => Promise<ActionState<number>>",
|
||||
"purpose": "P1-4 新增(从 messaging 迁移):获取当前用户未读通知计数",
|
||||
"deps": [
|
||||
"requirePermission",
|
||||
"data-access.getUnreadNotificationCount"
|
||||
],
|
||||
"usedBy": [
|
||||
"notification-dropdown.tsx"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "markNotificationAsReadAction",
|
||||
"permission": "MESSAGE_READ",
|
||||
"signature": "(notificationId: string) => Promise<ActionState<string>>",
|
||||
"purpose": "P1-4 新增(从 messaging 迁移):标记单条通知已读;P2-11 新增 trackEvent 埋点",
|
||||
"deps": [
|
||||
"requirePermission",
|
||||
"schema.NotificationIdSchema",
|
||||
"data-access.markNotificationAsRead",
|
||||
"trackEvent",
|
||||
"revalidatePath"
|
||||
],
|
||||
"usedBy": [
|
||||
"notification-dropdown.tsx",
|
||||
"notification-list.tsx"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "markAllNotificationsAsReadAction",
|
||||
"permission": "MESSAGE_READ",
|
||||
"signature": "() => Promise<ActionState<string>>",
|
||||
"purpose": "P1-4 新增(从 messaging 迁移):标记所有通知已读;P2-11 新增 trackEvent 埋点",
|
||||
"deps": [
|
||||
"requirePermission",
|
||||
"data-access.markAllNotificationsAsRead",
|
||||
"trackEvent",
|
||||
"revalidatePath"
|
||||
],
|
||||
"usedBy": [
|
||||
"notification-dropdown.tsx",
|
||||
"notification-list.tsx"
|
||||
]
|
||||
}
|
||||
],
|
||||
"dispatcher": [
|
||||
@@ -10029,6 +10107,18 @@
|
||||
"messaging (via re-export)"
|
||||
]
|
||||
}
|
||||
],
|
||||
"components": [
|
||||
{
|
||||
"name": "NotificationList",
|
||||
"file": "components/notification-list.tsx",
|
||||
"purpose": "P1-4 新增(从 messaging 迁移):通知完整列表(全部标记已读、单条标记已读、查看链接)"
|
||||
},
|
||||
{
|
||||
"name": "NotificationDropdown",
|
||||
"file": "components/notification-dropdown.tsx",
|
||||
"purpose": "P1-4 新增(从 messaging 迁移):SiteHeader 通知下拉菜单(Bell 图标 + 未读数 Badge,每 30 秒轮询,滚动列表,标记已读,查看全部链接)"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
@@ -12517,6 +12607,8 @@
|
||||
"lib/node-summary.ts",
|
||||
"lib/rf-mappers.ts",
|
||||
"config/block-registry.tsx",
|
||||
"providers/lesson-plan-provider.tsx",
|
||||
"services/default-data-service.ts",
|
||||
"data-access.ts",
|
||||
"data-access-versions.ts",
|
||||
"data-access-templates.ts",
|
||||
@@ -12549,7 +12641,29 @@
|
||||
"components/blocks/text-study-block.tsx",
|
||||
"components/blocks/exercise-block.tsx",
|
||||
"components/blocks/reflection-block.tsx"
|
||||
]
|
||||
],
|
||||
"i18n": {
|
||||
"namespace": "lessonPreparation",
|
||||
"status": "implemented",
|
||||
"messageFiles": [
|
||||
"shared/i18n/messages/zh-CN/lesson-preparation.json",
|
||||
"shared/i18n/messages/en/lesson-preparation.json"
|
||||
]
|
||||
},
|
||||
"auditFixes": {
|
||||
"P0-1": "publish-service.ts 跨模块直查修复:改用 exams/classes data-access",
|
||||
"P0-2": "i18n 接入:17 个组件改造为 useTranslations/getTranslations",
|
||||
"P0-3": "buildScopeCondition 按 scope 类型精确过滤(class_taught/grade_managed/class_members/children)",
|
||||
"P1-1": "as never 断言替换为类型守卫函数;BLOCK_TYPE_LABELS/LESSON_PLAN_STATUS_LABELS 改为 i18n 键",
|
||||
"P1-2": "新增 LessonPlanErrorBoundary 错误边界",
|
||||
"P1-3": "新增 4 个 Skeleton 骨架屏组件",
|
||||
"P1-4": "alert/confirm/window.location.reload 替换为 toast/AlertDialog/router.refresh",
|
||||
"P1-5": "新增 LessonPlanProvider + Context 注入数据服务,支持多实例",
|
||||
"P1-7": "新增 4 个角色配置(TEACHER/ADMIN/STUDENT/PARENT)+ ROLE_CONFIGS 注册表",
|
||||
"P1-8": "新增 BLOCK_REGISTRY 注册表,node-edit-panel 配置驱动渲染",
|
||||
"P2-1": "5 个组件添加 role=dialog/aria-modal/aria-label",
|
||||
"P2-4": "预留 LessonPlanTracker 接口 + noopTracker 默认实现"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dbTables": {
|
||||
|
||||
Reference in New Issue
Block a user