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

11 KiB
Raw Blame History

前端设计系统 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.cssshim+ 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. 标准页面骨架

<!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">

<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 图标

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

Portal.toast("保存成功", { tone: "success", duration: 3000 });
Portal.toast("拉取失败", { tone: "danger" });

4.5 Copy

Portal.copyText(value, { success: "已复制", failure: "复制失败" });

4.6 Theme

Portal.theme.set("dark" | "light" | "auto"); // 写入 localStorage + html[data-theme]
Portal.theme.get(); // 当前生效 theme

4.7 Modern Nav

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. 测试门禁

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

# 启动本地 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(颜色 / 间距 / 字号)。示例:

/* 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 张截图