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` | 按知识点/题目反查课案 |
|
||||
|
||||
Reference in New Issue
Block a user