From 30d31fb31838562dd12fee75c1c0168a89824f4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E8=87=AA=E8=BE=BE?= <980324341@qq.com> Date: Fri, 11 Sep 2026 16:09:56 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E4=BB=A3=E7=90=86):=20=E4=BB=A3=E7=90=86?= =?UTF-8?q?=E9=85=8D=E7=BD=AE=E6=8C=89=E7=99=BB=E5=BD=95=E7=94=A8=E6=88=B7?= =?UTF-8?q?=E9=9A=94=E7=A6=BB=E2=80=94=E2=80=94=E6=A1=8C=E9=9D=A2=E7=AB=AF?= =?UTF-8?q?=20current=5Fuid=20=E7=99=BB=E5=BD=95=E5=90=8C=E6=AD=A5=20+=20b?= =?UTF-8?q?rand=20=E9=A1=B5=20proxy=5Fusers[uid]=20=E8=AF=BB=E5=86=99?= =?UTF-8?q?=EF=BC=8C=E5=90=84=E7=94=A8=E6=88=B7=E5=90=84=E8=87=AA=E4=BB=A3?= =?UTF-8?q?=E7=90=86=E6=B1=A0=E5=90=84=E8=87=AA=E8=AE=A1=E8=B4=B9=E5=A4=8D?= =?UTF-8?q?=E7=94=A8=EF=BC=88=E6=9C=AA=E7=99=BB=E5=BD=95=E5=9B=9E=E9=80=80?= =?UTF-8?q?=E5=85=A8=E5=B1=80=20proxy=5Furl=20=E5=85=BC=E5=AE=B9=E6=97=A7?= =?UTF-8?q?=E9=85=8D=E7=BD=AE=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../BrandApiSecretSettingsButton.vue | 48 +++++++++++++++---- .../src/pages/login/DesktopLoginPage.vue | 20 ++++++++ frontend-vue/src/shared/bridges/pywebview.ts | 2 + 3 files changed, 61 insertions(+), 9 deletions(-) diff --git a/frontend-vue/src/pages/brand/components/BrandApiSecretSettingsButton.vue b/frontend-vue/src/pages/brand/components/BrandApiSecretSettingsButton.vue index 3b9ec8cd..a55c7394 100644 --- a/frontend-vue/src/pages/brand/components/BrandApiSecretSettingsButton.vue +++ b/frontend-vue/src/pages/brand/components/BrandApiSecretSettingsButton.vue @@ -163,7 +163,7 @@ import { type ApiSecretModuleKey, type ApiSecretRetention, } from '@/shared/utils/api-secret-store' -import { getPywebviewApi, type ProxyMode } from '@/shared/bridges/pywebview' +import { getPywebviewApi, type ProxyMode, type DesktopConfigUpdate } from '@/shared/bridges/pywebview' type SecretState = { value: string @@ -275,6 +275,39 @@ function clearSecret(moduleKey: ApiSecretModuleKey) { ElMessage.success('已清空密钥') } +function proxyUserId() { + if (typeof window === 'undefined') return '0' + return window.localStorage.getItem('uid') || '0' +} + +/** 代理地址按登录用户隔离:proxy_users[uid],各自计费各自复用; + * 未登录(uid=0)回退全局 proxy_url(兼容旧版本已保存的配置)。 */ +function readUserProxy(config: Record | null | undefined) { + const uid = proxyUserId() + if (uid !== '0') { + const users = (config?.proxy_users ?? {}) as Record + const own = (users[uid] ?? {}) as Record + return { + url: typeof own.proxy_url === 'string' ? own.proxy_url : '', + mode: Number(own.proxy_mode) === 2 ? 2 : 1, + } + } + return { + url: typeof config?.proxy_url === 'string' ? config.proxy_url : '', + mode: Number(config?.proxy_mode) === 2 ? 2 : 1, + } +} + +function userProxyPatch(nextProxyUrl: string, nextProxyMode: ProxyMode) { + const uid = proxyUserId() + if (uid === '0') { + return { proxy_url: nextProxyUrl, proxy_mode: nextProxyMode } + } + const users: Record = {} + users[uid] = { proxy_url: nextProxyUrl, proxy_mode: nextProxyMode } + return { proxy_users: users } +} + async function loadProxyConfig() { const requestId = ++proxyLoadRequestId proxyLoading.value = true @@ -294,10 +327,9 @@ async function loadProxyConfig() { try { const config = await api.read_config() if (requestId !== proxyLoadRequestId || !dialogVisible.value) return - const nextUrl = typeof config?.proxy_url === 'string' ? config.proxy_url : '' - const nextMode: ProxyMode = Number(config?.proxy_mode) === 2 ? 2 : 1 - proxyUrl.value = nextUrl - proxyMode.value = nextMode + const own = readUserProxy(config as Record) + proxyUrl.value = own.url + proxyMode.value = (own.mode === 2 ? 2 : 1) as ProxyMode proxyReady.value = true } catch (error) { if (requestId !== proxyLoadRequestId || !dialogVisible.value) return @@ -331,10 +363,8 @@ async function saveAll() { if (shouldSaveProxy) { const api = getPywebviewApi() if (!api?.save_config) throw new Error('当前客户端未提供代理配置保存能力') - await api.save_config({ - proxy_url: nextProxyUrl, - proxy_mode: nextProxyMode, - }) + // 按登录用户保存:proxy_users[uid](未登录回退全局 proxy_url 字段) + await api.save_config(userProxyPatch(nextProxyUrl, nextProxyMode) as DesktopConfigUpdate) proxyUrl.value = nextProxyUrl proxyDirty.value = false } diff --git a/frontend-vue/src/pages/login/DesktopLoginPage.vue b/frontend-vue/src/pages/login/DesktopLoginPage.vue index abcec4d5..1d912664 100644 --- a/frontend-vue/src/pages/login/DesktopLoginPage.vue +++ b/frontend-vue/src/pages/login/DesktopLoginPage.vue @@ -333,6 +333,17 @@ async function submitLogin() { } catch { /* 忽略 */ } + // 同步登录用户到桌面客户端配置:Python 端按 uid 选用该用户自己的 + // 代理密钥/代理池(各自计费、各自复用省钱)。网页形态无 pywebview + // 桥,静默跳过。 + try { + const bridge = (window as unknown as { pywebview?: { api?: { save_config?: (data: Record) => Promise } } }).pywebview + if (bridge?.api && typeof bridge.api.save_config === 'function') { + void bridge.api.save_config({ current_uid: String(data.userId) }) + } + } catch { + /* 忽略 */ + } } if (data.username) { try { @@ -365,6 +376,15 @@ onMounted(() => { } catch { /* 忽略 */ } + // 同步清掉客户端的登录用户标记:Python 端回退到全局/默认代理池 + try { + const bridge = (window as unknown as { pywebview?: { api?: { save_config?: (data: Record) => Promise } } }).pywebview + if (bridge?.api && typeof bridge.api.save_config === 'function') { + void bridge.api.save_config({ current_uid: '' }) + } + } catch { + /* 忽略 */ + } // 显式登出/切号后不再自动登录(保留记住的账号,仅关闭自动登录) autoLogin.value = false lsSet(AUTO_LOGIN_KEY, '0') diff --git a/frontend-vue/src/shared/bridges/pywebview.ts b/frontend-vue/src/shared/bridges/pywebview.ts index b2c8e8e7..c94449b2 100644 --- a/frontend-vue/src/shared/bridges/pywebview.ts +++ b/frontend-vue/src/shared/bridges/pywebview.ts @@ -56,6 +56,8 @@ export interface DesktopConfig { export interface DesktopConfigUpdate { proxy_url?: string; proxy_mode?: ProxyMode; + current_uid?: string; + proxy_users?: Record; aliprice_usename?: string; aliprice_pwd?: string; }