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

 前言:

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


1、做法

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

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

具体效果如下图:

 

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 

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
A:要展示后端返回的树形数据,可以使用Vue的组件(recursive component)来实现。组件通过嵌套自身的方式来展示树形结构,这种方式非常适合展示树形数据。 首先,需要定义一个组件,并为组件传入数据。在组件内部,可以通过v-for指令遍历子节点,并通过组件的形式来展示子节点。具体的操作如下: 1. 定义组件: ``` <template> <div class="item"> {{ item.name }} <div v-if="item.children"> <tree-item v-for="child in item.children" :key="child.id" :item="child"></tree-item> </div> </div> </template> <script> export default { name: 'TreeItem', props: { item: { type: Object, required: true } }, components: { TreeItem: this } } </script> ``` 在组件的定义中,首先展示当前节点的名称,然后通过v-if指令判断当前节点是否有子节点,如果有子节点,则通过循环遍历子节点,并将每个子节点传组件,以便展示子节点的信息。 2. 在父组件中使用组件: ``` <template> <div> <tree-item v-for="item in treeData" :key="item.id" :item="item"></tree-item> </div> </template> <script> import TreeItem from './TreeItem.vue' export default { name: 'Tree', components: { TreeItem }, data () { return { treeData: [] } }, mounted () { // 从后端获取树形数据 axios.get('/api/getTreeData').then(res => { this.treeData = res.data }).catch(err => console.log(err)) } } </script> ``` 在父组件中,先定义一个数据属性treeData来存储从后端获取的树形数据。在mounted钩子函数中通过axios库向后端发送请求,获取树形数据,并将获取到的数据存储到treeData中。然后,通过v-for指令遍历每个节点,并将节点传组件进行展示。 通过这种方式,就可以从后端获取树形数据,并在前端展示出来。同时,由于使用了Vue的组件,这种方式还具有很好的可维护性,可以方便地增加或修改组件的结构和样式。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Iam_楠

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值