From fef2c8c3c234672322aac7a7dfa9faae782e1e0f Mon Sep 17 00:00:00 2001 From: shaw Date: Thu, 11 Sep 2025 11:52:41 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96ProxyConfig=E7=BB=84?= =?UTF-8?q?=E4=BB=B6=E6=B7=BB=E5=8A=A0=E4=BB=A3=E7=90=86URL=E6=99=BA?= =?UTF-8?q?=E8=83=BD=E8=AF=86=E5=88=AB=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增快速配置输入框,支持粘贴完整代理URL自动填充表单 - 支持多种格式自动识别:socks5://、http://、https://、host:port - 自动忽略#后的别名部分 - 粘贴即解析,输入即智能识别 - 添加实时解析成功/失败提示 - 优化用户体验,无需失去焦点即可触发解析 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- .../src/components/accounts/ProxyConfig.vue | 160 ++++++++++++++++++ 1 file changed, 160 insertions(+) diff --git a/web/admin-spa/src/components/accounts/ProxyConfig.vue b/web/admin-spa/src/components/accounts/ProxyConfig.vue index d38bb28b..c769f325 100644 --- a/web/admin-spa/src/components/accounts/ProxyConfig.vue +++ b/web/admin-spa/src/components/accounts/ProxyConfig.vue @@ -30,6 +30,45 @@ + +
+ +
+ + +
+

+ + {{ parseError }} +

+

+ + 代理配置已自动填充 +

+
+ +
+
props.modelValue, @@ -246,6 +290,122 @@ function emitUpdate() { }, 100) // 100ms 延迟 } +// 解析代理URL +function parseProxyUrl() { + parseError.value = '' + parseSuccess.value = false + + if (!proxyUrl.value) { + return + } + + try { + // 移除 # 后面的别名部分 + const urlWithoutAlias = proxyUrl.value.split('#')[0].trim() + + if (!urlWithoutAlias) { + return + } + + // 正则表达式匹配代理URL格式 + // 支持格式:protocol://[username:password@]host:port + const proxyPattern = /^(socks5|https?):\/\/(?:([^:@]+):([^@]+)@)?([^:]+):(\d+)$/i + const match = urlWithoutAlias.match(proxyPattern) + + if (!match) { + // 尝试简单格式:host:port(默认为socks5) + const simplePattern = /^([^:]+):(\d+)$/ + const simpleMatch = urlWithoutAlias.match(simplePattern) + + if (simpleMatch) { + proxy.value.type = 'socks5' + proxy.value.host = simpleMatch[1] + proxy.value.port = simpleMatch[2] + proxy.value.username = '' + proxy.value.password = '' + showAuth.value = false + parseSuccess.value = true + emitUpdate() + + // 3秒后清除成功提示 + setTimeout(() => { + parseSuccess.value = false + }, 3000) + return + } + + parseError.value = '无效的代理URL格式,请检查输入' + return + } + + // 解析匹配结果 + const [, protocol, username, password, host, port] = match + + // 填充表单 + proxy.value.type = protocol.toLowerCase() + proxy.value.host = host + proxy.value.port = port + + // 处理认证信息 + if (username && password) { + proxy.value.username = decodeURIComponent(username) + proxy.value.password = decodeURIComponent(password) + showAuth.value = true + } else { + proxy.value.username = '' + proxy.value.password = '' + showAuth.value = false + } + + parseSuccess.value = true + emitUpdate() + + // 3秒后清除成功提示 + setTimeout(() => { + parseSuccess.value = false + }, 3000) + } catch (error) { + // 解析代理URL失败 + parseError.value = '解析失败,请检查URL格式' + } +} + +// 清空快速配置输入 +function clearProxyUrl() { + proxyUrl.value = '' + parseError.value = '' + parseSuccess.value = false +} + +// 处理粘贴事件 +function handlePaste() { + // 延迟一下以确保v-model已经更新 + setTimeout(() => { + parseProxyUrl() + }, 0) +} + +// 处理输入事件 +function handleInput() { + // 检测是否输入了代理URL格式 + const value = proxyUrl.value.trim() + + // 如果输入包含://,说明可能是完整的代理URL + if (value.includes('://')) { + // 检查是否看起来像完整的URL(有协议、主机和端口) + if ( + /^(socks5|https?):\/\/[^:]+:\d+/i.test(value) || + /^(socks5|https?):\/\/[^:@]+:[^@]+@[^:]+:\d+/i.test(value) + ) { + parseProxyUrl() + } + } + // 如果是简单的 host:port 格式,并且端口号输入完整 + else if (/^[^:]+:\d{2,5}$/.test(value)) { + parseProxyUrl() + } +} + // 组件销毁时清理定时器 onUnmounted(() => { if (updateTimer) {