# 穿山甲 · 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 的本地 `