From 4ed4ba35b6cf04fd40e60c20bd5244f55818fafe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B2=A1=E5=A4=8D=E4=B9=A0?= <2353956224@qq.com> Date: Wed, 7 Jan 2026 21:36:52 +0800 Subject: [PATCH] =?UTF-8?q?=E5=8E=8B=E7=BC=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/index.html | 16 ++- src/utils/pythonBridge.js | 192 ++++++++++++++++++++------------ src/views/HomeView.vue | 14 +-- src/views/hosts/workbenches.vue | 19 ++-- 4 files changed, 147 insertions(+), 94 deletions(-) diff --git a/public/index.html b/public/index.html index 0d4cbae..5e60580 100644 --- a/public/index.html +++ b/public/index.html @@ -9,7 +9,7 @@ <%= webpackConfig.name %> - + @@ -21,12 +21,20 @@ - \ No newline at end of file + diff --git a/src/utils/pythonBridge.js b/src/utils/pythonBridge.js index edec806..8b5959f 100644 --- a/src/utils/pythonBridge.js +++ b/src/utils/pythonBridge.js @@ -1,74 +1,100 @@ // pythonBridge.js +// 适配 pywebview API (替代原有的 QWebChannel) import { ref, onMounted } from 'vue'; const bridge = ref(null); +const isReady = ref(false); -// 统一安全调用,确保 Qt 响应有回调可执行 -const callBridge = (method, ...args) => { - if (!bridge.value || typeof bridge.value[method] !== 'function') return; - const last = args[args.length - 1]; - const hasCallback = typeof last === 'function'; - const callback = hasCallback ? args.pop() : () => { }; - bridge.value[method](...args, callback); -}; - -// 防御:若 Qt 返回了未知 id,忽略以免 execCallbacks 报错 -const patchQWebChannel = () => { - if (!window.QWebChannel || QWebChannel.__patchedIgnoreMissing) return; - const originalHandleResponse = QWebChannel.prototype.handleResponse; - QWebChannel.__patchedIgnoreMissing = true; - QWebChannel.prototype.handleResponse = function (message) { - const cb = this.execCallbacks && this.execCallbacks[message.id]; - if (message.id && typeof cb !== 'function') { - console.warn('忽略未知的 WebChannel 响应', message); +/** + * 等待 pywebview API 准备就绪 + * @returns {Promise} + */ +const waitForPywebview = () => { + return new Promise((resolve) => { + // 如果已经存在,直接返回 + if (window.pywebview && window.pywebview.api) { + resolve(); return; } - return originalHandleResponse.call(this, message); - }; + // 监听 pywebviewready 事件 + window.addEventListener('pywebviewready', () => { + resolve(); + }, { once: true }); + }); }; -// 初始化 QWebChannel -const initBridge = () => { - if (/localhost/.test(window.location.href)) return; - patchQWebChannel(); - new QWebChannel(qt.webChannelTransport, (channel) => { - // 兜底:任何缺失的回调都返回空函数,避免 execCallbacks 报错 - channel.execCallbacks = new Proxy(channel.execCallbacks || {}, { - get(target, prop) { - const val = target[prop]; - if (typeof val === 'function') return val; - // 返回空函数,确保 handleResponse 可调用 - return () => {}; - }, - set(target, prop, value) { - target[prop] = value; - return true; - }, - }); +/** + * 统一安全调用 pywebview API + * pywebview 的方法调用会返回 Promise + */ +const callBridge = async (method, ...args) => { + if (!bridge.value || typeof bridge.value[method] !== 'function') { + console.warn(`[pythonBridge] 方法不存在: ${method}`); + return null; + } + try { + const result = await bridge.value[method](...args); + return result; + } catch (error) { + console.error(`[pythonBridge] 调用 ${method} 失败:`, error); + return null; + } +}; - bridge.value = channel.objects.bridge; +/** + * 初始化 pywebview 桥接 + */ +const initBridge = async () => { + // 监听 DevTools 快捷键 (Ctrl + Shift + P) + window.addEventListener('keydown', (e) => { + // 1. 自定义快捷键: Ctrl + Shift + P -> 调用后端 + if (e.ctrlKey && e.shiftKey && (e.key === 'p' || e.key === 'P')) { + e.preventDefault(); + callBridge('openDevTools'); + return; + } + + // 2. 屏蔽 F12 (防止用户按下 F12 打开) + if (e.key === 'F12') { + e.preventDefault(); + return; + } }); + + // 开发环境 (localhost) 不初始化 + if (/localhost/.test(window.location.href)) { + console.log('[pythonBridge] 开发环境,跳过初始化'); + return; + } + + await waitForPywebview(); + + if (window.pywebview && window.pywebview.api) { + bridge.value = window.pywebview.api; + isReady.value = true; + console.log('[pythonBridge] 初始化成功'); + } else { + console.error('[pythonBridge] pywebview API 初始化失败'); + } }; export function usePythonBridge() { // 调用 Python 方法 - const fetchDataConfig = (data) => { - return new Promise((resolve) => { - if (!bridge.value) return resolve(null); - callBridge('fetchDataConfig', data, (result) => { - resolve(result); - }); - }); + const fetchDataConfig = async (data) => { + if (!bridge.value) return null; + return await callBridge('fetchDataConfig', data); }; // 查询获取主播的数据 - const fetchDataCount = () => { - return new Promise((resolve) => { - if (!bridge.value) return resolve(null); - callBridge('fetchDataCount', (result) => { - resolve(result); - }); - }); + const fetchDataCount = async () => { + if (!bridge.value) return null; + const result = await callBridge('fetchDataCount'); + // pywebview 返回的是字符串,需要解析 + try { + return typeof result === 'string' ? JSON.parse(result) : result; + } catch { + return result; + } }; // 打开 tk 后台 @@ -91,23 +117,25 @@ export function usePythonBridge() { }; // 查询登录状态 - const backStageloginStatus = () => { - return new Promise((resolve) => { - if (!bridge.value) return resolve(null); - callBridge('backStageloginStatus', (result) => { - resolve(result); - }); - }); + const backStageloginStatus = async () => { + if (!bridge.value) return null; + const result = await callBridge('backStageloginStatus'); + try { + return typeof result === 'string' ? JSON.parse(result) : result; + } catch { + return result; + } }; // 查询登录状态(副账号) - const backStageloginStatusCopy = () => { - return new Promise((resolve) => { - if (!bridge.value) return resolve(null); - callBridge('backStageloginStatusCopy', (result) => { - resolve(result); - }); - }); + const backStageloginStatusCopy = async () => { + if (!bridge.value) return null; + const result = await callBridge('backStageloginStatusCopy'); + try { + return typeof result === 'string' ? JSON.parse(result) : result; + } catch { + return result; + } }; // 导出表格 @@ -120,19 +148,33 @@ export function usePythonBridge() { }; // 获取版本号 - const getVersion = () => { - return new Promise((resolve) => { - if (!bridge.value) return resolve(null); - callBridge('currentVersion', (result) => { - resolve(result); - }); - }); + const getVersion = async () => { + if (!bridge.value) return null; + return await callBridge('currentVersion'); + }; + + // 存储账号信息 + const storageAccountInfo = async (key, data) => { + if (!bridge.value) return false; + return await callBridge('storageAccountInfo', key, JSON.stringify(data)); + }; + + // 读取账号信息 + const readAccountInfo = async (key) => { + if (!bridge.value) return null; + const result = await callBridge('readAccountInfo', key); + try { + return typeof result === 'string' ? JSON.parse(result) : result; + } catch { + return result; + } }; // 在组件挂载时初始化桥接 onMounted(initBridge); return { + isReady, fetchDataConfig, fetchDataCount, loginBackStage, @@ -143,5 +185,7 @@ export function usePythonBridge() { exportToExcel, stopScript, getVersion, + storageAccountInfo, + readAccountInfo, }; } diff --git a/src/views/HomeView.vue b/src/views/HomeView.vue index e3f1641..36bb925 100644 --- a/src/views/HomeView.vue +++ b/src/views/HomeView.vue @@ -171,8 +171,8 @@ const onSubmit = () => { \ No newline at end of file + diff --git a/src/views/hosts/workbenches.vue b/src/views/hosts/workbenches.vue index 594d5cf..1bb7793 100644 --- a/src/views/hosts/workbenches.vue +++ b/src/views/hosts/workbenches.vue @@ -411,14 +411,15 @@ const submit = () => { //开启查询次数 getHostTimer.value = setInterval(() => { fetchDataCount().then((res) => { - hostData.value = JSON.parse(res); - if (isLimit.value) { - if (hostData.value.canInvitationCount >= hostNum.value) { - unsubmit(); - alert('爬取完毕') + if (res) { + hostData.value = res; + if (isLimit.value) { + if (hostData.value.canInvitationCount >= hostNum.value) { + unsubmit(); + alert('爬取完毕') + } } } - }) }, 1000); getNumTimer.value = setInterval(() => { @@ -511,7 +512,7 @@ const openTK = () => { function getloginStatus() { backStageloginStatus().then((res) => { - const data = JSON.parse(res); + const data = res; tkData.value[data.index].code = data.code if (data.code == 1) { @@ -524,7 +525,7 @@ function getloginStatus() { } function getloginStatusCopy() { backStageloginStatusCopy().then((res) => { - const data = JSON.parse(res); + const data = res; tkData.value[data.index].code = data.code if (data.code == 1) { @@ -949,4 +950,4 @@ const checkVPN = async () => { /* 垂直居中 */ /* 示例高度,根据需要调整 */ } - \ No newline at end of file +