CMVR-IOT-UI/src/views/flow/components/ParamsDrawer.vue

530 lines
14 KiB
Vue
Raw Normal View History

2026-04-13 10:31:55 +08:00
<template>
<el-drawer
v-model="props.drawer"
:title="props.data.properties?.name || '参数配置'"
:before-close="handleClose"
>
<div class="input__container" v-if="props.data.type === 'start'">
<div class="title">
<div class="left">
<div class="tag"></div>
<div class="text">输入</div>
</div>
<div class="right">
<el-button
@click="addFormItem('inputParams')"
:circle="true"
class="addFormItem"
>
<el-icon :size="20"><Plus /></el-icon>
</el-button>
</div>
</div>
<el-form
:inline="true"
:model="formData"
:rules="rules"
ref="dynamicForm"
label-position="top"
label-width="auto"
>
<FormItemRecursive
formType="input"
:current-list="formData.inputParams"
prop-path="inputParams"
:depth="0"
:is-first-level="true"
:endDepth="2"
:parent-path="[]"
@delete-item="(path) => deleteTopLevelItem(path, 'inputParams')"
/>
</el-form>
</div>
<div v-if="props.data.type === 'serviceNode'">
<div class="input__container">
<div class="title">
<div class="left">
<div class="tag"></div>
<div class="text">输入</div>
</div>
<div class="right">
<el-button
:circle="true"
@click="addFormItem('nodeParams')"
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"
>
<div v-for="(property, index) in formData.nodeParams" :key="index">
<el-row>
<el-form-item
:label="index === 0 ? '参数名' : ''"
:prop="`nodeParams.${index}.name`"
:rules="[
{ required: true, message: '请输入参数名', trigger: 'blur' },
]"
>
<el-input
:disabled="property?.disabled || false"
class="param-name"
v-model="property.name"
placeholder="请输入"
clearable
/>
</el-form-item>
<el-form-item
:label="index === 0 ? '参数值' : ''"
:prop="`nodeParams.${index}.type`"
>
<el-select
v-model="property.type"
class="param-type"
@change="handleTypeChange(index)"
>
<el-option label="引用" value="quote" />
<el-option label="输入" value="input" />
</el-select>
<el-form-item
v-if="property.type === 'input'"
:rules="[
{
required: property?.required ?? true,
message: '请输入参数值',
trigger: 'blur',
},
]"
:prop="`nodeParams.${index}.input`"
>
<el-input-number
class="param-value"
v-if="property.componentType === 'number'"
v-model="property.input"
:min="0"
:max="property.max || Infinity"
:controls="false"
:step-strictly="true"
placeholder="请输入"
clearable
/>
<el-select
v-model="property.input"
class="param-value"
v-else-if="property.componentType === 'select'"
>
<el-option
v-for="item in property.selectOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
<el-input
class="param-value"
v-else
v-model="property.input"
placeholder="请输入"
clearable
/>
</el-form-item>
<el-form-item
v-if="property.type === 'quote'"
:rules="[
{
required: true,
message: '请选择参数值',
trigger: 'blur',
},
]"
:prop="`nodeParams.${index}.quote`"
>
<el-cascader
:ref="
(el) => {
if (el) cascaderRefs[index] = el;
}
"
v-model="property.quote"
:checkStrictly="true"
:options="quoteOptions"
placeholder="请选择"
@visible-change="
(visible) => visibleChange(visible, index, property.quote)
"
@change="(value) => cascaderChange(value, index)"
/>
</el-form-item>
</el-form-item>
</el-row>
</div>
</el-form>
</div>
</div>
<div class="output__container">
<div class="title">
<div class="left">
<div class="tag"></div>
<div class="text">输出</div>
</div>
<div class="right">
<el-button
@click="addFormItem('outputParams')"
class="addFormItem"
>
<el-icon :size="20"><Plus /></el-icon>
</el-button>
</div>
</div>
<div class="form__container">
<el-form
:inline="true"
:model="formData"
label-position="top"
label-width="auto"
:rules="outputRules"
ref="outputFormRef"
>
<FormItemRecursive
formType="output"
:current-list="formData.outputParams"
prop-path="outputParams"
:depth="0"
:is-first-level="true"
:endDepth="2"
:parent-path="[]"
@delete-item="(path) => deleteTopLevelItem(path, 'outputParams')"
/>
</el-form>
</div>
</div>
</div>
<template #footer>
<el-button @click="handleClose">关闭</el-button>
<el-button type="primary" @click="confirm">确定</el-button>
</template>
</el-drawer>
</template>
<script setup>
import FormItemRecursive from "./FormItemRecursive.vue";
import { Plus } from "@element-plus/icons-vue";
import { getInput, filterEmptyName } from "@/utils/flow";
import { emitter } from "@/utils/eventBus";
const props = defineProps({
drawer: Boolean,
data: Object
})
const emits = defineEmits(['close'])
const formData = reactive({
inputParams: [],
nodeParams: [],
outputParams: [],
});
const rules = reactive({});
const handleClose = (done) => {
const filteredObj = {
inputParams: filterEmptyName(formData.inputParams),
nodeParams: filterEmptyName(formData.nodeParams),
outputParams: filterEmptyName(formData.outputParams),
};
lf.setProperties(props.data.id, {
...props.data.properties,
...filteredObj
});
emitter.emit("setProperties", { id: props.data.id});
emits('close')
}
// 删除顶层表单项
const deleteTopLevelItem = (fullPath, propPath) => {
// 从顶层数据开始查找
let currentLevel = formData[propPath];
// 遍历路径(除最后一个索引,因为最后一个是要删除的项)
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);
}
const addFormItem = (type) => {
const obj ={
name: "",
type: type === 'nodeParams' ? 'input' : 'string',
required: false,
children: [],
}
if (type === 'inputParams') {
obj.desc = ''
} else if (type === 'nodeParams' || type === 'outputParams') {
obj.input = ''
}
formData[type].push(obj);
};
const quoteOptions = ref([]);
const handleTypeChange = (index) => {
if (formData.nodeParams[index].type === "input") {
formData.nodeParams[index].quote = "";
} else {
formData.nodeParams[index].input = "";
const option = getInput(props.model.id);
if (option) {
quoteOptions.value = option;
}
}
};
const cascaderRefs = ref([]);
const cascaderChange = (value, index) => {
const selectedOptions = cascaderRefs.value[index].getCheckedNodes(true);
formData.nodeParams[index].quote = value;
formData.nodeParams[index].quoteType = selectedOptions[0].data.type;
};
const visibleChange = (value, index, quote) => {
if (value) {
const option = getInput(props.data.id);
if (option) {
quoteOptions.value = option;
const currentValue = [...quote];
// 强制级联选择器重新处理选中值与选项的匹配
if (cascaderRefs.value[index]) {
// 等待DOM更新后再设置值确保新options已生效
setTimeout(() => {
// 如果当前有选中值,重新设置一次以触发重新匹配
if (currentValue.length) {
formData.nodeParams[index].quote = [];
// 确保响应式更新
setTimeout(() => {
formData.nodeParams[index].quote = currentValue;
}, 0);
}
// 手动触发级联选择器的重新渲染
// cascaderRefs.value[index].updatePopper();
}, 0);
}
}
}
};
watch(
() => props.data,
() => {
if (props.data.type === "start") {
formData.inputParams = props.data.properties?.inputParams || [];
} else if (props.data.type === "serviceNode") {
formData.nodeParams = props.data.properties.nodeParams;
formData.outputParams = props.data.properties.outputParams;
console.log('formData', formData)
}
},
{
immediate: true,
deep: true,
}
);
</script>
<style lang="scss" scoped>
.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;
:deep(.el-row) {
align-items: end;
.el-form-item {
margin-right: 12px;
}
.param-name {
width: 160px;
}
.param-type {
width: 80px;
}
.param-value {
width: 160px;
}
}
.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;
}
}
}
}
}
.output__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;
:deep(.el-row) {
align-items: end;
.el-form-item {
margin-right: 12px;
}
.param-name {
width: 160px;
}
.param-type {
width: 80px;
}
.param-desc {
width: 160px;
}
}
}
}
</style>