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

243 lines
6.6 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!--
* 文件说明递归参数表单负责嵌套字段的增删和类型切换
* 作用范围仅服务于流程设计器模块
-->
<!-- components/FormItemRecursive.vue -->
<template>
<div class="form-item-recursive">
<!-- 循环渲染当前层级的所有表单项 -->
<div v-for="(item, index) in currentList" :key="index">
<el-row>
<!-- 名称输入 -->
<el-form-item
:label="isFirstLevel && index === 0 ? '变量名' : ''"
:prop="`${propPath}.${index}.name`"
:rules="[{ required: true, message: '请输入', trigger: 'blur' }]"
>
<el-input
class="param-name"
v-model="item.name"
:disabled="item.disabled || false"
placeholder="请输入"
clearable
/>
</el-form-item>
<!-- 类型选择 -->
<el-form-item
:label="isFirstLevel && index === 0 ? '变量类型' : ''"
:prop="`${propPath}.${index}.type`"
:rules="[{ required: true, message: '请输入', trigger: 'blur' }]"
>
<el-select
v-model="item.type"
class="param-type"
@change="handleTypeChange(item)"
>
<el-option label="String" value="string" />
<el-option label="Number" value="number" />
<el-option label="Boolean" value="boolean" />
<el-option v-if="depth < endDepth" label="Object" value="object" />
<el-option label="Array<String>" value="array<string>" />
<el-option label="Array<Number>" value="array<number>" />
<el-option label="Array<Boolean>" value="array<boolean>" />
<el-option v-if="depth < endDepth" label="Array<Object>" value="array<object>" />
</el-select>
</el-form-item>
<!-- 描述输入 -->
<el-form-item
:label="isFirstLevel && index === 0 ? '描述' : ''"
:prop="`${propPath}.${index}.desc`"
:rules="[{ required: true, message: '请输入', trigger: 'blur' }]"
>
<el-input
class="param-desc"
v-model="item.desc"
placeholder="请输入"
clearable
/>
</el-form-item>
<!-- 是否必填 -->
<el-form-item v-if="formType === 'input'" :label="isFirstLevel && index === 0 ? '必填' : ''">
<el-switch
v-model="item.required"
/>
</el-form-item>
<!-- 删除按钮第一层第一个不能删 -->
<el-button
:icon="Minus"
circle
size="small"
@click="handleDelete(index)"
class="deleteBtn"
/>
<!-- 添加子项按钮当类型是object/array<object> -->
<el-button
v-if="['object', 'array<object>'].includes(item.type)"
:icon="Plus"
circle
size="small"
@click="handleAddChild(item)"
class="addBtn"
/>
</el-row>
<!-- 递归渲染子项(如果有子项且类型匹配) -->
<!-- 层级越深,缩进越多 -->
<div
v-if="['object', 'array<object>'].includes(item.type) && item.children.length"
class="nested-form-items"
:style="{ marginLeft: `${depth * 20}px` }"
>
<!-- 递归调用自身,处理下一层级 -->
<!-- 非第一层 -->
<!-- 复用添加逻辑 -->
<FormItemRecursive
:formType="formType"
:current-list="item.children"
:prop-path="`${propPath}.${index}.children`"
:depth="depth + 1"
:is-first-level="false"
:endDepth="endDepth"
:parent-path="[...parentPath, index] "
@add-item="handleAddChild(item)"
@delete-item="handleChildDelete"
/>
</div>
</div>
</div>
</template>
<script setup>
import { Plus, Minus } from "@element-plus/icons-vue";
// 定义props
const { formType, currentList, propPath, depth, isFirstLevel, endDepth, parentPath } = defineProps({
formType: {
type: String,
default: 'input'
},
currentList: {
type: Array,
default: () => []
},
propPath: {
type: String,
default: ''
},
depth: {
type: Number,
default: 0
},
isFirstLevel: {
type: Boolean,
default: true
},
endDepth: {
type: Number,
default: 2
},
parentPath: {
type: Array,
default: () => []
},
});
// 定义emit事件
const emit = defineEmits(['add-item', 'delete-item'])
// 处理类型变化(清空或添加子项)
/**
* 处理 handleTypeChange 对应的数据或交互;作用范围仅限当前组件或模块。
* @param {*} item 调用方传入的 item 参数
*/
const handleTypeChange = (item) => {
if (!["object", "array<object>"].includes(item.type)) {
item.children = []; // 非对象类型清空子项
} else if (item.children.length === 0) {
handleAddChild(item); // 首次切换为对象类型时添加一个子项
}
};
// 添加子项
/**
* 处理 handleAddChild 对应的数据或交互;作用范围仅限当前组件或模块。
* @param {*} parentItem 调用方传入的 parentItem 参数
*/
const handleAddChild = (parentItem) => {
parentItem.children.push({
name: "",
type: "string",
desc: "",
required: false,
children: [],
});
// emit("add-item", parentItem); // 通知父组件(可选)
};
// 删除当前项
/**
* 处理 handleDelete 对应的数据或交互;作用范围仅限当前组件或模块。
* @param {*} index 目标项索引
*/
const handleDelete = (index) => {
// 完整路径 = 父级路径 + 当前索引如果是顶层parentPath为undefined
const fullPath = parentPath ? [...parentPath, index] : [index];
emit("delete-item", fullPath); // 向上传递完整路径
}
// 接收子组件的删除事件,继续向上传递
/**
* 处理 handleChildDelete 对应的数据或交互;作用范围仅限当前组件或模块。
* @param {*} childFullPath 调用方传入的 childFullPath 参数
*/
const handleChildDelete = (childFullPath) => {
emit("delete-item", childFullPath);
}
</script>
<style lang="scss" scoped>
.nested-form-items {
border-left: 1px dashed #ccc; /* 层级连接线 */
padding-left: 12px;
margin-left: 0 !important;
margin-top: 8px;
margin-bottom: 8px;
}
.deleteBtn {
margin-bottom: 22px;
cursor: pointer;
}
.addBtn {
margin-bottom: 22px;
cursor: pointer;
}
:deep(.el-row) {
align-items: end;
.el-form-item {
margin-right: 12px;
}
.param-name {
width: 120px;
}
.param-type {
width: 80px;
}
.param-desc {
width: 120px;
}
}
</style>