赞
踩
最近业务需求,要做一个树形的下拉框,分别是一级分类、二级分类,而且一级互斥,二级多选,现有的结构都满足不了业务需求,并且样式什么的也不一样,所以只能手写一个,下面是设计图样式
需求:一级分类只能选择一个,每次选择要清除二级分类,重新计算二级分类内容,二级分类可以多选
废话不多说,上代码:
// 父组件
<template slot="clazzIdForm" slot-scope="{row}">
<store-classify ref="storeClassify" @validateClazzId="validateClazzId" :classifyData="classifyData" :storeClassifyVisible.sync="storeClassifyVisible" :secondClassifyVisible.sync="secondClassifyVisible" :secondClassifyData.sync="secondClassifyData" :clazzName.sync="clazzName" :clazzId.sync="clazzId" :tags.sync="tags"></store-classify>
</template>
// 子组件
<template>
<div class="classify">
<div @click.stop="isShowClassify">
<span v-if="clazzName" :class="[ !tags.length ? 'mb5' : '']">{{ clazzName }}<i class="el-icon-close" @click.stop="clearClassify" :gutter="10"></i></span>
<p v-else>请选择 店铺分类</p>
<span class="secondClazz" v-for="item in tags" :key="item">{{ item }}<i class="el-icon-close" @click.stop="deleteClassify(item)" :gutter="10"></i></span>
</div>
<div @click.stop="isShowClassify">
<i class="el-icon-arrow-down" v-if="!storeClassifyVisible"></i>
<i class="el-icon-arrow-up" v-else></i>
</div>
<div class="storeClassify" v-if="storeClassifyVisible" v-ClickOutSide>
<ul class="first">
<li v-for="item in classifyData" :key="item.id" @click="selectFirstClassify(item)" :class="[clazzName === item.name ? 'active' : '']">{{ item.name }}</li>
</ul>
<!-- <el-divider></el-divider> -->
<ul class="second" v-if="secondClassifyVisible">
<li v-for="item in secondClassifyData" :key="item" @click="selectSecondClassify(item)" :class="[tags.includes(item) ? 'active' : '']">{{ item }}</li>
</ul>
</div>
</div>
</template>
<script>
let that
export default {
name: 'MainStoreClassify',
props: {
classifyData:{
type: Array,
default: [],
},
storeClassifyVisible:{
type: Boolean,
default: false,
},
secondClassifyVisible:{
type: Boolean,
default: false,
},
secondClassifyData:{
type: Array,
default: [],
},
clazzName:{
type: String,
default: '',
},
clazzId:{
type: String,
default: '',
},
tags:{
type: Array,
default: [],
},
},
data() {
return {
// classifyData: [],
// storeClassifyVisible: false,
// secondClassifyVisible: false,
// secondClassifyData: [],
// clazzName: '',
// clazzId: '',
// tags: [],
handleFun: null,
};
},
created() {
that = this
},
directives: {
ClickOutSide: {
bind(el, binding) {
async function handleFun(e) { // 判断所点击dom是否为el的节点
let classifyElement = await document.querySelector('.classify')
console.log(classifyElement);
if (el.contains(e.target) || classifyElement.contains(e.target)) {
console.log('在节点内')
} else {
console.log('在节点外')
that.storeClassifyVisible = false
that.$emit('update:storeClassifyVisible', that.storeClassifyVisible)
window.removeEventListener('mousedown', el.function)
}
}
el.function = handleFun
that.handleFun = el.function
window.addEventListener('mousedown', handleFun)
}
}
},
methods: {
// 是否展开分类树
isShowClassify(e){
// console.log(e.target.nodeName);
if(e.target.nodeName === 'SPAN') return
this.storeClassifyVisible = !this.storeClassifyVisible
this.$emit("update:storeClassifyVisible", this.storeClassifyVisible)
if(this.storeClassifyVisible && this.clazzName){
this.$nextTick(()=>{
// console.log(this.classifyData.filter(item=> item.name === this.clazzName));
this.secondClassifyData = this.classifyData.filter(item=> item.name === this.clazzName)[0].subClazzName
this.secondClassifyVisible = true
this.$emit("update:secondClassifyData", this.secondClassifyData)
this.$emit("update:secondClassifyVisible", this.secondClassifyVisible)
})
}
},
// 选择一级分类
selectFirstClassify(item){
// console.log(item);
this.tags = []
if(this.clazzName === item.name) {
// this.$nextTick(()=>{
this.secondClassifyData = []
this.clazzName = ''
this.clazzId = ''
this.secondClassifyVisible = false
// this.secondClassifyData = []
this.$emit("update:tags", this.tags)
this.$emit("update:secondClassifyVisible", this.secondClassifyVisible)
this.$emit("update:clazzName", this.clazzName)
this.$emit("update:clazzId", this.clazzId)
this.$emit("update:secondClassifyVisible", this.secondClassifyVisible)
// })
this.$emit('validateClazzId')
return
}
this.clazzName = item.name
this.clazzId = item.id
this.secondClassifyVisible = true
this.secondClassifyData = [...item.subClazzName]
this.$emit("update:tags", this.tags)
this.$emit("update:clazzName", this.clazzName)
this.$emit("update:clazzId", this.clazzId)
this.$emit("update:secondClassifyVisible", this.secondClassifyVisible)
this.$emit("update:secondClassifyData", this.secondClassifyData)
this.$emit('validateClazzId')
},
// 选择二级分类
selectSecondClassify(item){
// console.log(item);
if(this.tags.includes(item)){
this.tags.splice(this.tags.indexOf(item), 1)
this.$emit("update:tags", this.tags)
return
}
this.tags.push(item)
},
// 清除一级分类
clearClassify(){
this.clazzName = ''
this.tags = []
this.secondClassifyData = []
this.secondClassifyVisible = false
this.$emit("update:clazzName", this.clazzName)
this.$emit("update:tags", this.tags)
this.$emit("update:secondClassifyData", this.secondClassifyData)
this.$emit("update:secondClassifyVisible", this.secondClassifyVisible)
this.$emit('validateClazzId')
},
// 清除二级分类
deleteClassify(item){
this.tags.splice(this.tags.indexOf(item), 1)
this.$emit("update:tags", this.tags)
},
},
};
</script>
<style lang="scss" scoped>
:deep(.el-form-item__content) {
min-height: 32px;
}
.classify {
padding-left: 5px;
position: relative;
display: flex;
flex-wrap: wrap;
width: 100%;
height: auto;
min-height: 32px;
box-sizing: border-box;
border-radius: 4px;
border: 1px solid #DCDFE6;
color: #606266;
user-select: none;
& > div:nth-child(1) {
flex: 1;
display: flex;
flex-wrap: wrap;
font-family: 'OPPOSans';
font-style: normal;
font-weight: 400;
font-size: 12px;
.el-icon-close {
padding-left: 8px;
cursor: pointer;
&:hover {
color: red;
}
}
span {
margin-left: 5px;
margin-top: 5px;
display: flex;
align-items: center;
padding: 0px 10px;
height: 24px;
// width: 59px;
background: #FFFFFF;
border: 1px solid #DCDFE6;
border-radius: 2px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
&.mb5 {
margin-bottom: 5px;
}
}
p {
margin: 0;
padding-left: 10px;
color: #c0c4cc;
font-size: 13px;
}
// ul {
// margin: 0;
// display: flex;
.secondClazz {
margin-top: 5px;
display: flex;
flex-wrap: wrap;
align-items: center;
padding: 0px 10px;
height: 24px;
line-height: 24px;
// width: 59px;
background: #FFFFFF;
border: 1px solid #DCDFE6;
border-radius: 2px;
&:last-child {
margin-bottom: 5px;
}
}
// }
}
& > div:nth-child(2) {
padding-right: 5px;
display: flex;
justify-content: center;
align-items: center;
width: 25px;
i {
color: #C0C4CC;
}
}
.storeClassify {
padding: 13px;
position: absolute;
top: 100%;
left: 0;
right: 0;
max-height: 500px;
overflow-y: auto;
border-radius: 4px;
border: 1px solid #DCDFE6;
background-color: #fff;
transform: translateY(10px);
z-index: 999;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
.first {
padding-left: 0;
margin: 0;
display: flex;
flex-wrap: wrap;
// justify-content: space-between;
border-bottom: 1px solid rgba(220, 223, 230, 1);
li {
margin-right: 6px;
margin-bottom: 6px;
width: 88px;
height: 30px;
text-align: center;
border: 1px solid #DCDFE6;
border-radius: 4px;
cursor: pointer;
&.active {
background-color: rgba(0, 168, 255, 1);
border: 1px solid rgba(0, 168, 255, 1);
color: #fff;
}
}
}
.second {
padding-left: 0;
padding-top: 13px;
margin: 0;
display: flex;
flex-wrap: wrap;
// justify-content: space-between;
// padding: 13px;
li {
margin-right: 6px;
margin-bottom: 6px;
width: 88px;
height: 30px;
text-align: center;
border: 1px solid #DCDFE6;
border-radius: 4px;
cursor: pointer;
&.active {
background-color: rgba(230, 246, 255, 1);
border: 1px solid rgba(0, 168, 255, 1);
color: rgba(0, 168, 255, 1);
}
}
}
}
}
</style>
注意点:
(1)父组件使用.sync,双向绑定数据,子组件可以通过this.$emit(“update:secondClassifyData”,
this.secondClassifyData)修改父组件数据
(2)可以使用自定义指令来判断是否需要点击外部区域关闭select框,点击事件为mousedown,不能为click
所有功能到这里就已经完成了,剩下的就是父组件的一些处理了,比如按ESC关闭框,再按一下才关闭dialog等等,这些都是小问题,就不放代码了,记录一下~
最后效果图
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。