Files
sub2api-cn-relay-manager/docs/2026-06-03-FRONTEND-DESIGN-SYSTEM-RUNBOOK.md
phamnazage-jpg e804a830a0
Some checks failed
CI / Build & Test (push) Has been cancelled
CI / Lint (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
CI / Docker Build (push) Has been cancelled
CI / Release (push) Has been cancelled
docs(portal): record 2026-06-03 frontend visual upgrade + design system runbook
- EXECUTION_BOARD.md: new 2026-06-03 entry with full evidence trail
  (asset tests, browser smoke, screenshot list, conclusion=已闭环)
- 2026-06-03-FRONTEND-DESIGN-SYSTEM-RUNBOOK.md (new, 10KB):
  * file structure + design token quick reference
  * standard page skeleton + component API (stat-card, statusbar,
    Portal.icons, Portal.toast, Portal.copyText, Portal.theme,
    Portal.renderModernAdminNav)
  * test-contract string rules (70+ strings must remain in HTML)
  * common pitfalls (duplicate <!doctype>, duplicate const AdminCommon,
    stat-card ID drift, accidental script removal)
  * submission workflow + screenshot evidence commands
2026-06-03 09:11:18 +08:00

270 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 前端设计系统 Runbook
> 适用:`deploy/tksea-portal/` 下所有 HTML 静态页(含 public portal + 7 个 admin 页)
> 建立时间2026-06-03
> 来源:把原本「像 demo」的多页 HTML 升级为对齐宿主 sub2api Vue+Tailwind teal/slate 体系的 Linear/Vercel 信息建筑派设计系统。
## 1. 文件结构
```
deploy/tksea-portal/
├── portal.css # 真设计系统 (777 行) — 唯一新视觉真源
├── portal.js # window.Sub2ApiPortal — toast / icon / nav / theme / drawer
├── admin-common.css # 4KB legacy shim — 老类名映射到新 token
├── admin-common.js # 313 行原 nav 渲染契约(未动)
├── index.html # public portal (light)
├── admin/
│ ├── index.html # admin 入口
│ ├── logical-groups.html
│ ├── route-health.html
│ ├── accounts.html
│ ├── providers.html
│ └── batch-import.html # → /portal/admin-batch-import.html 1.5KB redirect
└── admin-batch-import.html # 旧地址实页(历史兼容)
```
**新页面 = `portal.css` + `portal.js` + 自己的 `<style>`(仅页内布局)**
**老页面 = `portal.css` + `admin-common.css`shim+ `admin-common.js` + `portal.js` + 自己的 `<style>`(仅页内布局)**
## 2. Design Token 速查
### 颜色
| Token | 值 | 用途 |
| --------------------------- | ---------------------------------------------- | ------------------------ |
| `--color-primary` | `#14b8a6` | teal 主色,对齐宿主 |
| `--color-primary-soft` | `rgba(20,184,166,.12)` | 主色背景层 |
| `--color-success` / `-soft` | `#22c55e` / `rgba(34,197,94,.1)` | healthy / live / success |
| `--color-warning` / `-soft` | `#f59e0b` / `rgba(245,158,11,.1)` | cooldown / warn |
| `--color-danger` / `-soft` | `#ef4444` / `rgba(239,68,68,.1)` | failing / dead / danger |
| `--color-neutral` / `-soft` | `#64748b` / `rgba(100,116,139,.1)` | disabled / neutral |
| `--text-strong` | `--slate-50` (dark) / `--slate-900` (light) | 标题 |
| `--text-default` | `--slate-200` / `--slate-700` | 正文 |
| `--text-muted` | `--slate-400` / `--slate-500` | 辅助 |
| `--bg-base` | `#0f172a` / `#f8fafc` | 背景 |
| `--bg-elev-1` | `#1e293b` / `#ffffff` | 卡片 1 |
| `--bg-elev-2` | `#334155` / `#f1f5f9` | 卡片 2 |
| `--border-subtle` | `rgba(148,163,184,.16)` / `rgba(15,23,42,.08)` | 边框 |
### 间距 (s-\*)
`--s-1`=4 · `--s-2`=8 · `--s-3`=12 · `--s-4`=16 · `--s-5`=24 · `--s-6`=32 · `--s-7`=48
### 字号
`--fs-xs`=12 · `--fs-sm`=13 · `--fs-md`=14 · `--fs-base`=15 · `--fs-lg`=17 · `--fs-xl`=20 · `--fs-2xl`=24 · `--fs-3xl`=32 · `--fs-display`=44
### 圆角
`--r-sm`=6 · `--r-md`=10 · `--r-lg`=14 · `--r-xl`=20 · `--r-full`=9999
## 3. 标准页面骨架
```html
<!doctype html>
<html lang="zh-CN" data-theme="dark">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>...</title>
<link rel="stylesheet" href="/portal/portal.css" />
<link rel="stylesheet" href="/portal/admin-common.css" />
<!-- 老页兼容用 -->
<style>
/* 仅本页独有布局。token 全部用 var(--...) */
</style>
</head>
<body>
<main class="shell fade-in">
<nav class="topnav" data-admin-nav data-admin-current="accounts"></nav>
<section class="page-hero">
<div>
<span class="page-hero__eyebrow">Section Name</span>
<h1>页面主标题</h1>
<p>副标题描述,用 token 控制字号行高。</p>
</div>
<div class="stack" style="gap: var(--s-3);">
<div class="stat-card">
<div class="stat-icon stat-icon-primary" id="m1"></div>
<div class="min-w-0">
<p class="stat-label">API Root</p>
<p class="stat-value" id="metric-api-root">-</p>
</div>
</div>
<!-- 更多 stat-card -->
</div>
</section>
<section class="layout">
<!-- 你的页内 grid -->
...
</section>
</main>
<script src="/portal/admin-common.js"></script>
<script src="/portal/portal.js"></script>
<script>
const AdminCommon = window.Sub2ApiAdminCommon;
const Portal = window.Sub2ApiPortal;
AdminCommon.renderAdminNav(
document.querySelector("[data-admin-nav]"),
"accounts",
);
Portal.renderModernAdminNav(
document.querySelector("[data-admin-nav]"),
"accounts",
);
Portal.icons.shield.mount("#m1");
Portal.icons.group.mount("#m2");
// ... 你的页逻辑
</script>
</body>
</html>
```
## 4. 组件 API
### 4.1 `<div class="stat-card">`
```html
<div class="stat-card">
<div
class="stat-icon stat-icon-{primary|info|success|warning|danger}"
id="unique-id"
></div>
<div class="min-w-0">
<p class="stat-label">label</p>
<p class="stat-value" id="metric-foo">0</p>
</div>
</div>
```
### 4.2 `<div class="statusbar">`
`.statusbar` 是统一的提示条;颜色用 `data-tone="success|warning|danger|note|neutral"` 或直接 class `tone-healthy|tone-cooldown|tone-failing|tone-disabled|tone-ready|tone-note|tone-warn`
### 4.3 Portal 图标
```js
Portal.icons.shield.mount("#icon-slot"); // 渲染 22px lucide stroke 1.75
Portal.icons.group.mount("#icon-slot", { size: 18 });
```
可用:`home / group / route / health / account / provider / import / check / x / alert / info / copy / edit / trash / plus / refresh / download / upload / eye / eyeoff / play / pause / search / filter / shield / activity / package / key / send / log-out / external-link / arrow-right / chevron-right`
### 4.4 Toast
```js
Portal.toast("保存成功", { tone: "success", duration: 3000 });
Portal.toast("拉取失败", { tone: "danger" });
```
### 4.5 Copy
```js
Portal.copyText(value, { success: "已复制", failure: "复制失败" });
```
### 4.6 Theme
```js
Portal.theme.set("dark" | "light" | "auto"); // 写入 localStorage + html[data-theme]
Portal.theme.get(); // 当前生效 theme
```
### 4.7 Modern Nav
```js
Portal.renderModernAdminNav(container, currentKey);
// 渲染带 SVG icon + 渐变 hover 的现代 nav并存于 AdminCommon.renderAdminNav 之上
```
## 5. 关键约束
1. **不要破坏测试契约字符串**`logical_group` / `shadow_host_id` / `matched_account_state` / `account_resolution` / `package_tier` / `visibility_scope` / `gpt-5.4` / `MiniMax-M2.7-highspeed` / `deepseek-chat` / `Batch Import Admin` / `route_status` / `cooldown_seconds` / `failover_threshold` / `Smoke Logical Group` / `Smoke Provider Account` / `smoke-admin` / `smoke-route-primary` 等 70+ 字符串必须在 HTML 中出现(前端门禁 grep 断言)。
2. **不要修改 `admin-common.js`**:保留 `window.Sub2ApiAdminCommon` 的全局 API 契约。`portal.js` 是叠加层,不是替代层。
3. **不要使用 emoji / 渐变紫 / generic stock photo**:用 SVG icon + token 调色板。
4. **不要写裸 `style="color:#xxx"`**:用 `var(--text-muted)` 等 token。
5. **JS 引用 DOM ID 时**:新增的 page-hero / stat-card ID 必须和 `getElementById()` 调用对应。修改 stat-card label 时同步检查 JS 里的引用。
## 6. 测试门禁
```bash
cd /home/long/project/sub2api-cn-relay-manager
bash scripts/test/test_tksea_portal_assets.sh # 70+ 字符串契约断言
bash scripts/test/verify_frontend_smoke.sh # chromium headless 渲染 7 页 + public
bash scripts/acceptance/verify_frontend_acceptance_matrix.sh # 总入口
```
修改任何 `deploy/tksea-portal/*.html` / `portal.css` / `portal.js` / `admin-common.css` 后必须三门都过。
## 7. 截图 evidence
```bash
# 启动本地 server端口 8765映射 /portal/* → deploy/tksea-portal/*
python3 /tmp/portal_server.py &
# 截图 8 个页面
mkdir -p /tmp/portal-screenshots
for url in \
"/portal/admin/index.html" \
"/portal/admin/logical-groups.html" \
"/portal/admin/route-health.html" \
"/portal/admin/accounts.html" \
"/portal/admin/providers.html" \
"/portal/admin-batch-import.html" \
"/portal/admin/batch-import.html" \
"/portal/index.html"; do
name=$(basename "$url" .html)
google-chrome --headless --disable-gpu --no-sandbox --hide-scrollbars \
--window-size=1440,2400 --virtual-time-budget=4000 \
--screenshot=/tmp/portal-screenshots/${name}.png \
"http://127.0.0.1:8765${url}"
done
```
## 8. 常见错误
- **重复 `<!doctype html>`**:原页有 inline `<link>` 紧跟 `<style>`,新 head 注入会拼接出两个 doctype。修复删掉第一个 doctype 到第二个 doctype 之间的内容。
- **`const AdminCommon` 重复声明**:批量处理时容易把 `renderAdminNav` 块和原 script 块拼接,两块都包含 `const AdminCommon = window.Sub2ApiAdminCommon;` → SyntaxError。修复保留一处。
- **stat-card ID 改名导致 `getElementById` 找不到**JS 引用 `metric-total` 而 DOM 是 `metric-account-count` → loadAccounts 抛错。修复:保持 ID 不变。
- **去掉 inline `<style>` 时连 `<script>` 也误删**:原页有 inline `<script>``<style>` 之后,被批量处理误删。修复:从 `git show HEAD:<file>` 提取原始 script 块插回。
## 9. 进阶:自定义页内布局
每个页都可以有自己的 `<style>` 块,**只放页内布局**grid / flex**不放 token**(颜色 / 间距 / 字号)。示例:
```css
/* accounts.html */
.layout {
display: grid;
grid-template-columns: 420px minmax(0, 1fr);
gap: var(--s-5);
}
.field-grid {
display: grid;
gap: 12px;
}
.field-grid.two {
grid-template-columns: 1fr 1fr;
}
.catalog {
display: grid;
gap: 12px;
max-height: 32rem;
overflow: auto;
}
```
## 10. 提交规范
- 设计系统变更:`portal.css` / `portal.js` / `admin-common.css` 单独一个 commitmessage `style(portal): <token/组件>`
- 页面改造:每个 page 一个 commitmessage `refactor(portal/<page>): <change>`
- 文档同步:`docs/EXECUTION_BOARD.md` + 本 runbook 一个 docs commit与代码 commit 分开
- 一次 push 前跑:`test_tksea_portal_assets` + `verify_frontend_smoke` + 8 张截图