树形组件(可动态添加属性、无限嵌套)及递归展示tree数据

简介: 树形组件(可动态添加属性、无限嵌套)及递归展示tree数据

前言:

公司的业务要求,做一个动态添加子属性或统计属性的一个业务,网上没有找到特别合适的,就自己参考了一些demo做了一个小组件,希望大佬们可以多多留言指点!

1、做法

要实现动态的渲染我们拿到的不知道有几层的菜单数据,有两种解决方案:

  • 操作 dom 去一层一层 添加子菜单(vue 不推荐操作 dom,所以不推荐此方案)
  • 将我们的菜单封装到组件中,通过递归组件实现菜单渲染

具体效果如下图:

20210527153548522.png

2、数据

options: [
        {
          id: "473d342d-20d9-459c-ab56-bac4abceb7ad",
          tagId: 1,
          joinMethod: "and",
          byProperty: "CompanyName",
          operate: "=",
          targetValue: "尖沙咀新港中心",
          parentTagRuleId: 23,
          child: [
            {
              id: "fd749e7b-7708-41e1-be97-0f5dce627e94",
              tagId: 1,
              joinMethod: "and",
              byProperty: "CompanyName",
              operate: "=",
              targetValue: "尖沙咀新港中心",
              parentTagRuleId: "473d342d-20d9-459c-ab56-bac4abceb7ad",
              child: [
                {
                  id: "fd749e7b-7708-41e1-be97-0f5dce627e94",
                  tagId: 1,
                  joinMethod: "and",
                  byProperty: "CompanyName",
                  operate: "=",
                  targetValue: "尖沙咀新港中心",
                  parentTagRuleId: "473d342d-20d9-459c-ab56-bac4abceb7ad",
                  child: null,
                },
              ],
            },
          ],
        },
        {
          id: "473d342d-20d9-459c-ab56-bac4abceb7ad",
          tagId: 1,
          joinMethod: "and",
          byProperty: "CompanyName",
          operate: "=",
          targetValue: "尖沙咀新港中心",
          parentTagRuleId: 23,
          child: [
            {
              id: "fd749e7b-7708-41e1-be97-0f5dce627e94",
              tagId: 1,
              joinMethod: "and",
              byProperty: "CompanyName",
              operate: "=",
              targetValue: "尖沙咀新港中心",
              parentTagRuleId: "473d342d-20d9-459c-ab56-bac4abceb7ad",
              child: null,
            },
          ],
        },
],

3、组件

父组件:

<template>
  <div style="margin: 0 100px">
    <tag-rule :options="options" :isFirst="isFirst" ref="andOr"></tag-rule>
  </div>
</template>
<script>
import tagRule from "./components/tagRule.vue";
export default {
  components: { tagRule },
  data() {
    return {
      isFirst: true,
      options: [
        {
          id: "473d342d-20d9-459c-ab56-bac4abceb7ad",
          tagId: 1,
          joinMethod: "and",
          byProperty: "CompanyName",
          operate: "=",
          targetValue: "尖沙咀新港中心",
          parentTagRuleId: 23,
          child: [
            {
              id: "fd749e7b-7708-41e1-be97-0f5dce627e94",
              tagId: 1,
              joinMethod: "and",
              byProperty: "CompanyName",
              operate: "=",
              targetValue: "尖沙咀新港中心",
              parentTagRuleId: "473d342d-20d9-459c-ab56-bac4abceb7ad",
              child: [
                {
                  id: "fd749e7b-7708-41e1-be97-0f5dce627e94",
                  tagId: 1,
                  joinMethod: "and",
                  byProperty: "CompanyName",
                  operate: "=",
                  targetValue: "尖沙咀新港中心",
                  parentTagRuleId: "473d342d-20d9-459c-ab56-bac4abceb7ad",
                  child: null,
                },
              ],
            },
          ],
        },
        {
          id: "473d342d-20d9-459c-ab56-bac4abceb7ad",
          tagId: 1,
          joinMethod: "and",
          byProperty: "CompanyName",
          operate: "=",
          targetValue: "尖沙咀新港中心",
          parentTagRuleId: 23,
          child: [
            {
              id: "fd749e7b-7708-41e1-be97-0f5dce627e94",
              tagId: 1,
              joinMethod: "and",
              byProperty: "CompanyName",
              operate: "=",
              targetValue: "尖沙咀新港中心",
              parentTagRuleId: "473d342d-20d9-459c-ab56-bac4abceb7ad",
              child: null,
            },
          ],
        },
      ],
    };
  },
};
</script>
<style>
</style>

子组件:

子组件这里的重点就是组件的name,用来递归调用

