feat: 分组代理密钥优化

This commit is contained in:
tbphp
2025-07-26 22:38:46 +08:00
parent f90558f14a
commit 74a729398c
3 changed files with 196 additions and 147 deletions

View File

@@ -0,0 +1,183 @@
<script setup lang="ts">
import { copy } from "@/utils/clipboard";
import { Copy, Key } from "@vicons/ionicons5";
import { NButton, NIcon, NInput, NInputNumber, NModal, NSpace, useMessage } from "naive-ui";
import { ref } from "vue";
interface Props {
modelValue: string;
placeholder?: string;
size?: "small" | "medium" | "large";
}
interface Emits {
(e: "update:modelValue", value: string): void;
}
const props = withDefaults(defineProps<Props>(), {
placeholder: "多个密钥请用英文逗号 , 分隔",
size: "small",
});
const emit = defineEmits<Emits>();
const message = useMessage();
// 密钥生成弹窗相关
const showKeyGeneratorModal = ref(false);
const keyCount = ref(1);
const isGenerating = ref(false);
// 生成随机字符串
function generateRandomString(length: number): string {
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
let result = "";
for (let i = 0; i < length; i++) {
result += chars.charAt(Math.floor(Math.random() * chars.length));
}
return result;
}
// 生成密钥
function generateKeys(): string[] {
const keys: string[] = [];
for (let i = 0; i < keyCount.value; i++) {
keys.push(`sk-${generateRandomString(48)}`);
}
return keys;
}
// 打开密钥生成器弹窗
function openKeyGenerator() {
showKeyGeneratorModal.value = true;
keyCount.value = 1;
}
// 确认生成密钥
function confirmGenerateKeys() {
if (isGenerating.value) {
return;
}
try {
isGenerating.value = true;
const newKeys = generateKeys();
const currentValue = props.modelValue || "";
let updatedValue = currentValue.trim();
// 处理逗号兼容情况
if (updatedValue && !updatedValue.endsWith(",")) {
updatedValue += ",";
}
// 添加新生成的密钥
if (updatedValue) {
updatedValue += newKeys.join(",");
} else {
updatedValue = newKeys.join(",");
}
emit("update:modelValue", updatedValue);
showKeyGeneratorModal.value = false;
message.success(`成功生成 ${keyCount.value} 个密钥`);
} finally {
isGenerating.value = false;
}
}
// 复制代理密钥
async function copyProxyKeys() {
const proxyKeys = props.modelValue || "";
if (!proxyKeys.trim()) {
message.warning("暂无密钥可复制");
return;
}
// 将逗号分隔的密钥转换为换行分隔
const formattedKeys = proxyKeys
.split(",")
.map(key => key.trim())
.filter(key => key.length > 0)
.join("\n");
const success = await copy(formattedKeys);
if (success) {
message.success("密钥已复制到剪贴板");
} else {
message.error("复制失败,请手动复制");
}
}
// 处理输入框值变化
function handleInput(value: string) {
emit("update:modelValue", value);
}
</script>
<template>
<div class="proxy-keys-input">
<n-input
:value="modelValue"
:placeholder="placeholder"
clearable
:size="size"
@update:value="handleInput"
>
<template #suffix>
<n-space :size="4" :wrap-item="false">
<n-button text type="primary" :size="size" @click="openKeyGenerator">
<template #icon>
<n-icon :component="Key" />
</template>
生成
</n-button>
<n-button text type="tertiary" :size="size" @click="copyProxyKeys" style="opacity: 0.7">
<template #icon>
<n-icon :component="Copy" />
</template>
复制
</n-button>
</n-space>
</template>
</n-input>
<!-- 密钥生成器弹窗 -->
<n-modal
v-model:show="showKeyGeneratorModal"
preset="dialog"
title="生成代理密钥"
positive-text="确认生成"
negative-text="取消"
:positive-button-props="{ loading: isGenerating }"
@positive-click="confirmGenerateKeys"
>
<n-space vertical :size="16">
<div>
<p style="margin: 0 0 8px 0; color: #666; font-size: 14px">
请输入要生成的密钥数量最大100个
</p>
<n-input-number
v-model:value="keyCount"
:min="1"
:max="100"
placeholder="请输入数量"
style="width: 100%"
:disabled="isGenerating"
/>
</div>
<div style="color: #999; font-size: 12px; line-height: 1.4">
<p style="margin: 0">密钥格式sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx</p>
<p style="margin: 4px 0 0 0">生成的密钥将会插入到当前输入框内容的后面以逗号分隔</p>
</div>
</n-space>
</n-modal>
</div>
</template>
<style scoped>
.proxy-keys-input {
width: 100%;
}
</style>

View File

