feat: 分组代理密钥优化
This commit is contained in:
183
web/src/components/common/ProxyKeysInput.vue
Normal file
183
web/src/components/common/ProxyKeysInput.vue
Normal 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>
|
@@ -1,6 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { keysApi } from "@/api/keys";
|
import { keysApi } from "@/api/keys";
|
||||||
import { settingsApi } from "@/api/settings";
|
import { settingsApi } from "@/api/settings";
|
||||||
|
import ProxyKeysInput from "@/components/common/ProxyKeysInput.vue";
|
||||||
import type { Group, GroupConfigOption, UpstreamInfo } from "@/types/models";
|
import type { Group, GroupConfigOption, UpstreamInfo } from "@/types/models";
|
||||||
import { Add, Close, HelpCircleOutline, Remove } from "@vicons/ionicons5";
|
import { Add, Close, HelpCircleOutline, Remove } from "@vicons/ionicons5";
|
||||||
import {
|
import {
|
||||||
@@ -610,9 +611,10 @@ async function handleSubmit() {
|
|||||||
</n-tooltip>
|
</n-tooltip>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<n-input
|
<proxy-keys-input
|
||||||
v-model:value="formData.proxy_keys"
|
v-model="formData.proxy_keys"
|
||||||
placeholder="多个密钥请用英文逗号 , 分隔"
|
placeholder="多个密钥请用英文逗号 , 分隔"
|
||||||
|
size="medium"
|
||||||
/>
|
/>
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
|
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { settingsApi, type SettingCategory } from "@/api/settings";
|
import { settingsApi, type SettingCategory } from "@/api/settings";
|
||||||
import { copy } from "@/utils/clipboard";
|
import ProxyKeysInput from "@/components/common/ProxyKeysInput.vue";
|
||||||
import { Copy, HelpCircle, Key, Save } from "@vicons/ionicons5";
|
import { HelpCircle, Save } from "@vicons/ionicons5";
|
||||||
import {
|
import {
|
||||||
NButton,
|
NButton,
|
||||||
NCard,
|
NCard,
|
||||||
@@ -12,7 +12,6 @@ import {
|
|||||||
NIcon,
|
NIcon,
|
||||||
NInput,
|
NInput,
|
||||||
NInputNumber,
|
NInputNumber,
|
||||||
NModal,
|
|
||||||
NSpace,
|
NSpace,
|
||||||
NTooltip,
|
NTooltip,
|
||||||
useMessage,
|
useMessage,
|
||||||
@@ -25,11 +24,6 @@ const form = ref<Record<string, string | number>>({});
|
|||||||
const isSaving = ref(false);
|
const isSaving = ref(false);
|
||||||
const message = useMessage();
|
const message = useMessage();
|
||||||
|
|
||||||
// 密钥生成弹窗相关
|
|
||||||
const showKeyGeneratorModal = ref(false);
|
|
||||||
const keyCount = ref(1);
|
|
||||||
const isGenerating = ref(false);
|
|
||||||
|
|
||||||
fetchSettings();
|
fetchSettings();
|
||||||
|
|
||||||
async function fetchSettings() {
|
async function fetchSettings() {
|
||||||
@@ -65,88 +59,6 @@ async function handleSubmit() {
|
|||||||
isSaving.value = false;
|
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>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -198,36 +110,19 @@ async function copyProxyKeys() {
|
|||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
size="small"
|
size="small"
|
||||||
/>
|
/>
|
||||||
|
<proxy-keys-input
|
||||||
|
v-else-if="item.key === 'proxy_keys'"
|
||||||
|
v-model="form[item.key] as string"
|
||||||
|
placeholder="请输入内容"
|
||||||
|
size="small"
|
||||||
|
/>
|
||||||
<n-input
|
<n-input
|
||||||
v-else
|
v-else
|
||||||
v-model:value="form[item.key] as string"
|
v-model:value="form[item.key] as string"
|
||||||
placeholder="请输入内容"
|
placeholder="请输入内容"
|
||||||
clearable
|
clearable
|
||||||
size="small"
|
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-form-item>
|
||||||
</n-grid-item>
|
</n-grid-item>
|
||||||
</n-grid>
|
</n-grid>
|
||||||
@@ -253,36 +148,5 @@ async function copyProxyKeys() {
|
|||||||
{{ isSaving ? "保存中..." : "保存设置" }}
|
{{ isSaving ? "保存中..." : "保存设置" }}
|
||||||
</n-button>
|
</n-button>
|
||||||
</div>
|
</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>
|
</n-space>
|
||||||
</template>
|
</template>
|
||||||
|
Reference in New Issue
Block a user