|
@@ -21,16 +21,12 @@ let currentWorkflowFolderPath = null;
|
|
|
* @param {string} folderPath - 工作流文件夹路径(可选,如果不提供则使用 currentWorkflowFolderPath)
|
|
* @param {string} folderPath - 工作流文件夹路径(可选,如果不提供则使用 currentWorkflowFolderPath)
|
|
|
*/
|
|
*/
|
|
|
async function logMessage(message, folderPath = null) {
|
|
async function logMessage(message, folderPath = null) {
|
|
|
- // 尝试写入日志文件(异步,不阻塞主流程)
|
|
|
|
|
- // 注意:不输出到 console,只写入日志文件
|
|
|
|
|
try {
|
|
try {
|
|
|
- const targetFolderPath = folderPath || currentWorkflowFolderPath;
|
|
|
|
|
|
|
+ const targetFolderPath = folderPath || currentWorkflowFolderPath
|
|
|
if (targetFolderPath && electronAPI.appendLog) {
|
|
if (targetFolderPath && electronAPI.appendLog) {
|
|
|
- electronAPI.appendLog(targetFolderPath, message).catch(err => {
|
|
|
|
|
- // 静默失败,不影响主流程
|
|
|
|
|
- });
|
|
|
|
|
|
|
+ await electronAPI.appendLog(targetFolderPath, message)
|
|
|
}
|
|
}
|
|
|
- } catch (error) {
|
|
|
|
|
|
|
+ } catch (err) {
|
|
|
// 静默失败,不影响主流程
|
|
// 静默失败,不影响主流程
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
@@ -60,9 +56,10 @@ async function logOutVars(action, variableContext, folderPath = null) {
|
|
|
const path = require('path')
|
|
const path = require('path')
|
|
|
const fs = require('fs')
|
|
const fs = require('fs')
|
|
|
const { spawnSync } = require('child_process')
|
|
const { spawnSync } = require('child_process')
|
|
|
-const funcDir = path.join(__dirname, 'Func')
|
|
|
|
|
|
|
+const funcDir = path.join(__dirname, 'fun')
|
|
|
const projectRoot = path.resolve(__dirname, '..', '..')
|
|
const projectRoot = path.resolve(__dirname, '..', '..')
|
|
|
const adbInteractPath = path.join(projectRoot, 'nodejs', 'adb', 'adb-interact.js')
|
|
const adbInteractPath = path.join(projectRoot, 'nodejs', 'adb', 'adb-interact.js')
|
|
|
|
|
+const { sendSystemButton } = require(path.join(projectRoot, 'nodejs', 'adb', 'adb-sys-btn.js'))
|
|
|
const { generateHistorySummary, getHistorySummary } = require(path.join(funcDir, 'chat', 'chat-history.js'))
|
|
const { generateHistorySummary, getHistorySummary } = require(path.join(funcDir, 'chat', 'chat-history.js'))
|
|
|
const { executeOcrChat } = require(path.join(funcDir, 'chat', 'ocr-chat.js'))
|
|
const { executeOcrChat } = require(path.join(funcDir, 'chat', 'ocr-chat.js'))
|
|
|
const { executeImgBoundingBoxLocation } = require(path.join(funcDir, 'img-bounding-box-location.js'))
|
|
const { executeImgBoundingBoxLocation } = require(path.join(funcDir, 'img-bounding-box-location.js'))
|
|
@@ -72,6 +69,7 @@ const { executeReadLastMessage } = require(path.join(funcDir, 'chat', 'read-last
|
|
|
const { executeReadTxt, readTextFile } = require(path.join(funcDir, 'read-txt.js'))
|
|
const { executeReadTxt, readTextFile } = require(path.join(funcDir, 'read-txt.js'))
|
|
|
const { executeSaveTxt, writeTextFile } = require(path.join(funcDir, 'save-txt.js'))
|
|
const { executeSaveTxt, writeTextFile } = require(path.join(funcDir, 'save-txt.js'))
|
|
|
const { executeSmartChatAppend } = require(path.join(funcDir, 'chat', 'smart-chat-append.js'))
|
|
const { executeSmartChatAppend } = require(path.join(funcDir, 'chat', 'smart-chat-append.js'))
|
|
|
|
|
+const actionRegistry = require(path.join(__dirname, 'components', 'actions', 'index.js'))
|
|
|
|
|
|
|
|
function runAdb(action, args = [], deviceId = '') {
|
|
function runAdb(action, args = [], deviceId = '') {
|
|
|
const r = spawnSync('node', [adbInteractPath, action, ...args, deviceId], { encoding: 'utf-8', timeout: 10000 })
|
|
const r = spawnSync('node', [adbInteractPath, action, ...args, deviceId], { encoding: 'utf-8', timeout: 10000 })
|
|
@@ -82,7 +80,13 @@ const sendSwipe = (device, x1, y1, x2, y2, duration) => runAdb('swipe-coords', [
|
|
|
const sendKeyEvent = (device, keyCode) => runAdb('keyevent', [String(keyCode)], device)
|
|
const sendKeyEvent = (device, keyCode) => runAdb('keyevent', [String(keyCode)], device)
|
|
|
const sendText = (device, text) => runAdb('text', [String(text)], device)
|
|
const sendText = (device, text) => runAdb('text', [String(text)], device)
|
|
|
function appendLog(folderPath, message) {
|
|
function appendLog(folderPath, message) {
|
|
|
- fs.appendFileSync(path.join(folderPath, 'log.txt'), message + '\n')
|
|
|
|
|
|
|
+ if (!folderPath || typeof folderPath !== 'string') return Promise.resolve()
|
|
|
|
|
+ const logDir = path.resolve(folderPath)
|
|
|
|
|
+ const logFile = path.join(logDir, 'log.txt')
|
|
|
|
|
+ if (!fs.existsSync(logDir)) {
|
|
|
|
|
+ fs.mkdirSync(logDir, { recursive: true })
|
|
|
|
|
+ }
|
|
|
|
|
+ fs.appendFileSync(logFile, message + '\n')
|
|
|
return Promise.resolve()
|
|
return Promise.resolve()
|
|
|
}
|
|
}
|
|
|
const _stub = (name) => ({ success: false, error: `${name} 需在主进程实现` })
|
|
const _stub = (name) => ({ success: false, error: `${name} 需在主进程实现` })
|
|
@@ -849,7 +853,18 @@ function parseActions(actions) {
|
|
|
|
|
|
|
|
// 新格式:使用 type 字段
|
|
// 新格式:使用 type 字段
|
|
|
if (action.type) {
|
|
if (action.type) {
|
|
|
- parsedActions.push(parseNewFormatAction(action));
|
|
|
|
|
|
|
+ // 若该类型已接入 components/actions/*-parser.js,用其解析
|
|
|
|
|
+ if (actionRegistry.registry[action.type]) {
|
|
|
|
|
+ const parseContext = {
|
|
|
|
|
+ extractVarName,
|
|
|
|
|
+ resolveValue,
|
|
|
|
|
+ variableContext,
|
|
|
|
|
+ parseActions: (arr) => parseActions(arr),
|
|
|
|
|
+ };
|
|
|
|
|
+ parsedActions.push(actionRegistry.parseAction(action.type, action, parseContext));
|
|
|
|
|
+ } else {
|
|
|
|
|
+ parsedActions.push(parseNewFormatAction(action));
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
// 旧格式:向后兼容
|
|
// 旧格式:向后兼容
|
|
|
else {
|
|
else {
|
|
@@ -1383,6 +1398,30 @@ async function executeAction(action, device, folderPath, resolution) {
|
|
|
return { success: true, skipped: true };
|
|
return { success: true, skipped: true };
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ // 若该类型已接入 components/actions/*-parser.js,用其执行
|
|
|
|
|
+ if (actionRegistry.registry[action.type]) {
|
|
|
|
|
+ const ctx = {
|
|
|
|
|
+ device,
|
|
|
|
|
+ folderPath,
|
|
|
|
|
+ resolution,
|
|
|
|
|
+ variableContext,
|
|
|
|
|
+ api: electronAPI,
|
|
|
|
|
+ extractVarName,
|
|
|
|
|
+ resolveValue,
|
|
|
|
|
+ replaceVariablesInString,
|
|
|
|
|
+ evaluateCondition,
|
|
|
|
|
+ evaluateExpression,
|
|
|
|
|
+ getActionName,
|
|
|
|
|
+ logMessage,
|
|
|
|
|
+ logOutVars,
|
|
|
|
|
+ calculateSwipeCoordinates,
|
|
|
|
|
+ parseDelayString,
|
|
|
|
|
+ calculateWaitTime,
|
|
|
|
|
+ DEFAULT_SCROLL_DISTANCE,
|
|
|
|
|
+ };
|
|
|
|
|
+ return await actionRegistry.executeAction(action.type, action, ctx);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
switch (action.type) {
|
|
switch (action.type) {
|
|
|
case 'adb': {
|
|
case 'adb': {
|
|
|
// 统一 ADB 操作
|
|
// 统一 ADB 操作
|
|
@@ -1685,11 +1724,8 @@ async function executeAction(action, device, folderPath, resolution) {
|
|
|
keyCode = '4';
|
|
keyCode = '4';
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- if (!electronAPI || !electronAPI.sendSystemKey) {
|
|
|
|
|
- return { success: false, error: '系统按键 API 不可用' };
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- const keyResult = await electronAPI.sendSystemKey(device, String(keyCode));
|
|
|
|
|
|
|
+ // 直接通过 adb-sys-btn 发送系统按键(home/back),不依赖主进程 sendSystemKey
|
|
|
|
|
+ const keyResult = sendSystemButton(String(keyCode), device);
|
|
|
if (!keyResult.success) {
|
|
if (!keyResult.success) {
|
|
|
return { success: false, error: `按键失败: ${keyResult.error}` };
|
|
return { success: false, error: `按键失败: ${keyResult.error}` };
|
|
|
}
|
|
}
|
|
@@ -2133,7 +2169,7 @@ async function executeAction(action, device, folderPath, resolution) {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // 调用 Func 目录下的执行函数
|
|
|
|
|
|
|
+ // 调用 fun 目录下的执行函数
|
|
|
// 确保 regionArea 是对象格式(如果是字符串,尝试解析为 JSON)
|
|
// 确保 regionArea 是对象格式(如果是字符串,尝试解析为 JSON)
|
|
|
let regionParam = regionArea;
|
|
let regionParam = regionArea;
|
|
|
if (regionArea && typeof regionArea === 'string') {
|
|
if (regionArea && typeof regionArea === 'string') {
|
|
@@ -2396,219 +2432,6 @@ async function executeAction(action, device, folderPath, resolution) {
|
|
|
return { success: true, summary: result.summary };
|
|
return { success: true, summary: result.summary };
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- case 'set': {
|
|
|
|
|
- // 设置变量
|
|
|
|
|
- if (action.variable) {
|
|
|
|
|
- const varName = extractVarName(action.variable);
|
|
|
|
|
- // 如果 value 是字符串且包含算术运算符,尝试计算表达式
|
|
|
|
|
- let finalValue = action.value;
|
|
|
|
|
- if (typeof action.value === 'string' && /[+\-*/]/.test(action.value)) {
|
|
|
|
|
- finalValue = evaluateExpression(action.value, variableContext);
|
|
|
|
|
- } else if (typeof action.value === 'string') {
|
|
|
|
|
- // 如果 value 是字符串,检查是否包含变量引用({variable} 格式)
|
|
|
|
|
- // 替换字符串中的所有变量引用
|
|
|
|
|
- const varPattern = /\{([\w-]+)\}/g;
|
|
|
|
|
- let hasVariables = false;
|
|
|
|
|
- finalValue = action.value.replace(varPattern, (match, varName) => {
|
|
|
|
|
- hasVariables = true;
|
|
|
|
|
- const value = variableContext[varName];
|
|
|
|
|
- if (value === undefined || value === null) {
|
|
|
|
|
- return match; // 如果变量不存在,保留原样
|
|
|
|
|
- }
|
|
|
|
|
- return String(value);
|
|
|
|
|
- });
|
|
|
|
|
- // 如果没有找到变量引用,使用 resolveValue 解析(处理单个变量引用的情况)
|
|
|
|
|
- if (!hasVariables) {
|
|
|
|
|
- finalValue = resolveValue(action.value, variableContext);
|
|
|
|
|
- }
|
|
|
|
|
- } else {
|
|
|
|
|
- // 否则使用 resolveValue 解析变量引用
|
|
|
|
|
- finalValue = resolveValue(action.value, variableContext);
|
|
|
|
|
- }
|
|
|
|
|
- variableContext[varName] = finalValue;
|
|
|
|
|
- }
|
|
|
|
|
- return { success: true };
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- case 'random': {
|
|
|
|
|
- // 生成随机数
|
|
|
|
|
- // 支持新格式:inVars[min, max] 和 outVars[{variable}]
|
|
|
|
|
- let varName, min, max;
|
|
|
|
|
-
|
|
|
|
|
- if (action.outVars && Array.isArray(action.outVars) && action.outVars.length > 0) {
|
|
|
|
|
- varName = extractVarName(action.outVars[0]);
|
|
|
|
|
- } else if (action.variable) {
|
|
|
|
|
- // 向后兼容旧格式
|
|
|
|
|
- varName = extractVarName(action.variable);
|
|
|
|
|
- } else {
|
|
|
|
|
- return { success: false, error: 'random 操作缺少 variable 或 outVars 参数' };
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if (action.inVars && Array.isArray(action.inVars) && action.inVars.length >= 2) {
|
|
|
|
|
- // 新格式:从 inVars 读取 min 和 max
|
|
|
|
|
- min = Number(action.inVars[0]);
|
|
|
|
|
- max = Number(action.inVars[1]);
|
|
|
|
|
- } else {
|
|
|
|
|
- // 向后兼容旧格式
|
|
|
|
|
- min = action.min !== undefined ? Number(action.min) : 0;
|
|
|
|
|
- max = action.max !== undefined ? Number(action.max) : 100;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- const integer = action.integer !== undefined ? action.integer : true;
|
|
|
|
|
-
|
|
|
|
|
- if (isNaN(min) || isNaN(max)) {
|
|
|
|
|
- return { success: false, error: 'random 操作的 min 和 max 必须是数字' };
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if (min > max) {
|
|
|
|
|
- return { success: false, error: 'random 操作的 min 不能大于 max' };
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- let randomValue;
|
|
|
|
|
- if (integer) {
|
|
|
|
|
- randomValue = Math.floor(Math.random() * (max - min + 1)) + min;
|
|
|
|
|
- } else {
|
|
|
|
|
- randomValue = Math.random() * (max - min) + min;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- variableContext[varName] = randomValue;
|
|
|
|
|
- return { success: true };
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- case 'echo': {
|
|
|
|
|
- try {
|
|
|
|
|
- // 打印信息到 console.log、UI 和 log.txt 文件
|
|
|
|
|
- // 支持 inVars 或 value 字段
|
|
|
|
|
- let message = '';
|
|
|
|
|
-
|
|
|
|
|
- if (action.inVars && Array.isArray(action.inVars) && action.inVars.length > 0) {
|
|
|
|
|
- // 从 inVars 中读取变量值
|
|
|
|
|
- const messages = action.inVars.map(varWithBraces => {
|
|
|
|
|
- const varName = extractVarName(varWithBraces);
|
|
|
|
|
- const varValue = variableContext[varName];
|
|
|
|
|
- // 如果变量包含 {variable} 或 {{variable}} 格式,尝试解析为变量名
|
|
|
|
|
- if (varWithBraces.startsWith('{') && varWithBraces.endsWith('}')) {
|
|
|
|
|
- return varValue !== undefined ? String(varValue) : varWithBraces;
|
|
|
|
|
- }
|
|
|
|
|
- // 如果不是变量格式,直接使用原值
|
|
|
|
|
- return varWithBraces;
|
|
|
|
|
- });
|
|
|
|
|
- message = messages.join(' ');
|
|
|
|
|
- } else if (action.value) {
|
|
|
|
|
- // 使用 value 字段,支持变量替换(只支持 {{variable}} 格式,用于字符串拼接)
|
|
|
|
|
- // 先提取所有变量名,用于调试
|
|
|
|
|
- const doubleBracePattern = /\{\{([\w-]+)\}\}/g;
|
|
|
|
|
- const variablesInValue = [];
|
|
|
|
|
- let match;
|
|
|
|
|
- while ((match = doubleBracePattern.exec(action.value)) !== null) {
|
|
|
|
|
- variablesInValue.push(match[1]);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- message = replaceVariablesInString(action.value, variableContext);
|
|
|
|
|
-
|
|
|
|
|
- // 如果消息为空或只包含原始变量格式,添加调试信息
|
|
|
|
|
- if (!message || message === action.value) {
|
|
|
|
|
- const missingVars = variablesInValue.filter(varName => {
|
|
|
|
|
- const varValue = variableContext[varName];
|
|
|
|
|
- return varValue === undefined || varValue === null || varValue === '';
|
|
|
|
|
- });
|
|
|
|
|
- if (missingVars.length > 0) {
|
|
|
|
|
- message = `${action.value} [变量未定义或为空: ${missingVars.join(', ')}]`;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- } else {
|
|
|
|
|
- // 如果没有提供任何内容,输出空字符串
|
|
|
|
|
- message = '';
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // 输出到 console.log(仅限小红书随机浏览工作流)
|
|
|
|
|
- if (folderPath && folderPath.includes('小红书随机浏览工作流')) {
|
|
|
|
|
- console.log(message);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // 写入 log.txt 文件(只有 echo 类型写入文件)
|
|
|
|
|
- // 添加系统时间到消息中
|
|
|
|
|
- // 即使 message 为空也记录,以便调试
|
|
|
|
|
- const now = new Date();
|
|
|
|
|
- const year = now.getFullYear();
|
|
|
|
|
- const month = String(now.getMonth() + 1).padStart(2, '0');
|
|
|
|
|
- const day = String(now.getDate()).padStart(2, '0');
|
|
|
|
|
- const hour = String(now.getHours()).padStart(2, '0');
|
|
|
|
|
- const minute = String(now.getMinutes()).padStart(2, '0');
|
|
|
|
|
- const second = String(now.getSeconds()).padStart(2, '0');
|
|
|
|
|
- const timeStr = `${year}/${month}/${day} ${hour}:${minute}:${second}`;
|
|
|
|
|
- const messageWithTime = message ? `${message} [系统时间: ${timeStr}]` : `[空消息] [系统时间: ${timeStr}]`;
|
|
|
|
|
- await logMessage(messageWithTime, folderPath);
|
|
|
|
|
-
|
|
|
|
|
- // 发送 log 消息事件到 UI
|
|
|
|
|
- const logEvent = new CustomEvent('log-message', {
|
|
|
|
|
- detail: {
|
|
|
|
|
- message: message
|
|
|
|
|
- }
|
|
|
|
|
- });
|
|
|
|
|
- window.dispatchEvent(logEvent);
|
|
|
|
|
-
|
|
|
|
|
- return { success: true };
|
|
|
|
|
- } catch (error) {
|
|
|
|
|
- // 如果 echo 执行出错,记录错误到 log.txt
|
|
|
|
|
- const now = new Date();
|
|
|
|
|
- const year = now.getFullYear();
|
|
|
|
|
- const month = String(now.getMonth() + 1).padStart(2, '0');
|
|
|
|
|
- const day = String(now.getDate()).padStart(2, '0');
|
|
|
|
|
- const hour = String(now.getHours()).padStart(2, '0');
|
|
|
|
|
- const minute = String(now.getMinutes()).padStart(2, '0');
|
|
|
|
|
- const second = String(now.getSeconds()).padStart(2, '0');
|
|
|
|
|
- const timeStr = `${year}/${month}/${day} ${hour}:${minute}:${second}`;
|
|
|
|
|
- const errorMsg = `[错误] echo 执行失败: ${error.message} [系统时间: ${timeStr}]`;
|
|
|
|
|
- await logMessage(errorMsg, folderPath);
|
|
|
|
|
- return { success: false, error: `echo 执行失败: ${error.message}` };
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- case 'log': { // 向后兼容,但不写入 log.txt
|
|
|
|
|
- // 打印信息到 console.log 和 UI(不写入 log.txt)
|
|
|
|
|
- // 支持 inVars 或 value 字段
|
|
|
|
|
- let message = '';
|
|
|
|
|
-
|
|
|
|
|
- if (action.inVars && Array.isArray(action.inVars) && action.inVars.length > 0) {
|
|
|
|
|
- // 从 inVars 中读取变量值
|
|
|
|
|
- const messages = action.inVars.map(varWithBraces => {
|
|
|
|
|
- const varName = extractVarName(varWithBraces);
|
|
|
|
|
- const varValue = variableContext[varName];
|
|
|
|
|
- // 如果变量包含 {variable} 或 {{variable}} 格式,尝试解析为变量名
|
|
|
|
|
- if (varWithBraces.startsWith('{') && varWithBraces.endsWith('}')) {
|
|
|
|
|
- return varValue !== undefined ? String(varValue) : varWithBraces;
|
|
|
|
|
- }
|
|
|
|
|
- // 如果不是变量格式,直接使用原值
|
|
|
|
|
- return varWithBraces;
|
|
|
|
|
- });
|
|
|
|
|
- message = messages.join(' ');
|
|
|
|
|
- } else if (action.value) {
|
|
|
|
|
- // 使用 value 字段,支持变量替换(只支持 {{variable}} 格式,用于字符串拼接)
|
|
|
|
|
- message = replaceVariablesInString(action.value, variableContext);
|
|
|
|
|
- } else {
|
|
|
|
|
- // 如果没有提供任何内容,输出空字符串
|
|
|
|
|
- message = '';
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // 输出到 console.log(仅限小红书随机浏览工作流)
|
|
|
|
|
- if (folderPath && folderPath.includes('小红书随机浏览工作流')) {
|
|
|
|
|
- console.log(message);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // 注意:log 类型不写入 log.txt 文件,只发送到 UI
|
|
|
|
|
-
|
|
|
|
|
- // 发送 log 消息事件到 UI
|
|
|
|
|
- const logEvent = new CustomEvent('log-message', {
|
|
|
|
|
- detail: {
|
|
|
|
|
- message: message
|
|
|
|
|
- }
|
|
|
|
|
- });
|
|
|
|
|
- window.dispatchEvent(logEvent);
|
|
|
|
|
-
|
|
|
|
|
- return { success: true };
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
case 'img-bounding-box-location': {
|
|
case 'img-bounding-box-location': {
|
|
|
// 图像区域定位
|
|
// 图像区域定位
|
|
|
// 支持新的 inVars/outVars 格式(都可以为空)
|
|
// 支持新的 inVars/outVars 格式(都可以为空)
|
|
@@ -2662,7 +2485,7 @@ async function executeAction(action, device, folderPath, resolution) {
|
|
|
return { success: false, error: '缺少完整截图路径,且无法自动获取设备截图(缺少设备ID)' };
|
|
return { success: false, error: '缺少完整截图路径,且无法自动获取设备截图(缺少设备ID)' };
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // 调用 Func 目录下的执行函数
|
|
|
|
|
|
|
+ // 调用 fun 目录下的执行函数
|
|
|
const result = await executeImgBoundingBoxLocation({
|
|
const result = await executeImgBoundingBoxLocation({
|
|
|
device,
|
|
device,
|
|
|
screenshot: screenshotPath,
|
|
screenshot: screenshotPath,
|
|
@@ -2725,7 +2548,7 @@ async function executeAction(action, device, folderPath, resolution) {
|
|
|
return { success: false, error: '缺少设备 ID,无法自动获取截图' };
|
|
return { success: false, error: '缺少设备 ID,无法自动获取截图' };
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // 调用 Func 目录下的执行函数
|
|
|
|
|
|
|
+ // 调用 fun 目录下的执行函数
|
|
|
const result = await executeImgCenterPointLocation({
|
|
const result = await executeImgCenterPointLocation({
|
|
|
device,
|
|
device,
|
|
|
template: templatePath,
|
|
template: templatePath,
|
|
@@ -3069,89 +2892,6 @@ async function executeAction(action, device, folderPath, resolution) {
|
|
|
return { success: true };
|
|
return { success: true };
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- case 'delay': {
|
|
|
|
|
- // 延迟
|
|
|
|
|
- const delayMs = parseDelayString(action.value || action.delay || '0s');
|
|
|
|
|
- if (delayMs > 0) {
|
|
|
|
|
- await new Promise(resolve => setTimeout(resolve, delayMs));
|
|
|
|
|
- }
|
|
|
|
|
- return { success: true };
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- case 'swipe': {
|
|
|
|
|
- // 滑动操作
|
|
|
|
|
- if (!electronAPI || !electronAPI.sendSwipe) {
|
|
|
|
|
- return { success: false, error: '滑动 API 不可用' };
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- const { x1, y1, x2, y2 } = calculateSwipeCoordinates(
|
|
|
|
|
- action.value,
|
|
|
|
|
- resolution.width,
|
|
|
|
|
- resolution.height
|
|
|
|
|
- );
|
|
|
|
|
-
|
|
|
|
|
- const swipeResult = await electronAPI.sendSwipe(device, x1, y1, x2, y2, 300);
|
|
|
|
|
-
|
|
|
|
|
- if (!swipeResult.success) {
|
|
|
|
|
- return { success: false, error: `滑动失败: ${swipeResult.error}` };
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // 成功滑动
|
|
|
|
|
- return { success: true };
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- case 'string-press': {
|
|
|
|
|
- // 向后兼容:文字识别并点击
|
|
|
|
|
- if (!electronAPI || !electronAPI.findTextAndGetCoordinate) {
|
|
|
|
|
- return { success: false, error: '文字识别 API 不可用' };
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- const matchResult = await electronAPI.findTextAndGetCoordinate(device, action.value);
|
|
|
|
|
-
|
|
|
|
|
- if (!matchResult.success) {
|
|
|
|
|
- return { success: false, error: `文字识别失败: ${matchResult.error}` };
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- const { clickPosition } = matchResult;
|
|
|
|
|
- const { x, y } = clickPosition;
|
|
|
|
|
-
|
|
|
|
|
- if (!electronAPI || !electronAPI.sendTap) {
|
|
|
|
|
- return { success: false, error: '点击 API 不可用' };
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- const tapResult = await electronAPI.sendTap(device, x, y);
|
|
|
|
|
-
|
|
|
|
|
- if (!tapResult.success) {
|
|
|
|
|
- return { success: false, error: `点击失败: ${tapResult.error}` };
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // 成功点击文字
|
|
|
|
|
- return { success: true };
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- case 'scroll': {
|
|
|
|
|
- // 滚动操作(小幅度滚动)
|
|
|
|
|
- if (!electronAPI || !electronAPI.sendScroll) {
|
|
|
|
|
- return { success: false, error: '滚动 API 不可用' };
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- const scrollResult = await electronAPI.sendScroll(
|
|
|
|
|
- device,
|
|
|
|
|
- action.value,
|
|
|
|
|
- resolution.width,
|
|
|
|
|
- resolution.height,
|
|
|
|
|
- DEFAULT_SCROLL_DISTANCE,
|
|
|
|
|
- 500
|
|
|
|
|
- );
|
|
|
|
|
-
|
|
|
|
|
- if (!scrollResult.success) {
|
|
|
|
|
- return { success: false, error: `滚动失败: ${scrollResult.error}` };
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // 成功滚动
|
|
|
|
|
- return { success: true };
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
default:
|
|
default:
|
|
|
return { success: false, error: `未知的操作类型: ${action.type}` };
|
|
return { success: false, error: `未知的操作类型: ${action.type}` };
|
|
|
}
|
|
}
|