feat(parent-portal): 完整实现 P4+P5+P6 家长端微前端

实现内容(仲裁裁决驱动,首次即最终方案):

P4 核心功能
- 认证:localStorage token 存储(F12)+ REST 登录(ISSUE-004)+ refreshAccessToken 竞态防护
- 子女切换:ChildSwitcher(Tab ≤3 / 下拉 ≥4)+ Zustand store(ISSUE-009 纯前端切换)
- 数据查询:urql GraphQL 消费 parent-bff(F9)+ TanStack Query 缓存
- 通知中心:NotificationFeed + 已读/全部已读 mutations
- 通知偏好:三维矩阵 + ISSUE-033 localStorage 降级
- 5 层状态管理:URL/Server/Client/Global UI/Form
- 跨标签同步:BroadcastChannel + storage 事件

P5 实时推送
- WebSocket 连接 push-gateway + 指数退避重连
- HTTP 轮询降级(60s)+ 实时通知 Hook

P6 硬化
- Web Vitals 上报 + OTel trace
- i18n 5 语言(zh-CN/en-US/zh-TW/ja-JP/ar-SA 含 RTL)
- PWA manifest + Service Worker
- CSP 安全头 + 权限点 F7 命名 + 设计令牌三层

测试与构建
- Vitest 92 测试全通过(utils/auth/child-store/ChildSwitcher/NotificationFeed/login)
- MSW mock 未就绪上游(parent-bff GraphQL + iam REST + iam GetChildrenByParent P0 阻塞用 fixtures)
- Dockerfile 多阶段构建(G1,端口 4002,HEALTHCHECK /api/health)
- typecheck + lint 零错误

经验沉淀
- known-issues.md §2.13 追加 12 条实现期经验(无 AI 身份标注)
- arch.db 已更新(15 TS 模块 / 482 符号 / 138 proto)

依据:02-architecture-design.md(回写总裁裁决)、coord-final-decisions.md、
president-final-rulings.md、parent-portal_workline.md、parent-portal_contract.md
This commit is contained in:
SpecialX
2026-07-10 17:40:27 +08:00
parent b54bfd101b
commit 5661938cc0
76 changed files with 9525 additions and 70 deletions

View File

@@ -0,0 +1,87 @@
// parent-portal next.config.js
// Module Federation Remote 角色Shell = teacher-portal :4000
// 当 NEXT_PUBLIC_MF_ENABLED=enabled 时启用 MF否则作为独立 Next.js 应用运行
//
// 依据:
// - 02-architecture-design.md §1.2 MF 配置
// - ARB-002 MF Shell 暴露清单
// - F10 MF 暴露粒度AppShell 整体)
// - F9 GraphQLurql
const NextFederationPlugin =
process.env.NEXT_PUBLIC_MF_ENABLED === "enabled"
? require("@module-federation/nextjs-mf")
: null;
const remotes = (isServer) => ({
teacher: `teacher_app@http://localhost:4000/_next/static/${isServer ? "ssr" : "chunks"}/remoteEntry.js`,
});
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
// ESM 兼容MF 2.0 需要)
experimental: {
externalDir: true,
},
// 安全头F-安全硬化,复用 Shell 配置)
async headers() {
return [
{
source: "/(.*)",
headers: [
{ key: "X-Frame-Options", value: "SAMEORIGIN" },
{ key: "X-Content-Type-Options", value: "nosniff" },
{ key: "Referrer-Policy", value: "strict-origin-when-cross-origin" },
{
key: "Permissions-Policy",
value: "camera=(), microphone=(), geolocation=()",
},
],
},
];
},
// API 代理:/api/v1/* → api-gateway
async rewrites() {
return [
{
source: "/api/v1/:path*",
destination: `${process.env.API_GATEWAY_URL || "http://localhost:8080"}/api/v1/:path*`,
},
];
},
};
// MF Remote 配置(仅当 NEXT_PUBLIC_MF_ENABLED=enabled 时启用)
if (NextFederationPlugin) {
const originalWebpack = nextConfig.webpack;
nextConfig.webpack = (config, options) => {
config.plugins.push(
new NextFederationPlugin({
name: "parent_app",
filename: "static/chunks/remoteEntry.js",
remotes: remotes(options.isServer),
exposes: {
"./pages": "./src/pages",
"./ChildSwitcher": "./src/components/ChildSwitcher",
},
shared: {
react: { singleton: true, requiredVersion: "^18.3.0" },
"react-dom": { singleton: true, requiredVersion: "^18.3.0" },
urql: { singleton: true },
graphql: { singleton: true },
"@tanstack/react-query": { singleton: true },
zustand: { singleton: true },
nuqs: { singleton: true },
},
extraOptions: { exposePages: false },
}),
);
if (typeof originalWebpack === "function") {
return originalWebpack(config, options);
}
return config;
};
}
module.exports = nextConfig;