delete group

This commit is contained in:
hptangxi
2025-07-06 15:49:33 +08:00
parent fd9bcc1aba
commit cad61239eb
4 changed files with 82 additions and 40 deletions

View File

@@ -245,11 +245,9 @@ async function handleSubmit() {
if (props.group?.id) { if (props.group?.id) {
// 编辑模式 // 编辑模式
res = await keysApi.updateGroup(props.group.id, submitData); res = await keysApi.updateGroup(props.group.id, submitData);
message.success("分组更新成功");
} else { } else {
// 新建模式 // 新建模式
res = await keysApi.createGroup(submitData); res = await keysApi.createGroup(submitData);
message.success("分组创建成功");
} }
emit("success", res); emit("success", res);
@@ -295,11 +293,7 @@ async function handleSubmit() {
<h4 class="section-title">基础信息</h4> <h4 class="section-title">基础信息</h4>
<n-form-item label="分组名称" path="name"> <n-form-item label="分组名称" path="name">
<n-input <n-input v-model:value="formData.name" placeholder="请输入分组名称gemini" />
v-model:value="formData.name"
placeholder="请输入分组名称gemini"
:disabled="!!group"
/>
</n-form-item> </n-form-item>
<n-form-item label="显示名称" path="display_name"> <n-form-item label="显示名称" path="display_name">

View File

@@ -7,12 +7,13 @@ import {
NCard, NCard,
NCollapse, NCollapse,
NCollapseItem, NCollapseItem,
NFlex,
NForm, NForm,
NFormItem, NFormItem,
NGrid,
NGridItem,
NSpin, NSpin,
NTag, NTag,
useMessage, useDialog,
} from "naive-ui"; } from "naive-ui";
import { onMounted, ref, watch } from "vue"; import { onMounted, ref, watch } from "vue";
import GroupFormModal from "./GroupFormModal.vue"; import GroupFormModal from "./GroupFormModal.vue";
@@ -23,6 +24,7 @@ interface Props {
interface Emits { interface Emits {
(e: "refresh", value: Group): void; (e: "refresh", value: Group): void;
(e: "delete", value: Group): void;
} }
const props = defineProps<Props>(); const props = defineProps<Props>();
@@ -31,7 +33,7 @@ const emit = defineEmits<Emits>();
const stats = ref<GroupStats | null>(null); const stats = ref<GroupStats | null>(null);
const loading = ref(false); const loading = ref(false);
const message = useMessage(); const dialog = useDialog();
const showEditModal = ref(false); const showEditModal = ref(false);
onMounted(() => { onMounted(() => {
@@ -53,7 +55,9 @@ async function loadStats() {
try { try {
loading.value = true; loading.value = true;
stats.value = await keysApi.getGroupStats(props.group.id); if (props.group?.id) {
stats.value = await keysApi.getGroupStats(props.group.id);
}
} finally { } finally {
loading.value = false; loading.value = false;
} }
@@ -71,7 +75,26 @@ function handleGroupEdited(newGroup: Group) {
} }
function handleDelete() { function handleDelete() {
message.info("删除分组功能开发中..."); if (!props.group) {
return;
}
dialog.warning({
title: "删除分组",
content: `确定要删除分组 "${getGroupDisplayName(props.group)}" 吗?此操作不可恢复。`,
positiveText: "确定",
negativeText: "取消",
onPositiveClick: async () => {
try {
if (props.group?.id) {
await keysApi.deleteGroup(props.group.id);
emit("delete", props.group);
}
} catch (error) {
console.error("删除分组失败:", error);
}
},
});
} }
function formatNumber(num: number): string { function formatNumber(num: number): string {
@@ -108,12 +131,8 @@ function copyUrl(url: string) {
<div class="header-left"> <div class="header-left">
<h3 class="group-title"> <h3 class="group-title">
{{ group ? getGroupDisplayName(group) : "请选择分组" }} {{ group ? getGroupDisplayName(group) : "请选择分组" }}
<code <code v-if="group" class="group-url" @click="copyUrl(group?.endpoint || '')">
v-if="group" {{ group.endpoint }}
class="group-url"
@click="copyUrl(`https://gpt-load.com/${group?.name}`)"
>
https://gpt-load.com/{{ group?.name }}
</code> </code>
</h3> </h3>
</div> </div>
@@ -134,6 +153,7 @@ function copyUrl(url: string) {
@click="handleDelete" @click="handleDelete"
title="删除分组" title="删除分组"
type="error" type="error"
:disabled="!group"
> >
<template #icon> <template #icon>
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"> <svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor">
@@ -150,27 +170,40 @@ function copyUrl(url: string) {
<!-- 统计摘要区 --> <!-- 统计摘要区 -->
<div class="stats-summary"> <div class="stats-summary">
<n-spin :show="loading" size="small"> <n-spin :show="loading" size="small">
<n-flex class="status-cards-container"> <n-grid :cols="5" :x-gap="12" :y-gap="12" responsive="screen">
<n-card :title="`${stats?.active_keys || 0} / ${stats?.total_keys || 0}`" size="large"> <n-grid-item span="1">
<template #header-extra><span class="status-title">密钥数量</span></template> <n-card
</n-card> :title="`${stats?.active_keys || 0} / ${stats?.total_keys || 0}`"
<n-card size="large"
class="status-card-failure" >
:title="formatPercentage(stats?.failure_rate_24h || 0)" <template #header-extra><span class="status-title">密钥数量</span></template>
size="large" </n-card>
> </n-grid-item>
<template #header-extra><span class="status-title">失败率</span></template> <n-grid-item span="1">
</n-card> <n-card
<n-card :title="formatNumber(stats?.requests_1h || 0)" size="large"> class="status-card-failure"
<template #header-extra><span class="status-title">近1小时</span></template> :title="formatPercentage(stats?.failure_rate_24h || 0)"
</n-card> size="large"
<n-card :title="formatNumber(stats?.requests_24h || 0)" size="large"> >
<template #header-extra><span class="status-title">近24小时</span></template> <template #header-extra><span class="status-title">失败率</span></template>
</n-card> </n-card>
<n-card :title="formatNumber(stats?.requests_7d || 0)" size="large"> </n-grid-item>
<template #header-extra><span class="status-title">近7天</span></template> <n-grid-item span="1">
</n-card> <n-card :title="formatNumber(stats?.requests_1h || 0)" size="large">
</n-flex> <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 || 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 || 0)" size="large">
<template #header-extra><span class="status-title">近7天</span></template>
</n-card>
</n-grid-item>
</n-grid>
</n-spin> </n-spin>
</div> </div>

View File

@@ -30,6 +30,7 @@ export interface Group {
upstreams: UpstreamInfo[]; upstreams: UpstreamInfo[];
config: Record<string, unknown>; config: Record<string, unknown>;
api_keys?: APIKey[]; api_keys?: APIKey[];
endpoint?: string;
param_overrides: any; param_overrides: any;
created_at?: string; created_at?: string;
updated_at?: string; updated_at?: string;

View File

@@ -38,6 +38,16 @@ async function handleGroupRefresh() {
selectedGroup.value = groups.value.find(g => g.id === selectedGroup.value?.id) || null; selectedGroup.value = groups.value.find(g => g.id === selectedGroup.value?.id) || null;
} }
} }
function handleGroupDelete(deletedGroup: Group) {
// 从分组列表中移除已删除的分组
groups.value = groups.value.filter(g => g.id !== deletedGroup.id);
// 如果删除的是当前选中的分组,则切换到第一个分组
if (selectedGroup.value?.id === deletedGroup.id) {
selectedGroup.value = groups.value.length > 0 ? groups.value[0] : null;
}
}
</script> </script>
<template> <template>
@@ -56,7 +66,11 @@ async function handleGroupRefresh() {
<div class="main-content"> <div class="main-content">
<!-- 分组信息卡片更紧凑 --> <!-- 分组信息卡片更紧凑 -->
<div class="group-info"> <div class="group-info">
<group-info-card :group="selectedGroup" @refresh="handleGroupRefresh" /> <group-info-card
:group="selectedGroup"
@refresh="handleGroupRefresh"
@delete="handleGroupDelete"
/>
</div> </div>
<!-- 密钥表格区域占主要空间 --> <!-- 密钥表格区域占主要空间 -->