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:
@@ -948,7 +948,7 @@ src/auth.ts ──▶ import { ... } from "@/shared/lib/permissions"
|
||||
- Actions:`getAdminDashboardAction` / `getTeacherDashboardAction` / `getStudentDashboardAction` / `getParentDashboardAction`(均调用 `requirePermission()` 校验对应 `DASHBOARD_*_READ` 权限)
|
||||
- Data-access:`getAdminDashboardData`(并行调用 6 个模块 stats 函数)
|
||||
- Lib 纯函数:`toWeekday` / `countStudentAssignments` / `sortUpcomingAssignments` / `filterTodaySchedule` / `computeTeacherMetrics` / `getGreetingKey`
|
||||
- Components:`AdminDashboardView` / `TeacherDashboardView` / `StudentDashboard` / `UserGrowthChart`(均接入 next-intl i18n)
|
||||
- Components:`AdminDashboardView` / `TeacherDashboardView` / `StudentDashboard` / `UserGrowthChart` / `DashboardGreetingHeader`(共享问候头部,V2 抽象)/ `DashboardSection`(分区 Error Boundary + Suspense + 骨架屏)(均接入 next-intl i18n)
|
||||
|
||||
**依赖关系**:
|
||||
- 依赖:`shared/*`、`@/auth`、`classes`(通过 data-access)、`homework`(通过 data-access)、`users`(通过 data-access)、`parent`(通过 data-access.getParentDashboardData)、`textbooks`/`questions`/`exams`(通过各模块 dashboard stats 函数)、`recharts`、`next-intl`
|
||||
@@ -969,6 +969,10 @@ src/auth.ts ──▶ import { ... } from "@/shared/lib/permissions"
|
||||
- ✅ P2 已修复(2026-06-22):新增 `components/dashboard-section.tsx`,每个独立数据区块用 Error Boundary + Suspense + 骨架屏包裹,单区块崩溃/加载不波及整页(5 种骨架变体:stats/card/chart/table/list)
|
||||
- ℹ️ V1 新增:`AdminDashboardData` 类型含 `userGrowth`/`homeworkTrend` 字段,`data-access.ts` 当前返回空数组占位,待后续接入真实统计
|
||||
- ℹ️ parent 仪表盘组件仍位于 `modules/parent/components/parent-dashboard.tsx`,通过 `dashboard/actions.getParentDashboardAction` 调用(架构决策:保留在 parent 模块以避免移动文件破坏其他 import)
|
||||
- ✅ P0 已修复(2026-06-22 V2):10 个子组件 i18n 遗漏全部补齐(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 已修复(2026-06-22 V2):抽象共享组件 `DashboardGreetingHeader`,消除 teacher/student 头部 90% 重复代码
|
||||
- ✅ P2 已修复(2026-06-22 V2):为 6 个纯函数添加 31 个单元测试(`tests/integration/dashboard/dashboard-utils.test.ts`)
|
||||
- ✅ P2 已修复(2026-06-22 V2):a11y 增强 — admin 表格 `<caption>`、teacher/student 视图语义化标签(`<header>` / `<section aria-label>` / `<aside aria-label>`)
|
||||
|
||||
**文件清单**:
|
||||
| 文件 | 行数 | 职责 |
|
||||
@@ -978,10 +982,12 @@ src/auth.ts ──▶ import { ... } from "@/shared/lib/permissions"
|
||||
| `lib/dashboard-utils.ts` | 170 | 纯逻辑工具函数(weekday / 统计 / 排序 / 指标计算 / 问候语) |
|
||||
| `types.ts` | 74 | Admin / Teacher / Student 类型定义 |
|
||||
| `components/dashboard-section.tsx` | 165 | 分区 Error Boundary + Suspense + 骨架屏(5 种变体:stats/card/chart/table/list) |
|
||||
| `components/admin-dashboard/admin-dashboard.tsx` | 267 | 管理员仪表盘视图(i18n) |
|
||||
| `components/dashboard-greeting-header.tsx` | 36 | 共享问候头部组件(V2 抽象,消除 teacher/student 头部重复) |
|
||||
| `components/admin-dashboard/admin-dashboard.tsx` | 267 | 管理员仪表盘视图(i18n + a11y 表格 caption) |
|
||||
| `components/admin-dashboard/user-growth-chart.tsx` | 50 | recharts 折线图(i18n) |
|
||||
| `components/teacher-dashboard/*.tsx` | 9 文件 | 教师仪表盘组件(i18n) |
|
||||
| `components/student-dashboard/*.tsx` | 6 文件 | 学生仪表盘组件(i18n) |
|
||||
| `components/teacher-dashboard/*.tsx` | 9 文件 | 教师仪表盘组件(i18n + a11y 语义化标签) |
|
||||
| `components/student-dashboard/*.tsx` | 6 文件 | 学生仪表盘组件(i18n + a11y 语义化标签) |
|
||||
| `tests/integration/dashboard/dashboard-utils.test.ts` | 408 | 6 个纯函数的 31 个单元测试(V2 新增) |
|
||||
|
||||
---
|
||||
|
||||
@@ -990,57 +996,78 @@ src/auth.ts ──▶ import { ... } from "@/shared/lib/permissions"
|
||||
**职责**:站内私信(messages 表 CRUD)。
|
||||
|
||||
**导出函数**:
|
||||
- Actions:`sendMessageAction` / `getMessagesAction` / `getMessageAction` / `deleteMessageAction` / `getNotificationsAction` / `markNotificationReadAction` / `markAllNotificationsReadAction` / `getNotificationPreferencesAction` / `updateNotificationPreferencesAction`
|
||||
- Data-access:`getMessages` / `getMessageById` / `getMessageThread` / `createMessage` / `markMessageAsRead` / `deleteMessage` / `getUnreadMessageCount` / `getRecipients`(按 DataScope 过滤可发送对象:class_taught 教师→学生、grade_managed 年级管理员→教师/学生、all 管理员、class_members 学生→自己班级的任课教师/班主任、children 家长→孩子的班主任/任课教师;通过 classes data-access.getTeacherIdsByClassIds/getStudentActiveClassId 获取班级教师 ID)(通知 CRUD 通过 re-export 从 notifications 模块重导出,保持向后兼容)
|
||||
- Actions:`sendMessageAction` / `markMessageAsReadAction` / `deleteMessageAction` / `getMessagesAction` / `getMessageDetailAction` / `getRecipientsAction` / `getUnreadMessageCountAction` / `getNotificationPreferencesAction` / `updateNotificationPreferencesAction`(✅ P1-4 已修复:通知 CRUD Action 已迁移至 notifications 模块,messaging 仅保留私信和通知偏好 Action)
|
||||
- Data-access:`getMessages` / `getMessageById` / `getMessageThread` / `createMessage` / `markMessageAsRead` / `deleteMessage` / `getUnreadMessageCount` / `getRecipients`(按 DataScope 过滤可发送对象:class_taught 教师→学生、grade_managed 年级管理员→教师/学生、all 管理员、class_members 学生→自己班级的任课教师/班主任、children 家长→孩子的班主任/任课教师;通过 classes data-access.getTeacherIdsByClassIds/getStudentActiveClassId 获取班级教师 ID)/ `getMessagesPageData`(✅ P1-5 新增:消息首页编排函数,一次性获取消息列表和通知列表)
|
||||
- Hooks:`useMessageSearch`(✅ P1-7 新增:消息搜索 hook,含防抖和请求竞态取消)
|
||||
- Notification-preferences:~~re-export shim(实际逻辑在 `notifications/preferences.ts`)~~ ✅ P0-b 已修复:`notification-preferences.ts` 文件已删除(通知模块去重),消费方改为直接从 `@/modules/notifications/preferences` 导入 `getNotificationPreferences` / `upsertNotificationPreferences`
|
||||
|
||||
**依赖关系**:
|
||||
- 依赖:`shared/*`、`@/auth`、`notifications`(✅ P0-4 / P1-5 已修复:通过 `sendNotification` dispatcher 发送通知,通知 CRUD 和偏好已迁移至 notifications 模块)、`classes`(通过 data-access.getTeacherIdsByClassIds/getStudentActiveClassId 获取班级教师 ID,支持学生 class_members 和家长 children 数据范围)、`users`(通过 data-access.getUserNamesByIds 获取用户显示名称)
|
||||
- 被依赖:`notifications`(✅ 已消除反向依赖)、`settings`(通知偏好表单)、`layout`(通知下拉)
|
||||
- 依赖:`shared/*`、`@/auth`、`notifications`(✅ P0-4 / P1-5 已修复:通过 `sendNotification` dispatcher 发送通知,通知 CRUD 和偏好已迁移至 notifications 模块;✅ P1-4 已修复:通知 UI 组件已迁移至 notifications 模块)、`classes`(通过 data-access.getTeacherIdsByClassIds/getStudentActiveClassId 获取班级教师 ID,支持学生 class_members 和家长 children 数据范围)、`users`(通过 data-access.getUserNamesByIds 获取用户显示名称)
|
||||
- 被依赖:`notifications`(✅ 已消除反向依赖)、`settings`(通知偏好表单)、`layout`(✅ P1-4 已修复:通知下拉组件改为从 notifications 模块导入)
|
||||
|
||||
**已知问题**:
|
||||
- ✅ P0-4 已修复:~~`sendMessageAction` 绕过 notifications dispatcher 直接调用 `createNotification`~~ 改为调用 `notifications.sendNotification`,通知 CRUD 已迁移至 notifications 模块
|
||||
- ✅ P0 已修复:~~与 notifications 双向依赖 + 职责重叠~~ 通知相关表(messageNotifications / notificationPreferences)所有权已移交 notifications 模块,messaging 仅保留 messages 表
|
||||
- ✅ P1-5 已修复:~~同时管理 3 类数据(messages + messageNotifications + notificationPreferences)~~ 仅管理 messages 表,通知相关数据由 notifications 模块管理
|
||||
- ✅ P1 已修复:~~通知相关 Action 使用 `requireAuth()` 而非 `requirePermission()`~~ 5 个通知 Action(getNotifications / markNotificationAsRead / markAllNotificationsAsRead / getNotificationPreferences / updateNotificationPreferences)已改为 `requirePermission(Permissions.MESSAGE_READ)`
|
||||
- ✅ P1 已修复:~~通知相关 Action 使用 `requireAuth()` 而非 `requirePermission()`~~ 通知 Action 已迁移至 notifications 模块并改为 `requirePermission(Permissions.MESSAGE_READ)`
|
||||
- ✅ P1 已修复:~~`markMessageAsReadAction` / `deleteMessageAction` / `getMessageDetailAction` 缺少 Zod 校验~~ 已添加 `MessageIdSchema` 校验 messageId 参数
|
||||
- ✅ P1 已修复:~~`updateNotificationPreferencesAction` 缺少 Zod 校验~~ 已添加 `UpdateNotificationPreferencesSchema` 校验 8 个布尔字段
|
||||
- ✅ P2 已修复:`data-access.ts` 中 3 处 `or(...)!` 非空断言清理为安全守卫(条件 push)
|
||||
- ✅ P0-b 已修复:~~`notification-preferences.ts` re-export shim 文件~~ 已删除(通知模块去重),8 个消费方改为直接从 `@/modules/notifications/preferences` 导入 `getNotificationPreferences` / `upsertNotificationPreferences`,消除 messaging 模块对通知偏好的冗余 re-export 层
|
||||
- ✅ P1 已修复:~~全模块零 i18n,中英文案硬编码~~ 所有组件接入 next-intl(`useTranslations("messages")`),新增 `src/shared/i18n/messages/{zh-CN,en}/messages.json` 翻译字典(title/description/tabs/actions/form/status/meta/notificationType/search/empty/messages/error 共 13 个命名空间);所有页面 `page.tsx` 使用 `generateMetadata` + `getTranslations` 替代硬编码 metadata
|
||||
- ✅ P1 已修复:~~缺 Error Boundary~~ 新增 3 个 `error.tsx` 错误边界(`/messages`、`/messages/[id]`、`/messages/compose`),统一使用 `EmptyState` + i18n 错误文案 + 重试按钮
|
||||
- ✅ P2 已修复:a11y 改进,`message-list.tsx` / `notification-dropdown.tsx` 添加 `aria-label` / `aria-hidden`
|
||||
- ✅ P2 已修复:a11y 改进,`message-list.tsx` 添加 `aria-label` / `aria-hidden`
|
||||
- ✅ P1-4 已修复:~~通知组件(notification-list.tsx / notification-dropdown.tsx)放在 messaging/components 下,直接 import notifications 模块类型和 messaging/actions~~ 两个组件已迁移至 `notifications/components/`,通知 CRUD Action 已迁移至 `notifications/actions.ts`,messaging 模块仅保留私信组件
|
||||
- ✅ P1-5 已修复:~~页面层 `Promise.all` 编排 messaging 和 notifications 两个模块的 data-access~~ 新增 `getMessagesPageData` 编排函数,页面层仅调用单一函数
|
||||
- ✅ P1-7 已修复:~~消息列表客户端 `useEffect` + `setTimeout` 防抖搜索未取消已发出的请求~~ 搜索逻辑抽离为 `useMessageSearch` hook(含防抖 + 请求竞态取消);~~无分页 UI~~ 新增客户端分页 UI(PAGE_SIZE=20,ChevronLeft/ChevronRight 按钮)
|
||||
- ✅ P1-9 已修复:~~`deleteMessage` 两个独立 UPDATE 无事务~~ 改为 `db.transaction` 包裹 senderDeletedAt 和 receiverDeletedAt 更新,保证原子性
|
||||
- ✅ P2-11 已修复:~~发送/删除消息无埋点~~ `sendMessageAction` / `markMessageAsReadAction` / `deleteMessageAction` 新增 `trackEvent` 埋点(message.sent / message.marked_read / message.deleted)
|
||||
|
||||
**文件清单**:
|
||||
| 文件 | 行数 | 职责 |
|
||||
|------|------|------|
|
||||
| `actions.ts` | 276 | 9 个 Server Action(通知相关 Action 委托 notifications 模块) |
|
||||
| `data-access.ts` | 199 | 私信 CRUD + re-export 通知 CRUD(向后兼容) |
|
||||
| ~~`notification-preferences.ts`~~ | ~~11~~ | ~~re-export shim~~ ✅ P0-b 已删除(消费方改为直接从 notifications/preferences 导入) |
|
||||
| `schema.ts` | 41 | 私信发送校验 + messageId 校验 + 通知偏好更新校验 |
|
||||
| `types.ts` | 72 | 私信类型 + re-export 通知类型(向后兼容) |
|
||||
| `actions.ts` | ~260 | 7 个私信 Server Action + 2 个通知偏好 Action(✅ P1-4:通知 CRUD Action 已迁移至 notifications 模块) |
|
||||
| `data-access.ts` | ~270 | 私信 CRUD + `getMessagesPageData` 编排函数(✅ P1-5 新增) |
|
||||
| `schema.ts` | 44 | 私信发送校验 + messageId 校验 + 通知偏好更新校验 |
|
||||
| `types.ts` | 52 | 私信类型 + re-export 通知类型(向后兼容) |
|
||||
| `hooks/use-message-search.ts` | ~60 | ✅ P1-7 新增:消息搜索 hook(防抖 + 请求竞态取消) |
|
||||
|
||||
**组件清单**:
|
||||
| 组件 | 职责 |
|
||||
|------|------|
|
||||
| `components/message-list.tsx` | 消息列表(✅ P1-7:使用 `useMessageSearch` hook + 客户端分页 UI,PAGE_SIZE=20) |
|
||||
| `components/message-detail.tsx` | 消息详情(含回复) |
|
||||
| `components/message-compose.tsx` | 撰写新消息 |
|
||||
| `components/unread-message-badge.tsx` | 未读消息计数徽章(侧边栏,每 60 秒轮询 `getUnreadMessageCountAction`) |
|
||||
|
||||
**客户端行为**:
|
||||
- `message-list.tsx`:客户端调用 `getMessagesAction` 搜索消息(useMessageSearch hook,400ms 防抖,请求竞态取消)
|
||||
- `unread-message-badge.tsx`:每 60 秒轮询 `getUnreadMessageCountAction` 刷新未读计数
|
||||
|
||||
---
|
||||
|
||||
## 2.14 notifications(通知分发模块)
|
||||
|
||||
**职责**:多渠道通知分发(SMS/Email/WeChat/InApp)+ 站内通知 CRUD + 通知偏好管理。
|
||||
**职责**:多渠道通知分发(SMS/Email/WeChat/InApp)+ 站内通知 CRUD + 通知偏好管理 + 通知 UI 组件。
|
||||
|
||||
**导出函数**:
|
||||
- Actions:`sendNotificationAction` / `sendClassNotificationAction`
|
||||
- Actions:`sendNotificationAction` / `sendClassNotificationAction` / `getNotificationsAction` / `getUnreadNotificationCountAction` / `markNotificationAsReadAction` / `markAllNotificationsAsReadAction`(✅ P1-4 新增:后 4 个通知 CRUD Action 从 messaging 模块迁移)
|
||||
- Dispatcher:`sendNotification(payload)` / `sendBatchNotifications(payloads)`
|
||||
- Data-access:`createNotification` / `getNotifications` / `markNotificationAsRead` / `markAllNotificationsAsRead` / `getUnreadNotificationCount` / `getUserContactInfo` / `logNotificationSend` / `logNotificationSendBatch`(✅ P0-4 / P1-5 修复后从 messaging 迁移)
|
||||
- Preferences:`getNotificationPreferences` / `upsertNotificationPreferences`(✅ P0-4 / P1-5 修复后从 messaging 迁移)
|
||||
- Channels:`InAppChannelSender` / `SmsChannelSender` / `EmailChannelSender` / `WeChatChannelSender`
|
||||
- Components:`NotificationList` / `NotificationDropdown`(✅ P1-4 新增:从 messaging/components 迁移)
|
||||
|
||||
**依赖关系**:
|
||||
- 依赖:`shared/*`、`@/auth`、`classes`(✅ P1-1 已修复:通过 classes data-access.getClassExists/getStudentIdsByClassId)
|
||||
- 被依赖:`messaging`(✅ P0-4 / P1-5 已修复:messaging 通过 `sendNotification` dispatcher 发送通知,通知 CRUD 和偏好通过 re-export 保持向后兼容)
|
||||
- 被依赖:`messaging`(✅ P0-4 / P1-5 已修复:messaging 通过 `sendNotification` dispatcher 发送通知;✅ P1-4 已修复:通知 UI 组件由 notifications 模块自持,messaging 不再反向依赖)、`layout`(✅ P1-4 已修复:通知下拉组件直接从 notifications 模块导入)、`app/(dashboard)/messages`(✅ P1-4 已修复:通知列表组件直接从 notifications 模块导入)
|
||||
|
||||
**已知问题**:
|
||||
- ✅ P0-4 已修复:~~不拥有任何数据,全部依赖 messaging 模块~~ messageNotifications 和 notificationPreferences 表所有权已从 messaging 迁移至 notifications 模块
|
||||
- ✅ P0 已修复:~~与 messaging 双向依赖~~ notifications 不再反向依赖 messaging,in-app-channel 改为静态导入本地 createNotification
|
||||
- ✅ P1-1 已修复:~~`sendClassNotificationAction` 直查 `classes`/`classEnrollments`~~ 改为调用 `classes/data-access.getClassExists` / `getStudentIdsByClassId`
|
||||
- ✅ P1-4 已修复:~~通知 UI 组件放在 messaging/components 下,直接 import notifications 类型和 messaging/actions~~ `notification-list.tsx` 和 `notification-dropdown.tsx` 已迁移至 `notifications/components/`,通知 CRUD Action 已从 messaging 迁移至 `notifications/actions.ts`,消除 UI 层跨模块耦合
|
||||
- ✅ P2-11 已修复:~~通知标记已读无埋点~~ `markNotificationAsReadAction` / `markAllNotificationsAsReadAction` 新增 `trackEvent` 埋点(notification.marked_read / notification.marked_all_read)
|
||||
- ⚠️ P1:发送日志仅 console,无 `notification_logs` 表
|
||||
- ✅ 渠道抽象优秀(接口 + 工厂 + Mock 实现)
|
||||
|
||||
@@ -1050,10 +1077,21 @@ src/auth.ts ──▶ import { ... } from "@/shared/lib/permissions"
|
||||
| `dispatcher.ts` | 152 | 渠道选择 + 并行分发 |
|
||||
| `data-access.ts` | 177 | 站内通知 CRUD + 用户联系方式 + 日志(P0-4 / P1-5 修复后新增通知 CRUD) |
|
||||
| `preferences.ts` | 166 | 通知偏好 CRUD(P0-4 / P1-5 修复后从 messaging 迁移) |
|
||||
| `actions.ts` | 119 | 2 个 Server Action |
|
||||
| `actions.ts` | ~260 | 6 个 Server Action(✅ P1-4:新增 4 个通知 CRUD Action) |
|
||||
| `types.ts` | 120 | 通知负载 + 渠道配置 + 通知记录 + 偏好类型(P0-4 / P1-5 修复后扩充) |
|
||||
| `index.ts` | 61 | 对外导出入口 |
|
||||
| `index.ts` | ~75 | 对外导出入口(✅ P1-4:新增组件和 CRUD Action 导出) |
|
||||
| `channels/*` | 5 文件 | 4 个渠道实现 |
|
||||
| `components/notification-list.tsx` | ~140 | ✅ P1-4 新增(从 messaging 迁移):通知列表组件 |
|
||||
| `components/notification-dropdown.tsx` | ~180 | ✅ P1-4 新增(从 messaging 迁移):通知下拉菜单组件 |
|
||||
|
||||
**组件清单**:
|
||||
| 组件 | 职责 |
|
||||
|------|------|
|
||||
| `components/notification-list.tsx` | 通知列表(消息页底部,展示所有通知,支持标记已读) |
|
||||
| `components/notification-dropdown.tsx` | 通知下拉菜单(站点头部,每 30 秒轮询 `getNotificationsAction` + `getUnreadNotificationCountAction`) |
|
||||
|
||||
**客户端行为**:
|
||||
- `notification-dropdown.tsx`:每 30 秒轮询 `getNotificationsAction`(pageSize=10)和 `getUnreadNotificationCountAction` 刷新通知和未读计数
|
||||
|
||||
---
|
||||
|
||||
@@ -1090,7 +1128,7 @@ src/auth.ts ──▶ import { ... } from "@/shared/lib/permissions"
|
||||
|
||||
**导出函数**:
|
||||
- Actions:`getAnnouncementsAction` / `createAnnouncementAction` / `updateAnnouncementAction` / `deleteAnnouncementAction` / `publishAnnouncementAction` / `archiveAnnouncementAction`(✅ P1-2 已修复:actions 层不再直接访问 DB,全部下沉到 data-access;✅ 发布公告时触发通知模块 `sendBatchNotifications`)
|
||||
- Data-access:`getAnnouncements`(支持 `audience` 受众过滤)/ `getAnnouncementById` / `insertAnnouncement` / `updateAnnouncementById` / `deleteAnnouncementById` / `publishAnnouncementById` / `archiveAnnouncementById`(后 5 个为 P1-2 新增)
|
||||
- Data-access:`getAnnouncements`(支持 `audience` 受众过滤)/ `getAnnouncementById` / `insertAnnouncement` / `updateAnnouncementById` / `deleteAnnouncementById` / `publishAnnouncementById` / `archiveAnnouncementById`(后 5 个为 P1-2 新增)/ `getAdminAnnouncementsPageData` / `getEditAnnouncementPageData`(✅ P1-5 新增:管理端列表页和编辑页编排函数,页面层仅调用单一函数)
|
||||
|
||||
**依赖关系**:
|
||||
- 依赖:`shared/*`、`@/auth`、`school`(获取年级列表)、`classes`(获取班级列表 + 解析受众)、`users`(获取目标用户 ID 列表)、`notifications`(发布公告时发送通知)
|
||||
@@ -1107,14 +1145,26 @@ src/auth.ts ──▶ import { ... } from "@/shared/lib/permissions"
|
||||
- ✅ P1 已修复:~~全模块零 i18n,中英文案硬编码~~ 所有组件接入 next-intl(`useTranslations("announcements")`),新增 `src/shared/i18n/messages/{zh-CN,en}/announcements.json` 翻译字典(title/description/filter/status/type/form/actions/messages/meta/empty/error 共 11 个命名空间);所有页面 `page.tsx` 使用 `generateMetadata` + `getTranslations` 替代硬编码 metadata
|
||||
- ✅ P1 已修复:~~缺 Error Boundary~~ 新增 4 个 `error.tsx` 错误边界(`/announcements`、`/announcements/[id]`、`/admin/announcements`、`/admin/announcements/[id]`),统一使用 `EmptyState` + i18n 错误文案 + 重试按钮
|
||||
- ✅ P2 已修复:a11y 改进,`announcement-card.tsx` / `announcement-detail.tsx` 添加 `aria-label`
|
||||
- ✅ P1-5 已修复:~~页面层 `Promise.all` 编排 announcements/school/classes 三个模块的 data-access~~ 新增 `getAdminAnnouncementsPageData` 和 `getEditAnnouncementPageData` 编排函数,页面层仅调用单一函数
|
||||
- ✅ P1-6 已修复:~~`targetGradeId` / `targetClassId` 为 optional,未根据 `type` 做条件必填校验~~ `CreateAnnouncementSchema` 和 `UpdateAnnouncementSchema` 添加 `superRefine(refineAudience)`,年级公告强制 `targetGradeId`,班级公告强制 `targetClassId`
|
||||
- ✅ P2-11 已修复:~~发布/归档/删除公告无埋点~~ `createAnnouncementAction` / `updateAnnouncementAction` / `deleteAnnouncementAction` / `publishAnnouncementAction` / `archiveAnnouncementAction` 新增 `trackEvent` 埋点(announcement.created / announcement.updated / announcement.published / announcement.deleted / announcement.archived)
|
||||
|
||||
**文件清单**:
|
||||
| 文件 | 行数 | 职责 |
|
||||
|------|------|------|
|
||||
| `actions.ts` | ~270 | 6 个 Server Action + 通知触发逻辑(P1-2 已修复,无直接 DB 操作) |
|
||||
| `data-access.ts` | ~190 | 公告 CRUD + 发布/归档 + 受众过滤(含 P1-2 新增 5 个写函数) |
|
||||
| `schema.ts` | - | Zod 校验 |
|
||||
| `types.ts` | - | 类型定义(`GetAnnouncementsParams` 新增 `audience` 字段) |
|
||||
| `actions.ts` | ~330 | 6 个 Server Action + 通知触发逻辑 + trackEvent 埋点(P1-2 已修复,无直接 DB 操作) |
|
||||
| `data-access.ts` | ~230 | 公告 CRUD + 发布/归档 + 受众过滤 + `getAdminAnnouncementsPageData` / `getEditAnnouncementPageData` 编排函数(✅ P1-5 新增) |
|
||||
| `schema.ts` | ~70 | Zod 校验 + `refineAudience` 条件校验(✅ P1-6 新增 superRefine) |
|
||||
| `types.ts` | ~65 | 类型定义(`GetAnnouncementsParams` 新增 `audience` 字段) |
|
||||
|
||||
**组件清单**:
|
||||
| 组件 | 职责 |
|
||||
|------|------|
|
||||
| `components/announcement-list.tsx` | 公告列表(用户端,支持状态筛选) |
|
||||
| `components/announcement-card.tsx` | 公告卡片(列表项) |
|
||||
| `components/announcement-detail.tsx` | 公告详情(只读) |
|
||||
| `components/announcement-form.tsx` | 公告表单(创建/编辑,✅ P1-6:条件校验由 schema superRefine 保证) |
|
||||
| `components/admin-announcements-view.tsx` | 管理端公告视图(列表 + 筛选) |
|
||||
|
||||
---
|
||||
|
||||
@@ -1392,8 +1442,13 @@ src/auth.ts ──▶ import { ... } from "@/shared/lib/permissions"
|
||||
**导出函数**:
|
||||
- Actions:`getAiProvidersAction` / `createAiProviderAction` / `updateAiProviderAction` / `deleteAiProviderAction` / `testAiProviderAction`
|
||||
- Actions-password:`changePasswordAction`(✅ P1 已修复:使用 `requirePermission(USER_PROFILE_UPDATE)` + Zod 校验 + DB 操作下沉到 data-access)
|
||||
- Actions-avatar:`updateUserAvatarAction` / `removeUserAvatarAction`(✅ P2-8 新增:头像上传/删除,复用 `/api/upload` 路由)
|
||||
- Actions-notifications:`sendTestNotificationAction`(✅ P2-10 新增:发送测试通知,占位实现待接入真实通知服务)
|
||||
- Actions-system-settings:`getAdminSystemSettingsAction` / `saveAdminSystemSettingsAction`(✅ P0-3 新增:管理员系统设置 CRUD,4 分类 Zod 校验)
|
||||
- Actions-security:`getSecurityCenterAction` / `toggleTwoFactorAction`(✅ P2-9 新增:2FA 状态查询/切换 + 最近登录历史)
|
||||
- Data-access:`getAiProviderSummaries` / `countDefaultAiProviders` / `getAiProviderForUpdate` / `updateAiProvider` / `createAiProvider` / `getUserPasswordHash` / `getPasswordSecurityByUserId` / `updateUserPassword` / `upsertPasswordSecurityOnPasswordChange`(P1 新增,从 actions 下沉)
|
||||
- Components:`SettingsView`(统一设置页布局,5 标签页 General/Notifications/Appearance/Security/AI;角色差异通过 `resolveRoleSettingsConfig` 配置驱动 + `generalExtra` props 注入;Tab URL 持久化;每个 TabsContent 包裹 `SettingsSectionErrorBoundary` + `Suspense` 骨架屏;AI 标签页条件渲染需 `AI_CONFIGURE` 权限)、`SettingsServiceProvider` / `useSettingsService`(Context 注入 `SettingsService` 接口,解耦组件对 users/messaging actions 的直接依赖)、`SettingsSectionErrorBoundary`(分区 Error Boundary,局部失败不影响整页)、`QuickLinksCard`(快捷链接卡片,i18n 键驱动)、`ProfileStudentOverview` / `ProfileStudentOverviewSkeleton`(学生概览异步 Server Component + 骨架屏)、`ProfileTeacherOverview` / `ProfileTeacherOverviewSkeleton`(教师概览异步 Server Component + 骨架屏)、`AdminSettingsView`(系统设置视图,4 个 Card)
|
||||
- Data-access-system-settings:`getSystemSettingsByCategory` / `getAllSystemSettings` / `getSystemSetting` / `upsertSystemSetting` / `upsertSystemSettings`(✅ P0-3 新增:system_settings 表 CRUD,键值对存储模式)
|
||||
- Components:`SettingsView`(统一设置页布局,5 标签页 General/Notifications/Appearance/Security/AI;角色差异通过 `resolveRoleSettingsConfig` 配置驱动 + `generalExtra` props 注入;Tab URL 持久化;每个 TabsContent 包裹 `SettingsSectionErrorBoundary` + `Suspense` 骨架屏;AI 标签页条件渲染需 `AI_CONFIGURE` 权限)、`SettingsServiceProvider` / `useSettingsService`(Context 注入 `SettingsService` 接口,解耦组件对 users/messaging actions 的直接依赖)、`SettingsSectionErrorBoundary`(分区 Error Boundary,局部失败不影响整页)、`QuickLinksCard`(快捷链接卡片,i18n 键驱动)、`ProfileStudentOverview` / `ProfileStudentOverviewSkeleton`(学生概览异步 Server Component + 骨架屏)、`ProfileTeacherOverview` / `ProfileTeacherOverviewSkeleton`(教师概览异步 Server Component + 骨架屏)、`AdminSettingsView`(✅ P0-3 已修复:从 mock 改为真实数据层,通过 Server Actions 加载/保存到 system_settings 表)、`AvatarUpload`(✅ P2-8 新增:头像上传/预览/删除客户端组件,文件验证 + i18n)、`SecurityCenterCard`(✅ P2-9 新增:2FA 开关 + 最近登录历史卡片)、`ThemePreferencesCard`(✅ P2-11 已增强:集成 `LocaleSwitcher` 语言切换)
|
||||
- Config:`ROLE_SETTINGS_CONFIG` / `resolveRoleSettingsConfig`(配置驱动角色 → 设置视图映射,新增角色只需添加条目)
|
||||
- Lib:`buildStudentOverviewData` / `computeStudentStats` / `sortUpcomingAssignments` / `filterTodaySchedule` / `toWeekday`(纯数据计算函数,与 UI 分离,便于单元测试)
|
||||
- Types:`AiProviderSummary` / `AiProviderName` / `AiProviderExisting` / `SettingsService` / `ProfileService` / `NotificationPreferenceService`(服务接口定义,用于依赖注入解耦)
|
||||
@@ -1421,27 +1476,39 @@ src/auth.ts ──▶ import { ... } from "@/shared/lib/permissions"
|
||||
- ✅ P1 已修复:~~profile/page.tsx 业务逻辑与 UI 混合~~ 抽取 `buildStudentOverviewData` 等纯函数到 `lib/student-overview-data.ts`;拆分 `ProfileStudentOverview` / `ProfileTeacherOverview` 异步组件
|
||||
- ✅ 密码修改有速率限制
|
||||
- ✅ AI Provider 操作有 `AI_CONFIGURE` 权限校验
|
||||
- ✅ P0-3 已修复:~~AdminSettingsView 为 mock 实现,无数据持久化~~ 新增 `system_settings` 表(键值对存储)+ `data-access-system-settings.ts` + `actions-system-settings.ts`,AdminSettingsView 改为真实数据层
|
||||
- ✅ P2-8 已修复:~~无头像上传~~ 新增 `updateUserAvatar` data-access + `actions-avatar.ts` + `AvatarUpload` 组件,复用 `/api/upload` 路由
|
||||
- ✅ P2-9 已修复:~~无 2FA / 会话管理~~ 新增 `actions-security.ts` + `SecurityCenterCard` 组件(2FA 开关占位 + 最近登录历史来自 login_logs 表)
|
||||
- ✅ P2-10 已修复:~~通知偏好表单无测试通知按钮~~ 新增 `sendTestNotificationAction`,每个已启用渠道旁显示测试按钮
|
||||
- ✅ P2-11 已修复:~~语言切换未集成到设置页~~ `ThemePreferencesCard` 集成 `LocaleSwitcher` 到 Appearance 标签页
|
||||
|
||||
**文件清单**:
|
||||
| 文件 | 行数 | 职责 |
|
||||
|------|------|------|
|
||||
| `actions.ts` | 160 | AI Provider CRUD + 测试(P1 已修复,无直接 DB 操作) |
|
||||
| `actions-password.ts` | 87 | 修改密码(P1 已修复:requirePermission + Zod + data-access) |
|
||||
| `actions-avatar.ts` | 56 | 头像上传/删除(P2-8 新增:requirePermission + revalidatePath) |
|
||||
| `actions-notifications.ts` | 46 | 发送测试通知(P2-10 新增:占位实现待接入真实通知服务) |
|
||||
| `actions-system-settings.ts` | 186 | 管理员系统设置 CRUD(P0-3 新增:4 分类 Zod 校验 + upsert) |
|
||||
| `actions-security.ts` | 165 | 2FA 状态查询/切换 + 最近登录历史(P2-9 新增) |
|
||||
| `data-access.ts` | 158 | AI Provider CRUD + 密码修改 DB 操作(P1 新增) |
|
||||
| `data-access-system-settings.ts` | 119 | system_settings 表 CRUD(P0-3 新增:键值对存储模式) |
|
||||
| `types.ts` | 60 | 类型定义(AiProviderSummary + SettingsService/ProfileService/NotificationPreferenceService 接口) |
|
||||
| `config/role-settings-config.tsx` | 85 | 角色设置页配置驱动映射(ROLE_SETTINGS_CONFIG + resolveRoleSettingsConfig) |
|
||||
| `lib/student-overview-data.ts` | 150 | 学生概览纯数据计算(buildStudentOverviewData + computeStudentStats 等,便于单测) |
|
||||
| `components/settings-view.tsx` | 236 | SettingsView 统一设置页布局(5 标签页 + Error Boundary + Suspense + i18n) |
|
||||
| `components/settings-view.tsx` | 236 | SettingsView 统一设置页布局(5 标签页 + Error Boundary + Suspense + i18n + SecurityCenterCard 集成) |
|
||||
| `components/settings-service-context.tsx` | 39 | SettingsServiceProvider + useSettingsService(Context 注入服务接口) |
|
||||
| `components/settings-section-error-boundary.tsx` | 64 | 分区 Error Boundary(局部失败不影响整页) |
|
||||
| `components/quick-links-card.tsx` | 42 | 快捷链接卡片(i18n 键驱动) |
|
||||
| `components/profile-student-overview.tsx` | 91 | 学生概览异步 Server Component + 骨架屏 |
|
||||
| `components/profile-teacher-overview.tsx` | 115 | 教师概览异步 Server Component + 骨架屏 |
|
||||
| `components/admin-settings-view.tsx` | 195 | AdminSettingsView 系统设置视图(4 个 Card,i18n) |
|
||||
| `components/admin-settings-view.tsx` | 425 | AdminSettingsView 系统设置视图(P0-3 已修复:真实数据层 + 4 个 Card + i18n) |
|
||||
| `components/avatar-upload.tsx` | ~150 | 头像上传/预览/删除客户端组件(P2-8 新增:文件验证 + i18n) |
|
||||
| `components/security-center-card.tsx` | ~240 | 安全中心卡片(P2-9 新增:2FA 开关 + 最近登录历史) |
|
||||
| `components/profile-settings-form.tsx` | 158 | 个人资料表单(通过 SettingsService 注入,i18n) |
|
||||
| `components/notification-preferences-form.tsx` | ~140 | 通知偏好表单(通过 SettingsService 注入,i18n) |
|
||||
| `components/notification-preferences-form.tsx` | ~160 | 通知偏好表单(通过 SettingsService 注入,i18n + P2-10 测试按钮) |
|
||||
| `components/password-change-form.tsx` | ~130 | 密码修改表单(i18n + a11y) |
|
||||
| `components/theme-preferences-card.tsx` | ~60 | 主题偏好卡片(i18n) |
|
||||
| `components/theme-preferences-card.tsx` | ~80 | 主题偏好卡片(i18n + P2-11 LocaleSwitcher 集成) |
|
||||
| `components/ai-provider-settings-card.tsx` | ~200 | AI 服务商配置卡片(i18n) |
|
||||
|
||||
---
|
||||
@@ -1568,10 +1635,16 @@ src/auth.ts ──▶ import { ... } from "@/shared/lib/permissions"
|
||||
> 架构变更(2026-06-22,本次审计修复):
|
||||
> - **P0-1 跨模块直查修复**:`publish-service.ts` 不再直接 `db.insert(examQuestions)` 和本地实现 `getStudentIdsByClassIds`,改为调用 `exams/data-access.addExamQuestions` 和 `classes/data-access.getStudentIdsByClassIds`,恢复三层架构约束
|
||||
> - **P0-2 i18n 接入**:新增 `shared/i18n/messages/zh-CN/lesson-preparation.json` 和 `shared/i18n/messages/en/lesson-preparation.json`,注册 `lessonPreparation` 命名空间到 `src/i18n/request.ts`,17 个组件改造为 `useTranslations`/`getTranslations`
|
||||
> - **P0-3 DataScope 过滤修复**:`data-access.ts` 的 `buildScopeCondition` 按 scope 类型精确过滤——`class_taught` 增加 `subjectId IN teacher.subjects AND gradeId IN teacher.grades`,`grade_managed` 限制 `gradeId IN managedGrades`,`class_members`/`children` 仅允许查看 `published` 课案
|
||||
> - **P1-1 类型安全修复**:`as never` 断言全部替换为类型守卫函数(`isQuestionType`/`isV1Document`/`isV2Document`)+ `validTypes` 数组;`block-renderer.tsx` 使用 `as ExerciseBlockData`/`as TextStudyBlockData` 精确断言;`constants.ts` 的 `BLOCK_TYPE_LABELS`/`LESSON_PLAN_STATUS_LABELS` 改为 i18n 键(`BLOCK_TYPE_KEYS`/`LESSON_PLAN_STATUS_KEYS`)
|
||||
> - **P1 纯函数抽取**:新增 `lib/document-migration.ts`(migrateV1ToV2/normalizeDocument/buildInitialContent,使用类型守卫替代 as 断言)、`lib/node-summary.ts`(getNodeSummary + NODE_COLORS + getNodeColor,接受翻译函数注入)、`lib/rf-mappers.ts`(toRfNodes/toRfEdges/fromRfEdges),data-access.ts 改为从 lib/ 导入并 re-export 保持向后兼容
|
||||
> - **P1 错误边界 + 骨架屏**:新增 `components/lesson-plan-error-boundary.tsx`(LessonPlanErrorBoundary 类组件)和 `components/lesson-plan-skeleton.tsx`(VersionListSkeleton/QuestionBankSkeleton/KnowledgePointSkeleton/LessonPlanListSkeleton)
|
||||
> - **P1 Block 注册表**:新增 `config/block-registry.tsx`(BLOCK_REGISTRY 配置表 + getBlockComponent/isRichTextBlock),`node-edit-panel.tsx` 重构为配置驱动渲染,移除 if/else 链
|
||||
> - **P1 window.location.reload 修复**:`exercise-block.tsx` 改用 `router.refresh()` 精确刷新缓存
|
||||
> - **P1-2/P1-3 错误边界 + 骨架屏**:新增 `components/lesson-plan-error-boundary.tsx`(LessonPlanErrorBoundary 类组件)和 `components/lesson-plan-skeleton.tsx`(VersionListSkeleton/QuestionBankSkeleton/KnowledgePointSkeleton/LessonPlanListSkeleton)
|
||||
> - **P1-4 阻塞式 UI 修复**:`alert()` 全部替换为 `sonner` toast;`confirm()` 全部替换为 `AlertDialog`(shadcn);`window.location.reload` 替换为 `router.refresh()`;涉及 lesson-plan-card/version-history-drawer/inline-question-editor/text-study-block/exercise-block
|
||||
> - **P1-5/P1-7 多实例 + 角色配置驱动**:新增 `providers/lesson-plan-provider.tsx`(LessonPlanProvider + Context + 4 个角色配置 TEACHER/ADMIN/STUDENT/PARENT + LessonPlanDataService 接口 + LessonPlanTracker 埋点接口 + useLessonPlanContextSafe/useRoleConfig 等 hooks)和 `services/default-data-service.ts`(包装 Server Actions 为 DataService 实现),lesson-plan-list/lesson-plan-card 通过 Context 注入数据服务,useRoleConfig 控制按钮可见性
|
||||
> - **P1-8 Block 注册表**:新增 `config/block-registry.tsx`(BLOCK_REGISTRY 配置表 + getBlockComponent/isRichTextBlock),`node-edit-panel.tsx` 重构为配置驱动渲染,移除 if/else 链
|
||||
> - **P1-4 window.location.reload 修复**:`exercise-block.tsx` 改用 `router.refresh()` 精确刷新缓存
|
||||
> - **P2-1 a11y 修复**:5 个组件(question-bank-picker/publish-homework-dialog/knowledge-point-picker/exercise-block/text-study-block)添加 `role="dialog"`/`aria-modal`/`aria-label`;inline-question-editor 添加 `role="dialog"`/`aria-modal`/`aria-label`
|
||||
> - **P2-4 监控埋点预留**:`providers/lesson-plan-provider.tsx` 定义 `LessonPlanTracker` 接口 + `noopTracker` 默认空实现,生产环境可替换为真实埋点
|
||||
|
||||
**文件清单**:
|
||||
| 文件 | 职责 |
|
||||
@@ -1583,7 +1656,9 @@ src/auth.ts ──▶ import { ... } from "@/shared/lib/permissions"
|
||||
| `lib/node-summary.ts` | **纯函数**:getNodeSummary(接受翻译函数注入,支持 i18n)+ NODE_COLORS + getNodeColor |
|
||||
| `lib/rf-mappers.ts` | **纯函数**:toRfNodes/toRfEdges/fromRfEdges(LessonPlanNode/Edge ↔ React Flow Node/Edge 映射) |
|
||||
| `config/block-registry.tsx` | **配置驱动**:BLOCK_REGISTRY 注册表 + getBlockComponent/isRichTextBlock,node-edit-panel 通过配置渲染 Block |
|
||||
| `data-access.ts` | 课案 CRUD + 模板查询(migrateV1ToV2/normalizeDocument/buildInitialContent 从 lib/ 导入并 re-export 保持向后兼容) |
|
||||
| `providers/lesson-plan-provider.tsx` | **Provider + Context(P1-5/P1-7/P2-4)**:LessonPlanProvider 注入数据服务/角色配置/埋点;定义 LessonPlanDataService 接口、4 个角色配置(TEACHER/ADMIN/STUDENT/PARENT)、ROLE_CONFIGS 注册表、LessonPlanTracker 接口 + noopTracker;hooks:useLessonPlanContextSafe(返回 null 不抛错)/useLessonPlanContext/useRoleConfig/useLessonPlanService/useLessonPlanTracker |
|
||||
| `services/default-data-service.ts` | **默认数据服务实现**:createDefaultDataService() 包装 Server Actions 为 LessonPlanDataService 实现,测试可替换为 mock |
|
||||
| `data-access.ts` | 课案 CRUD + 模板查询(migrateV1ToV2/normalizeDocument/buildInitialContent 从 lib/ 导入并 re-export 保持向后兼容;buildScopeCondition 按 scope 类型精确过滤 P0-3) |
|
||||
| `data-access-versions.ts` | 版本管理(创建/查询/回滚/清理) |
|
||||
| `data-access-templates.ts` | 个人模板 CRUD |
|
||||
| `data-access-knowledge.ts` | 按知识点/题目反查课案 |
|
||||
|
||||
@@ -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": {
|
||||
|
||||
121
docs/architecture/audit/dashboard-audit-report-v2.md
Normal file
121
docs/architecture/audit/dashboard-audit-report-v2.md
Normal file
@@ -0,0 +1,121 @@
|
||||
# 仪表盘模块审计报告 v2
|
||||
|
||||
> 审查日期:2026-06-22(第二轮)
|
||||
> 审查范围:基于 v1 重构后代码(commit `868ac5f` + `21c1e7a`)的再次分析
|
||||
> 前置报告:`docs/architecture/audit/dashboard-audit-report.md`(v1)
|
||||
> 架构图参考:`docs/architecture/004_architecture_impact_map.md` §2.12、`docs/architecture/005_architecture_data.json`
|
||||
|
||||
---
|
||||
|
||||
## 一、v1 重构成果回顾
|
||||
|
||||
v1 报告识别的 P0/P1/P2 项目已完成的部分:
|
||||
|
||||
| # | 项目 | 状态 | 证据 |
|
||||
|---|------|------|------|
|
||||
| P0-1 | 权限校验 | ✅ 已完成 | [actions.ts](file:///e:/Desktop/CICD/src/modules/dashboard/actions.ts) 4 个 Server Action 均调用 `requirePermission()` |
|
||||
| P0-2 | 根重定向角色硬编码 | ✅ 已完成 | [dashboard/page.tsx](file:///e:/Desktop/CICD/src/app/(dashboard)/dashboard/page.tsx) 改用 `resolvePermissions()` |
|
||||
| P0-3 | i18n 零覆盖 | ⚠️ 部分完成 | 仅容器组件接入 i18n,**10 个子组件仍英文硬编码** |
|
||||
| P0-4 | 页面层越权编排 | ✅ 已完成 | teacher/student/parent 编排下沉至 actions.ts |
|
||||
| P1-1 | 业务逻辑耦合 UI | ✅ 已完成 | [lib/dashboard-utils.ts](file:///e:/Desktop/CICD/src/modules/dashboard/lib/dashboard-utils.ts) 抽取 6 个纯函数 |
|
||||
| P1-3 | 仅路由级错误边界 | ✅ 已完成 | [dashboard-section.tsx](file:///e:/Desktop/CICD/src/modules/dashboard/components/dashboard-section.tsx) 分区 Error Boundary + Suspense |
|
||||
| P2-2 | a11y 不足 | ❌ 未完成 | 仍缺语义化标签、表格 caption |
|
||||
|
||||
---
|
||||
|
||||
## 二、v2 新发现问题
|
||||
|
||||
### 2.1 i18n 覆盖严重不完整(P0 — v1 遗漏)
|
||||
|
||||
v1 仅对容器组件(`admin-dashboard.tsx`、`teacher-dashboard-view.tsx`、`teacher-dashboard-header.tsx`、`teacher-stats.tsx`、`teacher-todo-card.tsx`、`student-stats-grid.tsx`、`student-dashboard-header.tsx`、`parent-dashboard.tsx`、`user-growth-chart.tsx`)接入 i18n,**10 个子组件仍全英文硬编码**:
|
||||
|
||||
| # | 文件 | 硬编码示例 | 违反规则 |
|
||||
|---|------|-----------|----------|
|
||||
| 1 | [teacher-quick-actions.tsx](file:///e:/Desktop/CICD/src/modules/dashboard/components/teacher-dashboard/teacher-quick-actions.tsx) L12-24 | `"Create Assignment"` / `"Grade"` / `"My Classes"` | "所有用户可见文本必须适配 i18n" |
|
||||
| 2 | [teacher-classes-card.tsx](file:///e:/Desktop/CICD/src/modules/dashboard/components/teacher-dashboard/teacher-classes-card.tsx) L15-27 | `"My Classes"` / `"View all"` / `"No classes yet"` / `"Create a class to start managing students and schedules."` / `"Create class"` / `"Homeroom"` / `"Room"` | 同上 |
|
||||
| 3 | [teacher-homework-card.tsx](file:///e:/Desktop/CICD/src/modules/dashboard/components/teacher-dashboard/teacher-homework-card.tsx) L17-87 | `"Homework"` / `"Create new assignment"` / `"No assignments"` / `"Create an assignment to get started."` / `"Create"` / `"No due date"` / `"View all assignments"` | 同上 |
|
||||
| 4 | [teacher-schedule.tsx](file:///e:/Desktop/CICD/src/modules/dashboard/components/teacher-dashboard/teacher-schedule.tsx) L41-141 | `"Today's Schedule"` / `"No Classes Today"` / `"No timetable entries."` / `"View schedule"` / `"LIVE"` / `"Scroll for more"` / `"No more classes today"` | 同上 |
|
||||
| 5 | [recent-submissions.tsx](file:///e:/Desktop/CICD/src/modules/dashboard/components/teacher-dashboard/recent-submissions.tsx) L22-105 | `"Recent Submissions"` / `"No New Submissions"` / `"All caught up!..."` / `"View All"` / `"View submissions"` / `"Student"` / `"Assignment"` / `"Submitted"` / `"Action"` / `"Late"` / `"Grade"` | 同上 |
|
||||
| 6 | [teacher-grade-trends.tsx](file:///e:/Desktop/CICD/src/modules/dashboard/components/teacher-dashboard/teacher-grade-trends.tsx) L25-69 | `"Class Performance"` / `"Average scores for the last X assignments"` / `"No data available"` / `"Publish assignments to see class performance trends."` / `"Average Score (%)"` / `"X/Y submitted"` | 同上 |
|
||||
| 7 | [student-grades-card.tsx](file:///e:/Desktop/CICD/src/modules/dashboard/components/student-dashboard/student-grades-card.tsx) L30-101 | `"Recent Grades"` / `"No graded work yet"` / `"Finish and submit assignments to see your score trend."` / `"View all"` / `"Score (%)"` / `"Latest:"` / `"Points:"` / `"Assignment"` / `"Score"` / `"When"` | 同上 |
|
||||
| 8 | [student-today-schedule-card.tsx](file:///e:/Desktop/CICD/src/modules/dashboard/components/student-dashboard/student-today-schedule-card.tsx) L52-83 | `"Today's Schedule"` / `"View all"` / `"No classes today"` / `"Your timetable is clear for today."` / `"In Progress"` / `"Up Next"` | 同上 |
|
||||
| 9 | [student-upcoming-assignments-card.tsx](file:///e:/Desktop/CICD/src/modules/dashboard/components/student-dashboard/student-upcoming-assignments-card.tsx) L17-22,49-72 | `"Review"` / `"View"` / `"Continue"` / `"Start"` / `"Upcoming Assignments"` / `"View all"` / `"No assignments"` / `"You have no assigned homework right now."` / `"Title"` / `"Status"` / `"Due"` / `"Score"` / `"Action"` / `"Late"` | 同上 |
|
||||
| 10 | [admin-dashboard.tsx](file:///e:/Desktop/CICD/src/modules/dashboard/components/admin-dashboard/admin-dashboard.tsx) L212 | `{u.role ?? "unknown"}` 硬编码 `"unknown"` | 同上 |
|
||||
|
||||
**后果**:中文用户看到大量英文,体验割裂;无法切换语言;维护时需逐文件改字符串。
|
||||
|
||||
### 2.2 四角色仍零共享抽象(P1 — v1 未处理)
|
||||
|
||||
| 维度 | 现状 | 期望 |
|
||||
|------|------|------|
|
||||
| 问候语头部 | `TeacherDashboardHeader` 与 `StudentDashboardHeader` 代码 90% 重复(仅 props 名不同) | 抽象为 `DashboardGreetingHeader` |
|
||||
| 快捷操作 | admin 的 `QuickActionCard`(内联)、parent 的 `QUICK_ENTRIES`(内联)、teacher 的 `TeacherQuickActions` — 三套独立实现 | 抽象为 `DashboardQuickActions` |
|
||||
| 仪表盘布局容器 | admin/teacher/student 各写一套 `<div className="space-y-*">` | 抽象为 `DashboardLayout` |
|
||||
|
||||
**违反规则**:"最大化复用:识别四个角色共用的 UI 块和业务逻辑块,抽象为泛型组件和 hooks"。
|
||||
|
||||
### 2.3 无单测(P2 — v1 未处理)
|
||||
|
||||
`lib/dashboard-utils.ts` 抽取了 6 个纯函数但**无任何单测**:
|
||||
|
||||
| 函数 | 测试覆盖 | 风险 |
|
||||
|------|----------|------|
|
||||
| `toWeekday` | ❌ 无 | 周日映射错误未被发现 |
|
||||
| `countStudentAssignments` | ❌ 无 | 边界条件(无截止日期/已批改)未验证 |
|
||||
| `sortUpcomingAssignments` | ❌ 无 | 排序稳定性未验证 |
|
||||
| `filterTodaySchedule` | ❌ 无 | 空课表/排序未验证 |
|
||||
| `computeTeacherMetrics` | ❌ 无 | 提交率分母为零等边界未验证 |
|
||||
| `getGreetingKey` | ❌ 无 | 时段边界(12:00/18:00)未验证 |
|
||||
|
||||
**违反规则**:"数据获取、计算、格式化等纯逻辑全部放入纯函数或 hooks,与 UI 分离;导出清晰的接口类型以便 mock" + "可测试性"。
|
||||
|
||||
### 2.4 a11y 不足(P2 — v1 未处理)
|
||||
|
||||
| 位置 | 问题 | 违反规则 |
|
||||
|------|------|----------|
|
||||
| `admin-dashboard.tsx` 表格 | 无 `<caption>` | "语义化标签、ARIA 属性、键盘导航" |
|
||||
| `recent-submissions.tsx` 表格 | 无 `<caption>` | 同上 |
|
||||
| `student-upcoming-assignments-card.tsx` 表格 | 无 `<caption>` | 同上 |
|
||||
| `teacher-dashboard-view.tsx` 布局 | 无 `<section>` / `<aside>` 语义化标签 | 同上 |
|
||||
| `student-dashboard-view.tsx` 布局 | 同上 | 同上 |
|
||||
| `teacher-schedule.tsx` 时间线 | 无 `aria-label` 描述当前/过去/未来状态 | 同上 |
|
||||
|
||||
### 2.5 流式渲染未实现(P1 — v1 未处理)
|
||||
|
||||
所有 `page.tsx` 仍 `export const dynamic = "force-dynamic"` + `Promise.all` 等全部数据就绪后才渲染。虽然 `DashboardSection` 内部有 Suspense,但 page 层已无 Suspense 边界,无法流式渲染首屏。
|
||||
|
||||
---
|
||||
|
||||
## 三、改进优先级(v2)
|
||||
|
||||
### P0(紧急 — v1 遗漏的 i18n)
|
||||
|
||||
| # | 问题 | 改进方向 |
|
||||
|---|------|----------|
|
||||
| v2-P0-1 | 10 个组件英文硬编码 | 全部接入 `useTranslations` / `getTranslations`;补充翻译键 |
|
||||
|
||||
### P1(较严重 — 共享抽象 + 单测)
|
||||
|
||||
| # | 问题 | 改进方向 |
|
||||
|---|------|----------|
|
||||
| v2-P1-1 | 问候语头部重复 | 抽象 `DashboardGreetingHeader` 组件 |
|
||||
| v2-P1-2 | 纯函数无单测 | 为 `lib/dashboard-utils.ts` 6 个函数添加单测 |
|
||||
|
||||
### P2(优化 — a11y + 流式)
|
||||
|
||||
| # | 问题 | 改进方向 |
|
||||
|---|------|----------|
|
||||
| v2-P2-1 | 表格无 caption / 布局无语义化标签 | 补充 `<caption>` / `<section>` / `aria-label` |
|
||||
|
||||
---
|
||||
|
||||
## 四、架构图同步说明
|
||||
|
||||
v2 修改完成后需同步更新:
|
||||
|
||||
### 4.1 `004_architecture_impact_map.md`
|
||||
- §2.12 dashboard 章节:补充新增共享组件(`DashboardGreetingHeader`)、单测文件(`lib/dashboard-utils.test.ts`)
|
||||
|
||||
### 4.2 `005_architecture_data.json`
|
||||
- `modules.dashboard.exports.components`:新增 `DashboardGreetingHeader`
|
||||
- `modules.dashboard.exports.lib`:补充单测覆盖说明
|
||||
Reference in New Issue
Block a user