Bootstrap

vue3实现规则编辑器

组件用于创建和编辑复杂的条件规则,支持添加、删除条件和子条件,以及选择不同的条件类型。
可实现json数据和页面显示的转换。

在这里插入图片描述

在这里插入图片描述
代码实现 :
index.vue:

<template>
  <div class="allany-container">
    <div class="control-bar">
      <el-select v-model="pipe.condition" style="width: 200px; margin-right: 16px;" :disabled="disabled">
        <el-option label="满足以下所有条件" value="all"/>
        <el-option label="满足以下任一条件" value="any"/>
        <el-option label="不包含以下条件" value="not"/>
      </el-select>
      <el-button v-if="!disabled" type="primary" @click="addCondition('condition')">添加条件</el-button>
      <el-button v-if="!disabled" type="success" @click="addCondition('all_any')">添加子条件</el-button>
      <el-button v-if=" !disabled" type="danger" :icon="Delete" circle @click="delSelf"/>
    </div>
    <div class="conditions-wrapper">
      <!--   侧边显示条   -->
      <div :class="`conditions-wrapper-${pipe.condition}`"></div>
      <div class="conditions-wrapper--conditions">
        <div v-for="(child, idx) in pipe.children" :key="idx" class="conditions-wrapper--condition">
          <biz-rule-all-any v-if="child.type === 'all_any'" :pipe="child" :attrOptions="attrOptions"
                            :disabled="disabled" @delRule="handleDelRule(idx,child)"/>
          <biz-rule-condition v-else-if="child.type === 'condition'" :pipe="child" :attrOptions="attrOptions"
                              :disabled="disabled" @delRule="handleDelCondition(idx,child)"/>
        </div>
      </div>
    </div>
  </div>
</template>

<script setup>
import {ref, watch} from 'vue';
import BizRuleCondition from './BizRuleCondition.vue';
import {Delete} from "@element-plus/icons-vue";
import {deepClone} from "@/utils.js";

defineOptions({
  name: 'BizRuleAllAny'
})

const emit = defineEmits(['delRule']);

const props = defineProps({
  pipe: {
    type: Object,
    default: () => ({
      type: 'all_any',
      condition: 'all',
      children: [],
    })
  },
  attrOptions: {
    type: Array,
    default: () => {
      return []
    }
  },
  disabled: {
    type: Boolean,
    default: false
  }
});
const pipe = ref(props.pipe);


function addCondition(type) {
  if (type === 'all_any') {
    pipe.value.children.push({
      type: 'all_any',
      condition: 'all',
      children: [],
    });
  } else if (type === 'condition') {
    pipe.value.children.push({
      type: 'condition',
      name: '',
      operator: 'eq',
      value: '',
      val_type: 'string',
    });
  }
}

function delSelf() {
 emit('delRule')
}

// 删除条件组
const handleDelRule = (idx,child) => {
  pipe.value.children.splice(idx, 1);
}

// 删除条件组中的子数据
const handleDelCondition = (idx,child) => {
  pipe.value.children.splice(idx, 1);
}

</script>

<style scoped lang="scss">

.allany-container {

  .control-bar {
    display: flex;
    flex-direction: row;
  }

  .conditions-wrapper {
    display: flex;
    flex-direction: row;
  }


  .conditions-wrapper-all {
    width: 4px;
    margin: 5px 20px 0;
    border-radius: 5px;
    transition: background-color 400ms;
    background-color: #67C23A;

    &:hover {
      background-color: #529b2e;
    }
  }


  .conditions-wrapper-any {
    width: 4px;
    margin: 5px 20px 0;
    border-radius: 5px;
    transition: background-color 400ms;
    background-color: #E6A23C;

    &:hover {
      background-color: #b88230;
    }
  }


  .conditions-wrapper-not {
    width: 4px;
    margin: 5px 20px 0;
    border-radius: 5px;
    transition: background-color 400ms;
    background-color: rgb(245, 245, 245);

    &:hover {
      background-color: rgb(144, 147, 153);
    }
  }


  .conditions-wrapper--conditions {
    display: flex;
    flex-direction: column;
  }

  .conditions-wrapper--condition {
    padding-top: 15px;
  }
}

</style>

BizRuleCondition.vue:

<template>
  <div class="bizrulecondition-container">
    <el-select v-model="pipe.name" placeholder="字段名称" style="width: 150px; margin-right: 16px;" clearable :disabled="disabled">
      <el-option v-for="item in attrOptions" :key="item.value" :label="item.label" :value="item.value"/>
    </el-select>
    <el-select v-model="pipe.operator" style="width: 90px; margin-right: 16px;" :disabled="disabled">
      <el-option label="==" value="eq"/>
      <el-option label="!=" value="neq"/>
      <el-option label="<" value="lt"/>
      <el-option label=">" value="gt"/>
      <el-option label="<=" value="lte"/>
      <el-option label=">=" value="gte"/>
      <el-option label="in" value="in"/>
      <el-option label="not in" value="not_in"/>
    </el-select>
    <el-badge
        :value="pipe.val_type === 'string' ? '字符串' : '数字'"
        :type="pipe.val_type === 'string' ? 'info' : 'success'"
        @click.native="switchVarType($event, pipe)"
        style="margin-right: 50px;"
        :disabled="disabled"
    >
      <el-input
          v-model="pipe.value"
          placeholder="字段值"
          style="width: 150px;"
          clearable
          :disabled="disabled"
      />
    </el-badge>
    <el-button type="danger" :icon="Delete" circle @click="delSelf" :disabled="disabled"/>

  </div>
</template>

<script setup>
import {ref, defineProps, watch} from 'vue';
import {Delete} from "@element-plus/icons-vue";
import {deepClone} from "@/utils.js";

const emit = defineEmits(['delRule']);


const props = defineProps({
  pipe: Object,
  attrOptions:Array,
  disabled:Boolean
});
const pipe = ref({});

watch(() => props.pipe, (newData) => {
  if (!newData) return
  pipe.value = deepClone(newData)
}, {
  deep: true,
  immediate: true
})


function switchVarType(e, kv) {
  if (String(e.target.tagName).toUpperCase() === 'SUP') {
    kv.val_type = kv.val_type === 'number' ? 'string' : 'number';
  }
}

function delSelf() {
  emit('delRule')
}
</script>

<style lang="scss" scoped>
.bizrulecondition-container {
  display: flex;
  flex-direction: row;

  .el-badge__content {
    transition: 400ms;
    user-select: none;
  }

  .el-badge__content:hover {
    cursor: pointer;
  }
}
</style>

BizRuleAdapter.js:

<template>
  <div class="bizrulecondition-container">
    <el-select v-model="pipe.name" placeholder="字段名称" style="width: 150px; margin-right: 16px;" clearable :disabled="disabled">
      <el-option v-for="item in attrOptions" :key="item.value" :label="item.label" :value="item.value"/>
    </el-select>
    <el-select v-model="pipe.operator" style="width: 90px; margin-right: 16px;" :disabled="disabled">
      <el-option label="==" value="eq"/>
      <el-option label="!=" value="neq"/>
      <el-option label="<" value="lt"/>
      <el-option label=">" value="gt"/>
      <el-option label="<=" value="lte"/>
      <el-option label=">=" value="gte"/>
      <el-option label="in" value="in"/>
      <el-option label="not in" value="not_in"/>
    </el-select>
    <el-badge
        :value="pipe.val_type === 'string' ? '字符串' : '数字'"
        :type="pipe.val_type === 'string' ? 'info' : 'success'"
        @click.native="switchVarType($event, pipe)"
        style="margin-right: 50px;"
        :disabled="disabled"
    >
      <el-input
          v-model="pipe.value"
          placeholder="字段值"
          style="width: 150px;"
          clearable
          :disabled="disabled"
      />
    </el-badge>
    <el-button type="danger" :icon="Delete" circle @click="delSelf" :disabled="disabled"/>

  </div>
</template>

<script setup>
import {ref, defineProps, watch} from 'vue';
import {Delete} from "@element-plus/icons-vue";
import {deepClone} from "@/utils.js";

const emit = defineEmits(['delRule']);


const props = defineProps({
  pipe: Object,
  attrOptions:Array,
  disabled:Boolean
});
const pipe = ref({});

watch(() => props.pipe, (newData) => {
  if (!newData) return
  pipe.value = deepClone(newData)
}, {
  deep: true,
  immediate: true
})


function switchVarType(e, kv) {
  if (String(e.target.tagName).toUpperCase() === 'SUP') {
    kv.val_type = kv.val_type === 'number' ? 'string' : 'number';
  }
}

function delSelf() {
  emit('delRule')
}
</script>

<style lang="scss" scoped>
.bizrulecondition-container {
  display: flex;
  flex-direction: row;

  .el-badge__content {
    transition: 400ms;
    user-select: none;
  }

  .el-badge__content:hover {
    cursor: pointer;
  }
}
</style>
;