feat: 前端样式调整
This commit is contained in:
@@ -24,22 +24,6 @@ const isLoggedIn = computed(() => !!authKey.value);
|
||||
<style>
|
||||
#app-root {
|
||||
width: 100%;
|
||||
/* height: 100vh; */
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.app-transition-enter-active,
|
||||
.app-transition-leave-active {
|
||||
transition: all 0.4s ease;
|
||||
}
|
||||
|
||||
.app-transition-enter-from {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
}
|
||||
|
||||
.app-transition-leave-to {
|
||||
opacity: 0;
|
||||
transform: translateY(-20px);
|
||||
}
|
||||
</style>
|
||||
|
@@ -143,8 +143,8 @@ body {
|
||||
|
||||
/* 美化滚动条 */
|
||||
::-webkit-scrollbar {
|
||||
width: 2px;
|
||||
height: 2px;
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
|
@@ -58,7 +58,7 @@ onMounted(() => {
|
||||
<n-card
|
||||
:bordered="false"
|
||||
class="stat-card"
|
||||
:style="{ animationDelay: `${index * 0.07}s` }"
|
||||
:style="{ animationDelay: `${index * 0.05}s` }"
|
||||
>
|
||||
<div class="stat-header">
|
||||
<div class="stat-icon" :style="{ background: stat.color }">
|
||||
|
@@ -121,11 +121,9 @@ import NavBar from "@/components/NavBar.vue";
|
||||
}
|
||||
|
||||
.content-wrapper {
|
||||
max-width: 1400px;
|
||||
width: 1400px;
|
||||
margin: 0 auto;
|
||||
padding: 24px 12px;
|
||||
height: calc(100vh - 64px);
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
|
@@ -17,7 +17,7 @@ import {
|
||||
import { onMounted, ref, watch } from "vue";
|
||||
|
||||
interface Props {
|
||||
group: Group;
|
||||
group: Group | null;
|
||||
}
|
||||
|
||||
const props = defineProps<Props>();
|
||||
@@ -38,6 +38,11 @@ watch(
|
||||
);
|
||||
|
||||
async function loadStats() {
|
||||
if (!props.group) {
|
||||
stats.value = null;
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
loading.value = true;
|
||||
stats.value = await keysApi.getGroupStats(props.group.id);
|
||||
@@ -69,6 +74,17 @@ function formatNumber(num: number): string {
|
||||
function formatPercentage(num: number): string {
|
||||
return `${num.toFixed(1)}%`;
|
||||
}
|
||||
|
||||
function copyUrl(url: string) {
|
||||
navigator.clipboard
|
||||
.writeText(url)
|
||||
.then(() => {
|
||||
window.$message.success("地址已复制到剪贴板");
|
||||
})
|
||||
.catch(() => {
|
||||
window.$message.error("复制失败");
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -78,7 +94,14 @@ function formatPercentage(num: number): string {
|
||||
<div class="card-header">
|
||||
<div class="header-left">
|
||||
<h3 class="group-title">
|
||||
{{ group.display_name || group.name }}
|
||||
{{ group?.display_name || group?.name || "请选择分组" }}
|
||||
<code
|
||||
v-if="group"
|
||||
class="group-url"
|
||||
@click="copyUrl(`https://gpt-load.com/${group?.name}`)"
|
||||
>
|
||||
https://gpt-load.com/{{ group?.name }}
|
||||
</code>
|
||||
</h3>
|
||||
</div>
|
||||
<div class="header-actions">
|
||||
@@ -114,33 +137,36 @@ function formatPercentage(num: number): string {
|
||||
<!-- 统计摘要区 -->
|
||||
<div class="stats-summary">
|
||||
<n-spin :show="loading" size="small">
|
||||
<n-grid v-if="stats" :cols="5" :x-gap="12" :y-gap="12" responsive="screen">
|
||||
<n-grid :cols="5" :x-gap="12" :y-gap="12" responsive="screen">
|
||||
<n-grid-item span="1">
|
||||
<n-card :title="`${stats.active_keys} / ${stats.total_keys}`" size="large">
|
||||
<n-card
|
||||
:title="`${stats?.active_keys || 0} / ${stats?.total_keys || 0}`"
|
||||
size="large"
|
||||
>
|
||||
<template #header-extra><span class="status-title">密钥数量</span></template>
|
||||
</n-card>
|
||||
</n-grid-item>
|
||||
<n-grid-item span="1">
|
||||
<n-card
|
||||
class="status-card-failure"
|
||||
:title="formatPercentage(stats.failure_rate_24h)"
|
||||
:title="formatPercentage(stats?.failure_rate_24h || 0)"
|
||||
size="large"
|
||||
>
|
||||
<template #header-extra><span class="status-title">失败率</span></template>
|
||||
</n-card>
|
||||
</n-grid-item>
|
||||
<n-grid-item span="1">
|
||||
<n-card :title="formatNumber(stats.requests_1h)" size="large">
|
||||
<n-card :title="formatNumber(stats?.requests_1h || 0)" size="large">
|
||||
<template #header-extra><span class="status-title">近1小时</span></template>
|
||||
</n-card>
|
||||
</n-grid-item>
|
||||
<n-grid-item span="1">
|
||||
<n-card :title="formatNumber(stats.requests_24h)" size="large">
|
||||
<n-card :title="formatNumber(stats?.requests_24h || 0)" size="large">
|
||||
<template #header-extra><span class="status-title">近24小时</span></template>
|
||||
</n-card>
|
||||
</n-grid-item>
|
||||
<n-grid-item span="1">
|
||||
<n-card :title="formatNumber(stats.requests_7d)" size="large">
|
||||
<n-card :title="formatNumber(stats?.requests_7d || 0)" size="large">
|
||||
<template #header-extra><span class="status-title">近7天</span></template>
|
||||
</n-card>
|
||||
</n-grid-item>
|
||||
@@ -156,13 +182,15 @@ function formatPercentage(num: number): string {
|
||||
<div class="detail-section">
|
||||
<h4 class="section-title">基础信息</h4>
|
||||
<n-descriptions :column="2" size="small">
|
||||
<n-descriptions-item label="分组名称">{{ group.name }}</n-descriptions-item>
|
||||
<n-descriptions-item label="渠道类型">
|
||||
{{ group.channel_type }}
|
||||
<n-descriptions-item label="分组名称">
|
||||
{{ group?.name || "-" }}
|
||||
</n-descriptions-item>
|
||||
<n-descriptions-item label="排序">{{ group.sort }}</n-descriptions-item>
|
||||
<n-descriptions-item v-if="group.description" label="描述" :span="2">
|
||||
{{ group.description }}
|
||||
<n-descriptions-item label="渠道类型">
|
||||
{{ group?.channel_type || "openai" }}
|
||||
</n-descriptions-item>
|
||||
<n-descriptions-item label="排序">{{ group?.sort || 0 }}</n-descriptions-item>
|
||||
<n-descriptions-item v-if="group?.description || ''" label="描述" :span="2">
|
||||
{{ group?.description || "" }}
|
||||
</n-descriptions-item>
|
||||
</n-descriptions>
|
||||
</div>
|
||||
@@ -171,7 +199,7 @@ function formatPercentage(num: number): string {
|
||||
<h4 class="section-title">上游地址</h4>
|
||||
<n-descriptions :column="1" size="small">
|
||||
<n-descriptions-item
|
||||
v-for="(upstream, index) in group.upstreams"
|
||||
v-for="(upstream, index) in group?.upstreams ?? []"
|
||||
:key="index"
|
||||
:label="`上游 ${index + 1}`"
|
||||
>
|
||||
@@ -186,19 +214,19 @@ function formatPercentage(num: number): string {
|
||||
<div class="detail-section">
|
||||
<h4 class="section-title">配置信息</h4>
|
||||
<n-descriptions :column="2" size="small">
|
||||
<n-descriptions-item v-if="group.config.test_model" label="测试模型">
|
||||
{{ group.config.test_model }}
|
||||
<n-descriptions-item v-if="group?.config?.test_model || ''" label="测试模型">
|
||||
{{ group?.config?.test_model || "" }}
|
||||
</n-descriptions-item>
|
||||
<n-descriptions-item v-if="group.config.request_timeout" label="请求超时">
|
||||
{{ group.config.request_timeout }}ms
|
||||
<n-descriptions-item v-if="group?.config?.request_timeout || 0" label="请求超时">
|
||||
{{ group?.config?.request_timeout || 0 }}ms
|
||||
</n-descriptions-item>
|
||||
<n-descriptions-item
|
||||
v-if="Object.keys(group.config.param_overrides || {}).length > 0"
|
||||
v-if="Object.keys(group?.config?.param_overrides || {}).length > 0"
|
||||
label="参数覆盖"
|
||||
:span="2"
|
||||
>
|
||||
<pre class="config-json">{{
|
||||
JSON.stringify(group.config.param_overrides, null, 2)
|
||||
JSON.stringify(group?.config?.param_overrides || "", null, 2)
|
||||
}}</pre>
|
||||
</n-descriptions-item>
|
||||
</n-descriptions>
|
||||
@@ -245,6 +273,17 @@ function formatPercentage(num: number): string {
|
||||
margin: 0 0 8px 0;
|
||||
}
|
||||
|
||||
.group-url {
|
||||
font-size: 0.8rem;
|
||||
color: #2563eb;
|
||||
margin-left: 8px;
|
||||
font-family: monospace;
|
||||
background: rgba(37, 99, 235, 0.1);
|
||||
border-radius: 4px;
|
||||
padding: 2px 6px;
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
/* .group-meta {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
@@ -120,7 +120,7 @@ async function createDemoGroup() {
|
||||
<n-tag size="tiny" :type="getChannelTagType(group.channel_type)">
|
||||
{{ group.channel_type }}
|
||||
</n-tag>
|
||||
<span class="group-id">#{{ group.id }}</span>
|
||||
<span class="group-id">#{{ group.name }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -185,14 +185,12 @@ function formatRelativeTime(date: string) {
|
||||
}
|
||||
}
|
||||
|
||||
function getStatusClass(status: "active" | "inactive" | "error") {
|
||||
function getStatusClass(status: "active" | "inactive") {
|
||||
switch (status) {
|
||||
case "active":
|
||||
return "status-valid";
|
||||
case "inactive":
|
||||
return "status-invalid";
|
||||
case "error":
|
||||
return "status-error";
|
||||
default:
|
||||
return "status-unknown";
|
||||
}
|
||||
@@ -385,6 +383,8 @@ function changePageSize(size: number) {
|
||||
<!-- 主要信息行:Key + 快速操作 -->
|
||||
<div class="key-main">
|
||||
<div class="key-section">
|
||||
<n-tag v-if="key.status === 'active'" type="info">有效</n-tag>
|
||||
<n-tag v-else>无效</n-tag>
|
||||
<span class="key-text" :title="key.key_value">{{ maskKey(key.key_value) }}</span>
|
||||
<div class="quick-actions">
|
||||
<n-button size="tiny" text @click="toggleKeyVisibility(key)" title="显示/隐藏">
|
||||
@@ -417,12 +417,15 @@ function changePageSize(size: number) {
|
||||
</span>
|
||||
</div>
|
||||
<div class="key-actions">
|
||||
<n-button size="tiny" @click="testKey(key)" title="测试密钥">测试</n-button>
|
||||
<n-button type="info" size="tiny" @click="testKey(key)" title="测试密钥">
|
||||
测试
|
||||
</n-button>
|
||||
<n-button
|
||||
v-if="key.status !== 'active'"
|
||||
size="tiny"
|
||||
@click="restoreKey(key)"
|
||||
title="恢复密钥"
|
||||
type="warning"
|
||||
>
|
||||
恢复
|
||||
</n-button>
|
||||
|
@@ -3,7 +3,7 @@ export interface APIKey {
|
||||
id: number;
|
||||
group_id: number;
|
||||
key_value: string;
|
||||
status: "active" | "inactive" | "error";
|
||||
status: "active" | "inactive";
|
||||
request_count: number;
|
||||
failure_count: number;
|
||||
last_used_at?: string;
|
||||
|
@@ -55,7 +55,7 @@ function handleGroupRefresh() {
|
||||
<!-- 右侧主内容区域,占80% -->
|
||||
<div class="main-content">
|
||||
<!-- 分组信息卡片,更紧凑 -->
|
||||
<div v-if="selectedGroup" class="group-info">
|
||||
<div class="group-info">
|
||||
<group-info-card :group="selectedGroup" @refresh="handleGroupRefresh" />
|
||||
</div>
|
||||
|
||||
@@ -69,15 +69,6 @@ function handleGroupRefresh() {
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.keys-container {
|
||||
/* padding: 12px 0; */
|
||||
/* max-width: 1600px; */
|
||||
/* margin: 0 auto; */
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
margin-bottom: 12px;
|
||||
padding-bottom: 6px;
|
||||
@@ -101,6 +92,7 @@ function handleGroupRefresh() {
|
||||
.sidebar {
|
||||
width: 240px;
|
||||
flex-shrink: 0;
|
||||
height: calc(100vh - 88px);
|
||||
}
|
||||
|
||||
.main-content {
|
||||
|
@@ -43,14 +43,14 @@ async function handleSubmit() {
|
||||
|
||||
<template>
|
||||
<div class="settings-container">
|
||||
<!-- <div class="settings-header">
|
||||
<h2 class="settings-title">系统设置</h2>
|
||||
<p class="settings-subtitle">配置系统参数和选项</p>
|
||||
</div> -->
|
||||
|
||||
<div class="settings-content">
|
||||
<n-form ref="formRef" :model="form" label-placement="top" class="settings-form">
|
||||
<div v-for="(category, cIndex) in settingList" :key="cIndex" class="settings-category">
|
||||
<div
|
||||
v-for="(category, cIndex) in settingList"
|
||||
:key="cIndex"
|
||||
class="settings-category"
|
||||
:style="{ animationDelay: `${cIndex * 0.05}s` }"
|
||||
>
|
||||
<n-card class="category-card modern-card" :bordered="false" size="small">
|
||||
<template #header>
|
||||
<div class="category-header">
|
||||
@@ -134,11 +134,6 @@ async function handleSubmit() {
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
/* .settings-container {
|
||||
max-width: 1000px;
|
||||
margin: 0 auto;
|
||||
} */
|
||||
|
||||
.settings-header {
|
||||
margin-bottom: 32px;
|
||||
text-align: center;
|
||||
@@ -173,18 +168,6 @@ async function handleSubmit() {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.settings-category:nth-child(2) {
|
||||
animation-delay: 0.07s;
|
||||
}
|
||||
|
||||
.settings-category:nth-child(3) {
|
||||
animation-delay: 0.14s;
|
||||
}
|
||||
|
||||
.settings-category:nth-child(4) {
|
||||
animation-delay: 0.21s;
|
||||
}
|
||||
|
||||
.category-card {
|
||||
background: rgba(255, 255, 255, 0.98);
|
||||
}
|
||||
|
Reference in New Issue
Block a user