This commit is contained in:
2026-01-07 21:36:52 +08:00
parent 62dd79a2fc
commit 4ed4ba35b6
4 changed files with 147 additions and 94 deletions

View File

@@ -9,7 +9,7 @@
<title> <title>
<%= webpackConfig.name %> <%= webpackConfig.name %>
</title> </title>
<script src="qrc:///qtwebchannel/qwebchannel.js"></script> <!-- pywebview API 自动注入,无需手动引入脚本 -->
</head> </head>
<body> <body>
@@ -21,11 +21,19 @@
<!-- built files will be auto injected --> <!-- built files will be auto injected -->
</body> </body>
<style> <style>
html,
body { body {
margin: 0; margin: 0;
padding: 0; padding: 0;
/* width: 1600px; overflow: hidden;
height: 900px; */ width: 100%;
height: 100%;
}
#app {
width: 100%;
height: 100%;
overflow: hidden;
} }
</style> </style>

View File

@@ -1,74 +1,100 @@
// pythonBridge.js // pythonBridge.js
// 适配 pywebview API (替代原有的 QWebChannel)
import { ref, onMounted } from 'vue'; import { ref, onMounted } from 'vue';
const bridge = ref(null); const bridge = ref(null);
const isReady = ref(false);
// 统一安全调用,确保 Qt 响应有回调可执行 /**
const callBridge = (method, ...args) => { * 等待 pywebview API 准备就绪
if (!bridge.value || typeof bridge.value[method] !== 'function') return; * @returns {Promise<void>}
const last = args[args.length - 1]; */
const hasCallback = typeof last === 'function'; const waitForPywebview = () => {
const callback = hasCallback ? args.pop() : () => { }; return new Promise((resolve) => {
bridge.value[method](...args, callback); // 如果已经存在,直接返回
}; if (window.pywebview && window.pywebview.api) {
resolve();
// 防御:若 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);
return; return;
} }
return originalHandleResponse.call(this, message); // 监听 pywebviewready 事件
}; window.addEventListener('pywebviewready', () => {
resolve();
}, { once: true });
});
}; };
// 初始化 QWebChannel /**
const initBridge = () => { * 统一安全调用 pywebview API
if (/localhost/.test(window.location.href)) return; * pywebview 的方法调用会返回 Promise
patchQWebChannel(); */
new QWebChannel(qt.webChannelTransport, (channel) => { const callBridge = async (method, ...args) => {
// 兜底:任何缺失的回调都返回空函数,避免 execCallbacks 报错 if (!bridge.value || typeof bridge.value[method] !== 'function') {
channel.execCallbacks = new Proxy(channel.execCallbacks || {}, { console.warn(`[pythonBridge] 方法不存在: ${method}`);
get(target, prop) { return null;
const val = target[prop]; }
if (typeof val === 'function') return val; try {
// 返回空函数,确保 handleResponse 可调用 const result = await bridge.value[method](...args);
return () => {}; return result;
}, } catch (error) {
set(target, prop, value) { console.error(`[pythonBridge] 调用 ${method} 失败:`, error);
target[prop] = value; return null;
return true; }
}, };
/**
* 初始化 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;
}
}); });
bridge.value = channel.objects.bridge; // 开发环境 (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() { export function usePythonBridge() {
// 调用 Python 方法 // 调用 Python 方法
const fetchDataConfig = (data) => { const fetchDataConfig = async (data) => {
return new Promise((resolve) => { if (!bridge.value) return null;
if (!bridge.value) return resolve(null); return await callBridge('fetchDataConfig', data);
callBridge('fetchDataConfig', data, (result) => {
resolve(result);
});
});
}; };
// 查询获取主播的数据 // 查询获取主播的数据
const fetchDataCount = () => { const fetchDataCount = async () => {
return new Promise((resolve) => { if (!bridge.value) return null;
if (!bridge.value) return resolve(null); const result = await callBridge('fetchDataCount');
callBridge('fetchDataCount', (result) => { // pywebview 返回的是字符串,需要解析
resolve(result); try {
}); return typeof result === 'string' ? JSON.parse(result) : result;
}); } catch {
return result;
}
}; };
// 打开 tk 后台 // 打开 tk 后台
@@ -91,23 +117,25 @@ export function usePythonBridge() {
}; };
// 查询登录状态 // 查询登录状态
const backStageloginStatus = () => { const backStageloginStatus = async () => {
return new Promise((resolve) => { if (!bridge.value) return null;
if (!bridge.value) return resolve(null); const result = await callBridge('backStageloginStatus');
callBridge('backStageloginStatus', (result) => { try {
resolve(result); return typeof result === 'string' ? JSON.parse(result) : result;
}); } catch {
}); return result;
}
}; };
// 查询登录状态(副账号) // 查询登录状态(副账号)
const backStageloginStatusCopy = () => { const backStageloginStatusCopy = async () => {
return new Promise((resolve) => { if (!bridge.value) return null;
if (!bridge.value) return resolve(null); const result = await callBridge('backStageloginStatusCopy');
callBridge('backStageloginStatusCopy', (result) => { try {
resolve(result); return typeof result === 'string' ? JSON.parse(result) : result;
}); } catch {
}); return result;
}
}; };
// 导出表格 // 导出表格
@@ -120,19 +148,33 @@ export function usePythonBridge() {
}; };
// 获取版本号 // 获取版本号
const getVersion = () => { const getVersion = async () => {
return new Promise((resolve) => { if (!bridge.value) return null;
if (!bridge.value) return resolve(null); return await callBridge('currentVersion');
callBridge('currentVersion', (result) => { };
resolve(result);
}); // 存储账号信息
}); 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); onMounted(initBridge);
return { return {
isReady,
fetchDataConfig, fetchDataConfig,
fetchDataCount, fetchDataCount,
loginBackStage, loginBackStage,
@@ -143,5 +185,7 @@ export function usePythonBridge() {
exportToExcel, exportToExcel,
stopScript, stopScript,
getVersion, getVersion,
storageAccountInfo,
readAccountInfo,
}; };
} }

View File

@@ -171,8 +171,8 @@ const onSubmit = () => {
<style lang="less"> <style lang="less">
.main { .main {
width: 1600px; width: 100%;
height: 900px; height: 100vh;
overflow: hidden; overflow: hidden;
box-sizing: border-box; box-sizing: border-box;
@@ -185,14 +185,14 @@ const onSubmit = () => {
.container { .container {
display: flex; display: flex;
box-sizing: border-box; box-sizing: border-box;
width: 1600px; width: 100%;
height: 900px; height: 100%;
.right { .right {
box-sizing: border-box; box-sizing: border-box;
position: relative; position: relative;
width: 1600px; width: 100%;
height: 900px; height: 100%;
padding: 20px 40px 20px 50px; padding: 20px 40px 20px 50px;
border-left: 3px solid #23516e; border-left: 3px solid #23516e;
position: relative; position: relative;

View File

@@ -411,14 +411,15 @@ const submit = () => {
//开启查询次数 //开启查询次数
getHostTimer.value = setInterval(() => { getHostTimer.value = setInterval(() => {
fetchDataCount().then((res) => { fetchDataCount().then((res) => {
hostData.value = JSON.parse(res); if (res) {
hostData.value = res;
if (isLimit.value) { if (isLimit.value) {
if (hostData.value.canInvitationCount >= hostNum.value) { if (hostData.value.canInvitationCount >= hostNum.value) {
unsubmit(); unsubmit();
alert('爬取完毕') alert('爬取完毕')
} }
} }
}
}) })
}, 1000); }, 1000);
getNumTimer.value = setInterval(() => { getNumTimer.value = setInterval(() => {
@@ -511,7 +512,7 @@ const openTK = () => {
function getloginStatus() { function getloginStatus() {
backStageloginStatus().then((res) => { backStageloginStatus().then((res) => {
const data = JSON.parse(res); const data = res;
tkData.value[data.index].code = data.code tkData.value[data.index].code = data.code
if (data.code == 1) { if (data.code == 1) {
@@ -524,7 +525,7 @@ function getloginStatus() {
} }
function getloginStatusCopy() { function getloginStatusCopy() {
backStageloginStatusCopy().then((res) => { backStageloginStatusCopy().then((res) => {
const data = JSON.parse(res); const data = res;
tkData.value[data.index].code = data.code tkData.value[data.index].code = data.code
if (data.code == 1) { if (data.code == 1) {