效果图
组件封装
<!--
* @Descripttion: 配置规则
* @version: 1.0
* @Author: bingbing
* @Date: 2024年3月8日
-->
<template>
<div class="sd-rule">
<div class="sd-rule-content" v-for="(item, index) in ruleList" :ref="'btn-' + item.id" :key="index">
<el-dropdown class="rule-left" :class="{'rule-left-children': item.pid}" @command="handleCommand($event, item, index)">
<el-button type="primary" class="rule-btn">
<!-- <span :style="{'height': _height(item)}">{{item.logic == 'and' ? '并且' : '或者'}}</span> -->
<span>{{item.logic == 'and' ? '并且' : '或者'}}</span>
<!-- <div class="rule-line" :style="{'height': ((item.children.length -1) *52 - 16) + 'px'}"></div> -->
<div v-if="item.children.length > 0" class="rule-line" :style="{'height': height[item.id]}"></div>
<el-icon class="el-icon--right"><arrow-down /></el-icon>
</el-button>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item command="and">并且</el-dropdown-item>
<el-dropdown-item command="or">或者</el-dropdown-item>
<el-dropdown-item command="addCondition">添加条件</el-dropdown-item>
<el-dropdown-item command="addUniteCondition">添加联合条件</el-dropdown-item>
<el-dropdown-item v-if="item.pid" command="delete">删除</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
<div class="rule-right" :ref="'right-' + item.id">
<div v-for="(ele, num) in item.children" :key="num">
<div v-if="!ele.logic" class="data-select">
<el-select
v-model="ele.leftValue"
class="property"
:class="{'w-full': num > 0}"
placeholder="请选择"
filterable
clearable
>
<el-option
v-for="item in options"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
<el-select class="w-240" v-model="item.middleValue" filterable clearable placeholder="请选择">
<el-option
v-for="item in options"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
<el-input class="w-240" v-model="item.rightValue" placeholder="请输入"></el-input>
<el-button type="danger" icon="Delete" circle @click="deleteItem(item.children, num)"/>
</div>
<!-- <span v-else>{{ ele }}</span> -->
<!-- 递归、连线处理 -->
<sdRule v-else :ruleList="[ele]" :class="{'rule-left-40': num == 0}" @refresh="$emit('refresh')" @deleteItem="updateItem"></sdRule>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'sdRule',
props: {
ruleList: {
type: Array,
default: () => []
}
},
data() {
return {
height: {}
}
},
created() {
this.$nextTick(() => {
this.getHeight(this.ruleList)
})
},
methods: {
// 获取高度
getHeight(ruleList) {
ruleList.forEach(item => {
this._height(item)
if(item.children && item.children.length > 0) {
this.getHeight(item.children)
}
})
},
// 选择
handleCommand(command, item) {
if(command === 'and' || command === 'or') {
item.logic = command
}else if (command === 'addCondition') {
item.children.push({
id: Date.now(),
pid: item.id,
leftValue: '',
middleValue: '',
rightValue: '',
logic: '',
children: []
})
this.$emit('refresh')
}else if(command === 'delete') {
this.$emit('deleteItem', item)
this.$emit('refresh')
}else {
item.children.push({
id: Date.now(),
pid: item.id,
leftValue: '',
middleValue: '',
rightValue: '',
logic: 'and',
children: []
})
this.$emit('refresh')
}
},
// 计算chidren高度
// _height(item) {
// if(item.logic) {
// const btnY = this.$refs['btn-' + item.id][0].getBoundingClientRect().y
// const refData = this.$refs['right-' + item.id][0]
// const height = refData.children[refData.children.length -1]?.getBoundingClientRect().y
// this.height[item.id] = (height - btnY - 16) + 'px'
// }
// },
_height(item) {
if(item.logic) {
const btnRef = this.$refs['btn-' + item.id]
const refData = this.$refs['right-' + item.id]
if(!btnRef || !refData) return
const btnY = btnRef[0].getBoundingClientRect().y
const height = refData[0].children[refData[0].children.length -1]?.getBoundingClientRect().y
this.height[item.id] = (height - btnY - 16) + 'px'
}
},
// 删除子项
deleteItem(data, index) {
data.splice(index, 1)
this.$emit('refresh')
},
// 递归传送数据
updateItem(item) {
this.$emit('deleteItem', item)
}
}
}
</script>
<style lang="scss" scoped>
.sd-rule {
width: 100%;
.sd-rule-content {
display: flex;
.rule-left {
margin-bottom: 20px;
position: relative;
z-index: 999;
.rule-btn {
position: relative;
// &::before {
.rule-line {
position: absolute;
width: 1px;
transform: scaleX(0.5);
background: #333333;
left: 50%;
top: 32px;
}
}
}
.rule-left-children {
margin-left: 40px;
position: relative;
z-index: 88;
&::before {
content: '';
position: absolute;
width: 80px;
height: 1px;
background: #333333;
top: 15px;
left: -80px;
}
}
.rule-left-40 {
& > .sd-rule-content > .rule-left-children::before {
content: '';
position: absolute;
width: 40px;
height: 1px;
background: #333333;
top: 15px;
left: -40px;
}
}
.rule-right {
display: flex;
flex-direction: column;
.property {
width: 240px;
margin:0 20px 20px 40px;
position: relative;
&::before {
content: "";
position: absolute;
width: 40px;
height: 1px;
background: #333333;
top: 15px;
left: -40px;
}
}
.w-full::before{
content: "";
position: absolute;
width: 80px;
height: 1px;
background: #333333;
top: 15px;
left: -79px;
}
.data-select {
display: flex;
.w-240 {
width: 240px;
margin:0 20px 20px 0;
}
}
}
}
}
</style>
组件使用
<template>
<div style="padding: 20px; box-sizing: border-box;">
<sdRule :ruleList="ruleList" @refresh="refresh" @deleteItem="deleteItem" :key="key"></sdRule>
</div>
</template>
<script>
import sdRule from '@/components/sdRule/index';
export default {
components:{
sdRule
},
data() {
return {
// ruleList: [
// {
// id: 101,
// pid: '',
// leftValue: '',
// middleValue: '',
// rightValue: '',
// logic: 'and',
// children: [
// {
// id: 102,
// pid: '101',
// leftValue: '',
// middleValue: '',
// rightValue: '',
// logic: 'or',
// children: [
// {
// id: 103,
// pid: '102',
// leftValue: '',
// middleValue: '',
// rightValue: '',
// logic: '',
// },
// {
// id: 104,
// pid: '102',
// leftValue: '',
// middleValue: '',
// rightValue: '',
// logic: 'and',
// children: [
// {
// id: 105,
// pid: '104',
// leftValue: '',
// middleValue: '',
// rightValue: '',
// logic: '',
// },
// {
// id: 106,
// pid: '104',
// leftValue: '',
// middleValue: '',
// rightValue: '',
// logic: '',
// },
// {
// id: 107,
// pid: '104',
// leftValue: '',
// middleValue: '',
// rightValue: '',
// logic: 'and',
// children: [
// {
// id: 108,
// pid: '107',
// leftValue: '',
// middleValue: '',
// rightValue: '',
// logic: '',
// },
// {
// id: 109,
// pid: '107',
// leftValue: '',
// middleValue: '',
// rightValue: '',
// logic: '',
// },
// ]
// },
// ]
// },
// ]
// },
// {
// id: 110,
// pid: '101',
// leftValue: '',
// middleValue: '',
// rightValue: '',
// logic: '',
// },
// {
// id: 111,
// pid: '101',
// leftValue: '',
// middleValue: '',
// rightValue: '',
// logic: '',
// },
// {
// id: 112,
// pid: '101',
// leftValue: '',
// middleValue: '',
// rightValue: '',
// logic: '',
// },
// {
// id: 113,
// pid: '101',
// leftValue: '',
// middleValue: '',
// rightValue: '',
// logic: '',
// },
// {
// id: 114,
// pid: '101',
// leftValue: '',
// middleValue: '',
// rightValue: '',
// logic: 'and',
// children: []
// }
// ]
// }
// ],
ruleList: [
{
id: 101,
pid: '',
leftValue: '',
middleValue: '',
rightValue: '',
logic: 'and',
children: []
}
],
key: 0
}
},
methods: {
// 更新线的长度
refresh() {
this.key ++
},
// 删除联合条件
deleteItem(item) {
this.filterFn(this.ruleList, item.id)
},
// 过滤树
filterFn(treeData, id) {
return treeData.filter((obj) => {
if (obj.id == id) {
return false // 过滤该节点
}
// 对树节点的后代进行递归
if (obj.children && obj.children.length > 0) {
obj.children = this.filterFn(obj.children, id)
}
return true // 返回该节点
}
)
}
}
}
</script>
<style>
</style>