Files
Edu/apps/portal-shell/docs/needtodo/shared-NeedTodo.md
SpecialX 062d9e9582 feat(portal-shell): 管理域 §9.4 B5 全量迁移(24 + 21 补充批次共 44 页 + 42 features)
按 ARCHITECTURE.md §9.4 规划口径 + admin-NeedTodo.md §四补充批次完成管理域全量页面迁移:

【§9.4 规划 24 页(B5)】
- users(2) + roles(1) + permissions(1) + audit-logs(4) + invitation-codes(1)
- school(6: redirect/schools/classes/departments/academic-year/grades)
- announcements(1) + files(1) + ai-settings(1) + system(1) + viewports(1)
- students(1) + teachers(1) + organization(1) + plugins(1, config-service)
- 仪表盘已存在(/shell/admin/page.tsx)

【§四补充批次 21 页】
- course-plans(4) + elective(4) + questions(1) + lesson-plans(2) + error-book(1)
- scheduling(3: auto/changes/rules) + attendance(1) + curriculum-map(1)
- announcements 详情/编辑(2) + roles/[id] 详情(1) + users/import(1)

【实现要点】
- 全部使用 ListPageShell + loading/error/empty 三态规范(§11.3 DoD)
- 走 lib/api hooks;未就绪契约走 MSW + @contract-pending 注释(§11.4)
- 文案走 useTranslations(zh-CN + en 两份同步更新)
- 42 个 features/<domain>/transformations.ts 纯函数 + 配套 vitest 单测
- catch 块统一 notify.error;无空 catch;lint:tokens 通过
- 路由全部登记到 route-permissions.ts(39 EXACT + 8 PREFIX)

【验收】
- tsc --noEmit: 0 errors
- ESLint src: 0 errors (4 generated-files warnings, pre-existing)
- lint:tokens: 0 errors
- vitest: 1639/1639 passed (含 23 admin 测试文件 671 用例)
- check:routes: PASS (143 routes, 4 ghost entries pre-existing)
- check:pages: PASS (146 pages)
- check:codegen: PASS
- arch:scan: 24 modules, 8262 symbols

关联:ARCHITECTURE.md §9.4 / §10 P5 / §11.3 DoD / §11.6
2026-07-24 23:07:20 +08:00

40 KiB
Raw Blame History

共享路由Shared待完成功能分析

参考项目:e:\desktop\CICD\src\app\(dashboard)\ 当前项目:e:\Desktop\Edu\apps\portal-shell\src\app\shell\ 规划依据:apps\portal-shell\ARCHITECTURE.md §9.1 / §9.2 / §9.3 / §9.5(共享 ~7 页) 分析时间2026-07-24 分析范围:仅分析,不写代码


一、页面完成度总览

状态 路径 说明
/login 登录页
/shell 仪表盘根(按角色分发到 /shell/{teacher|student|parent|admin})。CICD 参考无单一分发器——CICD 根 src/app/page.tsx 直接 redirect("/dashboard")(教师仪表盘);各角色仪表盘分散于 (dashboard)/dashboard/(dashboard)/student/dashboard/(dashboard)/parent/dashboard/(dashboard)/admin/dashboard/。portal-shell 实现于 src/app/shell/[[...route]]/page.tsxcatch-all空路由 → redirect("/shell/{role}")+ src/app/shell/layout.tsxAppFrame 框架fail-closed 读 x-user-role 头)
/shell/forbidden 403 页(PUBLIC_ROUTES 白名单)
/shell/notifications 通知中心(缺失)
/shell/settings 设置(缺失)
/shell/settings/security 安全中心 / 改密码(缺失)
/shell/announcements 公告列表(缺失)
/shell/announcements/[id] 公告详情(缺失)
/shell/messages 消息(缺失)
/shell/messages/compose 撰写消息(缺失)
/shell/messages/[id] 消息详情(缺失)
/shell/profile 个人资料(缺失)

