树形条件组

制作一个如图条件组功能
1.index.vue页面

<template>
    <div>
        <div v-if="condition.children.length == 0" class="label">
            <n-button type="primary" size="small" @click="addData">
                <icon type="add" line></icon>{{btnName}}
            </n-button>
        </div>
        <template v-else>

            <ConditionItem ref="$conditionRef" 
            :data="condition" 
            :options="fieldList" 
            :valueTypeExclude="valueTypeExclude"
            :currentIndex="0" 
            :parentsInfo="condition" 
            :fieldExclude="fieldExclude"
            :currentFieldList="currentFieldList"></ConditionItem>
            
        </template>
    </div>
</template>

<script setup>
import { watch, ref } from 'vue';
// 组件
import ConditionItem from '@/components/condition/ConditionItem.vue';
// 方法
import { warning } from '@/extend/message'; 
import conditionParams from '@/components/condition/conditionParams'
// 存储
import { useParamsStore } from '@/stores/global'

const $paramsStore = useParamsStore()
const $conditionRef = ref(null)
const props = defineProps({
    // 条件数据
    condition: {
        type: Object,
        default: () => {}
    },
    // 字段left
    fieldList: {
        type: Array,
        default: () => []
    }, 
    // 右边的字段//可能与左边(fieldList)相同也可不同,一般指当前正在操作的表单数据
    currentFieldList:{
        type: Array,
        default: () => []
    },
    // 添加按钮名称
    btnName: {
        type: String,
        default: "数据筛选"
    }, 
    // 排除某种数据类型,
    valueTypeExclude:{
        type: Array,
        default: () => []
    }, 
    //字段排除某个类型
    fieldExclude: {
        type: Array,
        default: () => []
    } 
})  

// 添加第一个条件
const addData = () => {  
    if(props.fieldList.length<=0){
        warning('暂无字段数据')
        return false
    }
    props.condition.children.push({
        ...JSON.parse(JSON.stringify(conditionParams.conditionItem))
    })
} 

// 更新参数
$paramsStore.updateParamsList()

</script>

<style lang="scss" scoped>
.label {
    margin-bottom: 12px
}

.tree {
    --spacing: 22px;
    --radius: 10px;
    margin-left: -8px;
}
</style>

2.参数页面conditionParams.js

export default {
    // 条件组数据
    conditionGroup:{
        conditionType:1,
        conjunction:'and',
        children:[],   
        isOpen:true,
    }, 
    conditionItem:{
        field:null,
        fieldCategory:null,
        condition:1,
        conditionType:2,
        type:'value',
        minValue:null,
        maxValue:null,
        isOpen:true,
        option: {
            tag: 'input',
            props: null,
            code: null
        }
    },
    // type 判断
    notConditionField:['datagrid','upload','editor','tab','word','group','qrcode','titles','pagegroup'],
    // tag 判断
    conditionByField:{
        'input':[1,4,11,12,5,7],
        'region':[1,4,11,12,5,7],
        'input-number':[1,4,3,8,2,9,6,10],
        'date-picker':[1,4,3,8,2,9,6,10],
        'radio-group':[1,4,13,14],
        'checkbox-group':[1,4,13,14],
        'select':[1,4,13,14],
        'department':[1,4,13,14],
        'member':[1,4,13,14]
    }
}

2.递归页面ConditionItem.vue

