mirror of
https://github.com/Wei-Shaw/claude-relay-service.git
synced 2026-01-22 16:43:35 +00:00
feat: 优化ProxyConfig组件添加代理URL智能识别功能
- 新增快速配置输入框,支持粘贴完整代理URL自动填充表单 - 支持多种格式自动识别:socks5://、http://、https://、host:port - 自动忽略#后的别名部分 - 粘贴即解析,输入即智能识别 - 添加实时解析成功/失败提示 - 优化用户体验,无需失去焦点即可触发解析 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -30,6 +30,45 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 快速配置输入框 -->
|
||||
<div>
|
||||
<label class="mb-2 block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
快速配置
|
||||
<span class="ml-1 text-xs font-normal text-gray-500 dark:text-gray-400">
|
||||
(粘贴完整代理URL自动填充)
|
||||
</span>
|
||||
</label>
|
||||
<div class="relative">
|
||||
<input
|
||||
v-model="proxyUrl"
|
||||
class="form-input w-full border-gray-300 pr-10 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-200 dark:placeholder-gray-400"
|
||||
placeholder="例如: socks5://username:password@host:port 或 http://host:port"
|
||||
type="text"
|
||||
@input="handleInput"
|
||||
@keyup.enter="parseProxyUrl"
|
||||
@paste="handlePaste"
|
||||
/>
|
||||
<button
|
||||
v-if="proxyUrl"
|
||||
class="absolute inset-y-0 right-0 flex items-center pr-3 text-gray-400 hover:text-gray-600 dark:text-gray-500 dark:hover:text-gray-400"
|
||||
type="button"
|
||||
@click="clearProxyUrl"
|
||||
>
|
||||
<i class="fas fa-times" />
|
||||
</button>
|
||||
</div>
|
||||
<p v-if="parseError" class="mt-1 text-xs text-red-500">
|
||||
<i class="fas fa-exclamation-circle mr-1" />
|
||||
{{ parseError }}
|
||||
</p>
|
||||
<p v-else-if="parseSuccess" class="mt-1 text-xs text-green-500">
|
||||
<i class="fas fa-check-circle mr-1" />
|
||||
代理配置已自动填充
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="my-3 border-t border-gray-200 dark:border-gray-600"></div>
|
||||
|
||||
<div>
|
||||
<label class="mb-2 block text-sm font-medium text-gray-700 dark:text-gray-300"
|
||||
>代理类型</label
|
||||
@@ -159,6 +198,11 @@ const proxy = ref({ ...props.modelValue })
|
||||
const showAuth = ref(!!(proxy.value.username || proxy.value.password))
|
||||
const showPassword = ref(false)
|
||||
|
||||
// 快速配置相关
|
||||
const proxyUrl = ref('')
|
||||
const parseError = ref('')
|
||||
const parseSuccess = ref(false)
|
||||
|
||||
// 监听modelValue变化,只在真正需要更新时才更新
|
||||
watch(
|
||||
() => 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) {
|
||||
|
||||
Reference in New Issue
Block a user