前端: 浅色模式 + 系统自动主题

- 引入语义化 CSS 变量令牌(bg/surface/edge/ink/muted/accent 等), data-theme 三态切换
- theme store(localStorage + 系统偏好跟随), 首屏内联脚本防主题闪烁
- ThemeToggle 组件(浅色/深色/跟随系统) 置于导航与控制台顶栏
- 全量替换硬编码 zinc/emerald/red 类为令牌, 图表基线/状态色随主题适配
- 浅色对比度校准(accent/err/warn 深浅各一套)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Sakurasan
2026-08-15 16:20:43 +08:00
co-authored by Claude
parent 4846db9293
commit a422034b2b
24 changed files with 380 additions and 249 deletions
+45 -43
View File
@@ -1,32 +1,34 @@
<script setup lang="ts">
import { useAuthStore } from '@/stores/auth'
import ThemeToggle from '@/components/ui/ThemeToggle.vue'
const auth = useAuthStore()
</script>
<template>
<div class="min-h-[100dvh] bg-zinc-950 text-zinc-100">
<div class="min-h-[100dvh] bg-bg text-ink">
<!-- 导航 -->
<header class="sticky top-0 z-40 border-b border-zinc-800/60 bg-zinc-950/80 backdrop-blur">
<header class="sticky top-0 z-40 border-b border-edge/60 bg-bg/80 backdrop-blur">
<div class="mx-auto flex h-14 max-w-6xl items-center justify-between px-4">
<div class="flex items-center gap-2">
<img src="/favicon.svg" alt="" class="size-5" />
<span class="text-sm font-semibold tracking-tight">openteam</span>
</div>
<nav class="flex items-center gap-2">
<ThemeToggle />
<router-link
v-if="!auth.isAuthed"
to="/login"
class="rounded-md px-3 py-1.5 text-sm text-zinc-400 transition hover:text-zinc-100"
class="rounded-md px-3 py-1.5 text-sm text-muted transition hover:text-ink"
>
登录
</router-link>
<router-link v-else to="/console/dashboard" class="rounded-md px-3 py-1.5 text-sm text-zinc-400 transition hover:text-zinc-100">
<router-link v-else to="/console/dashboard" class="rounded-md px-3 py-1.5 text-sm text-muted transition hover:text-ink">
控制台
</router-link>
<router-link
v-if="!auth.isAuthed"
to="/register"
class="rounded-md bg-accent px-3.5 py-1.5 text-sm font-medium text-zinc-950 transition hover:bg-accent-strong"
class="rounded-md bg-accent px-3.5 py-1.5 text-sm font-medium text-ink transition hover:bg-accent-strong"
>
免费注册
</router-link>
@@ -39,49 +41,49 @@ const auth = useAuthStore()
<div class="pointer-events-none absolute inset-x-0 top-0 h-px bg-gradient-to-r from-transparent via-emerald-500/40 to-transparent" />
<div class="mx-auto grid max-w-6xl items-center gap-10 px-4 pt-20 pb-16 lg:grid-cols-2 lg:pt-24">
<div class="max-w-xl">
<p class="mb-3 font-mono text-xs tracking-wide text-emerald-400/80">LLM API 中转网关</p>
<p class="mb-3 font-mono text-xs tracking-wide text-accent">LLM API 中转网关</p>
<h1 class="text-4xl leading-none font-semibold tracking-tight md:text-5xl">
一个 Key,调用所有主流模型
</h1>
<p class="mt-5 max-w-md text-base leading-relaxed text-zinc-400">
<p class="mt-5 max-w-md text-base leading-relaxed text-muted">
统一 OpenAI 与 Anthropic 协议入口,三套 API 自动互转,用量、计费与 API Key 管理开箱即用。
</p>
<div class="mt-8 flex items-center gap-3">
<router-link
to="/register"
class="inline-flex h-10 items-center rounded-md bg-accent px-5 text-sm font-medium text-zinc-950 transition hover:bg-accent-strong active:scale-[0.98]"
class="inline-flex h-10 items-center rounded-md bg-accent px-5 text-sm font-medium text-ink transition hover:bg-accent-strong active:scale-[0.98]"
>
免费注册
</router-link>
<router-link
to="/login"
class="inline-flex h-10 items-center rounded-md border border-zinc-700 px-5 text-sm text-zinc-200 transition hover:border-zinc-500"
class="inline-flex h-10 items-center rounded-md border border-edge2 px-5 text-sm text-ink transition hover:border-edge2"
>
登录
</router-link>
</div>
<p class="mt-5 font-mono text-xs text-zinc-600">不用换 SDK,改一行 base_url 即可接入</p>
<p class="mt-5 font-mono text-xs text-muted">不用换 SDK,改一行 base_url 即可接入</p>
</div>
<!-- 调用演示(真实格式,非伪截图) -->
<div class="card overflow-hidden font-mono text-xs">
<div class="flex items-center gap-1.5 border-b border-zinc-800 px-4 py-2.5">
<span class="size-2.5 rounded-full bg-zinc-700" />
<span class="size-2.5 rounded-full bg-zinc-700" />
<span class="ml-2 text-zinc-500">curl api.openteam.dev</span>
<div class="flex items-center gap-1.5 border-b border-edge px-4 py-2.5">
<span class="size-2.5 rounded-full bg-surface2" />
<span class="size-2.5 rounded-full bg-surface2" />
<span class="ml-2 text-muted">curl api.openteam.dev</span>
</div>
<div class="space-y-3 p-4 leading-relaxed">
<div>
<p class="text-zinc-400"><span class="text-emerald-400">$</span> curl https://api.openteam.dev/v1/chat/completions</p>
<p class="text-zinc-400"> -H <span class="text-emerald-300">"Authorization: Bearer sk-..."</span> \</p>
<p class="text-zinc-400"> -d <span class="text-zinc-300">'{"model": "claude-sonnet-5", "messages": [{"role": "user", "content": "你好"}]}'</span></p>
<p class="text-muted"><span class="text-accent">$</span> curl https://api.openteam.dev/v1/chat/completions</p>
<p class="text-muted"> -H <span class="text-accent">"Authorization: Bearer sk-..."</span> \</p>
<p class="text-muted"> -d <span class="text-ink">'{"model": "claude-sonnet-5", "messages": [{"role": "user", "content": "你好"}]}'</span></p>
</div>
<div class="border-t border-zinc-800 pt-3 text-zinc-500">
<p class="text-zinc-600"># OpenAI 格式请求,网关自动转 Anthropic 协议</p>
<p class="text-zinc-300">data: {"id":"resp_1","model":"claude-sonnet-5","choices":[{</p>
<p class="text-zinc-300">&nbsp;&nbsp;"delta":{"content":"你好,这是流式回复"}</p>
<p class="text-zinc-300">}]}</p>
<p class="text-zinc-300">data: [DONE]</p>
<div class="border-t border-edge pt-3 text-muted">
<p class="text-muted"># OpenAI 格式请求,网关自动转 Anthropic 协议</p>
<p class="text-ink">data: {"id":"resp_1","model":"claude-sonnet-5","choices":[{</p>
<p class="text-ink">&nbsp;&nbsp;"delta":{"content":"你好,这是流式回复"}</p>
<p class="text-ink">}]}</p>
<p class="text-ink">data: [DONE]</p>
</div>
</div>
</div>
@@ -89,57 +91,57 @@ const auth = useAuthStore()
</section>
<!-- 协议入口 -->
<section class="border-t border-zinc-800/60">
<section class="border-t border-edge/60">
<div class="mx-auto max-w-6xl px-4 py-16">
<h2 class="text-2xl font-semibold tracking-tight">三套协议,一个入口</h2>
<p class="mt-2 max-w-xl text-sm text-zinc-500">
<p class="mt-2 max-w-xl text-sm text-muted">
对客户端暴露统一的 OpenAI 兼容入口;客户端协议与上游渠道不匹配时自动转换,无需关心背后接的是哪家。
</p>
<div class="card mt-8 divide-y divide-zinc-800/70">
<div class="card mt-8 divide-y divide-edge/70">
<div v-for="p in [
{ path: 'POST /v1/chat/completions', desc: 'OpenAI Chat,兼容面最广,SDK 与工具链最全', tag: 'OpenAI' },
{ path: 'POST /v1/responses', desc: 'OpenAI Responses,新一代 SDK 与 Agents 首选', tag: 'OpenAI' },
{ path: 'POST /v1/messages', desc: 'Anthropic Messages,Claude 生态原生格式', tag: 'Anthropic' },
{ path: 'GET /v1/models', desc: 'OpenAI 风格模型列表', tag: 'List' },
]" :key="p.path" class="grid gap-1 px-5 py-3.5 sm:grid-cols-3 sm:items-center">
<code class="font-mono text-sm text-emerald-300">{{ p.path }}</code>
<p class="text-sm text-zinc-400 sm:col-span-1">{{ p.desc }}</p>
<span class="hidden justify-self-end font-mono text-[11px] text-zinc-600 sm:block">{{ p.tag }}</span>
<code class="font-mono text-sm text-accent">{{ p.path }}</code>
<p class="text-sm text-muted sm:col-span-1">{{ p.desc }}</p>
<span class="hidden justify-self-end font-mono text-[11px] text-muted sm:block">{{ p.tag }}</span>
</div>
</div>
</div>
</section>
<!-- 网关能力 -->
<section class="border-t border-zinc-800/60">
<section class="border-t border-edge/60">
<div class="mx-auto max-w-6xl px-4 py-16">
<h2 class="text-2xl font-semibold tracking-tight">网关替你处理的事</h2>
<div class="mt-8 grid gap-4 lg:grid-cols-3">
<div class="card p-5 lg:col-span-2">
<p class="font-mono text-xs text-emerald-400/80">responses → claude-sonnet-5</p>
<p class="font-mono text-xs text-accent">responses → claude-sonnet-5</p>
<h3 class="mt-2 text-lg font-semibold">协议自动转换</h3>
<p class="mt-2 text-sm leading-relaxed text-zinc-500">
<p class="mt-2 text-sm leading-relaxed text-muted">
客户端按 Responses 调用 Claude 模型,网关转成 Anthropic 协议打给上游,再以 Responses 事件流式返回。直通优先,能力无损时零转换。
</p>
</div>
<div class="card p-5">
<p class="font-mono text-xs text-zinc-600">usage · daily</p>
<p class="font-mono text-xs text-muted">usage · daily</p>
<h3 class="mt-2 text-lg font-semibold">用量与计费</h3>
<p class="mt-2 text-sm leading-relaxed text-zinc-500">
<p class="mt-2 text-sm leading-relaxed text-muted">
请求级 token 统计,按模型价格自动扣费,日粒度报表与余额流水可追溯。
</p>
</div>
<div class="card p-5">
<p class="font-mono text-xs text-zinc-600">sk-…</p>
<p class="font-mono text-xs text-muted">sk-…</p>
<h3 class="mt-2 text-lg font-semibold">API Key 管理</h3>
<p class="mt-2 text-sm leading-relaxed text-zinc-500">
<p class="mt-2 text-sm leading-relaxed text-muted">
密钥仅存哈希,支持配额、过期与模型白名单,创建时一次性展示。
</p>
</div>
<div class="card p-5 lg:col-span-2">
<p class="font-mono text-xs text-zinc-600">channels · lb · health</p>
<p class="font-mono text-xs text-muted">channels · lb · health</p>
<h3 class="mt-2 text-lg font-semibold">多渠道接入</h3>
<p class="mt-2 text-sm leading-relaxed text-zinc-500">
<p class="mt-2 text-sm leading-relaxed text-muted">
一个模型绑定多个上游渠道,按权重与健康状态选择,故障自动转移。管理员在后台一键接入新渠道、导入模型并定价。
</p>
</div>
@@ -148,21 +150,21 @@ const auth = useAuthStore()
</section>
<!-- CTA -->
<section class="border-t border-zinc-800/60">
<section class="border-t border-edge/60">
<div class="mx-auto max-w-6xl px-4 py-20 text-center">
<h2 class="text-2xl font-semibold tracking-tight">自托管,密钥在自己手里</h2>
<p class="mx-auto mt-3 max-w-md text-sm text-zinc-500">Docker Compose 一键部署,PostgreSQL 存账,渠道密钥加密存储。</p>
<p class="mx-auto mt-3 max-w-md text-sm text-muted">Docker Compose 一键部署,PostgreSQL 存账,渠道密钥加密存储。</p>
<router-link
to="/register"
class="mt-8 inline-flex h-10 items-center rounded-md bg-accent px-6 text-sm font-medium text-zinc-950 transition hover:bg-accent-strong active:scale-[0.98]"
class="mt-8 inline-flex h-10 items-center rounded-md bg-accent px-6 text-sm font-medium text-ink transition hover:bg-accent-strong active:scale-[0.98]"
>
开始使用
</router-link>
</div>
</section>
<footer class="border-t border-zinc-800/60">
<div class="mx-auto flex max-w-6xl items-center justify-between px-4 py-6 text-xs text-zinc-600">
<footer class="border-t border-edge/60">
<div class="mx-auto flex max-w-6xl items-center justify-between px-4 py-6 text-xs text-muted">
<div class="flex items-center gap-2">
<img src="/favicon.svg" alt="" class="size-4" />
<span>openteam · LLM API 中转站</span>