<template>
    <div class="flex-condition"> 
        <div class="handle-condition">
            <div v-if="data.children.length>1">
                <n-button text type="primary" @click="toggleClassInfo(data.isOpen)">
                    <icon :type="data.isOpen?'arrow-up-s':'arrow-down-s'" line></icon> 
                </n-button>
            </div>
            <div class="line"></div>
            <n-popover trigger="hover" placement="bottom" class="popover popover-min">
                <template #trigger>
                    <span class="type-handle">{{data.conjunction=='and'?'且':'或'}}</span>
                </template>
                <ul>
                    <li v-for="item in optionsSelect" :key="item.value" @click="changeConditionType(item.value)">{{item.label}}</li>
                </ul>
            </n-popover>
            <div class="line"></div>
        </div>
        <div class="content-condition">
            <template v-for="(item,i) in data.children" :key="i">
                <template v-if="data.isOpen || (!data.isOpen && i==0)">
                    <template v-if="item.conditionType == 1">
                        <ConditionItem 
                        :data="item" 
                        :options="options" 
                        :currentIndex="i" 
                        :parentsInfo="data" 
                        :paramsList="paramsList" 
                        :currentFieldList="currentFieldList"></ConditionItem>
                    </template>
                    <div class="item"  v-else>
                        <form-item-extend>
                            <n-cascader class="input-width" placeholder="请选择数据" value-field="code" label-field="name"
                            v-model:value="item.field" :options="getFieldParamsOptions()" check-strategy="child"
                            @update:value="(value,option)=>changeField(value,option,i)"/> 
                        </form-item-extend>
                        
                        <form-item-extend>
                            <dict class="input-width-min" placeholder="请选择条件" type="condition.condition" 
                            v-model:value="item.condition"
                            :node-props="(options)=>nodePropsCondition(options,i)"
                            @update:value="(val)=>updateCondition(val,i)"></dict>
                        </form-item-extend>

                        <form-item-extend>
                            <dict class="input-width-min" v-model:value="item.type" type="condition.valueType" 
                            :node-props="(options)=>nodePropsType(options,i)"
                            @update:value="(val)=>updateValueType(val,i)"
                            :disabled="disabledFun(i)"></dict>
                        </form-item-extend>
                        
                        <!-- 根据选择的数据类型,选择不同的数据 -->
                        <template v-if="item.type=='param'">
                            <div class="value-item">
                                <form-item-extend>
                                    <n-select class="input" @updateValue="(value,option) => markLabel(value,option,item,0)" v-model:value="item.minValue" :options="paramsList" value-field="code" label-field="name"></n-select>
                                </form-item-extend>
                            </div>
                        </template>
                        <template v-else-if="item.type=='dynamic'">
                            <div class="value-item">
                                <form-item-extend>
                                    <n-tree-select class="input" check-strategy="child" default-expand-all key-field="code" label-field="name"	
                                     @updateValue="(value,option) => markLabel(value,option,item,0)" 
                                     v-model:value="item.minValue" 
                                     :options="currentFieldList" ></n-tree-select>
                                </form-item-extend>
                            </div>  
                        </template>
                        <template v-else-if="item.type=='system'">
                            <div class="value-item">
                                <form-item-extend>
                                    <dict class="input" :node-props="(options)=>nodePropsConditionSys(options,item)" @updateValue="(value,option) => markLabel(value,option,item,0)" placeholder="请选择" v-model:value="item.minValue" type="condition.systemValue"></dict>
                                </form-item-extend>
                            </div>
                        </template>
                        <template v-else-if="item.type=='field'">
                            <div class="value-item">
                                <form-item-extend>
                                    <n-select class="input" @updateValue="(value,option) => markLabel(value,option,item,0)" v-model:value="item.minValue" :options="getFieldParamsOptions()[0].children" value-field="code" label-field="name"></n-select>
                                </form-item-extend>
                            </div>
                        </template>
                        <template v-else>
                            <div class="value-item" :class="(item.condition==6 || item.condition==10)?'value-flex':''">
                                <form-item-extend>
                                    <render v-if="item.option.type=='datetime'" class="input"  :option="item.option" v-model:formatted-value="item.minValue" clearable :placeholder="item.condition==9?'请输入最小值':'请输入值'"></render>
                                    <render v-else class="input" :option="item.option" @updateValue="(value,option) => markLabel(value,option,item,0)" v-model:value="item.minValue" maxlength="10" :placeholder="(item.condition==6 || item.condition==10)?'请输入最小值':'请输入值'" clearable ></render>
                                </form-item-extend>
                                <template v-if="item.condition==6 || item.condition==10">
                                    <span class="line">-</span>
                                    <form-item-extend>
                                        <render v-if="item.option.type=='datetime'" class="input" :option="item.option" v-model:formatted-value="item.maxValue" clearable :placeholder="item.condition==9?'请输入最小值':'请输入值'"></render>
                                        <render v-else class="input" :option="item.option" @updateValue="(value,option) => markLabel(value,option,item,1)" v-model:value="item.maxValue" maxlength="10" placeholder="请输入最大值" clearable ></render>
                                    </form-item-extend>
                                </template>
                            </div>
                        </template>
                        
                        <!-- 操作 -->
                        <div class="action">
                            <n-popover trigger="click" placement="right" class="popover">
                                <template #trigger>
                                    <n-button text type="success">
                                        <icon type="add-circle" fill></icon> 
                                    </n-button>
                                </template>
                                <ul>
                                    <li @click="toggleAction('add','1',i)">添加条件组</li>
                                    <li @click="toggleAction('add','2',i)">添加条件</li>
                                </ul>
                            </n-popover>
                            <n-popover trigger="click" placement="right" class="popover" v-if="i == 0">
                                    <template #trigger>
                                        <n-button text type="error">
                                            <icon type="indeterminate-circle" fill></icon>
                                        </n-button>
                                    </template>
                                    <ul>
                                        <li @click="toggleAction('reduce', '1', i)">删除条件组</li>
                                        <li @click="toggleAction('reduce', '2', i)">删除条件</li>
                                    </ul>
                                </n-popover>
                                <n-tooltip trigger="hover" v-else class="tooltip-padding">
                                    <template #trigger>
                                        <n-button text type="error" @click="toggleAction('reduce', '2', i)">
                                            <icon type="indeterminate-circle" fill></icon>
                                        </n-button>
                                    </template>
                                    删除条件
                                </n-tooltip>
                        </div>
                    </div> 
                </template>
            </template>
        </div>
    </div>
