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>