@@ -1,6 +1,7 @@
<script setup lang="ts">
import { keysApi } from "@/api/keys";
import { settingsApi } from "@/api/settings";
import ProxyKeysInput from "@/components/common/ProxyKeysInput.vue";
import type { Group, GroupConfigOption, UpstreamInfo } from "@/types/models";
import { Add, Close, HelpCircleOutline, Remove } from "@vicons/ionicons5";
import {
@@ -610,9 +611,10 @@ async function handleSubmit() {
</n-tooltip>
</div>
</template>
<n-input
v-model:value="formData.proxy_keys"
<proxy-keys-input
v-model="formData.proxy_keys"
placeholder="多个密钥请用英文逗号 , 分隔"
size="medium"
/>
</n-form-item>

View File

@@ -1,7 +1,7 @@
<script setup lang="ts">
import { settingsApi, type SettingCategory } from "@/api/settings";
import { copy } from "@/utils/clipboard";
import { Copy, HelpCircle, Key, Save } from "@vicons/ionicons5";
import ProxyKeysInput from "@/components/common/ProxyKeysInput.vue";
import { HelpCircle, Save } from "@vicons/ionicons5";
import {
NButton,
NCard,
@@ -12,7 +12,6 @@ import {
NIcon,
NInput,
NInputNumber,
NModal,
NSpace,
NTooltip,
useMessage,
@@ -25,11 +24,6 @@ const form = ref<Record<string, string | number>>({});
const isSaving = ref(false);
const message = useMessage();
// 密钥生成弹窗相关
const showKeyGeneratorModal = ref(false);
const keyCount = ref(1);
const isGenerating = ref(false);
fetchSettings();
async function fetchSettings() {
@@ -65,88 +59,6 @@ async function handleSubmit() {
isSaving.value = false;
}
}
// 生成随机字符串
function generateRandomString(length: number): string {
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
let result = "";
for (let i = 0; i < length; i++) {
result += chars.charAt(Math.floor(Math.random() * chars.length));
}
return result;
}
// 生成密钥
function generateKeys(): string[] {
const keys: string[] = [];
for (let i = 0; i < keyCount.value; i++) {
keys.push(`sk-${generateRandomString(48)}`);
}
return keys;
}
// 打开密钥生成器弹窗
function openKeyGenerator() {
showKeyGeneratorModal.value = true;
keyCount.value = 1;
}
// 确认生成密钥
function confirmGenerateKeys() {
if (isGenerating.value) {
return;
}
try {
isGenerating.value = true;
const newKeys = generateKeys();
const currentValue = (form.value["proxy_keys"] as string) || "";
let updatedValue = currentValue.trim();
// 处理逗号兼容情况
if (updatedValue && !updatedValue.endsWith(",")) {
updatedValue += ",";
}
// 添加新生成的密钥
if (updatedValue) {
updatedValue += newKeys.join(",");
} else {
updatedValue = newKeys.join(",");
}
form.value["proxy_keys"] = updatedValue;
showKeyGeneratorModal.value = false;
message.success(`成功生成 ${keyCount.value} 个密钥`);
} finally {
isGenerating.value = false;
}
}
// 复制代理密钥
async function copyProxyKeys() {
const proxyKeys = (form.value["proxy_keys"] as string) || "";
if (!proxyKeys.trim()) {
message.warning("暂无密钥可复制");
return;
}
// 将逗号分隔的密钥转换为换行分隔
const formattedKeys = proxyKeys
.split(",")
.map(key => key.trim())
.filter(key => key.length > 0)
.join("\n");
const success = await copy(formattedKeys);
if (success) {
message.success("密钥已复制到剪贴板");
} else {
message.error("复制失败,请手动复制");
}
}
</script>
<template>
@@ -198,36 +110,19 @@ async function copyProxyKeys() {
style="width: 100%"
size="small"
/>
<proxy-keys-input
v-else-if="item.key === 'proxy_keys'"
v-model="form[item.key] as string"
placeholder="请输入内容"
size="small"
/>
<n-input
v-else
v-model:value="form[item.key] as string"
placeholder="请输入内容"
clearable
size="small"
>
<template v-if="item.key === 'proxy_keys'" #suffix>
<n-space :size="4" :wrap-item="false">
<n-button text type="primary" size="small" @click="openKeyGenerator">
<template #icon>
<n-icon :component="Key" />
</template>
生成
</n-button>
<n-button
text
type="tertiary"
size="small"
@click="copyProxyKeys"
style="opacity: 0.7"
>
<template #icon>
<n-icon :component="Copy" />
</template>
复制
</n-button>
</n-space>
</template>
</n-input>
/>
</n-form-item>
</n-grid-item>
</n-grid>
@@ -253,36 +148,5 @@ async function copyProxyKeys() {
{{ isSaving ? "保存中..." : "保存设置" }}
</n-button>
</div>
<!-- 密钥生成器弹窗 -->
<n-modal
v-model:show="showKeyGeneratorModal"
preset="dialog"
title="生成代理密钥"
positive-text="确认生成"
negative-text="取消"
:positive-button-props="{ loading: isGenerating }"
@positive-click="confirmGenerateKeys"
>
<n-space vertical :size="16">
<div>
<p style="margin: 0 0 8px 0; color: #666; font-size: 14px">
请输入要生成的密钥数量最大100个
</p>
<n-input-number
v-model:value="keyCount"
:min="1"
:max="100"
placeholder="请输入数量"
style="width: 100%"
:disabled="isGenerating"
/>
</div>
<div style="color: #999; font-size: 12px; line-height: 1.4">
<p style="margin: 0">密钥格式sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx</p>
<p style="margin: 4px 0 0 0">生成的密钥将会插入到当前输入框内容的后面以逗号分隔</p>
</div>
</n-space>
</n-modal>
</n-space>
</template>