分类(类别)组件

请添加图片描述

此分类组件支持无限级
categoryData:分类树节点集合,数据格式为:[id: 1000000, pid: 0, name: ‘Level 1’, children: []], 其中children字段中的每一项与当前项的结构一致,值可为:null或[]或[id: 1000000, pid: 0, name: ‘Level 1’, children: []],也可以不存在
events:为事件集合
selectAllEvent:为点击节点分类中某一个等级的全部按钮事件,返回的是当前节点的信息,此时的id为-1,-1表示的是全选
selectSingleEvent:为点击当前分类节点的按钮事件,返回的是当前节点对应的数据

一、父组件

父组件文件路径:/src/views/CategoryContainer.vue

<template>
  <Category :categoryData="categoryData" :events="events"></Category>
</template>

<script>
import Category from "@/components/category.vue";
export default {
  name: "CategoryContainer",
  components: {
    Category,
  },
  data() {
    return {
      categoryData: [],
      events: {
        selectAllEvent(data) {
          const { id, pid } = data;
          console.log("全选", { id, pid });
        },
        selectSingleEvent(data) {
          const { id, pid } = data;
          console.log("单选", { id, pid });
        },
      },
    };
  },
  created() {
    this.getCategoryData();
  },
  mounted() {},
  methods: {
    // 模拟获取分类树节点数据
    getCategoryData() {
      let categoryData = [];
      /**
       * 模拟分类树结构数据,以下四个字段名称为必有项,并且类型不能变更
       * @param {Number}[id] - 当前树节点的id
       * @param {Number}[pid] - 当前树节点的父id
       * @param {String}[name] - 当前树节点名称
       * @param {Array}[children=[]|null] - 当前树节点对应的子节点集合
       */
      for (let i = 0; i < 10; i++) {
        let first = {
          id: (i + 1) * 10 ** 6,
          pid: 0,
          name: `Level ${i + 1}`,
          children: [],
        };

        for (let j = 0; j < 10; j++) {
          let second = {
            id: (i + 1) * 10 ** 6 + (j + 1) * 10 ** 3,
            pid: (i + 1) * 10 ** 6,
            name: `Level ${i + 1}_${j + 1}`,
            children: [],
          };

          for (let q = 0; q < 10; q++) {
            let third = {
              id: (i + 1) * 10 ** 6 + (j + 1) * 10 ** 3 + (q + 1),
              pid: (i + 1) * 10 ** 6 + (j + 1) * 10 ** 3,
              name: `Level ${i + 1}_${j + 1}_${q + 1}`,
              children: [],
            };

            second.children.push(third);
          }

          first.children.push(second);
        }

        categoryData.push(first);
      }
      this.categoryData = categoryData;
    },
  },
};
</script>
二、子组件

子组件文件路径:/src/components/category.vue

<template>
  <article class="selector-wrap">
    <section class="selector">
      <aside class="selector-aside">
        <button :class="allClass" @click="clickItem('all', categoryData.length ? categoryData[0] : {})">全部</button>
      </aside>
      <main class="selector-main">
        <button v-for="category in categoryData" :key="'category_' + category.id" :class="activeId === category.id ? 'btn active' : 'btn'" @click="clickItem('item', category)">
          {{ category.name }}
        </button>
        <template v-for="category in categoryData">
          <CategoryCom v-if="category.children && category.children.length && showNodes(category.id)" :key="'category_component_' + category.id" :category-data="category.children" :events="events"></CategoryCom>
        </template>
      </main>
    </section>
  </article>
</template>

<script>
export default {
  name: "CategoryCom",
  components: {},
  mixins: [],
  props: {
    categoryData: {
      type: Array,
      require: true,
      default: () => [],
    },
    active: {
      type: Number,
      default: -1,
    },
    events: {
      type: Object,
      default: () => {},
    },
  },
  data() {
    return {
      activeId: -1, // 默认一级全选
    };
  },
  computed: {
    allClass() {
      return {
        btn: true,
        active: this.activeId === -1,
      };
    },
  },
  watch: {},
  created() {},
  mounted() {
    // this.initBtnsStatus();
  },
  destroyed() {},
  methods: {
    // 初始化默认选中的按钮
    initBtnsStatus() {
      /**
       * 判断 props 里面所需要的字段是否为空
       * 1:为空不做任何处理
       * 如果不为空,判断是否有子节点,如果有当前选中的按钮就位此节点
       *
       */
      if (this.categoryData.length) {
        if (this.categoryData[0].children && this.categoryData[0].children.length) {
          this.activeId = this.categoryData[0].id;
        } else {
          let category = { ...this.categoryData[0], ...{ id: -1 } };
          this.activeId = category.id;

          // 默认事件
          this.events.selectAllEvent && this.events.selectAllEvent(category);
        }
      }
    },
    // 点击所有分类
    clickItem(data) {
      let category = { ...data, ...{ id: -1 } };
      let hasCategory = this.cachData(category);
      if (!hasCategory && this.events.selectAllEvent) {
        this.events.selectAllEvent(category);
      }
    },
    // 点击分类事件
    clickItem(type, data) {
      let category = null;
      if (type === "all") {
        category = { ...data, ...{ id: -1 } };
      } else {
        category = data;
      }
      // 判断上次点击的按钮是否跟当前的一样,一样的话不做后续的操作
      if (this.activeId === category.id) {
        return false;
      }

      this.activeId = category.id;

      if (type === "all") {
        this.events.selectAllEvent && this.events.selectAllEvent(category);
      } else {
        this.events.selectSingleEvent && this.events.selectSingleEvent(category);
      }
    },
    showNodes(id) {
      return this.activeId === id;
    },
  },
};
</script>

<style lang="scss" scoped>
$font-size: 12px;

.selector {
  display: flex;
  flex-wrap: wrap;
  flex-direction: row;
  width: 100%;
  background-color: #fff;

  &-wrap {
    position: absolute;
    left: 0;
    display: block;
    width: calc(100% - 1em);
    padding: 0.5em;
    font-size: $font-size;
    background-color: #efefef;
    user-select: none;
  }

  &-aside {
    flex-grow: 0;
    flex-shrink: 0;
    padding-left: 1em;
    padding-top: 0.5em;
    box-sizing: border-box;
  }

  &-main {
    flex: 1;
    padding-left: 1em;
    padding-top: 0.5em;
    box-sizing: border-box;

    & > .btn {
      margin-right: 1em;
      margin-bottom: 0.5em;
    }
  }
}

.btn {
  display: inline-block;
  padding: 0.2em 0.8em;
  vertical-align: middle;
  color: #0d6efd;
  font-weight: 400;
  font-size: $font-size;
  line-height: 1.5;
  text-align: center;
  text-decoration: none;
  background-color: transparent;
  border: 1px solid #0d6efd;
  border-radius: 0.25rem;
  transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
  user-select: none;
  cursor: pointer;

  &:hover,
  &:focus,
  &:active,
  &.active {
    color: #fff;
    background-color: #0b5ed7;
    border-color: #0a58ca;
  }
}
</style>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值