前端: 按 Web Interface Guidelines 全面重做

组件:
- Button: transition-all→明确属性列表, touch-action, aria-busy
- Input: name/spellcheck/inputmode/aria-invalid/aria-describedby, focus-visible ring,
  label 关联, 暴露 focus() 供错误定位, required 标记
- Modal: Escape 关闭 + focus trap + 焦点归还, overscroll-behavior: contain,
  aria-labelledby, body 滚动锁, touch-action
- 新增 Toast(aria-live polite) + pinia toast store

页面:
- ConsoleLayout: skip link, <main> 语义标签, aside/nav aria-label, aria-current,
  装饰 SVG aria-hidden, Intl 金额, translate=no 品牌/代码
- Dashboard: Intl.NumberFormat, 骨架屏 loading, aria-live 错误
- Keys: 字段级错误+焦点定位, toast 反馈(复制/吊销), name 属性
- Usage: select 加 label, 分页/筛选同步 URL(可深链), Intl 日期/金额, 表格 caption
- Login/Register: 字段级校验+错误定位, spellcheck=false, inputmode, autocomplete
- Landing: router-link 替换 href, aria-hidden, text-balance, scroll-mt
- index.html: theme-color + color-scheme

验证: playwright 22 项断言全过, WCAG AA 对比度 9 组全过, 零控制台错误
This commit is contained in:
Sakurasan
2026-08-15 13:32:18 +08:00
parent 5b67b66611
commit b25e9ec8a7
14 changed files with 646 additions and 185 deletions
+36 -23
View File
@@ -18,54 +18,67 @@ const protocols = [
<!-- 顶部导航 -->
<header class="sticky top-0 z-40 border-b border-ink-800 bg-ink-950/90 backdrop-blur">
<div class="mx-auto flex h-16 max-w-6xl items-center justify-between px-6">
<div class="flex items-center gap-2.5">
<span class="flex h-7 w-7 items-center justify-center rounded-md bg-signal-400">
<router-link to="/" class="flex items-center gap-2.5" aria-label="openteam 首页">
<span class="flex h-7 w-7 items-center justify-center rounded-md bg-signal-400" aria-hidden="true">
<svg width="14" height="14" viewBox="0 0 16 16" fill="none"><path d="M2 8h3l2-5 3 10 2-5h2" stroke="#0c0d0f" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/></svg>
</span>
<span class="text-[15px] font-semibold tracking-tight">openteam</span>
<span class="ml-1 rounded border border-ink-700 px-1.5 py-px font-mono text-[10px] text-paper-500">relay</span>
</div>
<nav class="flex items-center gap-2">
<a v-if="!auth.isAuthed" href="/login" class="rounded-md px-3 py-2 text-sm text-paper-500 transition-colors hover:text-paper-100">登录</a>
<a :href="primaryAction" class="rounded-md bg-signal-400 px-4 py-2 text-sm font-medium text-ink-950 transition-colors hover:bg-signal-300">开始使用</a>
<span class="text-[15px] font-semibold tracking-tight" translate="no">openteam</span>
<span class="ml-1 rounded border border-ink-700 px-1.5 py-px font-mono text-[10px] text-paper-500" translate="no">relay</span>
</router-link>
<nav class="flex items-center gap-2" aria-label="站内导航">
<router-link
v-if="!auth.isAuthed"
to="/login"
class="rounded-md px-3 py-2 text-sm text-paper-500 transition-colors hover:bg-ink-800 hover:text-paper-100 focus-visible:ring-2 focus-visible:ring-signal-400/60 focus:outline-none"
>登录</router-link>
<router-link
:to="primaryAction"
class="inline-flex h-9 touch-manipulation items-center rounded-md bg-signal-400 px-4 text-sm font-medium text-ink-950 transition-colors hover:bg-signal-300 focus-visible:ring-2 focus-visible:ring-signal-400/60 focus:outline-none"
>开始使用</router-link>
</nav>
</div>
</header>
<main>
<main id="landing-main">
<!-- Hero:左对齐,信号路径示意 -->
<section class="mx-auto grid max-w-6xl grid-cols-1 items-center gap-14 px-6 pt-16 pb-20 lg:grid-cols-[1.1fr_0.9fr]">
<div>
<p class="font-mono text-xs uppercase tracking-[0.2em] text-signal-400">self-hosted llm relay</p>
<h1 class="mt-4 text-4xl leading-[1.05] font-bold tracking-tight md:text-5xl">
<p class="font-mono text-xs uppercase tracking-[0.2em] text-signal-400" translate="no">self-hosted llm relay</p>
<h1 class="mt-4 text-4xl leading-[1.05] font-bold tracking-tight text-balance md:text-5xl">
一个 Key,<br />接入全部模型
</h1>
<p class="mt-5 max-w-[52ch] text-base leading-relaxed text-paper-500">
自托管 LLM API 中转网关。统一 OpenAI 与 Anthropic 协议入口,背后对接任意上游渠道,用量计费一目了然。
</p>
<div class="mt-8 flex items-center gap-3">
<a href="/register" class="inline-flex h-10 items-center rounded-md bg-signal-400 px-5 text-sm font-medium text-ink-950 transition-all hover:bg-signal-300 active:scale-[0.98]">立即开始</a>
<a href="#protocols" class="inline-flex h-10 items-center rounded-md border border-ink-600 px-5 text-sm text-paper-300 transition-colors hover:border-signal-400 hover:text-signal-300">查看端点</a>
<router-link
to="/register"
class="inline-flex h-10 touch-manipulation items-center rounded-md bg-signal-400 px-5 text-sm font-medium text-ink-950 transition-[background-color,transform] hover:bg-signal-300 active:scale-[0.98] focus-visible:ring-2 focus-visible:ring-signal-400/60 focus:outline-none"
>立即开始</router-link>
<a
href="#protocols"
class="inline-flex h-10 touch-manipulation items-center rounded-md border border-ink-600 px-5 text-sm text-paper-300 transition-colors hover:border-signal-400 hover:text-signal-300 focus-visible:ring-2 focus-visible:ring-signal-400/60 focus:outline-none"
>查看端点</a>
</div>
</div>
<!-- 信号路径:client → gateway → upstream -->
<div class="rounded-lg border border-ink-700 bg-ink-900 p-5 font-mono text-[13px]">
<div class="rounded-lg border border-ink-700 bg-ink-900 p-5 font-mono text-[13px]" aria-label="请求链路示意">
<div class="flex items-center gap-3 pb-4">
<span class="h-1.5 w-1.5 animate-pulse rounded-full bg-mint-400" />
<span class="text-xs text-paper-500">request path · live</span>
<span class="h-1.5 w-1.5 animate-pulse rounded-full bg-mint-400" aria-hidden="true" />
<span class="text-xs text-paper-500" translate="no">request path · live</span>
</div>
<div class="space-y-2">
<div class="rounded-md border border-ink-700 bg-ink-850 px-3 py-2 text-paper-300">client <span class="text-signal-400">──▶</span> <span class="text-paper-500">/v1/chat/completions</span></div>
<div class="rounded-md border border-ink-700 bg-ink-850 px-3 py-2 text-paper-300">gateway <span class="text-signal-400">──▶</span> <span class="text-paper-500">auth · quota · route</span></div>
<div class="rounded-md border border-ink-700 bg-ink-850 px-3 py-2 text-paper-300">upstream <span class="text-mint-400">◀──</span> <span class="text-paper-500">openai / anthropic</span></div>
<div class="rounded-md border border-ink-700 bg-ink-850 px-3 py-2 text-paper-300">billing <span class="text-mint-400">──▶</span> <span class="num text-paper-500">usage · cost · ledger</span></div>
<div class="rounded-md border border-ink-700 bg-ink-850 px-3 py-2 text-paper-300">client <span class="text-signal-400" aria-hidden="true">──▶</span> <span class="text-paper-500" translate="no">/v1/chat/completions</span></div>
<div class="rounded-md border border-ink-700 bg-ink-850 px-3 py-2 text-paper-300">gateway <span class="text-signal-400" aria-hidden="true">──▶</span> <span class="text-paper-500" translate="no">auth · quota · route</span></div>
<div class="rounded-md border border-ink-700 bg-ink-850 px-3 py-2 text-paper-300">upstream <span class="text-mint-400" aria-hidden="true">◀──</span> <span class="text-paper-500" translate="no">openai / anthropic</span></div>
<div class="rounded-md border border-ink-700 bg-ink-850 px-3 py-2 text-paper-300">billing <span class="text-mint-400" aria-hidden="true">──▶</span> <span class="num text-paper-500" translate="no">usage · cost · ledger</span></div>
</div>
</div>
</section>
<!-- 协议端点 -->
<section id="protocols" class="border-t border-ink-800 bg-ink-900/50">
<section id="protocols" class="scroll-mt-16 border-t border-ink-800 bg-ink-900/50">
<div class="mx-auto max-w-6xl px-6 py-16">
<h2 class="text-xl font-semibold tracking-tight">三套协议,一个入口</h2>
<p class="mt-2 max-w-[60ch] text-sm leading-relaxed text-paper-500">
@@ -73,7 +86,7 @@ const protocols = [
</p>
<div class="mt-8 grid grid-cols-1 gap-3 md:grid-cols-2">
<div v-for="p in protocols" :key="p.name" class="rounded-lg border border-ink-700 bg-ink-900 p-4">
<code class="font-mono text-[13px] text-signal-300">{{ p.name }}</code>
<code class="font-mono text-[13px] text-signal-300" translate="no">{{ p.name }}</code>
<p class="mt-1.5 text-[13px] text-paper-500">{{ p.desc }}</p>
</div>
</div>
@@ -84,7 +97,7 @@ const protocols = [
<footer class="border-t border-ink-800">
<div class="mx-auto flex max-w-6xl items-center justify-between px-6 py-6 text-xs text-paper-600">
<span>openteam · 自托管 LLM 中转网关</span>
<span class="font-mono">v0.1 · M1</span>
<span class="font-mono" translate="no">v0.1 · M1</span>
</div>
</footer>
</main>