diff --git a/.env.development b/.env.development index ab0a6cc..51d4ce3 100644 --- a/.env.development +++ b/.env.development @@ -7,8 +7,8 @@ VITE_APP_ENV = 'development' # 招商车研物联网平台/开发环境 VITE_APP_BASE_API = '/dev-api' -VITE_API_URL = http://localhost:13080 -VITE_WS_URL = ws://localhost:13080/ws +VITE_API_URL = http://192.168.0.222:13080 +VITE_WS_URL = ws://192.168.0.222:13080/ws VITE_PORT = 80 VITE_INSPECTION_TYPE = "inspection" VITE_MINIO_URL = "http://192.168.28.10:9000/cmvr-iot/" diff --git a/.gitignore b/.gitignore index 78a752d..dc89b16 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,5 @@ selenium-debug.log package-lock.json yarn.lock + +.history diff --git a/src/api/device/flow.js b/src/api/device/flow.js index f309674..5985597 100644 --- a/src/api/device/flow.js +++ b/src/api/device/flow.js @@ -68,4 +68,12 @@ export function getArmStatus(params) { method: 'get', params: params }) +} + +export function getAgvStatus(params) { + return request({ + url: '/api/agv/getRuntimeState', + method: 'get', + params: params + }) } \ No newline at end of file diff --git a/src/views/flow/components/ParamsDrawer.vue b/src/views/flow/components/ParamsDrawer.vue index 8bf7661..af78070 100644 --- a/src/views/flow/components/ParamsDrawer.vue +++ b/src/views/flow/components/ParamsDrawer.vue @@ -31,6 +31,7 @@ import CodeNodeParams from './params/CodeNodeParams.vue' import HttpNodeParams from './params/HttpNodeParams.vue' import SdAgentNodeParams from './params/SdAgentNodeParams.vue' import RecognizeNodeParams from './params/RecognizeNodeParams.vue' +import DeviceUniversal from './params/device_universal.vue' const props = defineProps({ drawer: Boolean, @@ -49,6 +50,7 @@ const currentComponent = computed(() => { if (type === 'http') return HttpNodeParams if (type === 'sdAgent') return SdAgentNodeParams if (type === 'recognize') return RecognizeNodeParams + if (type === 'device_universal') return DeviceUniversal return null }) diff --git a/src/views/flow/components/params/BasicNodeParams.vue b/src/views/flow/components/params/BasicNodeParams.vue index ee8eef7..cbdc827 100644 --- a/src/views/flow/components/params/BasicNodeParams.vue +++ b/src/views/flow/components/params/BasicNodeParams.vue @@ -106,7 +106,7 @@ import { Plus, Minus } from '@element-plus/icons-vue' import FormItemRecursive from '../FormItemRecursive.vue' import { getInput } from '@/utils/flow' import { useQuote } from './useQuote.js' -import { getArmStatus } from '@/api/device/flow' +import { getArmStatus, getAgvStatus } from '@/api/device/flow' import { de } from 'element-plus/es/locale/index.mjs' const props = defineProps({ @@ -219,7 +219,21 @@ const testRule = (property) => { } else { return Promise.reject(new Error('加速度必须大于速度')) } - }, trigger: 'blur' } + }, trigger: 'change' } + ] + + } + + if (property?.name === 'velocity') { + return [ + { required: true, message: '请输入参数值', trigger: 'blur' }, + { validator: () => { + if (formData.nodeParams.find(param => param.name === 'acceleration')?.input > formData.nodeParams.find(param => param.name === 'velocity')?.input) { + return Promise.resolve() + } else { + return Promise.reject(new Error('加速度必须大于速度')) + } + }, trigger: 'change' } ] } @@ -230,7 +244,31 @@ const robotFormAction = ref() const submitRobotForm = () => { if (robotFormAction.value === 'AGV_MOVE_TO_POINT') { - + robotFormRef.value.validate(async (valid) => { + if (valid) { + try { + const response = await getAgvStatus({ deviceId: robotForm.deviceId, terminalId: robotForm.terminalId }) + if (response && response.data) { + dialogVisible.value = false + const { x, y, theta } = response.data.pose + const testMap = { + x: x, + y: y, + theta: theta, + deviceId: robotForm.deviceId + } + // 将获取到的位置信息设置到 formData 中 + formData.nodeParams.forEach(param => { + if (testMap.hasOwnProperty(param.name)) { + param.input = testMap[param.name] + } + }) + } + } catch (error) { + console.error('获取位置信息失败:', error) + } + } + }) } else if (robotFormAction.value === 'ARM_MOVE_TO_POINT') { robotFormRef.value.validate(async (valid) => { if (valid) { diff --git a/src/views/flow/components/params/Device_universal.vue b/src/views/flow/components/params/Device_universal.vue new file mode 100644 index 0000000..165b95c --- /dev/null +++ b/src/views/flow/components/params/Device_universal.vue @@ -0,0 +1,463 @@ + + + + + + 输入参数 + + + + + + + + + + + + + + + + + + + + + + + + + + + visibleChange(visible, index, property.quote) + " + @change="(value) => cascaderChange(value, index, formData, 'nodeParams')" + /> + + + + + + + + + + + + + + + + 输出参数 + + + + + + addFormItem(e, 'outputParams')" + class="addFormItem" + > + + + + + + deleteTopLevelItem(path, 'outputParams')" + /> + + + + + + + + + + \ No newline at end of file diff --git a/src/views/flow/config.js b/src/views/flow/config.js index 02cb59b..a82eafe 100644 --- a/src/views/flow/config.js +++ b/src/views/flow/config.js @@ -323,6 +323,23 @@ function handler(params) { ], outputType: 'json' }, + { + icon: cameraSvg, + name: "通用指令", + type: "device_universal", + desc: "执行设备通用指令", + action: 'DEVICE_EXECUTE_JSON_COMMAND', + nodeType: 'EDGE', + nodeParams: [ + { name: "terminalId", type: "input", input: "", disabled: true }, + { name: "deviceId", type: "input", input: "", required: false, disabled: true }, + { name: "requestJson", type: "input", input: `{ + "a":123 +}`, required: false, disabled: true }, + ], + outputParams: [], + outputType: 'json' + }, { icon: videoSvg, name: "机械臂", diff --git a/src/views/flow/index.vue b/src/views/flow/index.vue index 1aa9636..c1554e6 100644 --- a/src/views/flow/index.vue +++ b/src/views/flow/index.vue @@ -193,7 +193,8 @@ v-if="property.componentType === 'number'" v-model="property.input" :min="0" - :controls="false" + :max="property.max || Infinity" + :step="property.step || 1" :step-strictly="true" placeholder="请输入" clearable @@ -880,6 +881,7 @@ const nodeAction = ref(""); const singleNodeExecution = (e) => { const { name, nodeType, nodeParams, action } = e.detail; + console.log('nodeParams', nodeParams) nodeName.value = name; visible.value = true; nodeAction.value = action; @@ -932,11 +934,80 @@ const clearRun = () => { totalRunningTime.value = 0; } -const handleDeleteKey = (event) => { - const activeElement = document.activeElement; - if (["INPUT", "TEXTAREA"].includes(activeElement?.tagName) || activeElement?.isContentEditable) return; +const NODE_PASTE_OFFSET = 40; +const NON_COPYABLE_NODE_TYPES = new Set(["start", "end", "selectArea"]); +let copiedNodes = []; +let pasteCount = 0; - if ((event.ctrlKey || event.metaKey) && event.key.toLowerCase() === "z") { +const isTextEditing = (event) => { + const element = event.target || document.activeElement; + return ["INPUT", "TEXTAREA", "SELECT"].includes(element?.tagName) + || element?.isContentEditable; +}; + +const copySelectedNodes = (event) => { + const nodes = lf.getSelectElements().nodes.filter( + (node) => !NON_COPYABLE_NODE_TYPES.has(node.type), + ); + if (!nodes.length) return; + + event.preventDefault(); + copiedNodes = JSON.parse(JSON.stringify(nodes)); + pasteCount = 0; +}; + +const pasteCopiedNodes = (event) => { + if (!copiedNodes.length || flowStore.disableForm) return; + + event.preventDefault(); + pasteCount += 1; + const offset = NODE_PASTE_OFFSET * pasteCount; + const pastedNodes = copiedNodes.map((sourceNode) => { + const properties = JSON.parse(JSON.stringify(sourceNode.properties || {})); + const parentId = properties.parentId; + delete properties.parentId; + + const newNode = lf.addNode({ + type: sourceNode.type, + x: Number(sourceNode.x || 0) + offset, + y: Number(sourceNode.y || 0) + offset, + properties, + }); + + if (sourceNode.type === "branch" && Array.isArray(properties.anchor)) { + const oldElseId = `${sourceNode.id}_else`; + const newElseId = `${newNode.id}_else`; + const anchors = properties.anchor.map((anchor) => ( + anchor.id === oldElseId ? { ...anchor, id: newElseId } : anchor + )); + lf.setProperties(newNode.id, { ...properties, anchor: anchors }); + } + + if (parentId && lf.getNodeModelById(parentId)) { + lf.addToGroup(parentId, newNode.id, { x: newNode.x, y: newNode.y }); + } + return newNode; + }); + + lf.clearSelectElements(); + if (pastedNodes.length === 1) lf.selectElementById(pastedNodes[0].id); +}; + +const handleKeyboardShortcut = (event) => { + if (isTextEditing(event)) return; + + const isModifierKey = event.ctrlKey || event.metaKey; + const key = event.key.toLowerCase(); + if (isModifierKey && key === "c") { + if (!flowStore.disableForm) copySelectedNodes(event); + return; + } + if (isModifierKey && key === "v") { + pasteCopiedNodes(event); + return; + } + + if (isModifierKey && key === "z") { if (flowStore.disableForm) return; event.preventDefault(); if (event.shiftKey) lf.redo(); @@ -972,7 +1043,7 @@ onMounted(() => { initFlow(); document.addEventListener("singleNodeExecution", singleNodeExecution); registerBeforeUnload(); - window.addEventListener("keydown", handleDeleteKey); + window.addEventListener("keydown", handleKeyboardShortcut); onFlowEvent("openParamsDrawer", (node) => { showParamsDrawer.value = true; @@ -1024,7 +1095,7 @@ onUnmounted(() => { if (window.lf === lf) delete window.lf; window.removeEventListener("beforeunload", handleBeforeUnload); - window.removeEventListener("keydown", handleDeleteKey); + window.removeEventListener("keydown", handleKeyboardShortcut); document.removeEventListener("singleNodeExecution", singleNodeExecution); }); diff --git a/src/views/flow/nodes/function/device_universal.vue b/src/views/flow/nodes/function/device_universal.vue new file mode 100644 index 0000000..3b06103 --- /dev/null +++ b/src/views/flow/nodes/function/device_universal.vue @@ -0,0 +1,135 @@ + + + + + + + + + + + + + {{ errorInfoData }} + + + + + + + + + diff --git a/src/views/flow/vue-flow/FlowNode.vue b/src/views/flow/vue-flow/FlowNode.vue index 93f36d1..43e4af1 100644 --- a/src/views/flow/vue-flow/FlowNode.vue +++ b/src/views/flow/vue-flow/FlowNode.vue @@ -74,6 +74,7 @@ import CodeNode from "../nodes/function/codeNode.vue"; import CurrentLoopNode from "../nodes/function/currentLoop.vue"; import SdAgentNode from "../nodes/function/sdAgent.vue"; import RecognizeNode from "../nodes/function/recognize.vue"; +import DeviceUniversal from "../nodes/function/device_universal.vue"; const props = defineProps({ id: String, @@ -98,6 +99,7 @@ const componentMap = { currentLoop: CurrentLoopNode, sdAgent: SdAgentNode, recognize: RecognizeNode, + device_universal: DeviceUniversal, }; const flowStore = useFlowStore(); diff --git a/src/views/test/log/index.vue b/src/views/test/log/index.vue index 39de363..d2a4255 100644 --- a/src/views/test/log/index.vue +++ b/src/views/test/log/index.vue @@ -170,9 +170,6 @@ border direction="vertical" > - {{ - item.logLevel - }} {{ item.nodeType }} @@ -180,7 +177,10 @@ item.message }} - {{ JSON.parse(item.params) }} + {{ JSON.parse(item.paramsIn) }} + + + {{ JSON.parse(item.paramsOut) }} @@ -297,6 +297,7 @@ function getInstList() { function cancel() { open.value = false; reset(); + console.log(open.value) } // 表单重置 @@ -404,6 +405,8 @@ function getLogList() { } function handleOpenLogDialog(row) { + console.log(row) + console.log('open', open.value) logQueryParams.value.instId = row.id; open.value = true; }
{{ JSON.parse(item.params) }}
{{ JSON.parse(item.paramsIn) }}
{{ JSON.parse(item.paramsOut) }}