feat: 新增节点

This commit is contained in:
zhanghao 2025-09-22 15:42:11 +08:00
parent e988f6a183
commit ec2d020ae8
9 changed files with 454 additions and 404 deletions

View File

@ -12,6 +12,10 @@ import startSvg from './icon/start.svg'
import endSvg from './icon/end.svg'
import microphoneSvg from './icon/microphone.svg'
import sleepSvg from './icon/sleep.svg'
import planSvg from './icon/plan.svg'
import awakenSvg from './icon/awaken.svg'
import dialogueSvg from './icon/dialogue.svg'
import audioSvg from './icon/audio.svg'
import { v4 as randomUUID } from 'uuid'
@ -106,7 +110,7 @@ export const collapseList = [
{ name: "deviceId", type: "input", input: "", componentType: 'select', selectOptions: deviceOptions(), disabled: true }
],
outputType: 'img',
outputParams: [{ name: 'imageUrl', type: 'array<string>', desc: '图片地址数组', disabled: true}]
outputParams: [{ name: 'imageUrl', type: 'array<string>', desc: '图片地址数组', children: [], disabled: true }]
},
{
icon: cameraSvg,
@ -145,7 +149,7 @@ export const collapseList = [
{ name: "deviceId", type: "input", input: "", componentType: 'select', selectOptions: deviceOptions(), disabled: true }
],
outputType: 'video',
outputParams: [{ name: 'videoUrl', type: 'array<string>', desc: '视频播放地址数组'}]
outputParams: [{ name: 'videoUrl', type: 'array<string>', children: [], desc: '视频播放地址数组'}]
}
],
},
@ -242,7 +246,7 @@ export const collapseList = [
outputType: 'json'
},
{
icon: microphoneSvg,
icon: audioSvg,
name: "播放音频",
type: "serviceNode",
desc: "用于播放音频的节点",
@ -269,7 +273,7 @@ export const collapseList = [
{ name: "vehType", type: "input", input: "su7", disabled: true },
],
outputType: 'json',
outputParams: [{ name: 'coordinates', type: 'Object', desc: '坐标对象'}]
outputParams: [{ name: 'coordinates', type: 'Object', children: [], desc: '坐标对象'}]
},
],
},
@ -277,17 +281,60 @@ export const collapseList = [
collapseTitle: "语音交互",
nodeList: [
{
icon: microphoneSvg,
icon: planSvg,
name: "方案处理",
type: "serviceNode",
desc: "语音交互-方案处理",
action: 'VI_SCHEME',
nodeParams: [{ name: 'schemeId', type: "input", input: "", disabled: true }],
outputType: 'json',
outputParams: [
{ name: 'wakeCorpus', type: 'object', desc: '唤醒语料', children: [], disabled: true},
{ name: 'testCorpus', type: 'array<object>', desc: '测试语料数组', children: [], disabled: true}
]
},
{
icon: awakenSvg,
name: "唤醒语料",
type: "serviceNode",
desc: "唤醒语料处理",
action: 'VI_WAKE_CORPUS',
nodeParams: [
{ name: "", type: "input", input: ""}
],
action: 'VI_CORPUS_WAKE',
outputType: 'json',
outputParams: [{ name: 'coordinates', type: 'Object', desc: '坐标对象'}]
nodeParams: [{ name: 'wakeCorpus', type: "input", input: "", disabled: true }],
outputParams: [{ name: 'audioPath', type: 'string', desc: '语音路径', disabled: true}]
},
{
icon: dialogueSvg,
name: "单次对话语料",
type: "serviceNode",
desc: "测试语料-单次对话语料处理",
action: 'VI_CORPUS_WAKE',
outputType: 'json',
nodeParams: [{ name: 'testCorpus', type: "input", input: "", disabled: true }],
outputParams: [{ name: 'audioPath', type: 'string', desc: '语音路径', disabled: true}]
},
{
icon: dialogueSvg,
name: "连续对话语料",
type: "serviceNode",
desc: "测试语料-连续对话语料处理",
action: 'VI_CORPUS_CONTINUOUS',
outputType: 'json',
nodeParams: [{ name: 'testCorpus', type: "input", input: "", disabled: true }],
outputParams: [{ name: 'audioPath', type: 'string', desc: '语音路径', disabled: true}]
},
{
icon: audioSvg,
name: "播放语料",
type: "serviceNode",
desc: "用于播放语料的节点",
action: 'VI_PLAY_CORPUS',
outputType: 'json',
nodeParams: [
{ name: 'audioPath', type: "input", input: "", disabled: true },
{ name: 'deviceId', type: "input", input: "", disabled: true },
],
}
],
},
]