<template>
  <div>
    <div
      v-if="options.length == 0 && parentTagRuleId == 23"
      class="and-or-template col-xs-12"
      :class="isFirst ? 'and-or-first' : ''"
    >
      <div class="btn-group col-xs-7 btn-and-or">
        <button @click.prevent="addLine" class="btn btn-xs btn-purple">
          {{ "+madd" }}
        </button>
        <button @click.prevent="addTopGroup()" class="btn btn-xs btn-purple">
          {{ "+(mGroup)" }}
        </button>
      </div>
    </div>
    <div
      v-for="(item, index) in options"
      :key="index"
      class="and-or-template col-xs-12"
      :class="isFirst ? 'and-or-first' : ''"
    >
      <!-- left:and-or   center:data  right:add  -->
      <div class="form-group col-sx-12">
        <div class="btn-group col-xs-7 btn-and-or">
          <button @click.prevent="addLine" class="btn btn-xs btn-purple">
            {{ "+add" }}
          </button>
          <button
            @click.prevent="addGroup(index)"
            class="btn btn-xs btn-purple"
          >
            {{ "+(Group)" }}
          </button>
          <button
            style="margin-right: 10px"
            @click.prevent="deleteGroup(item, index)"
            class="btn btn-xs btn-purple"
          >
            ×
            <i class="fa fa-fw fa-close"></i>
          </button>
        </div>
        <el-form
          :model="item"
          :rules="rules"
          ref="ruleForm"
          size="small"
          label-position="top"
          class="full"
        >
          <el-row style="margin-left: 5px" :gutter="20">
            <el-col :span="3">
              <el-select v-model="item.joinMethod">
                <el-option value="and" :label="'and'"></el-option>
                <el-option value="or" :label="'or'"></el-option>
              </el-select>
            </el-col>
            <el-col :span="6">
              <el-form-item prop="byProperty">
                <el-select v-model="item.byProperty">
                  <el-option
                    v-for="(item, index) in byProperty"
                    :key="index"
                    :label="item"
                    :value="item"
                  ></el-option>
                </el-select>
              </el-form-item>
            </el-col>
            <el-col :span="6">
              <el-form-item prop="operate">
                <el-select v-model="item.operate">
                  <el-option
                    v-for="(item, index) in operate"
                    :key="index"
                    :label="item"
                    :value="item"
                  ></el-option>
                </el-select>
              </el-form-item>
            </el-col>
            <el-col :span="6">
              <el-form-item prop="targetValue">
                <el-input v-model="item.targetValue"></el-input>
              </el-form-item>
            </el-col>
          </el-row>
        </el-form>
      </div>
      <!-- 递归嵌套 -->
      <div v-if="item.child">
        <tag-rule
          class="and-or-offset col-xs-11"
          :options="item.child"
        ></tag-rule>
      </div>
    </div>
  </div>
