mirror of
https://github.com/hellodigua/ChatLab.git
synced 2026-05-17 20:13:48 +08:00
feat: 支持配置代理(resolve #7)
This commit is contained in:
@@ -5,6 +5,7 @@ import * as fs from 'fs/promises'
|
||||
import { checkUpdate } from './update'
|
||||
import mainIpcMain from './ipcMain'
|
||||
import { initAnalytics, trackDailyActive } from './analytics'
|
||||
import { initProxy } from './network/proxy'
|
||||
|
||||
class MainProcess {
|
||||
mainWindow: BrowserWindow | null
|
||||
@@ -46,6 +47,7 @@ class MainProcess {
|
||||
// 初始化程序
|
||||
async init() {
|
||||
initAnalytics()
|
||||
initProxy() // 初始化代理配置
|
||||
|
||||
// 注册应用协议
|
||||
app.setAsDefaultProtocolClient('chatlab')
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
/**
|
||||
* 网络设置 IPC 处理器
|
||||
* 处理代理配置的读取、保存和测试
|
||||
*/
|
||||
|
||||
import { ipcMain } from 'electron'
|
||||
import type { IpcContext } from './types'
|
||||
import {
|
||||
loadProxyConfig,
|
||||
saveProxyConfig,
|
||||
testProxyConnection,
|
||||
validateProxyUrl,
|
||||
type ProxyConfig,
|
||||
} from '../network/proxy'
|
||||
|
||||
/**
|
||||
* 注册网络设置相关的 IPC 处理器
|
||||
*/
|
||||
export function registerNetworkHandlers(_context: IpcContext): void {
|
||||
console.log('[IpcMain] Registering network handlers...')
|
||||
|
||||
/**
|
||||
* 获取代理配置
|
||||
*/
|
||||
ipcMain.handle('network:getProxyConfig', (): ProxyConfig => {
|
||||
return loadProxyConfig()
|
||||
})
|
||||
|
||||
/**
|
||||
* 保存代理配置
|
||||
*/
|
||||
ipcMain.handle(
|
||||
'network:saveProxyConfig',
|
||||
(_event, config: ProxyConfig): { success: boolean; error?: string } => {
|
||||
try {
|
||||
// 如果启用了代理,验证 URL 格式
|
||||
if (config.enabled && config.url) {
|
||||
const validation = validateProxyUrl(config.url)
|
||||
if (!validation.valid) {
|
||||
return { success: false, error: validation.error }
|
||||
}
|
||||
}
|
||||
|
||||
saveProxyConfig(config)
|
||||
return { success: true }
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : String(error)
|
||||
return { success: false, error: `保存配置失败: ${errorMessage}` }
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
/**
|
||||
* 测试代理连接
|
||||
*/
|
||||
ipcMain.handle(
|
||||
'network:testProxyConnection',
|
||||
async (_event, proxyUrl: string): Promise<{ success: boolean; error?: string }> => {
|
||||
return testProxyConnection(proxyUrl)
|
||||
}
|
||||
)
|
||||
|
||||
console.log('[IpcMain] Network handlers registered')
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import { registerMergeHandlers, initMergeModule } from './ipc/merge'
|
||||
import { registerAIHandlers } from './ipc/ai'
|
||||
import { registerMessagesHandlers } from './ipc/messages'
|
||||
import { registerCacheHandlers } from './ipc/cache'
|
||||
import { registerNetworkHandlers } from './ipc/network'
|
||||
import { registerAnalyticsHandlers } from './analytics'
|
||||
// 导入 Worker 模块(用于异步分析查询和流式导入)
|
||||
import * as worker from './worker/workerManager'
|
||||
@@ -43,6 +44,7 @@ const mainIpcMain = (win: BrowserWindow) => {
|
||||
registerAIHandlers(context)
|
||||
registerMessagesHandlers(context)
|
||||
registerCacheHandlers(context)
|
||||
registerNetworkHandlers(context)
|
||||
registerAnalyticsHandlers()
|
||||
|
||||
console.log('[IpcMain] All IPC handlers registered successfully')
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
/**
|
||||
* 网络模块入口
|
||||
* 导出代理配置功能
|
||||
*/
|
||||
|
||||
export * from './proxy'
|
||||
@@ -0,0 +1,252 @@
|
||||
/**
|
||||
* 代理配置管理模块
|
||||
* 提供 HTTP/HTTPS 代理的配置存储、读取和连接测试
|
||||
*/
|
||||
|
||||
import * as fs from 'fs'
|
||||
import * as path from 'path'
|
||||
import { app, session } from 'electron'
|
||||
|
||||
// 代理配置接口
|
||||
export interface ProxyConfig {
|
||||
enabled: boolean
|
||||
url: string // 完整的代理 URL,如 http://127.0.0.1:7890
|
||||
}
|
||||
|
||||
// 默认配置
|
||||
const DEFAULT_CONFIG: ProxyConfig = {
|
||||
enabled: false,
|
||||
url: '',
|
||||
}
|
||||
|
||||
// 配置文件路径
|
||||
let CONFIG_PATH: string | null = null
|
||||
|
||||
function getConfigPath(): string {
|
||||
if (CONFIG_PATH) return CONFIG_PATH
|
||||
|
||||
try {
|
||||
const docPath = app.getPath('documents')
|
||||
CONFIG_PATH = path.join(docPath, 'ChatLab', 'settings', 'proxy.json')
|
||||
} catch {
|
||||
CONFIG_PATH = path.join(process.cwd(), 'settings', 'proxy.json')
|
||||
}
|
||||
|
||||
return CONFIG_PATH
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载代理配置
|
||||
*/
|
||||
export function loadProxyConfig(): ProxyConfig {
|
||||
const configPath = getConfigPath()
|
||||
|
||||
if (!fs.existsSync(configPath)) {
|
||||
return { ...DEFAULT_CONFIG }
|
||||
}
|
||||
|
||||
try {
|
||||
const content = fs.readFileSync(configPath, 'utf-8')
|
||||
const data = JSON.parse(content)
|
||||
return {
|
||||
enabled: Boolean(data.enabled),
|
||||
url: String(data.url || ''),
|
||||
}
|
||||
} catch {
|
||||
return { ...DEFAULT_CONFIG }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存代理配置
|
||||
*/
|
||||
export function saveProxyConfig(config: ProxyConfig): void {
|
||||
const configPath = getConfigPath()
|
||||
const dir = path.dirname(configPath)
|
||||
|
||||
if (!fs.existsSync(dir)) {
|
||||
fs.mkdirSync(dir, { recursive: true })
|
||||
}
|
||||
|
||||
fs.writeFileSync(configPath, JSON.stringify(config, null, 2), 'utf-8')
|
||||
|
||||
// 保存后立即应用代理设置到 Electron session
|
||||
applyProxyToSession()
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证代理 URL 格式
|
||||
*/
|
||||
export function validateProxyUrl(url: string): { valid: boolean; error?: string } {
|
||||
if (!url) {
|
||||
return { valid: false, error: '代理地址不能为空' }
|
||||
}
|
||||
|
||||
try {
|
||||
const parsed = new URL(url)
|
||||
if (!['http:', 'https:'].includes(parsed.protocol)) {
|
||||
return { valid: false, error: '仅支持 http:// 或 https:// 协议' }
|
||||
}
|
||||
if (!parsed.hostname) {
|
||||
return { valid: false, error: '代理地址格式无效' }
|
||||
}
|
||||
return { valid: true }
|
||||
} catch {
|
||||
return { valid: false, error: '代理地址格式无效,请使用 http://host:port 格式' }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将代理设置应用到 Electron session
|
||||
* 这会影响所有通过 Electron 发起的网络请求(包括主进程的 fetch)
|
||||
*/
|
||||
export async function applyProxyToSession(): Promise<void> {
|
||||
const config = loadProxyConfig()
|
||||
|
||||
try {
|
||||
if (config.enabled && config.url) {
|
||||
const validation = validateProxyUrl(config.url)
|
||||
if (validation.valid) {
|
||||
// 设置代理规则
|
||||
await session.defaultSession.setProxy({
|
||||
proxyRules: config.url,
|
||||
})
|
||||
console.log(`[Proxy] 代理已启用: ${config.url}`)
|
||||
}
|
||||
} else {
|
||||
// 清除代理设置
|
||||
await session.defaultSession.setProxy({
|
||||
proxyRules: '',
|
||||
})
|
||||
console.log('[Proxy] 代理已禁用')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[Proxy] 设置代理失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试代理连接
|
||||
* 通过代理请求一个可靠的 HTTPS 地址来验证代理是否可用
|
||||
*/
|
||||
export async function testProxyConnection(proxyUrl: string): Promise<{ success: boolean; error?: string }> {
|
||||
// 先验证格式
|
||||
const validation = validateProxyUrl(proxyUrl)
|
||||
if (!validation.valid) {
|
||||
return { success: false, error: validation.error }
|
||||
}
|
||||
|
||||
// 测试 URL 列表(按优先级)
|
||||
const testUrls = [
|
||||
'https://www.google.com',
|
||||
'https://www.cloudflare.com',
|
||||
'https://api.deepseek.com',
|
||||
]
|
||||
|
||||
try {
|
||||
// 临时设置代理
|
||||
await session.defaultSession.setProxy({
|
||||
proxyRules: proxyUrl,
|
||||
})
|
||||
|
||||
// 使用 Electron 的 net 模块测试连接
|
||||
const { net } = await import('electron')
|
||||
|
||||
let lastError: string = ''
|
||||
|
||||
for (const testUrl of testUrls) {
|
||||
try {
|
||||
const result = await new Promise<{ success: boolean; error?: string }>((resolve) => {
|
||||
const request = net.request({
|
||||
method: 'HEAD',
|
||||
url: testUrl,
|
||||
})
|
||||
|
||||
const timeout = setTimeout(() => {
|
||||
request.abort()
|
||||
resolve({ success: false, error: '连接超时' })
|
||||
}, 10000)
|
||||
|
||||
request.on('response', (response) => {
|
||||
clearTimeout(timeout)
|
||||
// 任何响应都说明代理可用
|
||||
if (response.statusCode < 500) {
|
||||
resolve({ success: true })
|
||||
} else {
|
||||
resolve({ success: false, error: `HTTP ${response.statusCode}` })
|
||||
}
|
||||
})
|
||||
|
||||
request.on('error', (error) => {
|
||||
clearTimeout(timeout)
|
||||
resolve({ success: false, error: error.message })
|
||||
})
|
||||
|
||||
request.end()
|
||||
})
|
||||
|
||||
if (result.success) {
|
||||
// 恢复之前的代理设置
|
||||
await applyProxyToSession()
|
||||
return { success: true }
|
||||
}
|
||||
|
||||
lastError = result.error || ''
|
||||
} catch (e) {
|
||||
lastError = e instanceof Error ? e.message : String(e)
|
||||
}
|
||||
}
|
||||
|
||||
// 恢复之前的代理设置
|
||||
await applyProxyToSession()
|
||||
return { success: false, error: lastError || '无法通过代理连接到测试服务器' }
|
||||
} catch (error) {
|
||||
// 恢复之前的代理设置
|
||||
await applyProxyToSession()
|
||||
|
||||
const errorMessage = error instanceof Error ? error.message : String(error)
|
||||
|
||||
// 友好的错误提示
|
||||
if (errorMessage.includes('ECONNREFUSED')) {
|
||||
return { success: false, error: '连接被拒绝,请检查代理服务器是否运行中' }
|
||||
}
|
||||
if (errorMessage.includes('ETIMEDOUT') || errorMessage.includes('timeout')) {
|
||||
return { success: false, error: '连接超时,请检查代理地址和端口' }
|
||||
}
|
||||
if (errorMessage.includes('ENOTFOUND')) {
|
||||
return { success: false, error: '无法解析代理服务器地址' }
|
||||
}
|
||||
|
||||
return { success: false, error: `代理连接失败: ${errorMessage}` }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前有效的代理 URL
|
||||
* 如果代理已启用且 URL 有效,返回代理 URL,否则返回 undefined
|
||||
*/
|
||||
export function getActiveProxyUrl(): string | undefined {
|
||||
const config = loadProxyConfig()
|
||||
if (config.enabled && config.url) {
|
||||
const validation = validateProxyUrl(config.url)
|
||||
if (validation.valid) {
|
||||
return config.url
|
||||
}
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化代理模块
|
||||
* 应用启动时调用,加载并应用代理配置
|
||||
*/
|
||||
export function initProxy(): void {
|
||||
// 延迟执行,确保 app ready
|
||||
if (app.isReady()) {
|
||||
applyProxyToSession()
|
||||
} else {
|
||||
app.whenReady().then(() => {
|
||||
applyProxyToSession()
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -2,9 +2,32 @@ import { dialog, app } from 'electron'
|
||||
import { autoUpdater } from 'electron-updater'
|
||||
import { platform } from '@electron-toolkit/utils'
|
||||
import { logger } from './logger'
|
||||
import { getActiveProxyUrl } from './network/proxy'
|
||||
|
||||
/**
|
||||
* 配置自动更新的代理设置
|
||||
* electron-updater 通过环境变量读取代理配置
|
||||
*/
|
||||
function configureUpdateProxy(): void {
|
||||
const proxyUrl = getActiveProxyUrl()
|
||||
|
||||
if (proxyUrl) {
|
||||
// 设置环境变量,electron-updater 会自动读取
|
||||
process.env.HTTPS_PROXY = proxyUrl
|
||||
process.env.HTTP_PROXY = proxyUrl
|
||||
logger.info(`[Update] 使用代理: ${proxyUrl}`)
|
||||
} else {
|
||||
// 清除代理环境变量
|
||||
delete process.env.HTTPS_PROXY
|
||||
delete process.env.HTTP_PROXY
|
||||
}
|
||||
}
|
||||
|
||||
let isFirstShow = true
|
||||
const checkUpdate = (win) => {
|
||||
// 配置代理
|
||||
configureUpdateProxy()
|
||||
|
||||
autoUpdater.autoDownload = false // 自动下载
|
||||
autoUpdater.autoInstallOnAppQuit = true // 应用退出后自动安装
|
||||
|
||||
|
||||
Vendored
+15
-1
@@ -382,6 +382,18 @@ interface CacheApi {
|
||||
) => Promise<{ success: boolean; filePath?: string; error?: string }>
|
||||
}
|
||||
|
||||
// Network API 类型 - 网络代理配置
|
||||
interface ProxyConfig {
|
||||
enabled: boolean
|
||||
url: string
|
||||
}
|
||||
|
||||
interface NetworkApi {
|
||||
getProxyConfig: () => Promise<ProxyConfig>
|
||||
saveProxyConfig: (config: ProxyConfig) => Promise<{ success: boolean; error?: string }>
|
||||
testProxyConnection: (proxyUrl: string) => Promise<{ success: boolean; error?: string }>
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
electron: ElectronAPI
|
||||
@@ -392,6 +404,7 @@ declare global {
|
||||
llmApi: LlmApi
|
||||
agentApi: AgentApi
|
||||
cacheApi: CacheApi
|
||||
networkApi: NetworkApi
|
||||
}
|
||||
}
|
||||
|
||||
@@ -403,11 +416,12 @@ export {
|
||||
LlmApi,
|
||||
AgentApi,
|
||||
CacheApi,
|
||||
NetworkApi,
|
||||
ProxyConfig,
|
||||
SearchMessageResult,
|
||||
AIConversation,
|
||||
AIMessage,
|
||||
LLMProviderInfo,
|
||||
LLMConfig,
|
||||
AIServiceConfigDisplay,
|
||||
LLMChatMessage,
|
||||
LLMChatOptions,
|
||||
|
||||
@@ -935,6 +935,35 @@ const agentApi = {
|
||||
},
|
||||
}
|
||||
|
||||
// Network API - 网络设置
|
||||
interface ProxyConfig {
|
||||
enabled: boolean
|
||||
url: string
|
||||
}
|
||||
|
||||
const networkApi = {
|
||||
/**
|
||||
* 获取代理配置
|
||||
*/
|
||||
getProxyConfig: (): Promise<ProxyConfig> => {
|
||||
return ipcRenderer.invoke('network:getProxyConfig')
|
||||
},
|
||||
|
||||
/**
|
||||
* 保存代理配置
|
||||
*/
|
||||
saveProxyConfig: (config: ProxyConfig): Promise<{ success: boolean; error?: string }> => {
|
||||
return ipcRenderer.invoke('network:saveProxyConfig', config)
|
||||
},
|
||||
|
||||
/**
|
||||
* 测试代理连接
|
||||
*/
|
||||
testProxyConnection: (proxyUrl: string): Promise<{ success: boolean; error?: string }> => {
|
||||
return ipcRenderer.invoke('network:testProxyConnection', proxyUrl)
|
||||
},
|
||||
}
|
||||
|
||||
// Cache API - 缓存管理
|
||||
interface CacheDirectoryInfo {
|
||||
id: string
|
||||
@@ -1057,6 +1086,7 @@ if (process.contextIsolated) {
|
||||
contextBridge.exposeInMainWorld('llmApi', llmApi)
|
||||
contextBridge.exposeInMainWorld('agentApi', agentApi)
|
||||
contextBridge.exposeInMainWorld('cacheApi', cacheApi)
|
||||
contextBridge.exposeInMainWorld('networkApi', networkApi)
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
@@ -1077,4 +1107,6 @@ if (process.contextIsolated) {
|
||||
window.agentApi = agentApi
|
||||
// @ts-ignore (define in dts)
|
||||
window.cacheApi = cacheApi
|
||||
// @ts-ignore (define in dts)
|
||||
window.networkApi = networkApi
|
||||
}
|
||||
|
||||
@@ -89,15 +89,6 @@ onMounted(() => {
|
||||
</UButton>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 更新策略提示 -->
|
||||
<UAlert
|
||||
class="mt-3"
|
||||
color="info"
|
||||
variant="subtle"
|
||||
icon="i-heroicons-light-bulb"
|
||||
title="更新策略"
|
||||
description="项目前期更新频繁,为避免打扰,小版本不会自动提醒,仅中版本以上才会推送。如需获取小版本更新,需手动点击检查更新。"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 隐私设置 -->
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { useLayoutStore } from '@/stores/layout'
|
||||
import { useColorMode } from '@vueuse/core'
|
||||
import NetworkSettingsSection from './NetworkSettingsSection.vue'
|
||||
|
||||
// Store
|
||||
const layoutStore = useLayoutStore()
|
||||
@@ -56,5 +57,8 @@ const colorModeOptions = [
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 网络设置 -->
|
||||
<NetworkSettingsSection />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -0,0 +1,210 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
|
||||
// 代理配置
|
||||
const proxyEnabled = ref(false)
|
||||
const proxyUrl = ref('')
|
||||
const proxyUrlError = ref('')
|
||||
const isSavingProxy = ref(false)
|
||||
const isTestingProxy = ref(false)
|
||||
const proxyTestResult = ref<{ success: boolean; message: string } | null>(null)
|
||||
|
||||
// 加载代理配置
|
||||
async function loadProxyConfig() {
|
||||
try {
|
||||
const config = await window.networkApi.getProxyConfig()
|
||||
proxyEnabled.value = config.enabled
|
||||
proxyUrl.value = config.url
|
||||
} catch (error) {
|
||||
console.error('获取代理配置失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 验证代理 URL 格式
|
||||
function validateProxyUrl(url: string): boolean {
|
||||
if (!url) {
|
||||
proxyUrlError.value = ''
|
||||
return true
|
||||
}
|
||||
|
||||
try {
|
||||
const parsed = new URL(url)
|
||||
if (!['http:', 'https:'].includes(parsed.protocol)) {
|
||||
proxyUrlError.value = '仅支持 http:// 或 https:// 协议'
|
||||
return false
|
||||
}
|
||||
proxyUrlError.value = ''
|
||||
return true
|
||||
} catch {
|
||||
proxyUrlError.value = '请输入有效的代理地址,格式如 http://127.0.0.1:7890'
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// 保存代理配置
|
||||
async function saveProxyConfig() {
|
||||
// 清除测试结果
|
||||
proxyTestResult.value = null
|
||||
|
||||
// 如果启用了代理但没填地址
|
||||
if (proxyEnabled.value && !proxyUrl.value.trim()) {
|
||||
proxyUrlError.value = '请输入代理地址'
|
||||
return
|
||||
}
|
||||
|
||||
// 验证格式
|
||||
if (proxyEnabled.value && !validateProxyUrl(proxyUrl.value)) {
|
||||
return
|
||||
}
|
||||
|
||||
isSavingProxy.value = true
|
||||
try {
|
||||
const result = await window.networkApi.saveProxyConfig({
|
||||
enabled: proxyEnabled.value,
|
||||
url: proxyUrl.value.trim(),
|
||||
})
|
||||
|
||||
if (!result.success) {
|
||||
proxyUrlError.value = result.error || '保存失败'
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('保存代理配置失败:', error)
|
||||
proxyUrlError.value = '保存失败'
|
||||
} finally {
|
||||
isSavingProxy.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 切换代理开关
|
||||
async function toggleProxy(enabled: boolean) {
|
||||
proxyEnabled.value = enabled
|
||||
proxyTestResult.value = null
|
||||
|
||||
// 如果关闭代理,立即保存
|
||||
if (!enabled) {
|
||||
await saveProxyConfig()
|
||||
}
|
||||
}
|
||||
|
||||
// 代理地址输入处理
|
||||
function handleProxyUrlInput() {
|
||||
proxyTestResult.value = null
|
||||
if (proxyUrl.value) {
|
||||
validateProxyUrl(proxyUrl.value)
|
||||
} else {
|
||||
proxyUrlError.value = ''
|
||||
}
|
||||
}
|
||||
|
||||
// 代理地址失去焦点时保存
|
||||
async function handleProxyUrlBlur() {
|
||||
if (proxyEnabled.value && proxyUrl.value.trim()) {
|
||||
await saveProxyConfig()
|
||||
}
|
||||
}
|
||||
|
||||
// 测试代理连接
|
||||
async function testProxyConnection() {
|
||||
if (!proxyUrl.value.trim()) {
|
||||
proxyUrlError.value = '请先输入代理地址'
|
||||
return
|
||||
}
|
||||
|
||||
if (!validateProxyUrl(proxyUrl.value)) {
|
||||
return
|
||||
}
|
||||
|
||||
isTestingProxy.value = true
|
||||
proxyTestResult.value = null
|
||||
|
||||
try {
|
||||
const result = await window.networkApi.testProxyConnection(proxyUrl.value.trim())
|
||||
proxyTestResult.value = {
|
||||
success: result.success,
|
||||
message: result.success ? '代理连接成功!' : result.error || '连接失败',
|
||||
}
|
||||
} catch (error) {
|
||||
proxyTestResult.value = {
|
||||
success: false,
|
||||
message: '测试失败:' + (error instanceof Error ? error.message : String(error)),
|
||||
}
|
||||
} finally {
|
||||
isTestingProxy.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 组件挂载时加载数据
|
||||
onMounted(() => {
|
||||
loadProxyConfig()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<h3 class="mb-3 flex items-center gap-2 text-sm font-semibold text-gray-900 dark:text-white">
|
||||
<UIcon name="i-heroicons-globe-alt" class="h-4 w-4 text-cyan-500" />
|
||||
网络设置
|
||||
</h3>
|
||||
<div class="rounded-lg border border-gray-200 bg-gray-50 p-4 dark:border-gray-700 dark:bg-gray-800/50">
|
||||
<!-- 代理开关 -->
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-sm font-medium text-gray-900 dark:text-white">启用代理</p>
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400">通过代理服务器连接网络(适用于需要代理的网络环境)</p>
|
||||
</div>
|
||||
<USwitch :model-value="proxyEnabled" @update:model-value="toggleProxy" />
|
||||
</div>
|
||||
|
||||
<!-- 代理地址输入 -->
|
||||
<div v-if="proxyEnabled" class="mt-4 space-y-3">
|
||||
<div>
|
||||
<label class="mb-1.5 block text-xs font-medium text-gray-700 dark:text-gray-300"> 代理地址 </label>
|
||||
<UInput
|
||||
v-model="proxyUrl"
|
||||
placeholder="http://127.0.0.1:7890"
|
||||
:color="proxyUrlError ? 'error' : 'neutral'"
|
||||
size="sm"
|
||||
class="w-full"
|
||||
@input="handleProxyUrlInput"
|
||||
@blur="handleProxyUrlBlur"
|
||||
/>
|
||||
<p v-if="proxyUrlError" class="mt-1 text-xs text-red-500">
|
||||
{{ proxyUrlError }}
|
||||
</p>
|
||||
<p v-else class="mt-1 text-xs text-gray-400">支持 HTTP/HTTPS 代理,格式如:http://127.0.0.1:7890</p>
|
||||
</div>
|
||||
|
||||
<!-- 测试连接按钮和结果 -->
|
||||
<div class="flex items-center gap-3">
|
||||
<UButton
|
||||
:loading="isTestingProxy"
|
||||
:disabled="isTestingProxy || !proxyUrl.trim()"
|
||||
color="neutral"
|
||||
variant="soft"
|
||||
size="sm"
|
||||
@click="testProxyConnection"
|
||||
>
|
||||
<UIcon name="i-heroicons-signal" class="mr-1 h-4 w-4" />
|
||||
{{ isTestingProxy ? '测试中...' : '测试连接' }}
|
||||
</UButton>
|
||||
|
||||
<div v-if="proxyTestResult" class="flex items-center gap-1.5">
|
||||
<UIcon
|
||||
:name="proxyTestResult.success ? 'i-heroicons-check-circle' : 'i-heroicons-x-circle'"
|
||||
:class="['h-4 w-4', proxyTestResult.success ? 'text-green-500' : 'text-red-500']"
|
||||
/>
|
||||
<span
|
||||
:class="[
|
||||
'text-xs',
|
||||
proxyTestResult.success ? 'text-green-600 dark:text-green-400' : 'text-red-600 dark:text-red-400',
|
||||
]"
|
||||
>
|
||||
{{ proxyTestResult.message }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user