组件说明
本组件依赖于vant开发,实现树状菜单的自定义展示和选择,可以进行多自定义内容 使用插槽方便用户自定义展示内容
插槽内容规范
title : 弹窗显示的标题内容
默认值: 请选择内容
closeBtn: 确认按钮
默认值: 确认
leftContent :左侧内容插槽
显示左侧内容要显示的信息
rightContent : 右侧内容插槽
显示右侧内容要显示的信息
参数规范说明
treeDatas : 所有树状结构数组
[ { // 关键信息 text: "#C5341B", status: "circle", // 其他可选信息 ext:'112', children: [ { // 关键信息 text: "学生", status: "circle", } ] } ]
selectData 已选中的值
{selectData: { "#5B8DF5": [ { status: "checked", text: "李四", }, ], }}
props : 树节点key定义
props: { default: { label: "text", children: "children", status: "status", }, },
组件代码
<template>
<van-popup
v-model="show"
round
position="bottom"
class="pom"
@click-overlay="close"
>
<div class="header">
<div class="line-box">
<div class="line"></div>
</div>
<div class="title">
<slot name="title"> 选择内容 </slot>
<div class="title-close" @click="close">
<slot name="closeBtn"> 确认 </slot>
</div>
</div>
</div>
<div class="list-item">
<div class="main">
<div class="left">
<div
v-for="(item, index) in treeDatas"
:key="item.text"
class="checkbox"
:class="{ active: active === item[props.label] }"
@click="toggle(index, item)"
>
<slot name="leftContent" :data="item">
{{ item[props.label] }}
</slot>
</div>
</div>
<div class="right">
<div class="all" @click="toggleAllCheckBox(checkedAll)">
<span>全部</span>
<span
><van-icon
:name="checkedAll ? 'checked' : 'circle'"
color="green"
/></span>
</div>
<div class="list">
<div
v-for="(subItem, subindex) in showChildrens"
:key="subItem.text"
class="checkbox"
@click="subToggle(subindex, subItem)"
>
<slot name="rightContent" :data="subItem">
<span>{{ subItem[props.label] }}</span>
<van-icon :name="subItem[props.status]||'circle'" color="green" />
</slot>
</div>
</div>
</div>
</div>
</div>
</van-popup>
</template>
<script>
export default {
props: {
treeDatas: {
default: [],
},
selectData: {
default: [],
},
props: {
default: {
label: "text",
children: "children",
status: "status",
},
},
},
data() {
return {
show: true,
locationCodes: [],
active: "",
activeIndex: 0,
checkedAll: false,
showChildrens: [],
subSelectItem: {},
hasSelectSubItem: [],
};
},
created() {
this.subSelectItem = this.selectData;
// 初始化选择对象状态
this.initCheckedObj();
},
methods: {
/**
* 初始化选择对象状态
*/
initCheckedObj() {
const fristKeys = Object.keys(this.selectData);
if (fristKeys && fristKeys.length > 0) {
this.treeDatas.forEach((item, index) => {
// 如果选中的对象存在,则设置一级对象的状态
if (fristKeys.indexOf(item[this.props.label]) > -1) {
if (!this.activeIndex) {
this.activeIndex = index;
}
if (this.selectData[item[this.props.label]].length > 0) {
item[this.props.status] =
this.selectData[item[this.props.label]].length === item.children.length
? "checked"
: "certificate";
item.children.forEach((children) => {
// 判断二级菜单存在不存在,如果存在则被选中
let isExits = this.selectData[item[this.props.label]].find(
(selectItem) => selectItem[this.props.label] === children[this.props.label]
);
if (isExits) {
children[this.props.status] = "checked";
}
});
} else {
item[this.props.status] = "circle";
}
}
});
}
if (this.treeDatas[this.activeIndex]) {
this.toggle(this.activeIndex, this.treeDatas[this.activeIndex]);
}
},
/**
* 点击一级菜单设置相关信息
* @param {*} index
* @param {*} item
*/
toggle(index, item) {
// 显示当前点击的一级对象为当前选中状态
this.active = item[this.props.label];
this.activeIndex = index;
// 为选中的一级对象初始化值
if (!this.subSelectItem[this.active]) {
this.subSelectItem[this.active] = [];
}
// 如果一级对象的状态已经被全部选中,这二级状态的全部选中按钮为选中状态
this.checkedAll = item[this.props.status] === "checked";
// 切换二级对象为当前一级对象关联的二级对象
this.showChildrens = item.children;
},
/**
* 点击二级菜单获取相关信息
* @param {*} index
*/
subToggle(index, subitem) {
// 获取选中状态的二级对象中是否包含当前选择的二级对象
const extisArray = this.subSelectItem[this.active];
if (extisArray.find((obj) => obj[this.props.label] === subitem[this.props.label])) {
// 如果存在则删除当前选项,将当前二级对象作为非选中状态
subitem[this.props.status] = "circle";
const index = this.subSelectItem[this.active].findIndex(
(i) => i[this.props.label] === subitem[this.props.label]
);
this.subSelectItem[this.active].splice(index, 1);
} else {
// 如果不存在则添加该二级对象为选中状态
subitem[this.props.status] = "checked";
this.subSelectItem[this.active].push(subitem);
}
// 根据当前选择的二级对象判断一级对象的状态
const newSubSelect = Array.from(this.subSelectItem[this.active]);
if (this.showChildrens.length === newSubSelect.length) {
// 全部选中
this.treeDatas[this.activeIndex][this.props.status] = "checked";
this.checkedAll = true;
} else if (newSubSelect.length > 0) {
// 部分选中
this.treeDatas[this.activeIndex][this.props.status] = "certificate";
this.checkedAll = false;
} else {
// 没有选中
this.treeDatas[this.activeIndex][this.props.status] = "circle";
this.checkedAll = false;
}
},
/**
* 切换全部选择和非全部选择
*/
toggleAllCheckBox() {
// 全选按钮切换
this.checkedAll = !this.checkedAll;
if (this.checkedAll) {
// 如果为全部选中
this.treeDatas.forEach((item) => {
if (item[this.props.label] === this.active) {
// 设置当前一级对象为全部选中状态
item[this.props.status] = "checked";
}
});
// 当前一级对象的二级对象为全部选中状态
this.subSelectItem[this.active] = [];
this.showChildrens.forEach((showItem) => {
showItem[this.props.status] = "checked";
this.subSelectItem[this.active].push(showItem);
});
} else {
//如果为非全选状态
this.treeDatas.forEach((item) => {
if (item[this.props.label] === this.active) {
// 一级对象为非选中状态
item[this.props.status] = "circle";
}
});
// // 当前二级对象设置为空
this.subSelectItem[this.active] = [];
this.showChildrens.forEach((showItem) => {
showItem[this.props.status] = "circle";
});
}
},
/**
* 关闭窗口
* */
close() {
this.show=false
// 返回选中的对象
this.$emit("close", this.subSelectItem);
},
},
};
</script>
<style lang="less" scoped>
.pom {
max-height: 80%;
z-index: 99999999 !important;
.header {
// margin-top: 37px;
background: #ffffff;
position: fixed;
height: 50px;
width: 100%;
padding-bottom: 20px;
z-index: 99;
border-top-left-radius: 16px;
border-top-right-radius: 16px;
.line-box {
margin-top: 10px;
display: flex;
justify-content: center;
align-items: center;
.line {
width: 40px;
height: 5px;
left: 168px;
top: 172px;
background: #ebebeb;
border-radius: 100px;
}
}
.title {
display: flex;
justify-content: space-between;
align-items: center;
margin: 22px 15px 0 15px;
.title-text {
font-family: PingFang SC;
font-style: normal;
font-weight: 500;
font-size: 16px;
line-height: 16px;
color: #323232;
}
.title-close {
font-family: PingFang SC;
font-style: normal;
font-weight: normal;
font-size: 16px;
line-height: 16px;
color: #6a6a6a;
}
}
.subtitle {
font-family: PingFang SC;
font-style: normal;
font-weight: normal;
font-size: 14px;
line-height: 14px;
/* identical to box height, or 100% */
display: flex;
align-items: center;
color: #5b5b5b;
margin: 8px 15px;
}
}
.list-item {
margin-top: 72px;
background: linear-gradient(
180deg,
#fbfbfb -51.25%,
rgba(251, 251, 251, 0) 100%
);
.main {
display: flex;
.left {
width: 120px;
height: 50vh;
border-right: 1px solid #f2f2f2;
overflow-y: auto;
background: #ffffff;
.checkbox {
width: 120px;
display: flex;
// background: #f2f2f2;
height: 40px;
padding: 10px;
box-sizing: border-box;
justify-content: space-between;
// border-bottom: 1px solid#ffffff;
position: relative;
.colorItem {
width: 20px;
height: 20px;
display: block;
}
}
.active {
background: #ffffff;
}
.active::before {
background: red;
content: "";
width: 5px;
position: absolute;
top: 0;
left: 0;
height: 40px;
}
}
.right {
width: calc(100% - 120px);
background: #f2f2f2;
.all {
display: flex;
justify-content: space-between;
padding: 0px;
background: #ffffff;
height: 40px;
box-sizing: border-box;
line-height: 40px;
// border-bottom: 1px solid #f2f2f2;
padding-bottom: 5px;
span {
margin: 0 10px;
}
}
.list {
height: calc(50vh - 40px);
overflow-y: auto;
.checkbox {
width: 100%;
display: flex;
height: 40px;
background: #ffffff;
padding: 10px;
box-sizing: border-box;
justify-content: space-between;
// border: 1px solid#f2f2f2;
.colorItem {
width: 20px;
height: 20px;
display: block;
}
}
}
}
.checkbox {
width: 120px;
display: flex;
background: #ffffff;
padding: 10px;
box-sizing: border-box;
justify-content: space-between;
// border: 1px solid#f2f2f2;
.colorItem {
width: 20px;
height: 20px;
display: block;
}
}
.van-cell {
position: relative;
// padding: 0px !important;
// padding-right: 3px !important;
}
}
}
}
</style>
使用案例
<div class="main">
<TreeSelect
:treeDatas="treeDatas"
:selectData="selectData"
:props="props"
@close="close"
>
<template #title>
<span class="title">选择城市</span>
</template>
<template #closeBtn> 确定 </template>
</TreeSelect>
</div>
</template>
<script>
import TreeSelect from "@/components/common/TreeSelect";
export default {
name: "DemoCity",
props: ["pageInfo"],
components: {
TreeSelect,
},
data() {
return {
treeDatas: [
{
// 导航名称
name: "北京",
checked: "circle",
children: [
{
name: "昌平",
checked: "circle",
},
{
name: "顺义",
checked: "circle",
},
],
},
{
// 导航名称
name: "天津",
checked: "circle",
children: [
{
name: "北辰",
checked: "circle",
},
{
name: "武清",
checked: "circle",
},
],
},
],
selectData: {},
props: {
label: "name",
children: "children",
status: "checked",
},
};
},
methods: {
close(params) {
this.selectData = params;
console.log(params);
},
},
};
</script>
<style lang="less" scoped>
.main {
margin: 10px;
.title {
font-size: 16px;
color: #6a6a6a;
}
.colorItem {
width: 20px;
height: 20px;
display: block;
}
}
</style>