按 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
40 KiB
共享路由(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.tsx(catch-all,空路由 → redirect("/shell/{role}"))+ src/app/shell/layout.tsx(AppFrame 框架,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 |
个人资料(缺失) |
备注 1:portal-shell 现存
widgets/topbar/notification-bell(顶栏铃铛下拉)与widgets/universal/notifications-widget(仪表盘内嵌通知卡片),但无独立的通知中心整页。 备注 2:widgets/universal/announcements-widget已在 P1-2 下线(数据源迁移到adminDashboard聚合查询),无用户端公告列表/详情页。 备注 3:shared/lib/navigation.ts与shared/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.ts:getNotifications/markNotificationAsRead/markAllNotificationsAsRead/archiveNotification/getUnreadNotificationCount(均cacheFn包装,tag=notifications,TTL=60s)- Server Actions:
notifications/actions.ts(384 行,标记已读/归档/批量操作) - 客户端实时流:
use-notification-stream(SSE)+use-desktop-notifications(浏览器桌面通知) - 表:
message_notifications(站内通知本体)、notification_preferences(偏好)、notification_logs(发送日志)
- Server Component +
- ARCHITECTURE.md 契约:✅
notifications(userId)(§9.1 line 645,B1 共享)- portal-shell 已有
GET_NOTIFICATIONS_LIST_DOCGraphQL document(universal.graphql.ts),但形状待 P1-7 修正(后端返回平铺数组,前端按{items, total}解析) useNotificationshook 已就绪(lib/api/universal.ts),可直接复用
- portal-shell 已有
- 多角色复用:教师 / 学生 / 家长 / 管理员(全角色,权限点复用
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 Component(
requireAuth,不要求特定权限点)+SettingsServiceProvider(Context 注入SettingsService接口) - Server Actions(
actions-service.ts):updateProfileAction/updateNotificationPreferencesAction/actions-avatar.ts/actions-password.ts/actions-security.ts/actions-brand.ts/actions-system-settings.ts - 数据源:
getUserProfile(users 模块)+getNotificationPreferences(notifications 模块) - 角色配置:
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.ts(2FA)、security-utils.ts(密码强度校验)
- Server Component(
- ARCHITECTURE.md 契约:✅(§9.1 line 646,B1 共享,契约字段未细化)
- portal-shell 当前无 settings 模块、无
/shell/settings路由、无navigation.ts入口、无route-permissions.ts登记
- portal-shell 当前无 settings 模块、无
- 多角色复用:教师 / 学生 / 家长 / 管理员(全角色,按
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(不在共享范围)
- 用户端公告列表(非管理端),分页展示当前用户可见的公告(school / grade / class 三类,按受众
- 技术栈:
- Server Component +
requirePermission(ANNOUNCEMENT_READ)+AnnouncementsServiceProvider(Context) announcements/data-access.ts(603 行,server-only):getUserAnnouncementsPageData(userId, dataScope, page, pageSize)受众过滤 + 分页isAnnouncementVisibleToAudience纯函数(单测覆盖三类矩阵)- 表:
announcements+announcement_reads(已读回执独立表) - 缓存:
cacheFn+invalidateFor双路径失效(列表 + 详情)
- Server Component +
- ARCHITECTURE.md 契约:❌(§9.2 line 668,"列表"契约未就绪,B3)
- portal-shell 的
announcements-widget已下线(P1-2),数据源迁到adminDashboard聚合,用户端列表契约完全缺失
- portal-shell 的
- 多角色复用:教师 / 学生 / 家长(查看自己受众范围内的公告);管理员走
/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
- Server Component +
- ARCHITECTURE.md 契约:🟡
announcement(id)已存在但形状/可见性语义待对齐(§9.2 line 668,B3) - 多角色复用:教师 / 学生 / 家长
- 优先级: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)
- CICD 把"消息列表"和"通知列表"合并展示在同一页(消息在上、通知在下,各自独立
- 技术栈:
- Server Component +
requirePermission(MESSAGE_READ)+ 双Suspense流式加载 messaging/data-access.ts(barrel,拆分为 core/bulk/group/templates/reports 5 子文件)- 表:
messages/message_drafts/message_templates/message_reports/user_blocks - Server Actions:
sendMessageAction/recallMessageAction(2 分钟窗口服务端校验,不乐观更新)/sendGroupMessageAction(fan-out on write)/reportMessageAction/blockUserAction - 乐观更新:
useOptimistic仅用于星标切换 - 收件人二次校验(P0-1):
isReceiverAllowed防 UI 绕过越权 - 通知集成:消息发送后调
notifications/dispatcher.sendNotification多通道分发(in-app/sms/email/wechat,尊重偏好)
- Server Component +
- ARCHITECTURE.md 契约:❌(§9.2 line 669,B3 末,契约完全缺失)
- portal-shell 当前无
messaging模块、无/shell/messages路由 - msg 子图仅暴露
notifications(userId),未暴露 messages 相关查询
- portal-shell 当前无
- 多角色复用:教师(可群发)/ 学生 / 家长 / 管理员(全角色私信,权限点
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.tsxe:\desktop\CICD\src\app\(dashboard)\messages\[id]\page.tsx+loading.tsxe:\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 - UI:
PageHeader+Card+Badge+Button+lucide-react图标
- Server Component +
- ARCHITECTURE.md 契约:🟡 未在 §9 显式列出独立
/shell/profile行,但 §9.5 计数 ~7 页共享 + CICD 有profile/page.tsx,推断属于共享路由隐含项- portal-shell 当前无
/shell/profile路由、无 settings 模块(profile 组件归属 settings 模块)
- portal-shell 当前无
- 多角色复用:教师 / 学生 / 家长 / 管理员(全角色,按
resolveProfileOverviewType切换底部概览) - 优先级:B1/B3(跟随 settings,建议 B1 一并实现)
2.9 /register(注册页)
- CICD 参考实现:
e:\desktop\CICD\src\app\(auth)\register\page.tsx(RSC,13 行,渲染<RegisterForm registerAction={registerAction} />);布局e:\desktop\CICD\src\app\(auth)\layout.tsx注入品牌配置(getBrandConfig(),失败回退默认) - 功能描述:账号注册页。表单字段:姓名 / 邮箱 / 密码 / 出生年月日 / 邀请码(可选,提供时覆盖默认 student 角色)。未成年人保护:根据
birthDate计算age,< 18触发监护人信息区(监护人姓名 / 电话 / 关系下拉)+ 监护人同意复选框。必勾"同意《隐私政策》与《用户协议》"复选框(链接到/privacy、/terms,target="_blank"新窗打开)。注册成功 →router.push("/login")跳登录页 - 技术栈:RSC +
RegisterForm客户端组件(modules/auth/components/register-form.tsx)+registerActionServer Action(modules/auth/actions)+next-intl(useTranslations("auth.register"))+ shadcn UI(Button / Input / Label / Checkbox / Select)+notifytoast +lucide-reactLoader2 - 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.tsx(RSC,纯静态内容) - 功能描述:隐私政策页(K12 教育场景),7 个章节:引言 / 信息收集说明(账户 / 身份 / 未成年人保护 / 学习数据 / 设备)/ 信息使用说明 / 信息保护措施(bcrypt + AES + RBAC + DataScope + HTTPS)/ 用户权利(查询 / 更正 / 删除 / 撤回同意 / 可携带)/ Cookie 政策 / 未成年人保护条款(未满 14 周岁须监护人陪同注册)/ 联系方式。底部"返回注册"链接到
/register - 技术栈:RSC +
next/link+ shadcn UI(Card / CardHeader / CardTitle / CardDescription / CardContent)+Metadata(SEO)+ 纯静态文案(无 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.tsx(RSC,纯静态内容) - 功能描述:用户服务协议页,7 个章节:引言 / 服务说明 / 用户注册(真实邮箱 + 未成年人监护人陪同 + 同意隐私政策与本协议)/ 用户行为规范 / 知识产权 / 免责声明(AI 生成内容仅供参考,可能存在偏差)/ 服务变更中断与终止 / 法律适用与争议解决(中华人民共和国法律)。底部"返回注册"链接到
/register - 技术栈:同 §2.10(RSC + 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.tsx(RSC + auth guard) - 路由组 loading:
e:\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 ActiongetOnboardingStatusAction,保留仅为兼容性指示) - 注:
(onboarding)路由组下有loading.tsx(组根级骨架屏)+onboarding/page.tsx(子目录页),并非"无 page.tsx"
- 页面:
- 功能描述:首次登录引导页。
getAuthContext()鉴权(未登录捕获PermissionDeniedError→redirect("/login"))→ 读 DBusers.onboardedAt判断是否需要引导(getOnboardingStatus,DB 为唯一真相源,废弃session.user.onboarded冗余检查)→ 不需要则redirect("/dashboard")→ 需要则渲染OnboardingStepper(Suspense包裹,支持useSearchParamsURL query 持久化步骤)。按角色差异化步骤:- 通用:更新基础资料(姓名 / 电话 / 住址)
- 学生:通过邀请码绑定班级(
enrollStudentByInvitationCode) - 教师:通过邀请码绑定任课科目(
enrollTeacherByInvitationCode,P0-3 多选循环绑定,修复 UI 多选但服务端只取第一个的 bug) - 家长:三因子验证绑定子女(邮箱 + 生日 YYYY-MM-DD + 手机后 4 位,组合空间 365×10000=3.65M 防枚举;支持多子女循环绑定 P1-4)
- 技术栈:
- RSC +
getAuthContext()(PermissionDeniedError→ redirect /login)+Suspense+OnboardingStepper客户端组件 - data-access(
modules/onboarding/data-access.ts,server-only):getOnboardingStatus(cacheFn300s,读users.onboardedAt+usersToRoles+roles)/getUserOnboardedAt(P0-5 幂等检查)/markUserOnboarded/updateUserProfile/bindParentToChild(三因子验证 + 幂等) - Server Actions(
modules/onboarding/actions.ts):getOnboardingStatusAction+completeOnboardingAction(ZodOnboardingSchema校验 / P0-5 服务端幂等已完成直接返回成功 / P1-2 局部失败收集不回滚整事务 / P0-4 审计日志onboarding.complete/ audit-P1-8 家长绑定速率限制每小时 5 次防三因子枚举 /invalidateFor("onboarding.complete")缓存失效) - 跨模块:
modules/classes/data-access(enrollStudentByInvitationCode/enrollTeacherByInvitationCode,含校验) - 表:
users.onboardedAt/usersToRoles/roles/parentStudentRelations
- RSC +
- 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 | 密码修改走 iam,2FA 走 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.ts → useNotifications |
形状待 P1-7 修正 |
| 顶栏通知 hook | lib/api/topbar.ts → useNotificationBell |
用于铃铛 |
| 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 key(messages/zh-CN.json / en.json) |
notifications.* / settings.* / announcements.* / messages.* / settings.profile.* 文案键缺失 |
六、实施建议(按批次与优先级)
6.1 B1 优先(契约就绪,可直接接真实查询)
/shell/notifications:复用现有useNotificationshook,新增 page + error + loading;同步登记EXACT_ROUTE_PERMISSIONS(requiredPermissions: ["MESSAGE_READ"],不限角色)+navigation.ts(建议放在顶栏 / 通用区分组,但因NAVIGATION_ITEMS按角色 group,需考虑通用入口的 group 归属——或只在顶栏铃铛加"查看全部"链接跳到/shell/notifications)/shell/settings+/shell/settings/security+/shell/profile:三者强关联(profile 复用 settings 模块的AvatarUpload/ 角色概览组件),建议一次性补齐;需新建modules/settings/模块(22+ 客户端组件 + Server Actions)
6.2 B3(契约缺失,先用 MSW 上 UI)
/shell/announcements+/shell/announcements/[id]:列表契约缺失,需先用 MSW mockannouncements(userScope)查询;announcement(id)已存在但可见性语义需对齐/shell/messages+ 子页:messages 契约完全缺失,需 msg 子图补messages(userId)/messageThread(id)/sendMessagemutation 等;建议 B3 末实施
6.3 强制同步项(每次新增路由必做)
shared/lib/route-permissions.ts:新增EXACT_ROUTE_PERMISSIONS条目(否则 fail-closed 拒绝)shared/lib/navigation.ts:如需侧边栏入口,新增NAVIGATION_ITEMS条目- i18n:
messages/zh-CN.json+messages/en.json补对应文案键 pnpm run arch:scan更新 arch.db(按 project_rules §1 强制)- 若架构设计意图变化,同步更新
ARCHITECTURE.md§9
七、关键风险与约束
- fail-closed 风险:
route-permissions.ts§5 规定/shell/**下未登记路由默认拒绝。若只加 page.tsx 不登记权限,访问会被 middleware 重定向到/shell/forbidden。 - 通知形状待修正:
notifications(userId)后端返回平铺数组,前端按{items, total}解析(P1-7 待修)。B1 实施时需同步修正universal.graphql.ts与universal.ts的类型,或后端补齐{items, total}包装。 - 消息撤回不乐观更新:CICD 的
recallMessageAction必须服务端校验 2 分钟窗口(MESSAGE_RECALL_WINDOW_MS = 2 * 60 * 1000),禁止useOptimistic乐观更新(会导致超时撤回误成功)。B3 实施时必须遵循此约束。 - 公告可见性越权(P0-1):
getAnnouncementByIdForUser必须在 data-access 层结合受众 + status 过滤,不可见时统一返回 404(不暴露存在性)。B3 实施时必须复刻此守卫。 - 消息收件人二次校验(P0-1):Server Action 必须调
isReceiverAllowed校验receiverId在 sender 的DataScope内,防 UI 绕过越权。 - 群发 fan-out on write:群发时每条收件人写独立行,共享同一
groupMessageId,避免读时聚合。 - 多 AI 模块边界:按 project_rules §14.2,通知/消息/公告/设置分属不同模块,需协调 AI 分工;
shared-proto契约变更由协调 AI 负责。 - profile 归属 settings 模块:CICD 中
profile页复用modules/settings/components/avatar-upload与profile-{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-shellsrc/app/page.tsx当前已就绪(redirect("/shell"),portal-shell spec §8.2 路由前缀/shell/*),无需补齐。