备注 1portal-shell 现存 widgets/topbar/notification-bell(顶栏铃铛下拉)与 widgets/universal/notifications-widget(仪表盘内嵌通知卡片),但无独立的通知中心整页。 备注 2widgets/universal/announcements-widget 已在 P1-2 下线(数据源迁移到 adminDashboard 聚合查询),无用户端公告列表/详情页。 备注 3shared/lib/navigation.tsshared/lib/route-permissions.ts 均未登记上述任一共享路由,按 route-permissions.ts §5 fail-closed 规则,未登记的 /shell/* 路由会被默认拒绝 → 即便加了 page.tsx 也访问不到,必须同步登记权限配置。


二、缺失页面详情

2.1 /shell/notifications(通知中心)

  • CICD 参考:无独立 notifications/page.tsx。CICD 中通知集成在三处:
    • widgets/topbar/notification-bell 的下拉(顶栏铃铛)
    • messages/page.tsx 内嵌的 NotificationListSection(与消息列表合并展示)
    • 通知偏好散落在 settings 页 + notifications/preferences.ts
  • 功能描述
    • 用户视角的"通知中心"整页:分页列出当前用户的站内通知(message_notifications 表),支持未读/已读筛选、归档、标记已读、全部已读、按优先级low/normal/high/urgent筛选
    • 通知类型包括 message / announcement / homework / grade / diagnostic 五类
    • 通知带 link 字段可点击跳转到来源页(如作业/成绩/公告详情)
    • 顶栏铃铛仅展示最近 N 条 + 未读角标,整页才是完整列表
  • 技术栈
    • Server Component + Suspense + SectionErrorBoundary(局部失败不影响其他区块)
    • notifications/data-access.tsgetNotifications / markNotificationAsRead / markAllNotificationsAsRead / archiveNotification / getUnreadNotificationCount(均 cacheFn 包装tag=notificationsTTL=60s
    • Server Actionsnotifications/actions.ts384 行,标记已读/归档/批量操作)
    • 客户端实时流:use-notification-streamSSE+ use-desktop-notifications(浏览器桌面通知)
    • 表:message_notifications(站内通知本体)、notification_preferences(偏好)、notification_logs(发送日志)
  • ARCHITECTURE.md 契约 notifications(userId)§9.1 line 645B1 共享)
    • portal-shell 已有 GET_NOTIFICATIONS_LIST_DOC GraphQL documentuniversal.graphql.ts),但形状待 P1-7 修正(后端返回平铺数组,前端按 {items, total} 解析)
    • useNotifications hook 已就绪(lib/api/universal.ts),可直接复用
  • 多角色复用:教师 / 学生 / 家长 / 管理员(全角色,权限点复用 MESSAGE_READ,无独立 NOTIFICATION_READ
  • 优先级B1§9.1 line 645

2.2 /shell/settings(设置)

  • CICD 参考e:\desktop\CICD\src\app\(dashboard)\settings\page.tsx + error.tsx + loading.tsx
  • 功能描述
    • 个人资料编辑表单(姓名/性别/年龄/手机/地址)+ 头像上传
    • 通知偏好表单(smsEnabled / emailEnabled / pushEnabled + homeworkNotifications / gradeNotifications 等业务开关)
    • 安全中心入口密码修改、最近登录记录、2FA、当前会话 User-Agent 标记)
    • 角色差异化:通过 resolveRoleSettingsConfig(roles) 注入 descriptionKey / backHref / generalExtra(如管理员额外展示学校信息/品牌配置/AI 提供商配置卡片)
    • 子页 /settings/security:独立密码修改页 + 安全提示
  • 技术栈
    • Server ComponentrequireAuth,不要求特定权限点)+ SettingsServiceProviderContext 注入 SettingsService 接口)
    • Server Actionsactions-service.tsupdateProfileAction / updateNotificationPreferencesAction / actions-avatar.ts / actions-password.ts / actions-security.ts / actions-brand.ts / actions-system-settings.ts
    • 数据源:getUserProfileusers 模块)+ getNotificationPreferencesnotifications 模块)
    • 角色配置:modules/settings/config/role-settings-config.tsx(按角色注入额外卡片)
    • 客户端组件:settings-view / profile-settings-form / notification-preferences-form / password-change-form / security-center-card / avatar-upload / theme-preferences-card 等 22 个组件
    • 工具:totp.ts2FAsecurity-utils.ts(密码强度校验)
  • ARCHITECTURE.md 契约§9.1 line 646B1 共享,契约字段未细化)
    • portal-shell 当前无 settings 模块、无 /shell/settings 路由、无 navigation.ts 入口、无 route-permissions.ts 登记
  • 多角色复用:教师 / 学生 / 家长 / 管理员(全角色,按 roles 动态注入角色专属卡片)
  • 优先级B1§9.1 line 646

2.3 /shell/settings/security(安全中心子页)

  • CICD 参考e:\desktop\CICD\src\app\(dashboard)\settings\security\page.tsx + error.tsx + loading.tsx
  • 功能描述
    • 独立的密码修改页(PasswordChangeForm+ 安全提示卡片
    • settings 主页的"安全中心卡片"互补,提供专注的改密体验
  • 技术栈
    • requireAuth + PasswordChangeForm(客户端组件,调 updatePasswordAction
    • 复用 modules/settings 模块
  • ARCHITECTURE.md 契约🟡 未显式列出,但属于 settings 共享子页§9.1 隐含)
  • 多角色复用:全角色
  • 优先级B1跟随 settings

2.4 /shell/announcements(公告列表)

  • CICD 参考e:\desktop\CICD\src\app\(dashboard)\announcements\page.tsx
  • 功能描述
    • 用户端公告列表(非管理端),分页展示当前用户可见的公告school / grade / class 三类,按受众 gradeIds[] / classIds[] 数组过滤P0-2 多受众修复)
    • 支持状态筛选(published / draft / archived / all),通过 searchParams.status 传递
    • AnnouncementList + AnnouncementPagination(客户端构建分页 URL不传函数 prop
    • 点击进入 /announcements/[id] 详情
    • 管理端在 /admin/announcements(不在共享范围)
  • 技术栈
    • Server Component + requirePermission(ANNOUNCEMENT_READ) + AnnouncementsServiceProviderContext
    • announcements/data-access.ts603 行server-onlygetUserAnnouncementsPageData(userId, dataScope, page, pageSize) 受众过滤 + 分页
    • isAnnouncementVisibleToAudience 纯函数(单测覆盖三类矩阵)
    • 表:announcements + announcement_reads(已读回执独立表)
    • 缓存:cacheFn + invalidateFor 双路径失效(列表 + 详情)
  • ARCHITECTURE.md 契约§9.2 line 668"列表"契约未就绪B3
    • portal-shell 的 announcements-widget 已下线P1-2数据源迁到 adminDashboard 聚合,用户端列表契约完全缺失
  • 多角色复用:教师 / 学生 / 家长(查看自己受众范围内的公告);管理员走 /shell/admin/announcements 管理端,不走共享列表
  • 优先级B3§9.2 line 668

2.5 /shell/announcements/[id](公告详情)

  • CICD 参考e:\desktop\CICD\src\app\(dashboard)\announcements\[id]\page.tsx
  • 功能描述
    • 公告详情页,展示标题/内容/类型/发布时间/作者
    • P0-1 越权修复getAnnouncementByIdForUser(id, userId, dataScope) 在 data-access 层结合受众与 status 过滤,不可见时统一返回 404不暴露公告存在性
    • canManage={false}(用户端只读,管理操作在 /admin/announcements/[id]/edit
    • 进入详情后可触发已读回执 markAnnouncementAsReadAction
  • 技术栈
    • Server Component + requirePermission(ANNOUNCEMENT_READ) + notFound() 守卫
    • getAnnouncementByIdForUser 受众过滤查询
    • AnnouncementDetail 客户端组件 + AnnouncementsServiceProvider
  • ARCHITECTURE.md 契约🟡 announcement(id) 已存在但形状/可见性语义待对齐§9.2 line 668B3
  • 多角色复用:教师 / 学生 / 家长
  • 优先级B3§9.2 line 668

2.6 /shell/messages(消息列表)

  • CICD 参考e:\desktop\CICD\src\app\(dashboard)\messages\page.tsx + error.tsx + loading.tsx
  • 功能描述
    • CICD 把"消息列表"和"通知列表"合并展示在同一页(消息在上、通知在下,各自独立 Suspense + SectionErrorBoundary,局部失败互不影响)
    • 消息区:MessageListSection(按 dataScope.type === "class_taught" 决定是否显示"群发"按钮,即教师可群发全班家长)
    • 通知区:NotificationListSection(调 getNotifications(userId, {page:1, pageSize:20})
    • 子页 compose:撰写新消息 + 草稿列表(getRecipients + getMessageDrafts
    • 子页 [id]:消息详情(getMessageDetailPageData,含撤回、星标、举报、附件)
    • 子页 group-compose教师群发fan-out on write
  • 技术栈
    • Server Component + requirePermission(MESSAGE_READ) + 双 Suspense 流式加载
    • messaging/data-access.tsbarrel拆分为 core/bulk/group/templates/reports 5 子文件)
    • 表:messages / message_drafts / message_templates / message_reports / user_blocks
    • Server ActionssendMessageAction / recallMessageAction2 分钟窗口服务端校验,不乐观更新)/ sendGroupMessageActionfan-out on write/ reportMessageAction / blockUserAction
    • 乐观更新:useOptimistic 仅用于星标切换
    • 收件人二次校验P0-1isReceiverAllowed 防 UI 绕过越权
    • 通知集成:消息发送后调 notifications/dispatcher.sendNotification 多通道分发in-app/sms/email/wechat尊重偏好
  • ARCHITECTURE.md 契约§9.2 line 669B3 末,契约完全缺失)
    • portal-shell 当前无 messaging 模块、无 /shell/messages 路由
    • msg 子图仅暴露 notifications(userId),未暴露 messages 相关查询
  • 多角色复用:教师(可群发)/ 学生 / 家长 / 管理员(全角色私信,权限点 MESSAGE_READ / MESSAGE_SEND
  • 优先级B3 末§9.2 line 669

2.7 /shell/messages/compose / /shell/messages/[id](消息子页)

  • CICD 参考
    • e:\desktop\CICD\src\app\(dashboard)\messages\compose\page.tsx + loading.tsx
    • e:\desktop\CICD\src\app\(dashboard)\messages\[id]\page.tsx + loading.tsx
    • e:\desktop\CICD\src\app\(dashboard)\messages\group-compose\page.tsx + loading.tsx
  • 功能描述
    • compose:撰写表单(收件人选择 + 主题 + 内容 + 附件)+ 草稿列表(MessageDraftList 支持恢复编辑/删除)+ 快捷模板选择器(message-template-picker
    • [id]消息详情含撤回2 分钟窗口)、星标、举报、屏蔽、附件预览
    • group-compose:教师选择班级 → fan-out 群发全班家长(共享 groupMessageId
  • 技术栈:同 §2.6
  • ARCHITECTURE.md 契约§9.2 line 669 隐含B3 末)
  • 多角色复用:教师(含群发)/ 学生 / 家长 / 管理员
  • 优先级B3 末

2.8 /shell/profile(个人资料)

  • CICD 参考e:\desktop\CICD\src\app\(dashboard)\profile\page.tsx + error.tsx + loading.tsx
  • 功能描述
    • 个人资料展示页(只读,编辑入口跳转到 /settings
    • 头像上传区(AvatarUpload,独立 SectionErrorBoundary
    • 个人信息卡:姓名 / 性别 / 年龄 / 手机 / 地址
    • 账户信息卡:邮箱 / 角色 Badge / 注册时间 / Onboarded 时间
    • 角色差异化概览resolveProfileOverviewType(roles) 决定底部展示 ProfileStudentOverview(学生学业概览)或 ProfileTeacherOverview(教师概览),均带 Suspense 骨架屏
  • 技术栈
    • Server Component + requireAuth(无特定权限点)+ getUserProfile(userId) + redirect("/login") 守卫
    • 客户端组件:AvatarUpload / ProfileStudentOverview / ProfileTeacherOverview
    • 角色配置:modules/settings/config/profile-overview-config.ts
    • UIPageHeader + Card + Badge + Button + lucide-react 图标
  • ARCHITECTURE.md 契约🟡 未在 §9 显式列出独立 /shell/profile 行,但 §9.5 计数 ~7 页共享 + CICD 有 profile/page.tsx,推断属于共享路由隐含项
    • portal-shell 当前无 /shell/profile 路由、无 settings 模块profile 组件归属 settings 模块)
  • 多角色复用:教师 / 学生 / 家长 / 管理员(全角色,按 resolveProfileOverviewType 切换底部概览)
  • 优先级B1/B3跟随 settings建议 B1 一并实现)

2.9 /register注册页

  • CICD 参考实现e:\desktop\CICD\src\app\(auth)\register\page.tsxRSC13 行,渲染 <RegisterForm registerAction={registerAction} />);布局 e:\desktop\CICD\src\app\(auth)\layout.tsx 注入品牌配置(getBrandConfig(),失败回退默认)
  • 功能描述:账号注册页。表单字段:姓名 / 邮箱 / 密码 / 出生年月日 / 邀请码(可选,提供时覆盖默认 student 角色)。未成年人保护:根据 birthDate 计算 age< 18 触发监护人信息区(监护人姓名 / 电话 / 关系下拉)+ 监护人同意复选框。必勾"同意《隐私政策》与《用户协议》"复选框(链接到 /privacy/termstarget="_blank" 新窗打开)。注册成功 → router.push("/login") 跳登录页
  • 技术栈RSC + RegisterForm 客户端组件(modules/auth/components/register-form.tsx+ registerAction Server Actionmodules/auth/actions+ next-intluseTranslations("auth.register")+ shadcn UIButton / Input / Label / Checkbox / Select+ notify toast + lucide-react Loader2
  • ARCHITECTURE.md 契约§9.5 line 723 缺口新增B2/B5 末 N。portal-shell 当前无 /register 路由、无 auth 模块(仅有 api/auth/login + api/auth/logout 两个 Route Handler注册走 iam 后端但前端页面与 Server Action 完全缺失
  • 多角色复用:公开页(未登录用户),不涉及角色权限
  • 优先级B5 末

2.10 /privacy隐私政策

  • CICD 参考实现e:\desktop\CICD\src\app\(auth)\privacy\page.tsxRSC纯静态内容
  • 功能描述隐私政策页K12 教育场景7 个章节:引言 / 信息收集说明(账户 / 身份 / 未成年人保护 / 学习数据 / 设备)/ 信息使用说明 / 信息保护措施bcrypt + AES + RBAC + DataScope + HTTPS/ 用户权利(查询 / 更正 / 删除 / 撤回同意 / 可携带)/ Cookie 政策 / 未成年人保护条款(未满 14 周岁须监护人陪同注册)/ 联系方式。底部"返回注册"链接到 /register
  • 技术栈RSC + next/link + shadcn UICard / CardHeader / CardTitle / CardDescription / CardContent+ MetadataSEO+ 纯静态文案(无 DB / 无 Server Action / 无客户端交互)
  • ARCHITECTURE.md 契约§9.5 line 723 缺口新增B2/B5 末 N。portal-shell 当前无 /privacy 路由;属纯静态页,无后端契约依赖,仅需补页面 + i18n 文案
  • 多角色复用:公开页(未登录用户,注册页链接到它)
  • 优先级B5 末

2.11 /terms服务条款

  • CICD 参考实现e:\desktop\CICD\src\app\(auth)\terms\page.tsxRSC纯静态内容
  • 功能描述用户服务协议页7 个章节:引言 / 服务说明 / 用户注册(真实邮箱 + 未成年人监护人陪同 + 同意隐私政策与本协议)/ 用户行为规范 / 知识产权 / 免责声明AI 生成内容仅供参考,可能存在偏差)/ 服务变更中断与终止 / 法律适用与争议解决(中华人民共和国法律)。底部"返回注册"链接到 /register
  • 技术栈:同 §2.10RSC + next/link + shadcn Card + Metadata + 纯静态文案,无 DB / 无 Server Action
  • ARCHITECTURE.md 契约§9.5 line 723 缺口新增B2/B5 末 N。portal-shell 当前无 /terms 路由;纯静态页,无后端契约依赖
  • 多角色复用:公开页(未登录用户,注册页链接到它)
  • 优先级B5 末

2.12 /onboarding首次登录引导

  • CICD 参考实现
    • 页面:e:\desktop\CICD\src\app\(onboarding)\onboarding\page.tsxRSC + auth guard
    • 路由组 loadinge:\desktop\CICD\src\app\(onboarding)\loading.tsx(骨架屏,匹配 stepper 卡片布局)
    • 模块:e:\desktop\CICD\src\modules\onboarding\data-access / actions / schema / components / hooks / types
    • ⚠️ e:\desktop\CICD\src\app\api\onboarding\status\route.ts@deprecated(返回 410 Gone已迁移到 Server Action getOnboardingStatusAction,保留仅为兼容性指示)
    • 注:(onboarding) 路由组下有 loading.tsx(组根级骨架屏)+ onboarding/page.tsx(子目录页),并非"无 page.tsx"
  • 功能描述:首次登录引导页。getAuthContext() 鉴权(未登录捕获 PermissionDeniedErrorredirect("/login"))→ 读 DB users.onboardedAt 判断是否需要引导(getOnboardingStatusDB 为唯一真相源,废弃 session.user.onboarded 冗余检查)→ 不需要则 redirect("/dashboard") → 需要则渲染 OnboardingStepperSuspense 包裹,支持 useSearchParams URL query 持久化步骤)。按角色差异化步骤:
    • 通用:更新基础资料(姓名 / 电话 / 住址)
    • 学生:通过邀请码绑定班级(enrollStudentByInvitationCode
    • 教师:通过邀请码绑定任课科目(enrollTeacherByInvitationCodeP0-3 多选循环绑定,修复 UI 多选但服务端只取第一个的 bug
    • 家长:三因子验证绑定子女(邮箱 + 生日 YYYY-MM-DD + 手机后 4 位,组合空间 365×10000=3.65M 防枚举;支持多子女循环绑定 P1-4
  • 技术栈
    • RSC + getAuthContext()PermissionDeniedError → redirect /login+ Suspense + OnboardingStepper 客户端组件
    • data-accessmodules/onboarding/data-access.tsserver-onlygetOnboardingStatuscacheFn 300susers.onboardedAt + usersToRoles + roles/ getUserOnboardedAtP0-5 幂等检查)/ markUserOnboarded / updateUserProfile / bindParentToChild(三因子验证 + 幂等)
    • Server Actionsmodules/onboarding/actions.tsgetOnboardingStatusAction + completeOnboardingActionZod OnboardingSchema 校验 / P0-5 服务端幂等已完成直接返回成功 / P1-2 局部失败收集不回滚整事务 / P0-4 审计日志 onboarding.complete / audit-P1-8 家长绑定速率限制每小时 5 次防三因子枚举 / invalidateFor("onboarding.complete") 缓存失效)
    • 跨模块:modules/classes/data-accessenrollStudentByInvitationCode / enrollTeacherByInvitationCode,含校验)
    • 表:users.onboardedAt / usersToRoles / roles / parentStudentRelations
  • ARCHITECTURE.md 契约§9.5 line 723 缺口新增B2/B5 末 N。portal-shell 当前无 /onboarding 路由、无 onboarding 模块iam 子图已有 userProfile,但 onboarding 状态查询(onboardedAt)与家长绑定子女(parentStudentRelations)契约需补;班级邀请码绑定依赖 classes 服务
  • 多角色复用:所有新注册的已登录用户(按角色差异化步骤:学生绑定班级码 / 教师绑定任课科目 / 家长绑定子女);管理员一般预分配 onboarded跳过引导
  • 优先级B5 末

三、按角色复用矩阵

共享页面 教师 学生 家长 管理员 备注
/shell/notifications 全角色,复用 MESSAGE_READ 权限点
/shell/settings 全角色,resolveRoleSettingsConfig 注入角色卡片
/shell/settings/security 全角色,密码修改 + 安全提示
/shell/announcements ⚠️ 管理员走 /shell/admin/announcements 管理端
/shell/announcements/[id] ⚠️ 同上,管理员走管理端
/shell/messages 全角色私信;教师额外有群发入口
/shell/messages/compose 全角色可发起私信
/shell/messages/[id] 全角色可查看自己参与的消息线程
/shell/messages/group-compose ⚠️ 仅教师(按所教班级)+ 管理员
/shell/profile 全角色,底部概览按角色切换

四、契约就绪情况ARCHITECTURE.md §9.5

共享页 契约状态 后端子图 备注
/shell/notifications msg notifications(userId) 已就绪;形状待 P1-7 修正(平铺数组 vs {items,total}
/shell/settings iam / msg userProfile + notificationPreferences 已就绪
/shell/settings/security iam 密码修改走 iam2FA 走 iam
/shell/announcements content 列表契约缺失,需 content 子图补 announcements(userScope) 查询
/shell/announcements/[id] 🟡 content announcement(id) 存在,可见性语义需对齐
/shell/messages msg messages 查询契约完全缺失,需 msg 子图补 messages(userId) / messageThread(id)
/shell/messages/compose / [id] msg 同上,依赖 messages 契约
/shell/profile iam userProfile 已就绪;角色概览查询待补(学生/教师)

节奏原则§9.5):契约就绪页先行; 页用 MSW 先上 UI契约工单跟踪后端补齐后切换真实查询。


五、portal-shell 现状盘点(已具备 / 待补齐)

5.1 已具备(可复用)

资产 路径 说明
通知铃铛 widget widgets/topbar/notification-bell/index.tsx 顶栏下拉,调 useNotificationBell
通知列表 widget widgets/universal/notifications-widget/index.tsx 仪表盘内嵌,调 useNotifications
通知 GraphQL 文档 lib/api/operations/universal.graphql.ts GET_NOTIFICATIONS_LIST_DOC
通知 hook lib/api/universal.tsuseNotifications 形状待 P1-7 修正
顶栏通知 hook lib/api/topbar.tsuseNotificationBell 用于铃铛
403 页 app/shell/forbidden/page.tsx 已就绪
角色仪表盘分发 app/shell/page.tsx 已就绪(按角色 redirect

5.2 待补齐(缺失)

缺失项 影响
app/shell/notifications/page.tsx + error.tsx + loading.tsx 通知中心整页
app/shell/settings/page.tsx + error.tsx + loading.tsx 设置主页
app/shell/settings/security/page.tsx + error.tsx + loading.tsx 安全中心子页
app/shell/announcements/page.tsx + error.tsx + loading.tsx 公告列表
app/shell/announcements/[id]/page.tsx + error.tsx + loading.tsx 公告详情
app/shell/messages/page.tsx + error.tsx + loading.tsx 消息列表(含通知区)
app/shell/messages/compose/page.tsx + loading.tsx 撰写消息
app/shell/messages/[id]/page.tsx + loading.tsx 消息详情
app/shell/messages/group-compose/page.tsx + loading.tsx 教师群发
app/shell/profile/page.tsx + error.tsx + loading.tsx 个人资料
modules/notifications/ 整模块 通知 data-access / actions / dispatcher / channels / hooks
modules/messaging/ 整模块 消息 data-access / actions / components / hooks
modules/announcements/ 整模块(用户端) 公告 data-access / actions / components
modules/settings/ 整模块 设置 components / actions / config / lib
shared/lib/navigation.ts 入口登记 5 个共享路由均未登记到 NAVIGATION_ITEMS
shared/lib/route-permissions.ts 权限登记 5 个共享路由均未登记到 EXACT_ROUTE_PERMISSIONS / PREFIX_ROUTE_PERMISSIONS,按 fail-closed 规则会被默认拒绝
i18n keymessages/zh-CN.json / en.json notifications.* / settings.* / announcements.* / messages.* / settings.profile.* 文案键缺失

六、实施建议(按批次与优先级)

6.1 B1 优先(契约就绪,可直接接真实查询)

  1. /shell/notifications:复用现有 useNotifications hook新增 page + error + loading同步登记 EXACT_ROUTE_PERMISSIONSrequiredPermissions: ["MESSAGE_READ"],不限角色)+ navigation.ts(建议放在顶栏 / 通用区分组,但因 NAVIGATION_ITEMS 按角色 group需考虑通用入口的 group 归属——或只在顶栏铃铛加"查看全部"链接跳到 /shell/notifications
  2. /shell/settings + /shell/settings/security + /shell/profile三者强关联profile 复用 settings 模块的 AvatarUpload / 角色概览组件),建议一次性补齐;需新建 modules/settings/ 模块22+ 客户端组件 + Server Actions

6.2 B3契约缺失先用 MSW 上 UI

  1. /shell/announcements + /shell/announcements/[id]:列表契约缺失,需先用 MSW mock announcements(userScope) 查询;announcement(id) 已存在但可见性语义需对齐
  2. /shell/messages + 子页messages 契约完全缺失,需 msg 子图补 messages(userId) / messageThread(id) / sendMessage mutation 等;建议 B3 末实施

6.3 强制同步项(每次新增路由必做)

  • shared/lib/route-permissions.ts:新增 EXACT_ROUTE_PERMISSIONS 条目(否则 fail-closed 拒绝)
  • shared/lib/navigation.ts:如需侧边栏入口,新增 NAVIGATION_ITEMS 条目
  • i18nmessages/zh-CN.json + messages/en.json 补对应文案键
  • pnpm run arch:scan 更新 arch.db按 project_rules §1 强制)
  • 若架构设计意图变化,同步更新 ARCHITECTURE.md §9

七、关键风险与约束

  1. fail-closed 风险route-permissions.ts §5 规定 /shell/** 下未登记路由默认拒绝。若只加 page.tsx 不登记权限,访问会被 middleware 重定向到 /shell/forbidden
  2. 通知形状待修正notifications(userId) 后端返回平铺数组,前端按 {items, total} 解析P1-7 待修。B1 实施时需同步修正 universal.graphql.tsuniversal.ts 的类型,或后端补齐 {items, total} 包装。
  3. 消息撤回不乐观更新CICD 的 recallMessageAction 必须服务端校验 2 分钟窗口(MESSAGE_RECALL_WINDOW_MS = 2 * 60 * 1000),禁止 useOptimistic 乐观更新会导致超时撤回误成功。B3 实施时必须遵循此约束。
  4. 公告可见性越权P0-1getAnnouncementByIdForUser 必须在 data-access 层结合受众 + status 过滤,不可见时统一返回 404不暴露存在性。B3 实施时必须复刻此守卫。
  5. 消息收件人二次校验P0-1Server Action 必须调 isReceiverAllowed 校验 receiverId 在 sender 的 DataScope 内,防 UI 绕过越权。
  6. 群发 fan-out on write:群发时每条收件人写独立行,共享同一 groupMessageId,避免读时聚合。
  7. 多 AI 模块边界:按 project_rules §14.2,通知/消息/公告/设置分属不同模块,需协调 AI 分工;shared-proto 契约变更由协调 AI 负责。
  8. profile 归属 settings 模块CICD 中 profile 页复用 modules/settings/components/avatar-uploadprofile-{student,teacher}-overview,实施时不能跳过 settings 模块直接做 profile。

八、根路径重定向说明(隐含覆盖页)

路径 实现位置 行为 CICD 对照
/(根) apps/portal-shell/src/app/page.tsx redirect("/shell") → 再由 /shell catch-all 按角色 redirect 到 /shell/{role} CICD src/app/page.tsx 直接 redirect("/dashboard")(教师仪表盘,无角色分发层)

说明portal-shell 比 CICD 多一层"角色分发"——根 //shell/shell/{role},因为 portal-shell 是多角色统一入口(教师 / 学生 / 家长 / 管理员共用一个 app而 CICD 默认进入教师仪表盘。此页属"隐含覆盖"(§一 总览未单列),已在 §一 /shell 行标注 CICD 参考路径。portal-shell src/app/page.tsx 当前已就绪(redirect("/shell")portal-shell spec §8.2 路由前缀 /shell/*),无需补齐。