</template>
<script>
// import { getTagViewProperties } from "@/api/uxretail/vip/vipTag.js";
export default {
  name: "tagRule",
  props: {
    options: {
      type: Array,
      require: true,
    },
    isFirst: {
      type: Boolean,
      default: false,
    },
    tagId: {},
  },
  data() {
    return {
      parentTagRuleId: "",
      byProperty: [],
      operate: [">", "=", "<"],
      rules: {
        byProperty: [{ required: true, trigger: ["blur", "change"] }],
        operate: [{ required: true, trigger: ["blur", "change"] }],
        targetValue: [{ required: true, trigger: ["blur", "change"] }],
      },
    };
  },
  methods: {
    addLine() {
      this.options.push({
        joinMethod: "",
        byProperty: "",
        operate: "",
        targetValue: "",
        tagId: this.tagId,
        child: null,
      });
    },
    addGroupItem(data) {
      if (data.child === null || data.child.length === 0) {
        data.child = [
          {
            joinMethod: "",
            byProperty: "",
            operate: "",
            targetValue: "",
            tagId: this.tagId,
            child: null,
          },
        ];
      } else if (data.child.length > 0) {
        data.child.push({
          joinMethod: "",
          byProperty: "",
          operate: "",
          targetValue: "",
          tagId: this.tagId,
          child: null,
        });
      }
      //    else {
      //     this.addGroupItem(data);
      //   }
    },
    addGroup(index) {
      this.addGroupItem(this.options[index]);
      //   console.log("this.$refs.ruleForm", this.$refs.ruleForm.validate());
    },
    addTopGroup() {
      this.options.push({
        joinMethod: "",
        byProperty: "",
        operate: "",
        targetValue: "",
        child: null,
      });
    },
    deleteGroup(item, index) {
      if (item.parentTagRuleId == 23) {
        this.parentTagRuleId = item.parentTagRuleId;
      }
      this.options.splice(index, 1);
      console.log("this.$refs.options", this.options);
    },
    // 校验数据
    // validateForm() {
    //   let flag = null;
    //   this.$refs["ruleForm"].validate((valid) => {
    //     if (valid) {
    //       flag = true;
    //     } else {
    //       flag = false;
    //     }
    //   });
    //   return flag;
    // },
  },
  created() {
    // getTagViewProperties().then((result) => {
    //   this.byProperty = result;
    // });
  },
};
</script>
<style lang="scss">
$more-color: rgb(24, 144, 255);
.and-or-template {
  padding: 8px;
  position: relative;
  border-radius: 3px;
  border: 1px solid $more-color;
  border-top: 3px solid #d2d6de;
  margin-bottom: 15px;
  /* width: 100%; */
  box-shadow: 0 1px 1px rgba(0, 0, 0, 0.1);
  border-top-color: $more-color;
  background-color: rgba(255, 255, 255, 0.9);
}
.and-or-template:before,
.and-or-template:after {
  content: "";
  position: absolute;
  left: -23px;
  width: 22px;
  height: calc(50% + 20px);
  border-color: #c0c5e2;
  border-style: solid;
}
.and-or-template:before {
  top: -18px;
  border-width: 0 0 2px 2px;
}
.and-or-template:after {
  top: 50%;
  border-width: 0 0 0 2px;
}
.and-or-first:before,
.and-or-first:after,
.and-or-template:last-child:after {
  border: none;
}
.col-xs-12 {
  width: 100%;
}
.form-group {
  margin-bottom: 15px;
  padding: 0;
}
.btn {
  display: inline-block;
  padding: 6px 12px;
  margin-bottom: 0;
  font-size: 14px;
  font-weight: 400;
  line-height: 1.42857143;
  cursor: pointer;
  text-align: center;
  white-space: nowrap;
  vertical-align: middle;
  -ms-touch-action: manipulation;
  touch-action: manipulation;
  -webkit-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
  user-select: none;
  background-image: none;
  border: 1px solid transparent;
  border-radius: 4px;
  padding: 1px 5px;
  font-size: 12px;
  line-height: 1.5;
  border-radius: 3px;
}
.and-or-offset {
  margin-left: 30px;
}
.col-xs-11 {
  position: relative;
  width: 91.66666667%;
  min-height: 1px;
  padding-left: 15px;
}
.btn-purple-outline {
  color: #6d77b8;
  background-color: transparent;
  background-image: none;
  border-color: #6d77b8;
}
.btn-purple-outline:hover {
  color: #fff;
  background-color: $more-color;
  background-image: none;
  border-color: $more-color;
}
.btn-radius {
  padding: 0;
  width: 22px;
  height: 22px;
  border-radius: 11px;
}
.btn-xs {
  padding: 1px 5px;
  font-size: 12px;
  line-height: 1.5;
  border-radius: 3px;
}
.btn-purple,
.btn-purple-outline:hover {
  color: #fff;
  background-color: $more-color;
  border-color: $more-color;
}
.btn-group {
  display: flex;
  flex-direction: row;
  justify-content: flex-end;
}
.col-xs-7,
.col-xs-11 {
  position: relative;
  min-height: 1px;
  padding-right: 5px;
  padding-left: 15px;
  padding-bottom: 10px;
}
.col-xs-7 {
  width: 100%;
}
.btn-and-or button {
  margin-left: 4px;
  margin: 0px 0px 5px 7px;
}
</style>

参考Demo地址:https://github.com/akumatus/FilterBuilder 

相关文章
|
JavaScript 前端开发 Java
ztree实现左边动态生成树,右边为具体信息功能
ztree实现左边动态生成树,右边为具体信息功能
62 0
如果数据给的是树形 转好的树形结构并且是有两个二级children的话 该如何写?
如果数据给的是树形 转好的树形结构并且是有两个二级children的话 该如何写?
|
5月前
|
JavaScript
vue 渲染树型数据(含树型数据转化为一维数组的方法,包括每个节点的深度和深度列表)
vue 渲染树型数据(含树型数据转化为一维数组的方法,包括每个节点的深度和深度列表)
62 0
|
7月前
|
测试技术
【sgLazyTree】自定义组件:动态懒加载el-tree树节点数据,实现增删改、懒加载及局部数据刷新。
【sgLazyTree】自定义组件:动态懒加载el-tree树节点数据,实现增删改、懒加载及局部数据刷新。
60EasyUI 树形菜单- 树形网格惰性加载节点
60EasyUI 树形菜单- 树形网格惰性加载节点
42 0
|
JavaScript
封装递归tree组件
封装递归tree组件
40 0
|
前端开发 JavaScript 数据库
树形结构表格与懒加载
树形结构表格与懒加载
585 0
树形结构表格与懒加载
|
XML 存储 JSON
多叉树结合JavaScript树形组件实现无限级树形结构(一种构建多级有序树形结构JSON(或XML)数据源的方法)
如何将数据库中的层次数据转换成对应的层次结构的JSON或XML格式的字符串,返回给客户端的JavaScript树形组件?这就是我们要解决的关键技术问题。
多叉树结合JavaScript树形组件实现无限级树形结构(一种构建多级有序树形结构JSON(或XML)数据源的方法)
|
缓存 JavaScript
理解递归组件与动态组件
递归组件与动态组件
|
SQL Java
JavaWeb - 多级菜单、分组嵌套“递归”写法
JavaWeb - 多级菜单、分组嵌套“递归”写法
304 0
JavaWeb - 多级菜单、分组嵌套“递归”写法