View File

@ -0,0 +1 @@
<svg t="1758526814700" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="11094" width="200" height="200"><path d="M983.779623 312.719408a510.292483 510.292483 0 0 0-109.795373-162.701951A511.088926 511.088926 0 0 0 512.000853 0.001707a508.756485 508.756485 0 0 0-361.983396 150.01575A511.088926 511.088926 0 0 0 0.001707 512.000853a508.756485 508.756485 0 0 0 150.01575 361.983397 511.088926 511.088926 0 0 0 162.701951 109.738484c63.089673 26.680844 130.161561 40.163489 199.281445 40.163488a508.756485 508.756485 0 0 0 199.281446-40.163488 508.358264 508.358264 0 0 0 162.701951-109.738484 511.088926 511.088926 0 0 0 109.738484-162.701951c26.680844-63.089673 40.163489-130.161561 40.163488-199.281446a508.642708 508.642708 0 0 0-40.106599-199.281445z m-55.295908 375.181597a451.583247 451.583247 0 0 1-96.881617 143.701093 451.583247 451.583247 0 0 1-143.701093 96.881617c-55.751018 23.60885-114.915364 35.498608-175.900152 35.498607a448.397475 448.397475 0 0 1-175.900151-35.498607 451.583247 451.583247 0 0 1-143.701094-96.881617 451.583247 451.583247 0 0 1-96.881616-143.701093A448.397475 448.397475 0 0 1 60.019384 512.000853c0-60.984787 11.889758-120.206022 35.498608-175.900151 22.755518-53.816799 55.409685-102.172274 96.881616-143.701094a451.583247 451.583247 0 0 1 143.701094-96.881616A448.397475 448.397475 0 0 1 512.000853 60.019384c60.984787 0 120.206022 11.889758 175.900152 35.498608 53.816799 22.755518 102.172274 55.409685 143.701093 96.881616 41.52882 41.52882 74.126099 89.884295 96.881617 143.701094 23.60885 55.751018 35.498608 114.915364 35.498607 175.900151s-11.889758 120.206022-35.498607 175.900152z" fill="#65BEE0" p-id="11095"></path><path d="M263.965711 416.029458a30.094172 30.094172 0 0 0-29.980394 29.980394v131.982002a30.094172 30.094172 0 0 0 60.017677 0V446.009852a30.094172 30.094172 0 0 0-29.980394-29.980394z m124.017571-63.999894a30.094172 30.094172 0 0 0-29.980394 29.980395v259.981789c0 16.49775 13.482644 29.980394 29.980394 29.980394 16.49775 0 30.037283-13.482644 30.037284-29.980394v-259.981789a30.094172 30.094172 0 0 0-30.037284-29.980395zM512.000853 256.00128a30.094172 30.094172 0 0 0-29.980394 29.980394v452.038358c0 16.49775 13.482644 29.980394 29.980394 29.980395 16.49775 0 29.980394-13.482644 29.980395-29.980395V285.981674A30.094172 30.094172 0 0 0 512.000853 256.00128z m248.035142 160.028178a30.094172 30.094172 0 0 0-30.037283 29.980394v131.982002c0 16.49775 13.482644 29.980394 29.980395 29.980395 16.49775 0 30.037283-13.482644 30.037283-29.980395V446.009852a30.094172 30.094172 0 0 0-30.037283-29.980394z m-124.017571-63.999894a30.094172 30.094172 0 0 0-30.037283 29.980395v259.981789a30.094172 30.094172 0 0 0 60.017678 0v-259.981789a30.094172 30.094172 0 0 0-29.980395-29.980395z" fill="#65BEE0" p-id="11096"></path></svg>

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

