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

222 lines
5.8 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"
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'])
// 处理类型变化(清空或添加子项)
const handleTypeChange = (item) => {
if (!["object", "array<object>"].includes(item.type)) {
item.children = []; // 非对象类型清空子项
} else if (item.children.length === 0) {
handleAddChild(item); // 首次切换为对象类型时添加一个子项
}
};
// 添加子项
const handleAddChild = (parentItem) => {
parentItem.children.push({
name: "",
type: "string",
desc: "",
required: false,
children: [],
});
// emit("add-item", parentItem); // 通知父组件(可选)
};
// 删除当前项
const handleDelete = (index) => {
// 完整路径 = 父级路径 + 当前索引如果是顶层parentPath为undefined
const fullPath = parentPath ? [...parentPath, index] : [index];
emit("delete-item", fullPath); // 向上传递完整路径
}
// 接收子组件的删除事件,继续向上传递
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>