| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449 |
- /**
- * 节点操作(复制、删除、创建、粘贴)
- */
- import { createEchoInputPorts } from '../node-renderer/echo/echo-node.js';
- import { createAppendInputPorts } from '../node-renderer/append/append-node.js';
- /**
- * 创建新节点
- * @param {string} nodeType - 节点类型
- * @param {number} x - X 坐标
- * @param {number} y - Y 坐标
- * @returns {Object} 新节点对象
- */
- export function createNode(nodeType, x, y, nodeData = {}) {
- const nodeId = `node_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
-
- // 根据节点类型创建默认数据
- const defaultData = { ...getDefaultNodeData(nodeType), ...nodeData };
-
- const { inputs, outputs } = getNodePortsFromType(nodeType, defaultData);
-
- return {
- id: nodeId,
- type: nodeType,
- label: getNodeLabelFromType(nodeType, defaultData),
- x: Math.round(x),
- y: Math.round(y),
- inputs,
- outputs,
- data: defaultData
- };
- }
- /**
- * 复制节点
- * @param {Object} node - 节点对象
- * @param {number} offsetX - X 偏移(默认 50)
- * @param {number} offsetY - Y 偏移(默认 50)
- * @returns {Object} 复制的节点
- */
- export function copyNode(node, offsetX = 50, offsetY = 50) {
- const newNodeId = `node_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
-
- return {
- ...node,
- id: newNodeId,
- x: node.x + offsetX,
- y: node.y + offsetY
- };
- }
- /**
- * 复制多个节点
- * @param {Array} nodes - 节点数组
- * @param {number} offsetX - X 偏移
- * @param {number} offsetY - Y 偏移
- * @returns {Array} 复制的节点数组
- */
- export function copyNodes(nodes, offsetX = 50, offsetY = 50) {
- // 计算节点组的边界
- const bounds = getNodesBounds(nodes);
-
- return nodes.map(node => {
- const newNodeId = `node_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
- return {
- ...node,
- id: newNodeId,
- x: node.x - bounds.minX + offsetX,
- y: node.y - bounds.minY + offsetY
- };
- });
- }
- /**
- * 获取节点组的边界
- * @param {Array} nodes - 节点数组
- * @returns {Object} 边界 {minX, minY, maxX, maxY}
- */
- export function getNodesBounds(nodes) {
- if (!nodes || nodes.length === 0) {
- return { minX: 0, minY: 0, maxX: 0, maxY: 0 };
- }
-
- let minX = Infinity;
- let minY = Infinity;
- let maxX = -Infinity;
- let maxY = -Infinity;
-
- nodes.forEach(node => {
- minX = Math.min(minX, node.x);
- minY = Math.min(minY, node.y);
- maxX = Math.max(maxX, node.x + 200); // 假设节点宽度 200
- maxY = Math.max(maxY, node.y + 100); // 假设节点高度 100
- });
-
- return { minX, minY, maxX, maxY };
- }
- /**
- * 根据节点类型获取默认数据
- * @param {string} nodeType - 节点类型
- * @returns {Object} 默认数据对象
- */
- function getDefaultNodeData(nodeType) {
- const defaults = {
- 'begin': { type: 'begin' },
- 'adb': { type: 'adb', method: 'click', inVars: [], outVars: [] },
- 'if': { type: 'if', condition: '1 == 1', ture: [], false: [] },
- 'while': { type: 'while', condition: '1 == 1', ture: [] },
- 'delay': { type: 'delay', value: '1s' },
- 'set': { type: 'set', variable: '{var}', value: '' },
- 'echo': { type: 'echo', value: '' },
- 'random': { type: 'random', inVars: ['0', '100'], outVars: ['{num}'] }
- };
-
- return defaults[nodeType] || { type: nodeType };
- }
- /**
- * 获取节点参数定义(入参配置)
- * @param {string} nodeType - 节点类型
- * @param {Object} data - 节点数据(包含 method 等信息)
- * @returns {Array} 参数定义数组 [{name, label, type, defaultValue}]
- */
- export function getNodeParameterDefinitions(nodeType, data = {}) {
- const paramDefs = [];
-
- if (nodeType === 'adb') {
- const method = data.method || 'click';
- switch (method) {
- case 'click':
- paramDefs.push({ name: 'position', label: '位置坐标', type: 'string', defaultValue: '0,0', description: '格式: "x,y" 或 JSON: "{\"x\":123,\"y\":456}"' });
- break;
- case 'input':
- paramDefs.push({ name: 'text', label: '文本内容', type: 'string', defaultValue: '' });
- break;
- case 'locate':
- paramDefs.push({ name: 'image', label: '图片/文字', type: 'string', defaultValue: '' });
- break;
- case 'swipe':
- paramDefs.push({ name: 'direction', label: '方向', type: 'string', defaultValue: 'up-down', description: 'up-down/down-up/left-right/right-left' });
- break;
- case 'scroll':
- paramDefs.push({ name: 'direction', label: '方向', type: 'string', defaultValue: 'up', description: 'up/down' });
- break;
- case 'press':
- paramDefs.push({ name: 'image', label: '图片路径', type: 'string', defaultValue: '' });
- break;
- case 'string-press':
- paramDefs.push({ name: 'text', label: '文字内容', type: 'string', defaultValue: '' });
- break;
- case 'keyevent':
- paramDefs.push({ name: 'key', label: '按键', type: 'string', defaultValue: '' });
- break;
- }
- } else if (nodeType === 'delay') {
- paramDefs.push({ name: 'value', label: '延迟时间', type: 'string', defaultValue: '1s', description: '格式: 1s, 2m, 3h' });
- } else if (nodeType === 'set') {
- paramDefs.push({ name: 'variable', label: '变量名', type: 'string', defaultValue: '{var}' });
- paramDefs.push({ name: 'value', label: '变量值', type: 'string', defaultValue: '' });
- } else if (nodeType === 'if') {
- paramDefs.push({ name: 'condition', label: '条件表达式', type: 'string', defaultValue: '1 == 1' });
- } else if (nodeType === 'while') {
- paramDefs.push({ name: 'condition', label: '循环条件', type: 'string', defaultValue: '1 == 1' });
- } else if (nodeType === 'echo') {
- paramDefs.push({ name: 'value', label: '输出内容', type: 'string', defaultValue: '' });
- } else if (nodeType === 'random') {
- // random 节点使用 inVars[min, max] 和 outVars[{variable}]
- // 从 data 中读取默认值(如果存在)
- const minDefault = (data.inVars && Array.isArray(data.inVars) && data.inVars.length > 0)
- ? parseInt(data.inVars[0]) || 0
- : 0;
- const maxDefault = (data.inVars && Array.isArray(data.inVars) && data.inVars.length > 1)
- ? parseInt(data.inVars[1]) || 100
- : 100;
-
- paramDefs.push({ name: 'min', label: '最小值', type: 'int', defaultValue: minDefault });
- paramDefs.push({ name: 'max', label: '最大值', type: 'int', defaultValue: maxDefault });
- } else if (nodeType === 'schedule') {
- paramDefs.push({ name: 'interval', label: '执行间隔', type: 'string', defaultValue: '1s', description: '格式: 1s, 2m, 3h' });
- paramDefs.push({ name: 'repeat', label: '重复次数', type: 'int', defaultValue: -1, description: '-1 表示无限循环' });
- }
-
- // 如果节点有自定义参数(通过 params 数组定义)
- if (data.params && Array.isArray(data.params)) {
- data.params.forEach((param, index) => {
- paramDefs.push({
- name: param.name || `param_${index}`,
- label: param.label || `参数${index + 1}`,
- type: param.type || 'string',
- defaultValue: param.defaultValue || ''
- });
- });
- }
-
- return paramDefs;
- }
- /**
- * 根据节点类型获取端口定义
- * @param {string} nodeType - 节点类型
- * @param {Object} data - 节点数据
- * @returns {Object} {inputs: [], outputs: []}
- */
- export function getNodePortsFromType(nodeType, data) {
- console.log(`🔌 [getNodePortsFromType] nodeType: ${nodeType}, data:`, data);
- const inputs = [];
- const outputs = [];
-
- // Begin 节点只有输出端口,没有输入端口
- if (nodeType === 'begin') {
- outputs.push({ id: 'output_0', label: '', type: 'execution' });
- console.log(` ✅ Begin 节点端口生成完成`);
- return { inputs, outputs };
- }
-
- // Echo 节点特殊处理:根据 value 字段动态创建端口
- if (nodeType === 'echo') {
- const value = data.value || '';
- console.log(` 🎤 Echo 节点 value: "${value}"`);
- const echoInputs = createEchoInputPorts(value);
- console.log(` ✅ Echo 输入端口数量: ${echoInputs.length}`, echoInputs);
- inputs.push(...echoInputs);
- outputs.push({ id: 'output_0', label: '', type: 'execution' });
- console.log(` ✅ Echo 节点端口生成完成 - inputs: ${inputs.length}, outputs: ${outputs.length}`);
- return { inputs, outputs };
- }
-
- // Append 节点特殊处理:根据 value 字段动态创建端口(纯数据节点)
- if (nodeType === 'append') {
- const value = data.value || '';
- console.log(` 📎 Append 节点 value: "${value}"`);
- const appendInputs = createAppendInputPorts(value);
- console.log(` ✅ Append 输入端口数量: ${appendInputs.length}`, appendInputs);
- inputs.push(...appendInputs);
- outputs.push({
- id: 'output_result',
- label: 'Result',
- type: 'data',
- paramType: 'string'
- });
- console.log(` ✅ Append 节点端口生成完成 - inputs: ${inputs.length}, outputs: ${outputs.length}`);
- return { inputs, outputs };
- }
-
- // If 节点特殊处理:简单的 Condition 数据端口
- if (nodeType === 'if') {
- console.log(` 🔀 If 节点`);
-
- // 执行输入
- inputs.push({ id: 'input_0', label: '', type: 'execution' });
-
- // 条件输入端口(简单的布尔输入,颜色应该是红色)
- inputs.push({
- id: 'input_condition',
- label: 'Condition',
- type: 'data',
- paramType: 'bool'
- });
-
- // True 和 False 输出(带方括号)
- outputs.push({ id: 'output_true', label: '[True]', type: 'execution' });
- outputs.push({ id: 'output_false', label: '[False]', type: 'execution' });
-
- console.log(` ✅ If 节点端口生成完成 - inputs: ${inputs.length}, outputs: ${outputs.length}`);
- return { inputs, outputs };
- }
-
- // 添加执行流程端口
- inputs.push({ id: 'input_0', label: '', type: 'execution' });
- outputs.push({ id: 'output_0', label: '', type: 'execution' });
-
- // 根据参数定义添加数据输入端口
- const paramDefs = getNodeParameterDefinitions(nodeType, data);
- paramDefs.forEach((param, index) => {
- inputs.push({
- id: `input_param_${param.name}`,
- label: param.label,
- type: 'data',
- paramName: param.name,
- paramType: param.type
- });
- });
-
- // 根据类型添加数据输出端口
- if (nodeType === 'random') {
- // random 节点输出 number 类型
- // 支持新格式:outVars[{variable}]
- if (data.outVars && Array.isArray(data.outVars) && data.outVars.length > 0) {
- const varName = data.outVars[0];
- outputs.push({ id: 'output_1', label: varName, type: 'data', paramType: 'number' });
- } else if (data.variable) {
- // 向后兼容旧格式
- outputs.push({ id: 'output_1', label: data.variable, type: 'data', paramType: 'number' });
- }
- } else if (nodeType === 'set' && data.variable) {
- // set 节点输出类型需要从变量定义中获取,默认 string
- outputs.push({ id: 'output_1', label: data.variable, type: 'data', paramType: 'string' });
- } else if (data.outVars && Array.isArray(data.outVars)) {
- // 其他节点的 outVars 输出
- data.outVars.forEach((varName, index) => {
- outputs.push({ id: `output_${index + 1}`, label: `out[${index}]`, type: 'data' });
- });
- }
-
- // schedule 和 while 需要循环输入端口(用于连回)
- if (nodeType === 'schedule' || nodeType === 'while') {
- inputs.push({ id: 'input_1', label: 'loop', type: 'execution' });
- }
-
- return { inputs, outputs };
- }
- /**
- * 根据节点类型获取标签
- * @param {string} nodeType - 节点类型
- * @param {Object} data - 节点数据(可选,用于获取 method 等信息)
- * @returns {string} 节点标签
- */
- function getNodeLabelFromType(nodeType, data = {}) {
- // 直接使用原始类型名称,与 processing.json 的 type 字段一致
- if (nodeType === 'adb' && data.method) {
- // adb 节点显示 method
- return `${nodeType}.${data.method}`;
- }
- return nodeType;
- }
- /**
- * 创建变量节点
- * @param {string} varName - 变量名称
- * @param {*} varValue - 变量值
- * @param {number} x - X 坐标
- * @param {number} y - Y 坐标
- * @param {string} mode - 模式:'get'(只有输出端口)或 'set'(有执行端口和数据输入端口)
- * @param {string} customId - 自定义节点 ID(可选,用于确保 ID 一致性)
- * @returns {Object} 变量节点对象
- */
- export function createVariableNode(varName, varValue, x, y, mode = 'get', customId = null) {
- // 如果没有提供自定义 ID,则生成一个(用于用户手动创建的节点)
- const nodeId = customId || `var_${mode}_${varName}_${Date.now()}`;
-
- // 根据变量值自动判断类型:bool > number > string
- let varType = 'string';
- if (typeof varValue === 'boolean') {
- varType = 'bool';
- } else if (typeof varValue === 'number') {
- varType = 'number';
- }
-
- let inputs = [];
- let outputs = [];
-
- if (mode === 'get') {
- // Get 节点:只有右侧输出数据端口
- outputs = [{
- id: 'output_0',
- label: varName,
- type: 'data',
- paramType: varType
- }];
- } else if (mode === 'set') {
- // Set 节点:有执行输入/输出端口 + 左侧数据输入端口
- inputs = [
- {
- id: 'input_exec',
- label: '',
- type: 'execution'
- },
- {
- id: 'input_value',
- label: varName,
- type: 'data',
- paramType: varType
- }
- ];
- outputs = [
- {
- id: 'output_exec',
- label: '',
- type: 'execution'
- }
- ];
- }
-
- return {
- id: nodeId,
- type: 'variable',
- varMode: mode, // 'get' 或 'set'
- label: varName,
- varName: varName,
- varType: varType,
- varValue: varValue,
- x: Math.round(x),
- y: Math.round(y),
- inputs,
- outputs,
- data: {
- varName,
- varValue,
- varType,
- varMode: mode
- }
- };
- }
- /**
- * 获取所有可用的节点类型列表
- * @returns {Array} 节点类型数组,按分类组织
- */
- export function getAvailableNodeTypes() {
- return [
- {
- category: '流程控制',
- types: [
- { type: 'begin', label: 'Begin' }
- ]
- },
- {
- category: '基础语法',
- types: [
- { type: 'schedule', label: '定时执行' },
- { type: 'if', label: '条件判断' },
- { type: 'while', label: '循环' }
- ]
- },
- {
- category: '内置操作',
- types: [
- { type: 'delay', label: '延迟' },
- { type: 'set', label: '设置变量' },
- { type: 'echo', label: '打印信息' },
- { type: 'random', label: '生成随机数' }
- ]
- },
- {
- category: 'ADB操作',
- types: [
- { type: 'adb', label: 'ADB操作', method: 'click' },
- { type: 'adb', label: 'ADB输入', method: 'input' },
- { type: 'adb', label: 'ADB滑动', method: 'swipe' },
- { type: 'adb', label: 'ADB滚动', method: 'scroll' },
- { type: 'adb', label: 'ADB定位', method: 'locate' },
- { type: 'adb', label: 'ADB按键', method: 'keyevent' }
- ]
- }
- ];
- }
|