Compare commits

2 Commits
main ... small

Author SHA1 Message Date
922b4dd83f 新包 2026-01-12 18:45:17 +08:00
4ed4ba35b6 压缩 2026-01-07 21:36:52 +08:00
4 changed files with 150 additions and 97 deletions

View File

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

View File

@@ -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<void>}
*/
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,
};
}

View File

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

View File

@@ -128,13 +128,13 @@
<div class="input-group">
<label>{{ $t('workbenchesSetup.setNum') }}</label>
<label style="color: #00000070; font-size: 15px;">({{ $t('workbenchesSetup.prompt')
}})</label>
}})</label>
<el-button type="primary" @click="isLimit = true" :disabled="!pyData.isStart">{{
$t('workbenchesSetup.setHostNum')
}}</el-button>
}}</el-button>
<el-button type="info" @click="isLimit = false" :disabled="!pyData.isStart">{{
$t('workbenchesSetup.unlimitedQuantity')
}}</el-button>
}}</el-button>
<!-- <el-input type='number' v-model="pyData.frequency.hour" @input="handleInputHour" -->
<div v-if="isLimit" class="center-justify">
<el-input type='number' v-model="hostNum" :placeholder="$t('workbenchesSetup.num')"
@@ -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 () => {
/* 垂直居中 */
/* 示例高度,根据需要调整 */
}
</style>
</style>