分类(类别)组件
此分类组件支持无限级
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>