From e2646346a6078c027c5fdbc9d1650ce91c8c3273 Mon Sep 17 00:00:00 2001 From: wangjia <809946525@qq.com> Date: Sat, 13 Jun 2026 15:23:18 +0800 Subject: [PATCH] =?UTF-8?q?feat(web/usercenter):=20Next.js=20=E7=94=A8?= =?UTF-8?q?=E6=88=B7=E4=B8=AD=E5=BF=83=E9=9D=99=E6=80=81=E5=AF=BC=E5=87=BA?= =?UTF-8?q?=20+=20mock/http=20=E5=8F=8C=E6=95=B0=E6=8D=AE=E5=B1=82=20(tsk?= =?UTF-8?q?=5F3FIPC8lSnAfJ)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新建 web/usercenter/:Next.js App Router + output:'export' 纯静态导出, 直接复用 design/ui_kits/usercenter React 源码(概览/订阅/兑换/邀请/设置)。 阶段 A(mock,本提交): - 复刻五大页面,明/暗 × zh/en 四态;colors_and_type.css 原样链入; 顶栏主题切换 + 语言段控,移动端底部 Tab + 左右滑动切换。 - 设置页新增:偏好(语言/主题) + 设备管理(列表/移除/二次确认+刷新) + TOTP 2FA(绑定二维码占位+密钥/验证/解禁),登录二段式 TOTP。 - 数据层 lib/api:ApiClient 抽象 + MockClient/HttpClient 双实现,构建期 NEXT_PUBLIC_API_MODE 切换;统一错误体 {code,message_zh,message_en} → 双语映射。 - 会话:access token 仅内存,refresh header token + localStorage,静默续期, 登出失效(取舍:静态导出无服务端 cookie 能力,详见 README)。 - 安全:构建期注入 SRI(sha384);_headers 严格 CSP + 安全基线; 红线词扫描(铁律13) CI 红线,零命中。 阶段 B(占位待联调):HttpClient 已写好域名池+退避重试+401 续期; TOTP/登录二段式端点占位待 #1 契约增补;me/redeem/devices 切真实链路依赖 #2/#3/#4。 验证:npm run lint / redline / build 均通过,静态产物 out/ 无服务端依赖。 Co-Authored-By: Claude Opus 4.8 --- web/usercenter/.env.example | 6 + web/usercenter/.eslintrc.json | 3 + web/usercenter/.gitignore | 6 + web/usercenter/README.md | 78 + web/usercenter/app/globals.css | 35 + web/usercenter/app/layout.tsx | 28 + web/usercenter/app/page.tsx | 5 + web/usercenter/components/Invite.tsx | 68 + web/usercenter/components/Login.tsx | 169 + web/usercenter/components/Overview.tsx | 107 + web/usercenter/components/Redeem.tsx | 89 + web/usercenter/components/Settings.tsx | 335 ++ web/usercenter/components/Subscription.tsx | 108 + web/usercenter/components/UserCenter.tsx | 178 + web/usercenter/components/icons.tsx | 91 + web/usercenter/components/shared.tsx | 50 + web/usercenter/lib/api/client.ts | 18 + web/usercenter/lib/api/errors.ts | 34 + web/usercenter/lib/api/http.ts | 170 + web/usercenter/lib/api/mock.ts | 158 + web/usercenter/lib/api/session.ts | 55 + web/usercenter/lib/api/types.ts | 96 + web/usercenter/lib/i18n.ts | 143 + web/usercenter/lib/theme.tsx | 69 + web/usercenter/next.config.mjs | 12 + web/usercenter/package-lock.json | 5375 ++++++++++++++++++++ web/usercenter/package.json | 28 + web/usercenter/public/_headers | 11 + web/usercenter/public/colors_and_type.css | 218 + web/usercenter/scripts/add-sri.mjs | 98 + web/usercenter/scripts/redline-scan.mjs | 63 + web/usercenter/tsconfig.json | 21 + 32 files changed, 7925 insertions(+) create mode 100644 web/usercenter/.env.example create mode 100644 web/usercenter/.eslintrc.json create mode 100644 web/usercenter/.gitignore create mode 100644 web/usercenter/README.md create mode 100644 web/usercenter/app/globals.css create mode 100644 web/usercenter/app/layout.tsx create mode 100644 web/usercenter/app/page.tsx create mode 100644 web/usercenter/components/Invite.tsx create mode 100644 web/usercenter/components/Login.tsx create mode 100644 web/usercenter/components/Overview.tsx create mode 100644 web/usercenter/components/Redeem.tsx create mode 100644 web/usercenter/components/Settings.tsx create mode 100644 web/usercenter/components/Subscription.tsx create mode 100644 web/usercenter/components/UserCenter.tsx create mode 100644 web/usercenter/components/icons.tsx create mode 100644 web/usercenter/components/shared.tsx create mode 100644 web/usercenter/lib/api/client.ts create mode 100644 web/usercenter/lib/api/errors.ts create mode 100644 web/usercenter/lib/api/http.ts create mode 100644 web/usercenter/lib/api/mock.ts create mode 100644 web/usercenter/lib/api/session.ts create mode 100644 web/usercenter/lib/api/types.ts create mode 100644 web/usercenter/lib/i18n.ts create mode 100644 web/usercenter/lib/theme.tsx create mode 100644 web/usercenter/next.config.mjs create mode 100644 web/usercenter/package-lock.json create mode 100644 web/usercenter/package.json create mode 100644 web/usercenter/public/_headers create mode 100644 web/usercenter/public/colors_and_type.css create mode 100644 web/usercenter/scripts/add-sri.mjs create mode 100644 web/usercenter/scripts/redline-scan.mjs create mode 100644 web/usercenter/tsconfig.json diff --git a/web/usercenter/.env.example b/web/usercenter/.env.example new file mode 100644 index 0000000..f09b927 --- /dev/null +++ b/web/usercenter/.env.example @@ -0,0 +1,6 @@ +# 数据层开关:mock(演示数据,UI 验收)| http(真实后端,阶段 B) +NEXT_PUBLIC_API_MODE=mock + +# 阶段 B:API 域名池(逗号分隔,N>=3,互不关联)。HttpClient 按序退避重试。 +# 与 doc/05 §1「API 域名池」灾备模型一致;mock 模式下忽略。 +NEXT_PUBLIC_API_DOMAINS=https://api1.example.com,https://api2.example.com,https://api3.example.com diff --git a/web/usercenter/.eslintrc.json b/web/usercenter/.eslintrc.json new file mode 100644 index 0000000..bffb357 --- /dev/null +++ b/web/usercenter/.eslintrc.json @@ -0,0 +1,3 @@ +{ + "extends": "next/core-web-vitals" +} diff --git a/web/usercenter/.gitignore b/web/usercenter/.gitignore new file mode 100644 index 0000000..8f29978 --- /dev/null +++ b/web/usercenter/.gitignore @@ -0,0 +1,6 @@ +node_modules/ +.next/ +out/ +*.tsbuildinfo +next-env.d.ts +.env*.local diff --git a/web/usercenter/README.md b/web/usercenter/README.md new file mode 100644 index 0000000..6289137 --- /dev/null +++ b/web/usercenter/README.md @@ -0,0 +1,78 @@ +# 穿山甲 · Web 用户中心(Next.js 静态导出) + +`design/ui_kits/usercenter/` 设计稿的生产实现。Next.js App Router + `output: 'export'` +**纯静态导出**(无任何 Node 服务端能力),动态数据全走客户端 API,可任意静态托管直接跑。 + +## 页面 +概览 · 订阅导入 · 兑换 & 购买 · 邀请返利 · 设置(语言/主题 + 设备管理 + TOTP 2FA)。 +明/暗主题 × 中/英单显四态,移动端底部 Tab + 左右滑动切换。 + +## 运行 + +```bash +cp .env.example .env.local # 默认 NEXT_PUBLIC_API_MODE=mock +npm install +npm run dev # http://localhost:3000 +``` + +## 构建(静态导出 → out/) + +```bash +npm run redline # 铁律 13 红线词扫描(CI 红线) +npm run build # next build + 导出 + 注入 SRI +# 产物在 out/,可直接静态托管(Cloudflare Pages 多镜像,同官网管线) +``` + +`npm run check` = 红线扫描 + 构建。 + +## 数据层(mock / http 双实现) + +`lib/api/` 抽象 `ApiClient`,构建期由 `NEXT_PUBLIC_API_MODE` 切换实现: + +- **MockClient**(默认):演示数据,零网络依赖,供阶段 A 的 UI 验收。 + - 演示触发:密码 `wrong`→凭证错误,`locked`→账户锁定+倒计时;兑换码 `INVALID`/`USED`/`LOCKED`→对应错误;TOTP 码 `000000`→验证失败。 +- **HttpClient**(阶段 B):真实后端薄客户端 —— API 域名池故障转移 + 指数退避重试 + + 统一错误体 `{code, message_zh, message_en}` 解析 + 401 静默续期重放。 + 域名池由 `NEXT_PUBLIC_API_DOMAINS`(逗号分隔,N≥3)配置。 + +接口:`login / loginTotp(登录二段式)/ refresh / getMe / getSubscription / +resetSubscription / listDevices / removeDevice / redeem / totpSetup / totpVerify / +totpDisable / logout`。 + +> **契约增补(依赖 #1 openapi)**:v1 暂无 TOTP 端点,已在 `lib/api/http.ts` 占位 +> `POST /v1/me/totp/setup|verify|disable` + 登录二段式 `/v1/auth/login/totp`。增补合入前 +> 2FA 仅 mock 验收,不阻塞本任务。 + +## 会话与安全 + +- **access token 仅存内存**(`lib/api/session.ts`),不落盘,降低 XSS 持久化窃取面。 +- **refresh token 走 header token + localStorage**,静默续期(401 自动 refresh 重放一次)。 + - 取舍说明:doc/05 §2 给「HttpOnly+Secure cookie 或 header token」两选。纯静态导出**无服务端 + 设置 cookie 的能力**,故取 header token 方案,配合内存 access + 登出使后端 refresh 白名单失效收敛风险。 +- **登出**调用 `POST /v1/auth/logout` 使 refresh 失效,并清本地令牌。 +- **登录限流在服务端**;前端只做失败提示与锁定倒计时。 + +## 红线 / 合规 + +- **SRI**:构建期 `scripts/add-sri.mjs` 为导出 HTML 的本地 `