# 穿山甲 · 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 的本地 `<script>/<link>` 注入
  `integrity`(sha384) + `crossorigin`。
- **CSP**：`public/_headers`（Cloudflare Pages）下发严格 CSP + 安全基线头。
  - 取舍：静态导出无 nonce 能力，Next 注入少量内联 bootstrap 脚本，故 `script-src` 保留
    `'unsafe-inline'`，以 SRI 补足自有 chunk 完整性；`style-src` 因设计稿大量内联样式同样需 `'unsafe-inline'`。
- **红线词扫描**（铁律 13）：`scripts/redline-scan.mjs` 扫 `app/ components/ lib/`，命中即非零退出（CI 红线）。
- 设计令牌 `colors_and_type.css` 原样链入（`public/`，单一真相源）；颜色全部走语义 token。

## 目录

```
app/            layout + page（静态外壳）
components/     UserCenter(外壳) + Login/Overview/Subscription/Redeem/Invite/Settings + icons/shared
lib/            i18n · theme(语言/主题 context) · api/(client 工厂 + mock + http + types + errors + session)
scripts/        add-sri.mjs · redline-scan.mjs
public/         colors_and_type.css(原样) · _headers(CSP)
```

## 部署（灾备口径同官网）

构建产物 `out/` 为纯静态，可在干净环境重放部署到任意新域名（Cloudflare Pages 多镜像）。
用户中心被打击时，客户端内同功能仍可用（doc/05 §1 灾备模型）。
