【无标题】

规则表达式(amis组合条件)

代码:

javascript 复制代码
<template>
  <div class="rule-expr">
    <div class="rule-body-panel">
      <rule-group :group="ruleTree" :level="0" :is-root="true" :create-group="createGroup"
        :create-condition="createCondition" :field-dict-type="fieldDictType"
        :func-dict-type="funcDictType"></rule-group>
    </div>

  </div>
</template>

<script>
import RuleGroup from './rule-group.vue';

let NODE_ID = 1;

export default {
  name: 'RuleExpr',
  components: {
    RuleGroup
  },
  props: {
    value: [Array, Object, String],
    fieldDictType: {
      type: String,
      default: 'CHAN_EXPR_VAR'
    },
    funcDictType: {
      type: String,
      default: 'CHAN_FUNC_TYPE'
    }
  },
  data() {
    return {
      ruleTree: this.createGroup()
    };
  },
  watch: {
    value: {
      handler(val) {
        this.ruleTree = this.resolveRuleBody(val);
      },
      immediate: true
    }
  },
  methods: {
    nextNodeId() {
      return `rule-node-${NODE_ID++}`;
    },
    normalizeConjunction(conjunction, fallback = 'and') {
      const value = String(conjunction || fallback || 'and').trim().toLowerCase();
      return value === 'or' ? 'or' : 'and';
    },
    createCondition(data = {}) {
      return {
        id: data.id || this.nextNodeId(),
        varName: data.varName || '',
        exprFunc: data.exprFunc || '',
        matchVal: data.matchVal || ''
      };
    },
    createGroup(data = {}) {
      const children = Array.isArray(data.children) && data.children.length
        ? data.children.map(child => this.buildEditorNode(child))
        : [];
      return {
        id: data.id || this.nextNodeId(),
        conjunction: this.normalizeConjunction(data.conjunction, 'and'),
        children
      };
    },
    isGroupNode(node) {
      return !!(node && Array.isArray(node.children));
    },
    buildEditorNode(node) {
      if (!node || typeof node !== 'object') {
        return this.createCondition();
      }

      if (Array.isArray(node.children)) {
        return this.createGroup(node);
      }

      return this.createCondition(node);
    },
    normalizeRuleTree(node) {
      if (!node) {
        return null;
      }
      if (this.isGroupNode(node)) {
        return {
          id: node.id || this.nextNodeId(),
          conjunction: this.normalizeConjunction(node.conjunction, 'and'),
          children: (node.children || []).map(child => this.normalizeRuleTree(child)).filter(Boolean)
        };
      }
      return {
        id: node.id || this.nextNodeId(),
        varName: node.varName || '',
        exprFunc: node.exprFunc || '',
        matchVal: node.matchVal || ''
      };
    },
    resolveRuleBody(ruleBody) {
      if (!ruleBody) {
        return this.createGroup();
      }

      let parsedRuleBody = ruleBody;
      if (typeof parsedRuleBody === 'string') {
        try {
          parsedRuleBody = JSON.parse(parsedRuleBody);
        } catch (e) {
          return this.createGroup();
        }
      }

      if (parsedRuleBody && parsedRuleBody.conditions) {
        parsedRuleBody = parsedRuleBody.conditions;
      }

      if (parsedRuleBody && Array.isArray(parsedRuleBody.children)) {
        return this.buildEditorNode(parsedRuleBody);
      }

      if (Array.isArray(parsedRuleBody)) {
        return this.createGroup();
      }

      if (parsedRuleBody && (parsedRuleBody.varName || parsedRuleBody.exprFunc || parsedRuleBody.matchVal)) {
        return this.createGroup({
          children: [this.createCondition(parsedRuleBody)]
        });
      }

      return this.createGroup();
    },
    validateNode(node) {
      if (!node) {
        return false;
      }
      if (this.isGroupNode(node)) {
        return Array.isArray(node.children) && node.children.length && node.children.every(child => this.validateNode(child));
      }
      return !!node.varName && !!node.exprFunc && !!String(node.matchVal || '').trim();
    },
    validateRuleBody() {
      const valid = this.validateNode(this.ruleTree);
      if (!valid) {
        this.$message.warning('请完整填写规则内容');
        return false;
      }
      return true;
    },
    getConditionsTree() {
      return this.normalizeRuleTree(this.ruleTree);
    }
  }
};
</script>