</template>

<script setup name="ConditionItem">
import { ref, computed } from 'vue';
// 方法
import conditionParams from '@/components/condition/conditionParams'
// 组件
import ConditionItem from '@/components/condition/ConditionItem.vue';
// 存储
import { useParamsStore } from '@/stores/global'
 
// 参数
const props = defineProps({
    data:{
        type:Object,
        default:()=>{}
    },
    // left根据数据源选择的字段
    options:{
        type:Array,
        default:()=>[]
    },
    // 右边的字段//可能与左边相同也可不同,一般指当前正在操作的表单数据
    currentFieldList:{
        type:Array,
        default:()=>[]
    }, 
    currentIndex:{
        type:[String,Number],
        default:0
    },
    parentsInfo:{
        type:Object,
        default:()=>{}
    },
    // 排除某种数据类型
    valueTypeExclude:{
        type:Array,
        default:()=>[]
    }, 
    //排除某个类型字段
    fieldExclude: {
        type:Array,
        default:()=>[]
    }
})  

// 获取参数数据列表
const $paramsStore = useParamsStore()
const paramsList = computed(()=>{
    return $paramsStore.value
})

// 选择字段或则参数
const fieldParamsOptions = ref([
    {
        code:'field',
        name:'字段', 
        children:computed(() => {
            return  JSON.parse(JSON.stringify(props.options))
        })
    },
    {
        code:'param',
        name:'表单参数',
        children:computed(() => {
            return $paramsStore.value
        }) 
    }
]) 

// 条件组类型
const optionsSelect = ref([
    {
        label:'且',
        value:'and'
    },
    {
        label:'或',
        value:'or'
    }
]) 
//根据设置排除某个类型字段
const getFieldParamsOptions = () => {
    return fieldParamsOptions.value.filter(v => !props.fieldExclude.includes(v.code))
}
// 选择条件,根据条件判断输入值框是否需要禁用,值类型是否重置为value
const updateCondition = (val,i)=>{
    let disabledCondition = [11,12,6,10]
    if(disabledCondition.indexOf(val)>=0){
        props.data.children[i].type = 'value'
    }
    props.data.children[i].minValue = null
    props.data.children[i].maxValue = null
    if(val==12 || val==11){
        props.data.children[i].option.props.disabled = true 
    } else {
        props.data.children[i].option.props.disabled = false
    }
}

// 禁用,选择值类型
const disabledFun = (i)=>{
    let disabled = false
    let condition = props.data.children[i].condition
    let disabledCondition = [11,12,6,10]
    if(disabledCondition.indexOf(condition)>=0){
        disabled = true
    }
    return disabled
}
//只有等于不等于,系统值显示本周本月...
const nodePropsConditionSys = (option, item) => {
    let dataRule = ['thisWeek','thisMonth','thisYear','last7Days','last30Days','last1Year']
    if ([1, 4].indexOf(item.condition) < 0 && dataRule.includes(option.value)) {
        option.disabled = true
    } else {
        option.disabled = false
    }
    return option
}
// 条件根据不同的选择进行禁用
const nodePropsCondition = (option,i)=>{
    let tag = props.data.children[i].option.tag
    if(conditionParams.conditionByField[tag].indexOf(option.value)<0){
        option.disabled = true
    } else {
        option.disabled = false
    }
    return option
}

// 数据类型是否有禁用
const nodePropsType = (option,i) =>{
    option.disabled = false
    if(props.valueTypeExclude.indexOf(option.value)>=0){
        option.disabled = true
    }
    return option
}

// 切换值的类型
const updateValueType = (val,i)=>{
    props.data.children[i].minValue = null
    props.data.children[i].maxValue = null
}

// 切换条件选择
const changeConditionType = (val)=>{
    props.data.conjunction = val
}

