后端 (Go/Gin/GORM): - 配置(viper+env)、SQLite/Postgres 迁移、argon2id、AES-GCM 渠道密钥、JWT+refresh cookie - 用户注册/登录/刷新/登出、API Key CRUD(仅存哈希、明文一次展示) - 代理网关: /v1/chat/completions、/v1/responses、/v1/models 直通 OpenAI 渠道 非流式+流式(SSE 零缓冲转发), 用量捕获(chat 末块/responses completed 嵌套), OpenAI 错误格式(401/402/404/502), 余额检查 - 异步批量记账 + 余额流水 + 日聚合, admin 用户/余额/配置 API - 单测: crypto/jwt/apikey/流式 usage 提取 前端 (Vue3+TS+Vite+Tailwind v4): - taste-skill 设计 tokens: 深色仪表盘, 石墨+信号铜色, Outfit+JetBrains Mono - Landing/登录/注册, 控制台(仪表盘图表/密钥管理/用量明细) - 基础组件 Button/Input/Badge/Modal, ECharts 用量图 部署: docker-compose(nginx+api+postgres), 双 Dockerfile, nginx SSE 反代 联调: scripts/mockupstream 本地 mock 上游, 端到端验证通过
62 lines
2.4 KiB
Vue
62 lines
2.4 KiB
Vue
<script setup lang="ts">
|
||
import { ref } from 'vue'
|
||
import { useRoute, useRouter } from 'vue-router'
|
||
import { useAuthStore } from '../stores/auth'
|
||
import Input from '../components/ui/Input.vue'
|
||
import Button from '../components/ui/Button.vue'
|
||
|
||
const auth = useAuthStore()
|
||
const router = useRouter()
|
||
const route = useRoute()
|
||
|
||
const username = ref('')
|
||
const password = ref('')
|
||
const error = ref('')
|
||
const loading = ref(false)
|
||
|
||
async function submit() {
|
||
error.value = ''
|
||
loading.value = true
|
||
try {
|
||
await auth.login(username.value, password.value)
|
||
const redirect = (route.query.redirect as string) || '/console/dashboard'
|
||
router.push(redirect)
|
||
} catch (e: any) {
|
||
error.value = e.response?.data?.error?.message || '登录失败,请检查用户名与密码'
|
||
} finally {
|
||
loading.value = false
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<template>
|
||
<div class="flex min-h-[100dvh] items-center justify-center bg-ink-950 px-4">
|
||
<div class="w-full max-w-sm">
|
||
<div class="mb-8 flex items-center gap-2.5">
|
||
<span class="flex h-7 w-7 items-center justify-center rounded-md bg-signal-400">
|
||
<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>
|
||
</div>
|
||
|
||
<h1 class="text-xl font-semibold tracking-tight">登录控制台</h1>
|
||
<p class="mt-1 text-sm text-paper-500">管理密钥、查看用量与余额</p>
|
||
|
||
<form class="mt-8 space-y-4" @submit.prevent="submit">
|
||
<Input v-model="username" label="用户名或邮箱" placeholder="alice" autocomplete="username" />
|
||
<Input v-model="password" label="密码" type="password" placeholder="••••••••" autocomplete="current-password" />
|
||
<p v-if="error" class="rounded-md border border-ember-500/40 bg-ember-500/10 px-3 py-2 text-[13px] text-ember-300">{{ error }}</p>
|
||
<Button type="submit" class="w-full" :loading="loading">登录</Button>
|
||
</form>
|
||
|
||
<p class="mt-6 text-center text-[13px] text-paper-500">
|
||
还没有账号?
|
||
<router-link to="/register" class="text-signal-300 hover:text-signal-200">注册</router-link>
|
||
</p>
|
||
<p class="mt-4 text-center">
|
||
<router-link to="/" class="text-xs text-paper-600 hover:text-paper-500">← 返回首页</router-link>
|
||
</p>
|
||
</div>
|
||
</div>
|
||
</template>
|