<style scoped lang="scss">
.rule-expr {
  display: flex;
  flex-direction: column;
}

.rule-body-panel {
  border-radius: 8px;
  background: #fff;
  padding: 20px 24px 16px;
}
</style>

rule-group.vue

javascript 复制代码
<template>
  <div class="rule-group" :class="{ 'is-root': isRoot }">
    <div class="group-layout">
      <div  class="group-rail">
        <span class="rail-point rail-point-top"></span>
        <span class="rail-line rail-line-top"></span>
        <span class="rail-tag" @click="toggleConjunction">{{ conjunctionText }}</span>
        <span class="rail-line rail-line-bottom"></span>
        <span class="rail-point rail-point-bottom"></span>
      </div>

      <div class="group-main">

        <div class="group-content">
          <div v-for="(child, index) in group.children" :key="child.id || index" class="group-item">
            <rule-group
              v-if="isGroup(child)"
              :group="child"
              :level="level + 1"
              :is-root="false"
              :create-group="createGroup"
              :create-condition="createCondition"
              :field-dict-type="fieldDictType"
              :func-dict-type="funcDictType"
              @remove="removeChild(index)">
            </rule-group>

            <div v-else class="condition-card">
              <el-select
                v-model="child.varName"
                style="width: 300px;"
                placeholder="请选择字段"
                clearable
                filterable
                >
                  <el-option v-for="item in exprVarOptions" :key="item.dictId" :label="item.dictName" :value="item.dictId"></el-option>
              </el-select>

              <el-select
                v-model="child.exprFunc"
                style="width: 200px;"
                clearable
                placeholder="请选择条件"
                filterable
                >
                  <el-option v-for="item in fileFuncOptions" :key="item.dictId" :label="item.dictName" :value="item.dictId"></el-option>
              </el-select>

              <el-input v-model="child.matchVal" class="condition-value" placeholder="请输入匹配值"></el-input>

              <el-button type="text" class="delete-icon-btn" @click="removeChild(index)">
                <i class="el-icon-delete"></i>
              </el-button>
            </div>
          </div>
        </div>

        <div class="group-actions">
          <a class="group-link" @click.prevent="addCondition">添加条件</a>
          <a class="group-link" @click.prevent="addGroup">添加条件组</a>
          <a v-if="!isRoot" class="group-link danger" @click.prevent="$emit('remove')">删除组</a>
        </div>
      </div>
    </div>
  </div>
</template>

<script>
export default {
  name: 'RuleGroup',
  props: {
    group: {
      type: Object,
      required: true
    },
    level: {
      type: Number,
      default: 0
    },
    isRoot: {
      type: Boolean,
      default: false
    },
    createGroup: {
      type: Function,
      required: true
    },
    createCondition: {
      type: Function,
      required: true
    },
    fieldDictType: {
      type: String,
      default: 'CHAN_EXPR_VAR'
    },
    funcDictType: {
      type: String,
      default: 'CHAN_FUNC_TYPE'
    }
  },
    data() {
	    return {
	      exprVarOptions: [
	       {
		       dictId: 'field01',
		       dictName:'字段1'
		      },
		      {
		      	dictId:'field02',
		      	dictName:'字段2'
		      }
	      ],  //选择字段
	      fileFuncOptions: [
		      {
		       dictId: 'eq',
		       dictName:'等于'
		      },
		      {
		      	dictId:'startWith',
		      	dictName:'匹配开头'
		      }
	      ]  //选择条件
	    };
	  },
  computed: {
    conjunctionText() {
      return String(this.group.conjunction || 'and').toLowerCase() === 'or' ? '或' : '且';
    },
  },
  methods: {
    isGroup(node) {
      return !!(node && Array.isArray(node.children));
    },
    addCondition() {
      this.group.children.push(this.createCondition());
    },
    addGroup() {
      this.group.children.push(this.createGroup());
    },
    toggleConjunction() {
      this.group.conjunction = this.group.conjunction === 'or' ? 'and' : 'or';
    },
    removeChild(index) {
      this.group.children.splice(index, 1);
    }
  }
};
</script>