// 切换增加和减少con1组,2条件
const toggleAction = async (type,con,i)=>{
    try {
        if(type=='add'){ 
            let info = JSON.parse(JSON.stringify(conditionParams.conditionItem))
            if(con==1){
                props.data.children.push({
                    ...conditionParams.conditionGroup,
                    children:[{
                        ...info
                    }]
                })
            } else {
                props.data.children.splice(i+1,0,{
                    ...info
                })
            }
        } else { 
            //如果删除的是组
            if (i == 0) {
                if (props.currentIndex == 0) { //最外层父元素
                    props.parentsInfo.children = []
                } else {
                    props.parentsInfo.children.splice(props.currentIndex,1)
                }
            } else {
                props.data.children.splice(i,1)
            }
        }
        await settingPath() 
    } catch (error) {
        return false
    }
}

// 切换
const toggleClassInfo = (val)=>{
    props.data.isOpen = val?false:true
}

// 选择字段
const changeField = (val,row,i)=>{
    props.data.children[i].minValue = null
    props.data.children[i].maxValue = null
    props.data.children[i].condition = 1
    props.data.children[i].type = 'value'
    props.data.children[i].fieldCategory = 'field'
    if(row.prop == undefined){
        props.data.children[i].fieldCategory = 'param'
        props.data.children[i].option = {
            tag: 'input',
            props: null,
            code:null
        }
        return 
    }
    let info = JSON.parse(row.prop) 
    props.data.children[i].option = {
        ...info,
        tag:(info.tag=='radio-group' || info.tag=='checkbox-group')?'select':info.tag,
        type:(info.type=='radio' || info.type=='checkbox')?'select':info.type,
    }
    props.data.children[i].option.props.disabled = false
    if(info.tag == 'checkbox-group'){
        props.data.children[i].option.props.multiple = true
    }
    if (props.data.children[i].option.type == 'region') {
        props.data.children[i].option.props.type = props.data.children[i].option.props.type=='city' ? 'city' : 'county'
    } 
    if(props.data.children[i].option?.props?.valueFormat && info?.props?.valueFormat){
        props.data.children[i].option.props.valueFormat = info.props.valueFormat.replace(/\./g,'-')
    } 
}
/**
 * @description: 在其他组件中要回显条件-记录label
 * @param {*} value
 * @param {*} option 选中的选项
 * @param {*} item 条件数据行
 * @param {*} tag 0最小值1最大值
 * @return {*}
 */
 const markLabel = (value, option, item, tag) => {
    item.fieldLabel =  item.option.label;  //字段名
    if (item.type == 'value') {
        if (['radio-group', 'checkbox-group', 'select'].includes(item.option.tag)) {
            if (tag == 0) {
                item.minValueLabel = option.label;
            } else {
                item.maxValueLabel = option.label;
            } 
        } else {
            item.maxValueLabel = null;
            item.minValueLabel = null;
        }
    } else {
        if (tag == 0) {
            item.minValueLabel = option.label;
        } else {
            item.maxValueLabel = option.label;
        } 
    }
}
</script>

<style lang="scss" scoped> 
.flex-condition{
    display: flex;
    justify-content: row;
    align-items: center; 
    height: 100%;
    position: relative;
    .handle-condition{  
        width:20px;  
        display: flex;
        flex-direction:column; 
        align-items: center;  
        position: absolute;
        height: calc(100% - 20px);
        margin-bottom: 20px;
        .line{
            flex: auto;
            width: 1px;
            background-color: #C9CDD4;
        }
        .type-handle{
            color: #165DFF;
            background-color: #fff;
            font-size: 14px;
            padding:0 4px;
            font-size: 12px;
            box-shadow: 0px 0px 2px #C9CDD4;
        }
    }
    .content-condition{
        flex: 1;
        margin-left: 26px;
    }
}
// 其他样式
.item{ 
    display: flex;
    align-items: center;
    :deep(.n-form-item){
        display: block; 
    }
    .summary-open{  
        color:#165DFF;  
        position: absolute;
        left: 11px;
        top: 8px; 
        border-radius: 50%;
        text-align: center;
        line-height: 18px;
        font-size: 28px; 
        cursor: pointer;
    }
    .summary-close{
        left: 8px;
        top: 10px;
    }
    .input-select{
        width: 60px;
        margin-right: 10px;
    }
    .input-width{
        width: 200px;
        margin-right: 10px;
    }
    .input-width-min{
        width: 110px;
        margin-right: 10px;
    }
    .action{
        display: flex;
        align-items: center;
        margin-top: -22px;
        :deep(.n-button){
            margin-right: 8px;
        }
    }
    .value-item{
        margin-right: 12px;
        width: 300px;
        &.value-flex{
            display: flex;
            justify-content: space-between;
            align-items: center;
            :deep(.n-form-item){
                width: 49%;
            }
        }
        .line{
            display: block;
            vertical-align: middle;
            margin: -23px 2px 0 2px; 
        } 
    }
}
</style>
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值