Files
openteam/web/src/views/LoginView.vue
T
Sakurasan 360c6b33a6 M0+M1: 基建 + 用户/密钥/核心代理
后端 (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 上游, 端到端验证通过
2026-08-15 13:10:47 +08:00

62 lines
2.4 KiB
Vue
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.
<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>