<style scoped lang="scss">
.rule-group {
  .group-layout {
    position: relative;
    display: flex;
    align-items: stretch;
    padding-left: 46px;
    min-height: 80px;
  }

  .group-rail {
    position: absolute;
    left: 0;
    top: 0;
    bottom: 0;
    width: 36px;
  }

  .rail-line {
    position: absolute;
    left: 17px;
    width: 1px;
    background: #c8d9ff;

    &.rail-line-top {
      top: 8px;
      height: calc(50% - 22px);
    }

    &.rail-line-bottom {
      top: calc(50% + 22px);
      bottom: 8px;
    }
  }

  .rail-point {
    position: absolute;
    left: 11px;
    width: 12px;
    height: 12px;
    border-radius: 50%;
    background: #dbe8ff;
    border: 1px solid #b7ccff;

    &.rail-point-top {
      top: 2px;
    }

    &.rail-point-bottom {
      bottom: 2px;
    }
  }

  .rail-tag {
    position: absolute;
    left: 0;
    top: 50%;
    transform: translateY(-50%);
    min-width: 34px;
    height: 28px;
    line-height: 28px;
    border-radius: 4px;
    background: #e8f0ff;
    color: #4d78ff;
    text-align: center;
    font-size: 13px;
    font-weight: 500;
    cursor: pointer;
    user-select: none;
  }

  .group-main {
    flex: 1;
    min-width: 0;
  }

  .group-content {
    display: flex;
    flex-direction: column;
    gap: 10px;
  }

  .group-item {
    width: 100%;
  }

  .condition-card {
    display: flex;
    align-items: center;
    gap: 12px;
    min-height: 46px;
    padding: 10px 14px;
    background: #f6f8fc;
    border-radius: 4px;
  }

  .condition-value {
    flex: 1;
    min-width: 160px;
  }

  .delete-icon-btn {
    flex: 0 0 auto;
    padding: 0;
    color: #909399;
    font-size: 16px;

    &:hover,
    &:focus {
      color: #4d78ff;
    }
  }

  .group-actions {
    display: flex;
    align-items: center;
    gap: 18px;
    margin-top: 10px;
    padding-left: 2px;
  }

  .group-link {
    color: #4d78ff;
    font-size: 14px;
    cursor: pointer;
    text-decoration: none;
  }
}
</style>

效果:

相关推荐
云飞云共享云桌面7 小时前
传统工作站 vs 云飞云共享云桌面:制造业设计云桌面选型深度对比
运维·服务器·前端·网络·3d·架构·制造
UXbot7 小时前
如何选择适合公司项目的UI设计工具?企业选型指南
前端·低代码·ui·团队开发·原型模式·设计规范·web app
llz_1128 小时前
web-第四次课后作业
前端·spring boot·web
武清伯MVP9 小时前
前端跨域方案大合集
前端·javascript
一杯奶茶¥9 小时前
基于springboot的失物招领管理系统带万字文档 校园失物招领管理系统 失物认领管理系统java springboot vue
java·vue.js·spring boot·java项目
小刘|9 小时前
Spring AI Alibaba 集成和风天气 API 实战
java·服务器·前端
星星在线9 小时前
我是怎么把页面图片流量砍掉一半的
前端·javascript
木叶子---10 小时前
前端打包出错
前端·人工智能·tensorflow
JAVA面经实录91710 小时前
前端系统化学习计划表(含完整知识思维导图)
前端·学习
本末倒置18311 小时前
开发了一个所见所得的md编辑器,致敬Typora大佬
前端