e2646346a6
新建 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 <noreply@anthropic.com>
35 lines
1.9 KiB
TypeScript
35 lines
1.9 KiB
TypeScript
// errors.ts — 错误码 → 双语文案映射表(阶段 B 落地)
|
|
import { ApiError } from './types';
|
|
import type { Lang } from '../i18n';
|
|
|
|
/**
|
|
* 后端统一返回 {code, message_zh, message_en};前端优先用后端文案,
|
|
* 兜底用本表(后端文案缺失/网络层错误时)。codes 与 #1 openapi 对齐。
|
|
*/
|
|
export const ERROR_TEXT: Record<string, { zh: string; en: string }> = {
|
|
invalid_credentials: { zh: '邮箱或密码错误', en: 'Wrong email or password' },
|
|
rate_limited: { zh: '操作过于频繁,请稍后再试', en: 'Too many requests, slow down' },
|
|
account_locked: { zh: '失败次数过多,账户已临时锁定', en: 'Too many attempts — account temporarily locked' },
|
|
totp_required: { zh: '需要双重认证动态码', en: 'Two-factor code required' },
|
|
totp_invalid: { zh: '动态码不正确,请重试', en: 'Invalid code, try again' },
|
|
code_invalid: { zh: '激活码无效', en: 'Invalid activation code' },
|
|
code_used: { zh: '激活码已被使用', en: 'Activation code already used' },
|
|
code_expired: { zh: '激活码已过期', en: 'Activation code expired' },
|
|
device_limit: { zh: '设备数量已达上限', en: 'Device limit reached' },
|
|
device_not_found: { zh: '设备不存在', en: 'Device not found' },
|
|
unauthorized: { zh: '登录已失效,请重新登录', en: 'Session expired, please log in again' },
|
|
network: { zh: '网络异常,请稍后重试', en: 'Network error, please retry' },
|
|
unknown: { zh: '操作失败,请稍后重试', en: 'Something went wrong, please retry' },
|
|
};
|
|
|
|
/** 把任意错误转成当前语言文案(后端文案优先,再查表,最后 unknown) */
|
|
export function bilingual(err: unknown, lang: Lang): string {
|
|
if (err instanceof ApiError) {
|
|
const fromServer = lang === 'zh' ? err.message_zh : err.message_en;
|
|
if (fromServer) return fromServer;
|
|
const m = ERROR_TEXT[err.code];
|
|
if (m) return m[lang];
|
|
}
|
|
return ERROR_TEXT.unknown[lang];
|
|
}
|