add trans hack
This commit is contained in:
		| @ -79,6 +79,10 @@ class WindowController { | ||||
|   async saveProxyInfo(args, event) { | ||||
|     return await windowService.saveProxyInfo(args, event); | ||||
|   } | ||||
|   // 测试代理连通性 | ||||
|   async testProxy(args, event) { | ||||
|     return await windowService.testProxy(args, event); | ||||
|   } | ||||
|   //打开当前会话控制台 | ||||
|   async openSessionDevTools(args, event) { | ||||
|     return await windowService.openSessionDevTools(args, event); | ||||
|  | ||||
| @ -47,6 +47,10 @@ contextBridge.exposeInMainWorld("electronAPI", { | ||||
|   statusUpdateNotify: (args) => { | ||||
|     return ipcRenderer.invoke("status-update-notify", args); | ||||
|   }, | ||||
|   detectLanguage: (args) => { | ||||
|     return ipcRenderer.invoke("detect-language", args); | ||||
|   }, | ||||
|  | ||||
|  | ||||
|   onMessageFromMain: (callback) => ipcRenderer.on('message-from-main', (event, message) => callback(message)), | ||||
| }); | ||||
|  | ||||
| @ -295,7 +295,7 @@ const ipcMainListener = () => { | ||||
|     const { platform, msgCount } = args; | ||||
|     const senderId = event.sender.id; | ||||
|     const mainWin = getMainWindow(); | ||||
|      | ||||
|  | ||||
|     // 校验主窗口是否存在且未被销毁 | ||||
|     if (!mainWin || mainWin.isDestroyed()) { | ||||
|       return; // 主窗口不存在时终止处理 | ||||
| @ -353,6 +353,27 @@ const ipcMainListener = () => { | ||||
|     return { status: true, data: app.wsBaseUrl }; | ||||
|   }); | ||||
|  | ||||
|  | ||||
|   // 语言检测(转发到后端) | ||||
|   ipcMain.handle("detect-language", async (event, args) => { | ||||
|     try { | ||||
|       const text = args?.text; | ||||
|       const texts = args?.texts; | ||||
|       const params = texts ? {} : { params: { text } }; | ||||
|       const url = app.baseUrl + '/detect_language'; | ||||
|       const { get, post } = require('axios'); | ||||
|       let res; | ||||
|       if (texts) { | ||||
|         res = await post(url, { texts }, { timeout: 15000 }); | ||||
|       } else { | ||||
|         res = await get(url, params, { timeout: 10000 }); | ||||
|       } | ||||
|       return res.data || { code: 5000, message: 'no data' }; | ||||
|     } catch (e) { | ||||
|       return { code: 5000, message: String(e) }; | ||||
|     } | ||||
|   }); | ||||
|  | ||||
|   // 增强监控功能的IPC处理器 | ||||
|  | ||||
|   // 头像变化通知 | ||||
|  | ||||
| @ -72,9 +72,11 @@ const initializeDatabase = async () => { | ||||
|         chineseDetectionStatus: "TEXT", | ||||
|         translatePreview: "TEXT", | ||||
|         interceptChinese: "TEXT", | ||||
|         interceptLanguages: "TEXT", | ||||
|         translateHistory: "TEXT", | ||||
|         autoTranslateGroupMessage: "TEXT", | ||||
|         historyTranslateRoute: "TEXT", | ||||
|         usePersonalConfig: 'TEXT DEFAULT "true"', | ||||
|       }, | ||||
|       constraints: [], | ||||
|     }, | ||||
|  | ||||
| @ -152,11 +152,45 @@ const getNewMsgCount = () => { | ||||
| onlineStatusCheck(); | ||||
| //========================用户基本信息获取结束============ | ||||
|  | ||||
| //中文检测 | ||||
| const containsChinese = (text)=> { | ||||
|     const regex = /[\u4e00-\u9fa5]/;  // 匹配中文字符的正则表达式 | ||||
|     return regex.test(text);  // 如果包含中文字符返回 true,否则返回 false | ||||
| // 语言检测与拦截支持 | ||||
| const containsChinese = (text)=> /[\u4e00-\u9fa5]/.test(text); | ||||
| const containsJapanese = (text)=> /[\u3040-\u30ff]/.test(text); | ||||
| const containsKorean = (text)=> /[\uac00-\ud7af]/i.test(text); | ||||
| const containsArabic = (text)=> /[\u0600-\u06FF\u0750-\u077F\u08A0-\u08FF]/.test(text); | ||||
| const containsRussian = (text)=> /[\u0400-\u04FF\u0500-\u052F]/.test(text); | ||||
| const isLikelyEnglish = (text)=> { | ||||
|   if (!text) return false; | ||||
|   if (/[^\x00-\x7F]/.test(text)) return false; | ||||
|   return /[A-Za-z]/.test(text); | ||||
| } | ||||
| const detectLanguageSet = (text = '') => { | ||||
|   const set = new Set(); | ||||
|   if (!text) return set; | ||||
|   if (containsChinese(text)) set.add('zh'); | ||||
|   if (containsJapanese(text)) set.add('ja'); | ||||
|   if (containsKorean(text)) set.add('ko'); | ||||
|   if (containsArabic(text)) set.add('ar'); | ||||
|   if (containsRussian(text)) set.add('ru'); | ||||
|   if (isLikelyEnglish(text)) set.add('en'); | ||||
|   if (/[àâäæçéèêëîïôœùûüÿñ¡¿]/i.test(text)) { | ||||
|     if (/ñ|¡|¿|á|é|í|ó|ú/i.test(text)) set.add('es'); | ||||
|     if (/ç|à|â|ä|é|è|ê|ë|î|ï|ô|œ|ù|û|ü|ÿ/i.test(text)) set.add('fr'); | ||||
|     if (/ã|õ|á|é|í|ó|ú/i.test(text)) set.add('pt'); | ||||
|   } | ||||
|   return set; | ||||
| }; | ||||
| const parseInterceptLanguages = (val) => { | ||||
|   if (!val) return []; | ||||
|   if (Array.isArray(val)) return val.filter(Boolean); | ||||
|   return String(val).split(',').map(s=>s.trim()).filter(Boolean); | ||||
| }; | ||||
| const shouldInterceptByLanguages = (text) => { | ||||
|   if (!trcConfig || trcConfig.interceptChinese !== 'true') return false; | ||||
|   const list = parseInterceptLanguages(trcConfig.interceptLanguages); | ||||
|   const targets = list.length ? list : ['zh']; | ||||
|   const found = detectLanguageSet(text); | ||||
|   return targets.some(code=>found.has(code)); | ||||
| }; | ||||
| const sendMsg = ()=> { | ||||
|     let sendButton = document.querySelectorAll('button.Button.send.main-button.default.secondary.round.click-allowed')[0] | ||||
|     if (sendButton) { | ||||
| @ -178,7 +212,8 @@ const sendTranslate = async (text,to)=>{ | ||||
|             styledTextarea.setTranslateStatus(true) | ||||
|             styledTextarea.setIsProcessing(false); | ||||
|         }else { | ||||
|             styledTextarea.setContent(res.message); | ||||
|             alert('翻译失败,未发送。请稍后重试或更换翻译通道。'); | ||||
|             styledTextarea.setContent('...'); | ||||
|             styledTextarea.setTranslateStatus(false) | ||||
|             styledTextarea.setIsProcessing(false); | ||||
|         } | ||||
| @ -372,7 +407,8 @@ const addTranslateListener = () => { | ||||
|                         styledTextarea.setContent('...'); | ||||
|                     },500) | ||||
|                 }else { | ||||
|                     styledTextarea.setContent(res.message); | ||||
|                     alert('翻译失败,未发送。请稍后重试或更换翻译通道。'); | ||||
|                     styledTextarea.setContent('...'); | ||||
|                     isProcessing = false; | ||||
|                     return; | ||||
|                 } | ||||
| @ -630,7 +666,8 @@ const monitorMainNode = ()=> { | ||||
|                 rightDiv.style.display = ''; | ||||
|             }else { | ||||
|                 leftDiv.style.color = 'red'; | ||||
|                 leftDiv.textContent = `${res.message}`; | ||||
|                 leftDiv.textContent = '翻译失败'; | ||||
|                 alert('翻译失败,未发送。请稍后重试或更换翻译通道。'); | ||||
|                 rightDiv.style.display = ''; | ||||
|             } | ||||
|         }); | ||||
|  | ||||
| @ -120,7 +120,8 @@ const sendTranslate = async (text,to)=>{ | ||||
|             styledTextarea.setTranslateStatus(true) | ||||
|             styledTextarea.setIsProcessing(false); | ||||
|         }else { | ||||
|             styledTextarea.setContent(res.message); | ||||
|             alert('翻译失败,未发送。请稍后重试或更换翻译通道。'); | ||||
|             styledTextarea.setContent('...'); | ||||
|             styledTextarea.setTranslateStatus(false) | ||||
|             styledTextarea.setIsProcessing(false); | ||||
|         } | ||||
| @ -320,7 +321,8 @@ const addTranslateListener = () => { | ||||
|                     styledTextarea.setTranslateStatus(false) | ||||
|                     styledTextarea.setContent('...') | ||||
|                 }else { | ||||
|                     styledTextarea.setContent(res.message); | ||||
|                     alert('翻译失败,未发送。请稍后重试或更换翻译通道。'); | ||||
|                     styledTextarea.setContent('...'); | ||||
|                     isProcessing = false; | ||||
|                 } | ||||
|             }else { | ||||
| @ -527,7 +529,8 @@ const monitorMainNode = ()=> { | ||||
|                 rightDiv.style.display = ''; | ||||
|             }else { | ||||
|                 leftDiv.style.color = 'red'; | ||||
|                 leftDiv.textContent = `${res.message}`; | ||||
|                 leftDiv.textContent = '翻译失败'; | ||||
|                 alert('翻译失败,未发送。请稍后重试或更换翻译通道。'); | ||||
|                 rightDiv.style.display = ''; | ||||
|             } | ||||
|         }); | ||||
| @ -558,7 +561,8 @@ const monitorMainNode = ()=> { | ||||
|                 rightDiv.style.display = ''; | ||||
|             }else { | ||||
|                 leftDiv.style.color = 'red'; | ||||
|                 leftDiv.textContent = `${res.message}`; | ||||
|                 leftDiv.textContent = '翻译失败'; | ||||
|                 alert('翻译失败,未发送。请稍后重试或更换翻译通道。'); | ||||
|                 rightDiv.style.display = ''; | ||||
|             } | ||||
|         } | ||||
|  | ||||
| @ -303,15 +303,98 @@ const getNewMsgCount = () => { | ||||
|   } | ||||
| }; | ||||
| onlineStatusCheck(); | ||||
| //中文检测 | ||||
| const containsChinese = (text) => { | ||||
|   const regex = /[\u4e00-\u9fa5]/; // 匹配中文字符的正则表达式 | ||||
|   return regex.test(text); // 如果包含中文字符返回 true,否则返回 false | ||||
| // 语言检测与拦截支持 | ||||
| // 中文检测(兼容旧逻辑) | ||||
| const containsChinese = (text) => /[\u4e00-\u9fa5]/.test(text); | ||||
| // 日语:平假名、片假名 | ||||
| const containsJapanese = (text) => /[\u3040-\u30ff]/.test(text); | ||||
| // 韩语:谚文字 | ||||
| const containsKorean = (text) => /[\uac00-\ud7af]/i.test(text); | ||||
| // 阿拉伯语 | ||||
| const containsArabic = (text) => /[\u0600-\u06FF\u0750-\u077F\u08A0-\u08FF]/.test(text); | ||||
| // 俄语(西里尔) | ||||
| const containsRussian = (text) => /[\u0400-\u04FF\u0500-\u052F]/.test(text); | ||||
| // 粗略英语:仅 ASCII 可打印字符,且不含其他脚本 | ||||
| const isLikelyEnglish = (text) => { | ||||
|   if (!text) return false; | ||||
|   // 若包含其它明显脚本则不判为英文 | ||||
|   if (containsChinese(text) || containsJapanese(text) || containsKorean(text) || containsArabic(text) || containsRussian(text)) { | ||||
|     return false; | ||||
|   } | ||||
|   // 只要包含英文字母即可(允许存在表情、全角标点等非 ASCII) | ||||
|   return /[A-Za-z]/.test(text); | ||||
| }; | ||||
| const sendMsg = () => { | ||||
|  | ||||
| const detectLanguageSet = (text = '') => { | ||||
|   const set = new Set(); | ||||
|   if (!text) return set; | ||||
|   if (containsChinese(text)) set.add('zh'); | ||||
|   if (containsJapanese(text)) set.add('ja'); | ||||
|   if (containsKorean(text)) set.add('ko'); | ||||
|   if (containsArabic(text)) set.add('ar'); | ||||
|   if (containsRussian(text)) set.add('ru'); | ||||
|   if (isLikelyEnglish(text)) set.add('en'); | ||||
|   // 其它拉丁语种(fr/es/pt等)可根据重音字符大致判断 | ||||
|   if (/[àâäæçéèêëîïôœùûüÿñ¡¿]/i.test(text)) { | ||||
|     // 常见重音组合,合并处理为拉丁扩展族 | ||||
|     // 为简单起见将其归到 fr/es/pt 共同集合 | ||||
|     if (/ñ|¡|¿|á|é|í|ó|ú/i.test(text)) set.add('es'); | ||||
|     if (/ç|à|â|ä|é|è|ê|ë|î|ï|ô|œ|ù|û|ü|ÿ/i.test(text)) set.add('fr'); | ||||
|     if (/ã|õ|á|é|í|ó|ú/i.test(text)) set.add('pt'); | ||||
|   } | ||||
|   return set; | ||||
| }; | ||||
|  | ||||
| const normalizeLang = (code) => { | ||||
|   const s = String(code || '').toLowerCase(); | ||||
|   if (!s) return ''; | ||||
|   if (s.startsWith('zh')) return 'zh'; | ||||
|   return s.split('-')[0]; | ||||
| }; | ||||
| const parseInterceptLanguages = (val) => { | ||||
|   if (!val) return []; | ||||
|   if (Array.isArray(val)) return val.map(normalizeLang).filter(Boolean); | ||||
|   // 以逗号存储 | ||||
|   return String(val) | ||||
|     .split(',') | ||||
|     .map((s) => s.trim()) | ||||
|     .map(normalizeLang) | ||||
|     .filter(Boolean); | ||||
| }; | ||||
|  | ||||
| const shouldInterceptByLanguages = async (text) => { | ||||
|   if (!trcConfig || trcConfig.interceptChinese !== 'true') return false; | ||||
|   const list = parseInterceptLanguages(trcConfig.interceptLanguages); | ||||
|   const targets = list.length ? list : ['zh']; | ||||
|   try { | ||||
|     const res = await ipc.detectLanguage({ text }); | ||||
|     if (res && res.code === 2000 && res.data) { | ||||
|       const lang = String(res.data.lang || 'und').toLowerCase(); | ||||
|       if (lang !== 'und') { | ||||
|         return targets.includes(lang); | ||||
|       } | ||||
|     } | ||||
|   } catch (e) {} | ||||
|   // 回退到本地粗略判断 | ||||
|   const found = detectLanguageSet(text); | ||||
|   return targets.some((code) => found.has(code)); | ||||
| }; | ||||
|  | ||||
| // 错误消息检测:拦截把错误当消息发送 | ||||
| const isErrorText = (text) => { | ||||
|   if (!text) return false; | ||||
|   const s = String(text); | ||||
|   const patterns = [ | ||||
|     '翻译失败', '请求失败', 'Client Error', 'Unauthorized', | ||||
|     'HTTPConnectionPool', 'timeout', '超时', '错误', 'Error', | ||||
|   ]; | ||||
|   return patterns.some((p) => s.includes(p)); | ||||
| }; | ||||
|  | ||||
| const sendMsg = async () => { | ||||
|   let sendButton = getSendBtn(); | ||||
|   // 新增:拦截中文逻辑 | ||||
|   if (trcConfig.interceptChinese === "true") { | ||||
|   if (trcConfig.interceptChinese === "true" || true) { | ||||
|     // 从 footer 开始查找输入框 | ||||
|     const footer = document.querySelector("footer._ak1i"); | ||||
|     if (!footer) { | ||||
| @ -322,7 +405,22 @@ const sendMsg = () => { | ||||
|     const richTextInput = footer.querySelector( | ||||
|       '.lexical-rich-text-input div[contenteditable="true"]' | ||||
|     ); | ||||
|     if (containsChinese(richTextInput.textContent)) { | ||||
|     let content = richTextInput?.textContent?.trim() || ''; | ||||
|     if (!content) { | ||||
|       const plainInput = document.querySelector('footer div[aria-owns="emoji-suggestion"][contenteditable="true"]'); | ||||
|       content = plainInput?.textContent?.trim() || ''; | ||||
|     } | ||||
|     // 1) 多语言拦截(兼容旧:仅中文) | ||||
|     try { | ||||
|       if (trcConfig.interceptChinese === "true" && (await shouldInterceptByLanguages(content))) { | ||||
|         alert("检测到被拦截语言内容,已阻止发送"); | ||||
|         return; | ||||
|       } | ||||
|     } catch (e) {} | ||||
|  | ||||
|     // 2) 错误内容拦截(翻译失败/接口错误等) | ||||
|     if (isErrorText(content)) { | ||||
|       alert("翻译失败,未发送。请稍后重试或更换翻译通道。"); | ||||
|       return; | ||||
|     } | ||||
|   } | ||||
| @ -374,7 +472,9 @@ const sendTranslate = async (text, to) => { | ||||
|       styledTextarea.setTranslateStatus(true); | ||||
|       styledTextarea.setIsProcessing(false); | ||||
|     } else { | ||||
|       styledTextarea.setContent(res.message); | ||||
|       // 不要把错误写入输入框或发送,弹窗提醒 | ||||
|       alert('翻译失败,未发送。请稍后重试或更换翻译通道。'); | ||||
|       styledTextarea.setContent('...'); | ||||
|       styledTextarea.setTranslateStatus(false); | ||||
|       styledTextarea.setIsProcessing(false); | ||||
|     } | ||||
| @ -648,23 +748,16 @@ const addTranslateListener = () => { | ||||
|  | ||||
|   //获取whatsapp输入框内容 | ||||
|   const whatsappContent = async () => { | ||||
|     let element = document.evaluate( | ||||
|       '//*[@id="main"]/footer/div[1]/div[2]/div/span/div/div[2]/div/div[3]', | ||||
|       document, | ||||
|       null, | ||||
|       XPathResult.FIRST_ORDERED_NODE_TYPE, | ||||
|       null | ||||
|     ).singleNodeValue; | ||||
|  | ||||
|     if (element) { | ||||
|       return element.innerText | ||||
|         .split("\n") | ||||
|         .map((line) => line.trim()) // 去掉首尾空格 | ||||
|         .filter((line) => line.length) // 去掉空行 | ||||
|         .join("\n"); | ||||
|     // 优先:富文本输入框 | ||||
|     const footer = document.querySelector("footer._ak1i"); | ||||
|     const rich = footer?.querySelector('.lexical-rich-text-input div[contenteditable="true"]'); | ||||
|     let text = rich?.textContent?.trim() || ''; | ||||
|     if (!text) { | ||||
|       // 退化:普通 contenteditable | ||||
|       const plain = document.querySelector('footer div[aria-owns="emoji-suggestion"][contenteditable="true"]'); | ||||
|       text = plain?.textContent?.trim() || ''; | ||||
|     } | ||||
|  | ||||
|     return ""; | ||||
|     return text; | ||||
|   }; | ||||
|   //判断是否需要翻译后发送 | ||||
|   async function handleSendTranslateFlow() { | ||||
| @ -676,10 +769,16 @@ const addTranslateListener = () => { | ||||
|     const sendTranslateStatus = trcConfig.sendTranslateStatus === "true"; | ||||
|     const sendPreview = trcConfig.translatePreview === "true"; | ||||
|     const translateStatus = styledTextarea.getTranslateStatus(); | ||||
|     const textContent = await whatsappContent(); | ||||
|     const textContent = (await whatsappContent())?.trim(); | ||||
|  | ||||
|     // 拦截:在翻译/发送前就判断原始输入语言 | ||||
|     if (trcConfig.interceptChinese === "true" && (await shouldInterceptByLanguages(textContent))) { | ||||
|       alert("检测到被拦截语言内容,已阻止发送"); | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     // 跳过空消息 | ||||
|     if (!textContent || textContent.trim() === "") return; | ||||
|     if (!textContent) return; | ||||
|  | ||||
|     styledTextarea.setIsProcessing(true); | ||||
|     updateSendButtonState(true); | ||||
| @ -1381,8 +1480,10 @@ const monitorMainNode = () => { | ||||
|     // 插入到消息元素右侧 | ||||
|     msgSpan.appendChild(translateDiv); | ||||
|  | ||||
|     // 检查是否已有翻译缓存并显示 | ||||
|     await checkAndDisplayExistingTranslation(text, leftDiv, rightDiv); | ||||
|     // 检查是否已有翻译缓存并显示(仅在允许显示时) | ||||
|     if (trcConfig && (trcConfig.receiveTranslateStatus === "true" || trcConfig.translateHistory === "true")) { | ||||
|       await checkAndDisplayExistingTranslation(text, leftDiv, rightDiv); | ||||
|     } | ||||
|  | ||||
|     const receiveTranslateStatus = trcConfig.receiveTranslateStatus; | ||||
|     if (receiveTranslateStatus === "true") { | ||||
|  | ||||
| @ -111,6 +111,17 @@ class TranslateService { | ||||
|           configById.translateRoute = 'youDao'; | ||||
|           console.log('已修复用户配置translateRoute为youDao'); | ||||
|         } | ||||
|         // 如果关闭了个人配置,则优先使用平台全局配置 | ||||
|         if (configById.usePersonalConfig === 'false') { | ||||
|           const globalConfig = await app.sdb.selectOne('translate_config', { platform: platform }) | ||||
|           if (globalConfig) { | ||||
|             if (!globalConfig.translateRoute || globalConfig.translateRoute === 'null') { | ||||
|               await app.sdb.update('translate_config', { translateRoute: 'youDao' }, { platform: platform }); | ||||
|               globalConfig.translateRoute = 'youDao'; | ||||
|             } | ||||
|             return { status: true, message: '查询成功(使用全局配置)', data: globalConfig } | ||||
|           } | ||||
|         } | ||||
|         return { status: true, message: '查询成功', data: configById } | ||||
|       } else { | ||||
|         const config = await app.sdb.selectOne('translate_config', { platform: platform }) | ||||
| @ -180,6 +191,7 @@ class TranslateService { | ||||
|           chineseDetectionStatus: "false", | ||||
|           translatePreview: "false", | ||||
|           interceptChinese: "false", | ||||
|           interceptLanguages: "", | ||||
|           translateHistory: "false", | ||||
|           autoTranslateGroupMessage: "false", | ||||
|           historyTranslateRoute: defaultHistoryTranslateRoute | ||||
| @ -316,9 +328,18 @@ class TranslateService { | ||||
|     if (!key) throw new Error('缺少必要参数:key'); | ||||
|     if (typeof value === 'undefined') return; | ||||
|     try { | ||||
|       // 规范化特殊字段 | ||||
|       let normalizedValue = value; | ||||
|       if (key === 'interceptLanguages') { | ||||
|         if (Array.isArray(value)) { | ||||
|           normalizedValue = value.join(','); | ||||
|         } else if (typeof value === 'object' && value !== null) { | ||||
|           normalizedValue = Object.values(value).join(','); | ||||
|         } | ||||
|       } | ||||
|       // 构建更新对象(使用动态属性名) | ||||
|       const updateData = { | ||||
|         [key]: value | ||||
|         [key]: normalizedValue | ||||
|       }; | ||||
|       console.log('updateData', updateData) | ||||
|       // 执行更新 | ||||
| @ -931,9 +952,11 @@ class TranslateService { | ||||
|       chineseDetectionStatus: "false", | ||||
|       translatePreview: "false", | ||||
|       interceptChinese: "false", | ||||
|       interceptLanguages: "", | ||||
|       translateHistory: "false", | ||||
|       autoTranslateGroupMessage: "false", | ||||
|       historyTranslateRoute: defaultHistoryTranslateRoute | ||||
|       historyTranslateRoute: defaultHistoryTranslateRoute, | ||||
|       usePersonalConfig: 'true' | ||||
|     } | ||||
|     await app.sdb.insert('translate_config', initialData); | ||||
|   } | ||||
| @ -957,9 +980,6 @@ class TranslateService { | ||||
|             enName: "Youdao Translate", | ||||
|             otherArgs: `{"apiUrl": "https://openapi.youdao.com/api","apiKey": "","secretKey": ""}`, | ||||
|             enable: 1 | ||||
|           }, | ||||
|             otherArgs: `{"apiUrl": "https://openapi.youdao.com/api","appId": "","apiKey": ""}`, | ||||
|             enable: 1 | ||||
|           }, | ||||
|           { | ||||
|             name: "tengXun", | ||||
|  | ||||
| @ -16,7 +16,7 @@ const { | ||||
| } = require("../utils/CommonUtils"); | ||||
| const { getMainWindow } = require("ee-core/electron"); | ||||
| const { get } = require("axios"); | ||||
|  | ||||
| const { net } = require("electron"); | ||||
| const { sockProxyRules } = require("electron-session-proxy"); | ||||
|  | ||||
| class WindowService { | ||||
| @ -860,6 +860,106 @@ class WindowService { | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * 测试代理连接是否可用(不修改现有会话配置) | ||||
|    * @param args { proxyType, proxyIp, proxyPort, userVerifyStatus, username, password } | ||||
|    */ | ||||
|   async testProxy(args, event) { | ||||
|     try { | ||||
|       const { | ||||
|         proxyType = "http", | ||||
|         proxyIp = "", | ||||
|         proxyPort = "", | ||||
|         userVerifyStatus = "false", | ||||
|         username = "", | ||||
|         password = "", | ||||
|       } = args || {}; | ||||
|  | ||||
|       if (!proxyIp || !proxyPort) { | ||||
|         return { status: false, message: "请填写完整的代理主机和端口" }; | ||||
|       } | ||||
|  | ||||
|       let server = `${String(proxyIp).trim()}:${String(proxyPort).trim()}`; | ||||
|       let proxyRules; | ||||
|  | ||||
|       const needAuth = userVerifyStatus === "true" && username && password; | ||||
|       if (needAuth) { | ||||
|         server = `${String(username).trim()}:${String(password).trim()}@${server}`; | ||||
|       } | ||||
|  | ||||
|       switch (proxyType) { | ||||
|         case "http": | ||||
|         case "https": | ||||
|           proxyRules = `http://${server}`; | ||||
|           break; | ||||
|         case "socks4": | ||||
|           proxyRules = needAuth | ||||
|             ? await sockProxyRules(`socks4://${server}`) | ||||
|             : `socks4=socks4://${server}`; | ||||
|           break; | ||||
|         case "socks5": | ||||
|           proxyRules = needAuth | ||||
|             ? await sockProxyRules(`socks5://${server}`) | ||||
|             : `socks5=socks5://${server}`; | ||||
|           break; | ||||
|         default: | ||||
|           return { status: false, message: `不支持的代理类型:${proxyType}` }; | ||||
|       } | ||||
|  | ||||
|       // 创建临时会话并设置代理 | ||||
|       const partition = `proxy-test-${Date.now()}-${Math.random()}`; | ||||
|       const tmpSession = session.fromPartition(partition, { cache: false }); | ||||
|       await tmpSession.setProxy({ mode: "fixed_servers", proxyRules }); | ||||
|  | ||||
|       const testUrls = [ | ||||
|         "https://cp.cloudflare.com/generate_204", | ||||
|         "http://www.msftconnecttest.com/connecttest.txt", | ||||
|         "https://www.baidu.com/", | ||||
|       ]; | ||||
|  | ||||
|       const tryRequest = (url) => | ||||
|         new Promise((resolve) => { | ||||
|           const req = net.request({ url, session: tmpSession }); | ||||
|           const timer = setTimeout(() => { | ||||
|             try { req.abort(); } catch (e) {} | ||||
|             resolve({ ok: false, code: "ETIMEOUT" }); | ||||
|           }, 12000); | ||||
|  | ||||
|           req.on("response", (res) => { | ||||
|             clearTimeout(timer); | ||||
|             // 2xx/3xx 认为成功 | ||||
|             if (res.statusCode >= 200 && res.statusCode < 400) { | ||||
|               resolve({ ok: true, code: res.statusCode }); | ||||
|             } else { | ||||
|               resolve({ ok: false, code: res.statusCode }); | ||||
|             } | ||||
|           }); | ||||
|           req.on("error", (err) => { | ||||
|             clearTimeout(timer); | ||||
|             resolve({ ok: false, code: err.code || err.message }); | ||||
|           }); | ||||
|           req.end(); | ||||
|         }); | ||||
|  | ||||
|       let last; | ||||
|       for (const u of testUrls) { | ||||
|         last = await tryRequest(u); | ||||
|         if (last.ok) break; | ||||
|       } | ||||
|  | ||||
|       // 复原临时会话代理 | ||||
|       try { await tmpSession.setProxy({ mode: "system" }); } catch (e) {} | ||||
|  | ||||
|       if (last && last.ok) { | ||||
|         return { status: true, message: "代理连接成功" }; | ||||
|       } | ||||
|       return { status: false, message: `代理连接失败,错误码:${last?.code ?? "UNKNOWN"}` }; | ||||
|     } catch (error) { | ||||
|       logger.error("测试代理失败:", error); | ||||
|       return { status: false, message: `测试代理失败:${error.message}` }; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * 同步语言设置到webview | ||||
|    * @param {BrowserView} view - webview实例 | ||||
|  | ||||
| @ -37,6 +37,7 @@ const ipcApiRoute = { | ||||
|   editProxyInfo: 'controller/window/editProxyInfo', | ||||
|   editGlobalProxyInfo: 'controller/window/editGlobalProxyInfo', | ||||
|   saveProxyInfo: 'controller/window/saveProxyInfo', | ||||
|   testProxy: 'controller/window/testProxy', | ||||
|   openSessionDevTools: 'controller/window/openSessionDevTools', | ||||
|   closeGlobalProxyPasswordVerification: 'controller/window/closeGlobalProxyPasswordVerification', | ||||
|  | ||||
|  | ||||
| @ -34,14 +34,14 @@ export default { | ||||
|     logoutConfirm: 'Are you sure you want to logout?', | ||||
|     copySuccess: 'Copied successfully!', | ||||
|     copyFailed: 'Copy failed!', | ||||
|      | ||||
|  | ||||
|     // Account Information | ||||
|     accountInfo: 'Account Information', | ||||
|     availableChars: 'Available Characters', | ||||
|     expirationTime: 'Expiration Time', | ||||
|     remainingDays: '{days} days remaining', | ||||
|     deviceId: 'Device ID', | ||||
|      | ||||
|  | ||||
|     // Core Features | ||||
|     coreFeatures: 'Core Features', | ||||
|     features: { | ||||
| @ -62,7 +62,7 @@ export default { | ||||
|         desc: 'Local API translation for data privacy' | ||||
|       } | ||||
|     }, | ||||
|      | ||||
|  | ||||
|     // Support and Help | ||||
|     supportAndHelp: 'Support & Help', | ||||
|     officialChannel: 'liangzi Channel', | ||||
| @ -224,9 +224,13 @@ export default { | ||||
|     currentContact: 'Current Contact', | ||||
|     globalContact: 'Global Contact', | ||||
|     notFoundPersonalizedTranslation: 'No personalized translation settings found', | ||||
|     interceptChinese: 'Intercept Chinese', | ||||
|     interceptChinese: 'Enable send interception', | ||||
|     interceptLanguagesLabel: 'Intercept languages', | ||||
|     selectInterceptLanguages: 'Select languages to intercept', | ||||
|     translateHistory: 'Translation History Messages', | ||||
|     autoTranslateGroupMessage: 'Auto Translate Group Messages', | ||||
|     usePersonalConfig: 'Enable personal settings for this contact', | ||||
|  | ||||
|     createPersonalizedTranslation: 'Create Personalized Translation', | ||||
|     chooseContactFirst: 'Please select a contact first', | ||||
|     noUserId: 'Unable to get user ID, please select a conversation or refresh the page', | ||||
| @ -309,7 +313,7 @@ export default { | ||||
|     proxyConfig: 'Proxy Config', | ||||
|     devtools: 'Developer Tools', | ||||
|     expand: 'Expand', | ||||
|     collapse: 'Collapse',  | ||||
|     collapse: 'Collapse', | ||||
|     screenshot: 'Screenshot', | ||||
|   }, | ||||
|   quickReplyConfig: { | ||||
| @ -333,7 +337,7 @@ export default { | ||||
|       remark: 'Remark', | ||||
|       enterRemark: 'Please enter remark', | ||||
|       type: 'Type', | ||||
|       content: 'Content',  | ||||
|       content: 'Content', | ||||
|       enterContent: 'Please enter content', | ||||
|       text: 'Text', | ||||
|       image: 'Image', | ||||
|  | ||||
| @ -187,7 +187,10 @@ export default { | ||||
|     batchCount: '数量', | ||||
|     batchProxySettings: '批量代理设置', | ||||
|     batchProxySetSuccess: '批量代理设置成功', | ||||
|     selectSessionFirst: '请先勾选会话' | ||||
|     selectSessionFirst: '请先勾选会话', | ||||
|     testProxy: '测试代理', | ||||
|     proxyTestSuccess: '代理连接成功', | ||||
|     proxyTestFailed: '代理连接失败' | ||||
|   }, | ||||
|   translate: { | ||||
|     google: '谷歌翻译', | ||||
| @ -217,9 +220,12 @@ export default { | ||||
|     currentContact: '当前联系人', | ||||
|     globalContact: '全局联系人', | ||||
|     notFoundPersonalizedTranslation: '未找到个性化翻译设置', | ||||
|     interceptChinese: '拦截中文', | ||||
|     interceptChinese: '启用发送拦截', | ||||
|     interceptLanguagesLabel: '拦截语言', | ||||
|     selectInterceptLanguages: '请选择要拦截的语言', | ||||
|     translateHistory: '翻译历史消息', | ||||
|     autoTranslateGroupMessage: '自动翻译群消息', | ||||
|     usePersonalConfig: '启用当前联系人配置', | ||||
|     createPersonalizedTranslation: '新建个性化翻译', | ||||
|     chooseContactFirst: '请先选择联系人', | ||||
|     noUserId: '无法获取用户ID,请选择会话或刷新页面', | ||||
|  | ||||
| @ -87,6 +87,9 @@ | ||||
|       <div class="content-right"> | ||||
|         <el-input :placeholder="t('session.enterPassword')" v-model="proxyInfo.password"></el-input> | ||||
|       </div> | ||||
|       <div class="footer-actions"> | ||||
|         <el-button type="warning" @click="testGlobalProxy">测试代理</el-button> | ||||
|       </div> | ||||
|     </div> | ||||
|   </div> | ||||
| </template> | ||||
| @ -222,6 +225,27 @@ const getConfigInfo = async () => { | ||||
|   } | ||||
| } | ||||
|  | ||||
| const testGlobalProxy = async () => { | ||||
|   const args = { | ||||
|     proxyType: proxyInfo.value.proxyType, | ||||
|     proxyIp: proxyInfo.value.proxyIp, | ||||
|     proxyPort: proxyInfo.value.proxyPort, | ||||
|     userVerifyStatus: proxyInfo.value.userVerifyStatus, | ||||
|     username: proxyInfo.value.username, | ||||
|     password: proxyInfo.value.password, | ||||
|   }; | ||||
|   try { | ||||
|     const res = await ipc.invoke(ipcApiRoute.testProxy, args); | ||||
|     if (res.status) { | ||||
|       ElMessage.success(res.message || '代理连接成功'); | ||||
|     } else { | ||||
|       ElMessage.error(res.message || '代理连接失败'); | ||||
|     } | ||||
|   } catch (e) { | ||||
|     ElMessage.error('代理连接失败'); | ||||
|   } | ||||
| } | ||||
|  | ||||
| onMounted(() => { | ||||
|   getConfigInfo(); | ||||
| }) | ||||
|  | ||||
| @ -39,6 +39,16 @@ | ||||
|             </el-radio-group> | ||||
|           </div> | ||||
|         </div> | ||||
|         <!-- 仅在“当前联系人”标签下显示:是否启用个人配置 --> | ||||
|         <div class="content-container-radio" v-if="activeTab === 'current'"> | ||||
|           <div class="content-left"> | ||||
|             <el-text>{{ t('translate.usePersonalConfig') }}</el-text> | ||||
|           </div> | ||||
|           <div class="content-right"> | ||||
|             <el-switch :active-value="'true'" :inactive-value="'false'" size="default" | ||||
|                        v-model="configInfo.usePersonalConfig" /> | ||||
|           </div> | ||||
|         </div> | ||||
|         <div class="content-container-select"> | ||||
|           <div class="content-left"> | ||||
|             <el-text>{{ t('translate.route') }}</el-text> | ||||
| @ -152,12 +162,23 @@ | ||||
|           </div> | ||||
|         </div> | ||||
|         <div class="content-container-radio"> | ||||
|           <div class="content-left"> | ||||
|             <el-text>{{ t('translate.interceptChinese') }}</el-text> | ||||
|           <div class="content-left intercept-left"> | ||||
|             <el-text>{{ t('translate.interceptLanguagesLabel') }}</el-text> | ||||
|           </div> | ||||
|           <div class="content-right"> | ||||
|           <div class="content-right intercept-row"> | ||||
|             <el-switch :active-value="'true'" :inactive-value="'false'" size="default" | ||||
|               v-model="configInfo.interceptChinese" /> | ||||
|             <el-select | ||||
|               v-if="configInfo.interceptChinese === 'true'" | ||||
|               v-model="configInfo.interceptLanguages" | ||||
|               multiple | ||||
|               collapse-tags | ||||
|               :placeholder="t('translate.selectInterceptLanguages')" | ||||
|               class="intercept-lang-select" | ||||
|               @change="onInterceptLangChange" | ||||
|             > | ||||
|               <el-option v-for="item in platformLanguageList" :key="item.code" :label="item[currentLanguageName]" :value="item.code" /> | ||||
|             </el-select> | ||||
|           </div> | ||||
|         </div> | ||||
|         <el-divider /> | ||||
| @ -233,6 +254,9 @@ const getConfigInfoIsNull = computed(() => { | ||||
|   return Object.keys(configInfo.value).length === 0; | ||||
| }); | ||||
|  | ||||
| // 加载/归一化阶段守卫,避免把初始化过程的值写回数据库 | ||||
| const isApplyingConfig = ref(false); | ||||
|  | ||||
| // watch( | ||||
| //     () => configInfo.value.friendTranslateStatus, | ||||
| //     async (newValue, oldValue) => { | ||||
| @ -267,9 +291,11 @@ const propertiesToWatch = [ | ||||
|   "chineseDetectionStatus", | ||||
|   "translatePreview", | ||||
|   "interceptChinese", | ||||
|   "interceptLanguages", | ||||
|   "translateHistory", | ||||
|   "autoTranslateGroupMessage", | ||||
|   "historyTranslateRoute" | ||||
|   "historyTranslateRoute", | ||||
|   "usePersonalConfig" | ||||
| ]; | ||||
|  | ||||
| let watchers = []; // 存储所有字段的监听器 | ||||
| @ -281,6 +307,11 @@ const addWatchers = () => { | ||||
|     watch( | ||||
|       () => unref(configInfo.value[property]), | ||||
|       (newValue, oldValue) => { | ||||
|         if (isApplyingConfig.value) return; // 初始化阶段不写回 | ||||
|         if (property === 'interceptLanguages') { | ||||
|           // 由 @change 显式持久化,避免初始化或平台切换时误写空 | ||||
|           return; | ||||
|         } | ||||
|         if (newValue !== "" && newValue !== oldValue) { | ||||
|           handlePropertyChange(property, newValue); | ||||
|         } | ||||
| @ -290,9 +321,18 @@ const addWatchers = () => { | ||||
| } | ||||
| // 自定义逻辑 | ||||
| const handlePropertyChange = async (property, value) => { | ||||
|   const args = { key: property, value: value, id: configInfo.value.id, partitionId: menuStore.currentPartitionId }; | ||||
|   const id = configInfo.value?.id; | ||||
|   if (!id) return; // 未就绪不提交 | ||||
|   const args = { key: property, value: value, id, partitionId: menuStore.currentPartitionId }; | ||||
|   await ipc.invoke(ipcApiRoute.updateTranslateConfig, args); | ||||
| } | ||||
|  | ||||
| // 显式提交拦截语言的更改 | ||||
| const onInterceptLangChange = async (val) => { | ||||
|   if (isApplyingConfig.value) return; | ||||
|   const list = Array.isArray(val) ? val.filter(Boolean) : []; | ||||
|   await handlePropertyChange('interceptLanguages', list); | ||||
| } | ||||
| // 移除所有字段的监听器 | ||||
| const removeWatchers = () => { | ||||
|   watchers.forEach((stopWatcher) => stopWatcher()); // 调用每个监听器的停止方法 | ||||
| @ -314,6 +354,7 @@ watch(activeTab, async (newVal) => { | ||||
|  | ||||
| const getConfigInfo = async () => { | ||||
|   loading.value = true; // 开始加载 | ||||
|   isApplyingConfig.value = true; | ||||
|   removeWatchers(); | ||||
|  | ||||
|   let type = activeTab.value; | ||||
| @ -325,17 +366,26 @@ const getConfigInfo = async () => { | ||||
|  | ||||
|   if (type === 'global') { | ||||
|     args.platform = menuStore.platform; | ||||
|     // args.partitionId = ''; | ||||
|   } else { | ||||
|     args.userId = menuStore.currentUserId; | ||||
|     // args.partitionId = menuStore.currentPartitionId; | ||||
|   } | ||||
|  | ||||
|   try { | ||||
|     const res = await ipc.invoke(ipcApiRoute.getTrsConfig, args); | ||||
|     if (res.status) { | ||||
|       // Object.assign(configInfo.value, res.data); // 更新表单数据 | ||||
|       configInfo.value = { ...res.data }; | ||||
|       const data = { ...res.data }; | ||||
|       // 兼容旧数据:当前联系人缺少 usePersonalConfig 字段时默认启用个人配置 | ||||
|       if (activeTab.value === 'current' && (data.usePersonalConfig === undefined || data.usePersonalConfig === null || data.usePersonalConfig === '')) { | ||||
|         data.usePersonalConfig = 'true'; | ||||
|       } | ||||
|       // 兼容:逗号字符串 -> 数组 | ||||
|       if (typeof data.interceptLanguages === 'string') { | ||||
|         data.interceptLanguages = data.interceptLanguages.length > 0 | ||||
|           ? data.interceptLanguages.split(',').map(s=>s.trim()).filter(Boolean) | ||||
|           : []; | ||||
|       } | ||||
|       if (!Array.isArray(data.interceptLanguages)) data.interceptLanguages = []; | ||||
|       configInfo.value = data; | ||||
|     } else { | ||||
|       configInfo.value = {}; | ||||
|     } | ||||
| @ -343,6 +393,7 @@ const getConfigInfo = async () => { | ||||
|     configInfo.value = {}; | ||||
|     console.log(err) | ||||
|   } finally { | ||||
|     isApplyingConfig.value = false; | ||||
|     addWatchers(); | ||||
|     loading.value = false; // 加载结束 | ||||
|   } | ||||
| @ -429,72 +480,92 @@ const getLanguageList = async () => { | ||||
| watch( | ||||
|   () => configInfo.value.translateRoute, // 监听的数据源 | ||||
|   async (newValue, oldValue) => { | ||||
|     if (!newValue || typeof newValue !== 'string') { | ||||
|       console.warn("watch(translateRoute): 无效的翻译平台值或值为空。"); | ||||
|       // 如果平台值无效或为空,可以考虑清空 platformLanguageList | ||||
|       // 并且将所有语言设置重置为 "auto",因为没有可用的平台语言 | ||||
|       configInfo.value.receiveSourceLanguage = "auto"; | ||||
|       configInfo.value.receiveTargetLanguage = "auto"; | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     // 确保 languageList 已经加载,如果为空,可能需要等待数据加载或提示错误 | ||||
|     if (languageList.value.length === 0) { | ||||
|       await getLanguageList(); | ||||
|  | ||||
|       if (languageList.value.length === 0) { | ||||
|         console.warn("watch(translateRoute): 翻译平台列表为空。"); | ||||
|     isApplyingConfig.value = true; | ||||
|     try { | ||||
|       if (!newValue || typeof newValue !== 'string') { | ||||
|         console.warn("watch(translateRoute): 无效的翻译平台值或值为空。"); | ||||
|         // 如果平台值无效或为空,可以考虑清空 platformLanguageList | ||||
|         // 并且将所有语言设置重置为 "auto",因为没有可用的平台语言 | ||||
|         configInfo.value.receiveSourceLanguage = "auto"; | ||||
|         configInfo.value.receiveTargetLanguage = "auto"; | ||||
|         return; | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     // 筛选出当前平台支持的语言列表 | ||||
|     const items = languageList.value.filter(item => item[newValue]); | ||||
|     platformLanguageList.value = items; // 更新平台支持的语言列表 | ||||
|      | ||||
|     // 检查是否有支持的语言 | ||||
|     if (items.length > 0) { | ||||
|       const findLanguageInItems = (code) => items.find(item => item.code === code); | ||||
|       // 确保 languageList 已经加载,如果为空,可能需要等待数据加载或提示错误 | ||||
|       if (languageList.value.length === 0) { | ||||
|         await getLanguageList(); | ||||
|  | ||||
|       // 处理接收(Receive)相关的语言设置 | ||||
|       if (configInfo.value.receiveSourceLanguage !== "auto") { | ||||
|         const foundReceiveSourceLanguage = findLanguageInItems(configInfo.value.receiveSourceLanguage); | ||||
|         if (!foundReceiveSourceLanguage) { | ||||
|           configInfo.value.receiveSourceLanguage = "auto"; | ||||
|         if (languageList.value.length === 0) { | ||||
|           console.warn("watch(translateRoute): 翻译平台列表为空。"); | ||||
|           return; | ||||
|         } | ||||
|       } | ||||
|  | ||||
|       const foundReceiveTargetLanguage = findLanguageInItems(configInfo.value.receiveTargetLanguage); | ||||
|       if (!foundReceiveTargetLanguage) { | ||||
|         configInfo.value.receiveTargetLanguage = items[0].code; | ||||
|       } | ||||
|       // 筛选出当前平台支持的语言列表 | ||||
|       const items = languageList.value.filter(item => item[newValue]); | ||||
|       platformLanguageList.value = items; // 更新平台支持的语言列表 | ||||
|  | ||||
|       // 处理发送(Send)相关的语言设置 | ||||
|       if (configInfo.value.sendSourceLanguage !== "auto") { | ||||
|         const foundSendSourceLanguage = findLanguageInItems(configInfo.value.sendSourceLanguage); | ||||
|         if (!foundSendSourceLanguage) { | ||||
|           configInfo.value.sendSourceLanguage = "auto"; | ||||
|       // 检查是否有支持的语言 | ||||
|       if (items.length > 0) { | ||||
|         const findLanguageInItems = (code) => items.find(item => item.code === code); | ||||
|  | ||||
|         // 接收 | ||||
|         if (configInfo.value.receiveSourceLanguage !== "auto") { | ||||
|           const ok = findLanguageInItems(configInfo.value.receiveSourceLanguage); | ||||
|           if (!ok) configInfo.value.receiveSourceLanguage = "auto"; | ||||
|         } | ||||
|         if (!findLanguageInItems(configInfo.value.receiveTargetLanguage)) { | ||||
|           configInfo.value.receiveTargetLanguage = items[0].code; | ||||
|         } | ||||
|       } | ||||
|  | ||||
|       const foundSendTargetLanguage = findLanguageInItems(configInfo.value.sendTargetLanguage); | ||||
|       if (!foundSendTargetLanguage) { | ||||
|         configInfo.value.sendTargetLanguage = items[0].code; | ||||
|         // 发送 | ||||
|         if (configInfo.value.sendSourceLanguage !== "auto") { | ||||
|           const ok2 = findLanguageInItems(configInfo.value.sendSourceLanguage); | ||||
|           if (!ok2) configInfo.value.sendSourceLanguage = "auto"; | ||||
|         } | ||||
|         if (!findLanguageInItems(configInfo.value.sendTargetLanguage)) { | ||||
|           configInfo.value.sendTargetLanguage = items[0].code; | ||||
|         } | ||||
|  | ||||
|         // 兼容:拦截语言多选列表同样使用平台语言(尽量映射而不是直接丢弃) | ||||
|         if (Array.isArray(configInfo.value.interceptLanguages)) { | ||||
|           const toLower = (s) => String(s || '').toLowerCase(); | ||||
|           const mapToSupported = (code) => { | ||||
|             const c = toLower(code); | ||||
|             // 1) 先精确匹配 | ||||
|             let found = items.find(it => toLower(it.code) === c); | ||||
|             if (found) return found.code; | ||||
|             // 2) 无地域码 -> 用 startsWith 匹配(如 'zh' -> 'zh-CN') | ||||
|             found = items.find(it => toLower(it.code).startsWith(c)); | ||||
|             if (found) return found.code; | ||||
|             // 3) 常见兜底 | ||||
|             if (c.startsWith('zh')) { | ||||
|               found = items.find(it => toLower(it.code).startsWith('zh')); | ||||
|               if (found) return found.code; | ||||
|             } | ||||
|             if (c.startsWith('en')) { | ||||
|               found = items.find(it => toLower(it.code).startsWith('en')); | ||||
|               if (found) return found.code; | ||||
|             } | ||||
|             return null; | ||||
|           }; | ||||
|           const mapped = configInfo.value.interceptLanguages | ||||
|             .map(mapToSupported) | ||||
|             .filter(Boolean); | ||||
|           configInfo.value.interceptLanguages = Array.from(new Set(mapped)); | ||||
|         } | ||||
|       } else { | ||||
|         // 如果当前平台没有支持的语言配置,则全部默认设置为 "auto" | ||||
|         console.warn(`当前平台 (${newValue}) 没有配置任何支持的语言,将语言设置重置为 \"auto\"。`); | ||||
|         configInfo.value.receiveSourceLanguage = "auto"; | ||||
|         configInfo.value.receiveTargetLanguage = "auto"; | ||||
|         configInfo.value.sendSourceLanguage = "auto"; | ||||
|         configInfo.value.sendTargetLanguage = "auto"; | ||||
|       } | ||||
|     } else { | ||||
|       // 如果当前平台没有支持的语言配置,则全部默认设置为 "auto" | ||||
|       console.warn(`当前平台 (${newValue}) 没有配置任何支持的语言,将语言设置重置为 "auto"。`); | ||||
|       configInfo.value.receiveSourceLanguage = "auto"; | ||||
|       configInfo.value.receiveTargetLanguage = "auto"; | ||||
|       configInfo.value.sendSourceLanguage = "auto"; | ||||
|       configInfo.value.sendTargetLanguage = "auto"; | ||||
|     } finally { | ||||
|       isApplyingConfig.value = false; | ||||
|     } | ||||
|   }, | ||||
|   { | ||||
|     immediate: true, // 立即执行一次监听器,处理组件挂载时的初始值 | ||||
|     deep: false // 对于基本类型或浅层对象,不需要深度监听 | ||||
|   } | ||||
| ); | ||||
|   }, { immediate: true }) | ||||
|  | ||||
| const handleTranslateRouteChange = () => { | ||||
|   // 处理翻译路线变更的逻辑 | ||||
| @ -514,7 +585,11 @@ const handleCreatePersonalConfig = async () => { | ||||
|   try { | ||||
|     await ipc.invoke(ipcApiRoute.createTranslateConfig, { userId: menuStore.currentUserId }); | ||||
|     ElMessage.success(t('common.success') || '创建成功'); | ||||
|     getConfigInfo(); | ||||
|     await getConfigInfo(); | ||||
|     if (configInfo.value && configInfo.value.id && (configInfo.value.usePersonalConfig === undefined || configInfo.value.usePersonalConfig === null || configInfo.value.usePersonalConfig === '')) { | ||||
|       await handlePropertyChange('usePersonalConfig', 'true'); | ||||
|       configInfo.value.usePersonalConfig = 'true'; | ||||
|     } | ||||
|   } catch (e) { | ||||
|     ElMessage.error((e && e.message) || t('common.failed') || '创建失败'); | ||||
|   } | ||||
| @ -552,7 +627,7 @@ const refreshTranslateRoutes = async () => { | ||||
|  | ||||
| <style scoped lang="less"> | ||||
| .translate-config { | ||||
|   width: 300px; | ||||
|   width: 320px; | ||||
|   height: 100%; | ||||
|   padding: 20px; | ||||
|   display: flex; | ||||
| @ -636,6 +711,28 @@ const refreshTranslateRoutes = async () => { | ||||
|       flex: 1; | ||||
|       align-items: center; | ||||
|     } | ||||
|  | ||||
|     .intercept-left { | ||||
|       flex: 0 0 90px; /* 左侧固定更窄,为右侧选择框腾出空间 */ | ||||
|     } | ||||
|     .intercept-row { | ||||
|       gap: 6px; /* 再缩小中间空隙 */ | ||||
|       flex: 1 1 auto; /* 右侧区域最大化 */ | ||||
|     } | ||||
|     .intercept-lang-select { | ||||
|       flex: 1; | ||||
|       width: 100%; | ||||
|       min-width: 0; /* 允许弹性收缩 */ | ||||
|     } | ||||
|     :deep(.intercept-row .el-select) { | ||||
|       width: 100% !important; /* 强制选择器撑满右侧 */ | ||||
|     } | ||||
|     :deep(.el-select__popper) { | ||||
|       min-width: 320px !important; /* 下拉浮层更宽,避免文字截断 */ | ||||
|     } | ||||
|     :deep(.el-select__tags) { | ||||
|       max-width: 100%; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   .content-container-radio-group { | ||||
|  | ||||
| @ -12,6 +12,32 @@ const { t } = useI18n(); | ||||
| const menuStore = useMenuStore(); | ||||
| // 存储选中ID的集合 | ||||
| const selectedRows = ref([]) | ||||
| // 搜索关键字(用于顶部搜索弹窗) | ||||
| const searchQuery = ref(''); | ||||
| // 代理测试结果(当前弹窗): null | 'success' | 'error' | ||||
| const proxyTestResult = ref(null); | ||||
|  | ||||
| // 过滤会话数据(受搜索影响) | ||||
| const filteredTableData = computed(() => { | ||||
|   const menus = (menuStore.getCurrentChildren() || []); | ||||
|   const raw = typeof menus === 'function' ? menus() : menus; // 兼容 getter 写法 | ||||
|   const list = Array.isArray(raw) ? raw : []; | ||||
|   const keyword = (searchQuery.value || '').trim().toLowerCase(); | ||||
|   if (!keyword) return list; | ||||
|   return list.filter((row) => { | ||||
|     const nick = String(row?.nickName || '').toLowerCase(); | ||||
|     const remarks = String(row?.remarks || '').toLowerCase(); | ||||
|     const user = String(row?.userName || '').toLowerCase(); | ||||
|     const webUrl = String(row?.webUrl || '').toLowerCase(); | ||||
|     return ( | ||||
|       nick.includes(keyword) || | ||||
|       remarks.includes(keyword) || | ||||
|       user.includes(keyword) || | ||||
|       webUrl.includes(keyword) | ||||
|     ); | ||||
|   }); | ||||
| }); | ||||
|  | ||||
| const tableData = ref([]); | ||||
| const getTableData = async () => { | ||||
|   tableData.value = await menuStore.getCurrentChildren(); | ||||
| @ -57,6 +83,29 @@ const getProxyInfo = async (row) => { | ||||
|   } | ||||
|   proxyDialogVisible.value = true; | ||||
| } | ||||
| const testProxyConfig = async () => { | ||||
|   const args = { | ||||
|     proxyStatus: proxyForm.value.proxyStatus, | ||||
|     proxyType: proxyForm.value.proxyType, | ||||
|     proxyIp: proxyForm.value.proxyIp, | ||||
|     proxyPort: proxyForm.value.proxyPort, | ||||
|     userVerifyStatus: proxyForm.value.userVerifyStatus, | ||||
|     username: proxyForm.value.username, | ||||
|     password: proxyForm.value.password, | ||||
|   }; | ||||
|   try { | ||||
|     const res = await ipc.invoke(ipcApiRoute.testProxy, args); | ||||
|     proxyTestResult.value = res.status ? 'success' : 'error'; | ||||
|     if (res.status) { | ||||
|       ElMessage.success(res.message || t('session.proxyTestSuccess') || '代理连接成功'); | ||||
|     } else { | ||||
|       ElMessage.error(res.message || t('session.proxyTestFailed') || '代理连接失败'); | ||||
|     } | ||||
|   } catch (e) { | ||||
|     proxyTestResult.value = 'error'; | ||||
|     ElMessage.error(t('session.proxyTestFailed') || '代理连接失败'); | ||||
|   } | ||||
| } | ||||
| const saveProxyConfig = async () => { | ||||
|   // 构建参数对象 | ||||
|   const args = { | ||||
| @ -73,6 +122,7 @@ const saveProxyConfig = async () => { | ||||
|   }; | ||||
|   // 调用主进程方法 | ||||
|   const res = await ipc.invoke(ipcApiRoute.saveProxyInfo, args); | ||||
|  | ||||
|   // 根据返回结果处理 | ||||
|   if (res.status) { | ||||
|     ElMessage({ | ||||
| @ -200,6 +250,7 @@ const startAll = async () => { | ||||
|       menuStore.addChildrenMenu(item); | ||||
|       menuStore.updateChildrenMenu(res.data); | ||||
|     } | ||||
|  | ||||
|   } | ||||
| } | ||||
| const closeAll = async () => { | ||||
| @ -383,7 +434,7 @@ const handleBatchProxySave = async () => { | ||||
|     </el-dialog> | ||||
|  | ||||
|     <!--代理设置弹出层--> | ||||
|     <el-dialog v-model="proxyDialogVisible" :title="t('session.proxyConfig')" width="550"> | ||||
|     <el-dialog v-model="proxyDialogVisible" :title="t('session.proxyConfig')" width="580"> | ||||
|       <div class="proxy-config-dialog-form"> | ||||
|         <!--    代理开关--> | ||||
|         <div class="content-container-radio"> | ||||
| @ -447,14 +498,22 @@ const handleBatchProxySave = async () => { | ||||
|           <div class="content-right"> | ||||
|             <el-input :placeholder="t('session.password')" v-model="proxyForm.password" | ||||
|               :type="showProxyPassword ? 'text' : 'password'"> | ||||
|               <!-- <template #suffix> | ||||
|                 <el-icon style="cursor:pointer;" @click="showProxyPassword = !showProxyPassword"> | ||||
|                   <component :is="showProxyPassword ? Hide : View" /> | ||||
|                 </el-icon> | ||||
|               </template> --> | ||||
|             </el-input> | ||||
|           </div> | ||||
|         </div> | ||||
|  | ||||
|         <!-- 连接状态提示 --> | ||||
|         <div class="content-container-input" v-if="proxyTestResult"> | ||||
|           <div class="content-left"> | ||||
|             <el-text>连接状态</el-text> | ||||
|           </div> | ||||
|           <div class="content-right"> | ||||
|             <el-tag :type="proxyTestResult === 'success' ? 'success' : 'danger'"> | ||||
|               {{ proxyTestResult === 'success' ? '代理连接成功' : '代理连接失败' }} | ||||
|             </el-tag> | ||||
|           </div> | ||||
|         </div> | ||||
|  | ||||
|         <!--    时区--> | ||||
|         <div class="content-container-input"> | ||||
|           <div class="content-left"> | ||||
| @ -490,6 +549,7 @@ const handleBatchProxySave = async () => { | ||||
|       <template #footer> | ||||
|         <span class="proxy-config-dialog-footer"> | ||||
|           <el-button @click="proxyDialogVisible = false">{{ t('common.cancel') }}</el-button> | ||||
|           <el-button type="warning" @click="testProxyConfig" style="margin-right: 8px;">{{ t('session.testProxy') || '测试代理' }}</el-button> | ||||
|           <el-button type="primary" @click="saveProxyConfig">{{ t('common.confirm') }}</el-button> | ||||
|         </span> | ||||
|       </template> | ||||
| @ -526,8 +586,18 @@ const handleBatchProxySave = async () => { | ||||
|             <el-button size="small" type="primary" :icon="Search" circle /> | ||||
|           </template> | ||||
|           <div class="search-bth"> | ||||
|             <el-input v-if="isCustomWeb" :placeholder="t('session.remarks')" /> | ||||
|             <el-input v-else :placeholder="t('session.searchPlaceholder')" /> | ||||
|             <el-input | ||||
|               v-if="isCustomWeb" | ||||
|               v-model="searchQuery" | ||||
|               :placeholder="t('session.remarks')" | ||||
|               clearable | ||||
|             /> | ||||
|             <el-input | ||||
|               v-else | ||||
|               v-model="searchQuery" | ||||
|               :placeholder="t('session.searchPlaceholder')" | ||||
|               clearable | ||||
|             /> | ||||
|           </div> | ||||
|         </el-popover> | ||||
|         <el-button size="small" type="primary" @click="batchProxyDialogVisible = true" style="margin-left: 8px;"> | ||||
| @ -537,7 +607,7 @@ const handleBatchProxySave = async () => { | ||||
|     </div> | ||||
|     <!--表格部分--> | ||||
|     <div class="table-data"> | ||||
|       <el-table border height="100%" :data="tableData" @selection-change="handleSelectionChange" row-key="partitionId"> | ||||
|       <el-table border height="100%" :data="filteredTableData" @selection-change="handleSelectionChange" row-key="partitionId"> | ||||
|         <el-table-column type="selection" /> | ||||
|         <el-table-column align="center" prop="createTime" :label="t('session.createTime')" /> | ||||
|         <el-table-column align="center" :label="t('session.sessionRecord')" min-width="100"> | ||||
|  | ||||
		Reference in New Issue
	
	Block a user
	 unknown
					unknown