Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 922b4dd83f | |||
| 4ed4ba35b6 |
@@ -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,12 +21,20 @@
|
|||||||
<!-- 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>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -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;
|
}
|
||||||
},
|
};
|
||||||
});
|
|
||||||
|
|
||||||
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() {
|
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,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -363,4 +363,4 @@ const onSubmit = () => {
|
|||||||
::v-deep(.el-input__inner::placeholder) {
|
::v-deep(.el-input__inner::placeholder) {
|
||||||
color: @bg-color;
|
color: @bg-color;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -128,13 +128,13 @@
|
|||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<label>{{ $t('workbenchesSetup.setNum') }}</label>
|
<label>{{ $t('workbenchesSetup.setNum') }}</label>
|
||||||
<label style="color: #00000070; font-size: 15px;">({{ $t('workbenchesSetup.prompt')
|
<label style="color: #00000070; font-size: 15px;">({{ $t('workbenchesSetup.prompt')
|
||||||
}})</label>
|
}})</label>
|
||||||
<el-button type="primary" @click="isLimit = true" :disabled="!pyData.isStart">{{
|
<el-button type="primary" @click="isLimit = true" :disabled="!pyData.isStart">{{
|
||||||
$t('workbenchesSetup.setHostNum')
|
$t('workbenchesSetup.setHostNum')
|
||||||
}}</el-button>
|
}}</el-button>
|
||||||
<el-button type="info" @click="isLimit = false" :disabled="!pyData.isStart">{{
|
<el-button type="info" @click="isLimit = false" :disabled="!pyData.isStart">{{
|
||||||
$t('workbenchesSetup.unlimitedQuantity')
|
$t('workbenchesSetup.unlimitedQuantity')
|
||||||
}}</el-button>
|
}}</el-button>
|
||||||
<!-- <el-input type='number' v-model="pyData.frequency.hour" @input="handleInputHour" -->
|
<!-- <el-input type='number' v-model="pyData.frequency.hour" @input="handleInputHour" -->
|
||||||
<div v-if="isLimit" class="center-justify">
|
<div v-if="isLimit" class="center-justify">
|
||||||
<el-input type='number' v-model="hostNum" :placeholder="$t('workbenchesSetup.num')"
|
<el-input type='number' v-model="hostNum" :placeholder="$t('workbenchesSetup.num')"
|
||||||
@@ -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) {
|
||||||
if (isLimit.value) {
|
hostData.value = res;
|
||||||
if (hostData.value.canInvitationCount >= hostNum.value) {
|
if (isLimit.value) {
|
||||||
unsubmit();
|
if (hostData.value.canInvitationCount >= hostNum.value) {
|
||||||
alert('爬取完毕')
|
unsubmit();
|
||||||
|
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) {
|
||||||
@@ -949,4 +950,4 @@ const checkVPN = async () => {
|
|||||||
/* 垂直居中 */
|
/* 垂直居中 */
|
||||||
/* 示例高度,根据需要调整 */
|
/* 示例高度,根据需要调整 */
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user