@ -0,0 +1 @@
<svg t="1758526221559" class="icon" viewBox="0 0 1142 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3938" width="200" height="200"><path d="M477.13739 76.859452c-260.63725 0-471.90562 211.26837-471.90562 471.90562 0 260.542127 211.26837 471.90562 471.90562 471.90562 260.63725 0 471.90562-211.26837 471.90562-471.90562C948.947887 288.127822 737.77464 76.859452 477.13739 76.859452zM477.13739 874.08602c-37.00288 0-67.156897-29.393033-67.156897-65.349559 0-4.661031 3.614677-8.370831 8.085462-8.370831l118.047747 0c4.470785 0 8.085462 3.7098 8.085462 8.370831C544.294287 844.692987 514.140269 874.08602 477.13739 874.08602zM696.966837 750.235764 254.834742 750.235764c-3.424431 0-6.278124-2.853693-6.278124-6.46837l0-47.085927c0-28.441802 32.532095-50.985973 75.337483-53.459173-5.23177-15.505063-7.895216-31.485741-7.895216-47.751788L315.998885 464.676266c0-66.776405 43.281003-124.991732 108.154947-146.394426L424.153832 304.964608c0-27.775941 23.210033-50.320111 51.746958-50.320111 28.536925 0 51.746958 22.544171 51.746958 50.320111l0 13.222109c64.873943 21.402694 108.154947 79.618021 108.154947 146.394426l0 130.889364c0 16.170924-2.663446 32.151602-7.990339 47.751788 42.805388 2.4732 75.337483 25.017371 75.337483 53.459173l0 47.085927C703.244961 747.382072 700.486391 750.235764 696.966837 750.235764z" p-id="3939" fill="#65BEE0"></path></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -0,0 +1 @@
<svg t="1758526744847" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="8113" width="200" height="200"><path d="M512 117.76c218.065455 0 395.636364 135.68 395.636364 302.545455s-177.570909 302.545455-395.636364 302.545454a144.058182 144.058182 0 0 0-50.501818 10.472727 791.272727 791.272727 0 0 0-162.909091 99.374546c-4.421818-128.232727-23.272727-168.96-56.087273-191.534546C162.909091 584.145455 116.363636 503.621818 116.363636 420.538182c0-167.098182 177.570909-302.545455 395.636364-302.545455m0-69.818182c-256 0-465.454545 166.632727-465.454545 372.363637 0 110.312727 59.810909 209.454545 155.22909 277.643636 23.272727 16.756364 27.694545 116.363636 27.694546 192.232727a41.192727 41.192727 0 0 0 40.494545 41.89091 40.029091 40.029091 0 0 0 23.272728-8.61091c58.414545-45.149091 141.730909-105.425455 192-124.50909a74.007273 74.007273 0 0 1 26.763636-6.05091c256 0 465.454545-166.865455 465.454545-372.363636s-209.454545-372.363636-465.454545-372.363636z" fill="#65BEE0" p-id="8114"></path><path d="M663.272727 325.818182h-302.545454a34.909091 34.909091 0 0 0 0 69.818182h302.545454a34.909091 34.909091 0 0 0 0-69.818182zM616.727273 488.727273h-209.454546a34.909091 34.909091 0 0 0 0 69.818182h209.454546a34.909091 34.909091 0 0 0 0-69.818182z" fill="#65BEE0" p-id="8115"></path></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -0,0 +1 @@
<svg t="1758526128932" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2666" width="200" height="200"><path d="M512 575.146667c-10.922667 0-21.845333-2.730667-31.402667-8.192L352.426667 494.933333l-48.810667 27.477334L512 639.658667l208.384-117.248L671.573333 494.933333l-128.170666 72.192c-9.557333 5.290667-20.48 8.021333-31.402667 8.021334z" fill="#65BEE0" p-id="2667"></path><path d="M512 708.437333c-10.922667 0-21.845333-2.730667-31.402667-8.192l-146.432-82.432-30.378666 17.237334L512 752.128l208.384-117.248-30.378667-17.237333-146.432 82.432c-9.728 5.632-20.650667 8.362667-31.573333 8.362666z" fill="#65BEE0" p-id="2668"></path><path d="M512 34.133333C248.149333 34.133333 34.133333 248.149333 34.133333 512s214.016 477.866667 477.866667 477.866667 477.866667-214.016 477.866667-477.866667S775.850667 34.133333 512 34.133333z m248.149333 432.128c20.138667 11.434667 32.768 32.768 32.768 55.978667 0 23.210667-12.458667 44.544-32.597333 55.978667l-0.853333 0.341333 0.682666 0.341333c20.138667 11.264 32.768 32.768 32.768 55.808 0 23.210667-12.458667 44.544-32.768 55.978667l-216.746666 122.026667a64.529067 64.529067 0 0 1-62.976 0l-216.746667-122.026667a64.494933 64.494933 0 0 1-32.768-55.978667c0-23.210667 12.629333-44.544 32.768-55.978666l0.682667-0.341334-0.682667-0.341333a64.494933 64.494933 0 0 1-32.768-55.978667c0-23.210667 12.629333-44.544 32.768-55.978666l18.944-10.581334-18.944-10.752a64.494933 64.494933 0 0 1-32.768-55.978666c0-23.210667 12.458667-44.544 32.768-55.978667l216.746667-122.026667c19.456-10.922667 43.52-10.922667 62.976 0l216.746666 122.026667c20.138667 11.434667 32.768 32.768 32.768 55.978667 0 23.210667-12.629333 44.544-32.768 55.978666l-18.944 10.752 18.944 10.752z" fill="#65BEE0" p-id="2669"></path><path d="M720.384 389.12L512 271.701333 303.616 389.12 512 506.368z" fill="#65BEE0" p-id="2670"></path></svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@ -11,7 +11,7 @@
:rules="[{ required: true, message: '请输入', trigger: 'blur' }]"
>
<el-input
:disabled="isFirstLevel && index === 0"
:disabled="item.disabled"
v-model="item.name"
placeholder="请输入"
clearable
@ -25,7 +25,7 @@
:rules="[{ required: true, message: '请输入', trigger: 'blur' }]"
>
<el-select
:disabled="isFirstLevel && index === 0"
:disabled="item.disabled"
v-model="item.type"
@change="handleTypeChange(item)"
>
@ -47,7 +47,7 @@
:rules="[{ required: true, message: '请输入', trigger: 'blur' }]"
>
<el-input
:disabled="isFirstLevel && index === 0"
:disabled="item.disabled"
v-model="item.desc"
placeholder="请输入"
clearable
@ -55,16 +55,16 @@
</el-form-item>
<!-- 是否必填 -->
<el-form-item :label="isFirstLevel && index === 0 ? '必填' : ''">
<el-form-item v-if="formType === 'input'" :label="isFirstLevel && index === 0 ? '必填' : ''">
<el-switch
:disabled="isFirstLevel && index === 0"
:disabled="item.disabled"
v-model="item.required"
/>
</el-form-item>
<!-- 删除按钮第一层第一个不能删 -->
<el-button
v-if="!(isFirstLevel && index === 0)"
v-if="!item?.disabled"
:icon="Minus"
circle
size="small"
@ -94,6 +94,7 @@
<!-- 非第一层 -->
<!-- 复用添加逻辑 -->
<FormItemRecursive
:formType="formType"
:current-list="item.children"
:prop-path="`${propPath}.${index}.children`"
:depth="depth + 1"
@ -112,7 +113,11 @@
import { Plus, Minus } from "@element-plus/icons-vue";
// props
const { currentList, propPath, depth, isFirstLevel, endDepth, parentPath } = defineProps({
const { formType, currentList, propPath, depth, isFirstLevel, endDepth, parentPath } = defineProps({
formType: {
type: String,
default: 'input'
},
currentList: {
type: Array,
default: () => []

View File

@ -131,18 +131,8 @@
:rules="outputRules"
ref="outputFormRef"
>
<!-- <el-row v-for="(item, index) in formData.outputParams">
<el-form-item :label="index === 0 ? '参数名' : ''" >
<el-input :value="item.name" :disabled="item?.disabled" />
</el-form-item>
<el-form-item :label="index === 0 ? '参数类型' : ''" >
<el-input :value="item.type" />
</el-form-item>
<el-form-item :label="index === 0 ? '描述' : ''">
<el-input :value="item.desc" />
</el-form-item>
</el-row> -->
<FormItemRecursive
formType="output"
:current-list="formData.outputParams"
prop-path="outputParams"
:depth="0"
@ -192,7 +182,6 @@ const handleTypeChange = (index) => {
} else {
formData.nodeParams[index].input = "";
const option = getInput(props.model.id);
console.log('option', option)
if (option) {
quoteOptions.value = option;
}
@ -203,6 +192,7 @@ const cascaderRefs = ref([])
const outputRules = reactive({});
const cascaderChange = (value, index) => {
const selectedOptions = cascaderRefs.value[index].getCheckedNodes(true);
formData.nodeParams[index].quote = value
formData.nodeParams[index].quoteType = selectedOptions[0].data.type
}
@ -220,10 +210,12 @@ const outputFormRef = ref()
const setNodeProperties = async () => {
try {
const result = await dynamicForm.value.validate();
console.log(123456)
const test = await outputFormRef.value.validate()
console.log('test', test)
if (result) {
let flag = true
if (outputFormRef.value) {
flag = await outputFormRef.value.validate()
}
if (result && flag) {
const data = toRaw(formData);
console.log('data', data)
const properties = lf.getProperties(props.model.id);
@ -492,15 +484,15 @@ defineExpose({
align-items: end;
.el-input {
--el-input-width: 120px;
--el-input-width: 148px;
}
.el-select {
--el-select-width: 120px;
--el-select-width: 148px;
}
.el-cascader {
--el-form-inline-content-width: 120px;
--el-form-inline-content-width: 148px;
}
}
}

View File

@ -1,367 +1,369 @@
<template>
<div class="node__container">
<keep-alive>
<NodeState :state="nodeOperatingStatus">
<template #input>
<JsonViewer :value="inputJsonData" copyable boxed sort theme="light" />
</template>
<!-- <template #output>
<JsonViewer :value="outputJsonData" copyable boxed sort theme="light" />
</template> -->
</NodeState>
</keep-alive>
<div class="title__container">
<div class="title">
<img src="../../icon/start.svg" alt="" />
<span class="text">Start</span>
</div>
<div class="subTitle">工作流的起始节点用于设定启动工作流需要的信息</div>
</div>
<div class="input__container">
<div class="title">
<div class="left">
<div class="tag"></div>
<div class="text">输入</div>
</div>
<div class="right">
<el-button
:disabled="flowStore.disableForm"
@click="addFormItem"
class="addFormItem"
>
<el-icon :size="20"><Plus /></el-icon>
</el-button>
</div>
</div>
<div class="form__container">
<el-form
:inline="true"
:model="formData"
:rules="rules"
ref="dynamicForm"
label-position="top"
label-width="auto"
:disabled="flowStore.disableForm"
>
<FormItemRecursive
:current-list="formData.inputParams"
prop-path="inputParams"
:depth="0"
:is-first-level="true"
:endDepth="2"
:parent-path="[]"
@delete-item="deleteTopLevelItem"
/>
</el-form>
</div>
</div>
</div>
</template>
<script setup>
import { watch, reactive, toRaw, ref, onMounted, onUnmounted } from "vue";
import { Plus } from "@element-plus/icons-vue";
import { useFlowStore } from "@/store/modules/flow";
import NodeState from "../../components/NodeState.vue";
import "vue3-json-viewer/dist/index.css";
import { emitter } from "@/utils/eventBus";
import FormItemRecursive from "./FormItemRecursive.vue";
const props = defineProps({
model: Object,
properties: Object,
});
const emits = defineEmits(["contentChange"]);
const flowStore = useFlowStore();
const formData = reactive({
inputParams: [
{
name: "terminalId",
type: "string",
desc: "机器人ip",
required: true,
children: [],
},
],
});
const rules = reactive({});
const addFormItem = () => {
formData.inputParams.push({
name: "",
type: "",
desc: "",
required: false,
children: [],
});
emits("contentChange");
};
const dynamicForm = ref();
const setNodeProperties = async () => {
try {
const result = await dynamicForm.value.validate();
if (result) {
const data = toRaw(formData);
lf.setProperties(props.model.id, {
...props.properties,
...data,
});
emits("contentChange");
}
} catch {
dynamicForm.value.clearValidate()
}
};
const nodeOperatingStatus = ref("NORMAL");
const inputJsonData = ref({});
const outputJsonData = ref({});
watch(
() => props.properties,
() => {
if (props.properties.inputParams && props.properties.inputParams.length > 0) {
formData.inputParams = props.properties.inputParams;
}
},
{
immediate: true,
deep: true,
}
);
//
const deleteTopLevelItem = (fullPath) => {
//
let currentLevel = formData.inputParams;
//
for (let i = 0; i < fullPath.length - 1; i++) {
const index = fullPath[i];
//
currentLevel = currentLevel[index].children;
}
//
const lastIndex = fullPath[fullPath.length - 1];
currentLevel.splice(lastIndex, 1);
emits("contentChange");
}
onMounted(() => {
setNodeProperties();
emitter.on("changeNodeState", (data) => {
if (data.nodeId === props.model.id) {
nodeOperatingStatus.value = data.status;
let inputData = {};
let output = {};
try {
inputData = JSON.parse(data.paramsIn) || {};
output = JSON.parse(data.paramsOut) || {};
} catch (error) {
inputData = data.paramsIn || {};
output = data.paramsOut || {};
}
inputJsonData.value = inputData;
outputJsonData.value = output;
emits("contentChange");
}
});
emitter.on("contentChange", (data) => {
if (data.id === props.model.id) {
nodeOperatingStatus.value = "NORMAL";
inputJsonData.value = {};
outputJsonData.value = {};
emits("contentChange");
}
});
});
const validateForm = async () => {
try {
const result = await dynamicForm.value.validate();
if (result) {
return { valid: true, message: "验证通过" };
} else {
return { valid: false, message: "验证失败" };
}
} catch {
return { valid: false, message: "验证失败" };
}
};
onUnmounted(() => {
emitter.off("changeNodeState");
emitter.off("contentChange");
});
defineExpose({
validateForm,
setNodeProperties
});
</script>
<style lang="scss" scoped>
.node__container {
width: 100%;
height: auto;
.title__container {
.title {
display: flex;
align-items: center;
img {
width: 24px;
height: 24px;
}
.text {
font-size: 16px;
font-weight: bold;
margin-left: 12px;
}
}
.subTitle {
color: #737a87;
font-size: 12px;
margin: 8px 0;
}
}
.input__container {
width: 100%;
background-color: #fafbfc;
padding: 0 16px;
border-radius: 8px;
box-sizing: border-box;
.title {
height: 32px;
display: flex;
align-items: center;
justify-content: space-between;
.left {
display: flex;
align-items: center;
.tag {
width: 3px;
height: 16px;
background: #1664ff;
border-radius: 0 4px 4px 0;
margin-right: 12px;
}
.text {
font-size: 16px;
font-weight: 700;
color: #0c0d0e;
}
}
.right {
.addFormItem {
border: none;
background: transparent;
cursor: pointer;
}
}
}
.form__container {
margin: 12px 0;
.sub-properties {
margin-left: 10px;
.zw {
margin-left: 15px;
&::before {
content: "";
position: absolute;
left: 0;
top: -4px;
width: 10px;
border-left: 1px solid gray;
border-bottom: 1px solid gray;
border-bottom-left-radius: 4px;
background-color: transparent;
height: 24px;
}
}
}
.gSon-properties {
margin-left: 10px;
.zw {
margin-left: 15px;
&::before {
content: "";
position: absolute;
left: 0;
top: -4px;
width: 10px;
border-left: 1px solid gray;
border-bottom: 1px solid gray;
border-bottom-left-radius: 4px;
background-color: transparent;
height: 24px;
}
}
}
.deleteBtn {
margin-bottom: 22px;
cursor: pointer;
}
.addBtn {
margin-bottom: 22px;
cursor: pointer;
}
:deep(.el-row) {
align-items: end;
.el-input {
--el-input-width: 120px;
}
.el-select {
--el-select-width: 120px;
}
.el-cascader {
--el-form-inline-content-width: 120px;
}
}
}
}
}
</style>
<style lang="scss">
.popover__container {
min-width: 300px !important;
.params__container {
margin-bottom: 12px;
.paramsTitle {
color: #0c0d0e;
font-size: 14px;
font-weight: 500;
}
}
}
</style>
<template>
<div class="node__container">
<keep-alive>
<NodeState :state="nodeOperatingStatus">
<template #input>
<JsonViewer :value="inputJsonData" copyable boxed sort theme="light" />
</template>
<!-- <template #output>
<JsonViewer :value="outputJsonData" copyable boxed sort theme="light" />
</template> -->
</NodeState>
</keep-alive>
<div class="title__container">
<div class="title">
<img src="../../icon/start.svg" alt="" />
<span class="text">Start</span>
</div>
<div class="subTitle">工作流的起始节点用于设定启动工作流需要的信息</div>
</div>
<div class="input__container">
<div class="title">
<div class="left">
<div class="tag"></div>
<div class="text">输入</div>
</div>
<div class="right">
<el-button
:disabled="flowStore.disableForm"
@click="addFormItem"
class="addFormItem"
>
<el-icon :size="20"><Plus /></el-icon>
</el-button>
</div>
</div>
<div class="form__container">
<el-form
:inline="true"
:model="formData"
:rules="rules"
ref="dynamicForm"
label-position="top"
label-width="auto"
:disabled="flowStore.disableForm"
>
<FormItemRecursive
formType="input"
:current-list="formData.inputParams"
prop-path="inputParams"
:depth="0"
:is-first-level="true"
:endDepth="2"
:parent-path="[]"
@delete-item="deleteTopLevelItem"
/>
</el-form>
</div>
</div>
</div>
</template>
<script setup>
import { watch, reactive, toRaw, ref, onMounted, onUnmounted } from "vue";
import { Plus } from "@element-plus/icons-vue";
import { useFlowStore } from "@/store/modules/flow";
import NodeState from "../../components/NodeState.vue";
import "vue3-json-viewer/dist/index.css";
import { emitter } from "@/utils/eventBus";
import FormItemRecursive from "./FormItemRecursive.vue";
const props = defineProps({
model: Object,
properties: Object,
});
const emits = defineEmits(["contentChange"]);
const flowStore = useFlowStore();
const formData = reactive({
inputParams: [
{
name: "terminalId",
type: "string",
desc: "机器人ip",
disabled: true,
required: true,
children: [],
},
],
});
const rules = reactive({});
const addFormItem = () => {
formData.inputParams.push({
name: "",
type: "",
desc: "",
required: false,
children: [],
});
emits("contentChange");
};
const dynamicForm = ref();
const setNodeProperties = async () => {
try {
const result = await dynamicForm.value.validate();
if (result) {
const data = toRaw(formData);
lf.setProperties(props.model.id, {
...props.properties,
...data,
});
emits("contentChange");
}
} catch {
dynamicForm.value.clearValidate()
}
};
const nodeOperatingStatus = ref("NORMAL");
const inputJsonData = ref({});
const outputJsonData = ref({});
watch(
() => props.properties,
() => {
if (props.properties.inputParams && props.properties.inputParams.length > 0) {
formData.inputParams = props.properties.inputParams;
}
},
{
immediate: true,
deep: true,
}
);
//
const deleteTopLevelItem = (fullPath) => {
//
let currentLevel = formData.inputParams;
//
for (let i = 0; i < fullPath.length - 1; i++) {
const index = fullPath[i];
//
currentLevel = currentLevel[index].children;
}
//
const lastIndex = fullPath[fullPath.length - 1];
currentLevel.splice(lastIndex, 1);
emits("contentChange");
}
onMounted(() => {
setNodeProperties();
emitter.on("changeNodeState", (data) => {
if (data.nodeId === props.model.id) {
nodeOperatingStatus.value = data.status;
let inputData = {};
let output = {};
try {
inputData = JSON.parse(data.paramsIn) || {};
output = JSON.parse(data.paramsOut) || {};
} catch (error) {
inputData = data.paramsIn || {};
output = data.paramsOut || {};
}
inputJsonData.value = inputData;
outputJsonData.value = output;
emits("contentChange");
}
});
emitter.on("contentChange", (data) => {
if (data.id === props.model.id) {
nodeOperatingStatus.value = "NORMAL";
inputJsonData.value = {};
outputJsonData.value = {};
emits("contentChange");
}
});
});
const validateForm = async () => {
try {
const result = await dynamicForm.value.validate();
if (result) {
return { valid: true, message: "验证通过" };
} else {
return { valid: false, message: "验证失败" };
}
} catch {
return { valid: false, message: "验证失败" };
}
};
onUnmounted(() => {
emitter.off("changeNodeState");
emitter.off("contentChange");
});
defineExpose({
validateForm,
setNodeProperties
});
</script>
<style lang="scss" scoped>
.node__container {
width: 100%;
height: auto;
.title__container {
.title {
display: flex;
align-items: center;
img {
width: 24px;
height: 24px;
}
.text {
font-size: 16px;
font-weight: bold;
margin-left: 12px;
}
}
.subTitle {
color: #737a87;
font-size: 12px;
margin: 8px 0;
}
}
.input__container {
width: 100%;
background-color: #fafbfc;
padding: 0 16px;
border-radius: 8px;
box-sizing: border-box;
.title {
height: 32px;
display: flex;
align-items: center;
justify-content: space-between;
.left {
display: flex;
align-items: center;
.tag {
width: 3px;
height: 16px;
background: #1664ff;
border-radius: 0 4px 4px 0;
margin-right: 12px;
}
.text {
font-size: 16px;
font-weight: 700;
color: #0c0d0e;
}
}
.right {
.addFormItem {
border: none;
background: transparent;
cursor: pointer;
}
}
}
.form__container {
margin: 12px 0;
.sub-properties {
margin-left: 10px;
.zw {
margin-left: 15px;
&::before {
content: "";
position: absolute;
left: 0;
top: -4px;
width: 10px;
border-left: 1px solid gray;
border-bottom: 1px solid gray;
border-bottom-left-radius: 4px;
background-color: transparent;
height: 24px;
}
}
}
.gSon-properties {
margin-left: 10px;
.zw {
margin-left: 15px;
&::before {
content: "";
position: absolute;
left: 0;
top: -4px;
width: 10px;
border-left: 1px solid gray;
border-bottom: 1px solid gray;
border-bottom-left-radius: 4px;
background-color: transparent;
height: 24px;
}
}
}
.deleteBtn {
margin-bottom: 22px;
cursor: pointer;
}
.addBtn {
margin-bottom: 22px;
cursor: pointer;
}
:deep(.el-row) {
align-items: end;
.el-input {
--el-input-width: 120px;
}
.el-select {
--el-select-width: 120px;
}
.el-cascader {
--el-form-inline-content-width: 120px;
}
}
}
}
}
</style>
<style lang="scss">
.popover__container {
min-width: 300px !important;
.params__container {
margin-bottom: 12px;
.paramsTitle {
color: #0c0d0e;
font-size: 14px;
font-weight: 500;
}
}
}
</style>

View File

@ -36,7 +36,7 @@ export default defineConfig(({mode, command}) => {
//赵 http://10.148.108.58:13080
// dev http://10.148.20.34:13080
// target: command === 'build' ? VITE_API_URL : 'http://10.148.20.34:13080',
target: command === 'build' ? VITE_API_URL : 'http://127.0.0.1:13080',
target: command === 'build' ? VITE_API_URL : 'http://192.168.0.10:13080',
changeOrigin: true,
rewrite: (p) => p.replace(/^\/dev-api/, '')
}