前言 因为项目需要一个带二层展开的树形选择器,用elment的select和tree组件组合起来虽然可以用但是改样式和添加其他功能太过麻烦,所以就仿照element的select样式自己写了一个类似的组件,其实主要功能很简单,主要是要模拟一些element的一些细节比较费时间,但好在是足够灵活,这样就可以想怎么改样式改功能都可以直接改。
参数说明
参数名 | 说明 |
---|---|
value | 监听值 |
optionList | 下拉项列表 |
operateVisiable | 是否展示操作栏 |
示例
vue
tip
change方法用于将组件内的数据更新到父组件传入的监听值,手动实现双向绑定
<treeOptionSelect
:optionList="typeOptionList"
:value="collectType"
@change="(s)=>{collectType = s}"
:operateVisiable="true"
></treeOptionSelect>
js
tip optionList 中的选项如果有children则为展开项
data() {
return {
collectType: "",
typeOptionList: [
{
value: "选项1",
label: "选项1",
children: [
{
label: "一级 1",
value: "一级 1"
},
{
label: "一级 2",
value: "一级 2"
}
]
},
{
value: "选项3",
label: "选项3"
}
]
};
}
源码
直接拷入项目(vue,element,scss)使用即可
tip
使用前需要全局引入一下我自己封装的一本通用css主要用于flex布局
http://www.oujin.fun/webPage/css/Universal.css
<template>
<div class="tree_select_box row-flex-start" @click="optionBoxVisble = !optionBoxVisble">
<i
class="el-icon-arrow-down arrow_icon"
:style="{transform:optionBoxVisble?'rotate('+180+'deg)':'rotate('+0+'deg)'}"
></i>
<transition name="slide">
<div class="triangle_up" v-if="optionBoxVisble"></div>
</transition>
<transition name="slide">
<div
class="option_box column-start-center"
:style="{paddingBottom:operateVisiable?'60px':'0px'}"
v-if="optionBoxVisble"
@click.stop
>
<div class="option_wrapper">
<template v-for="(item,index) in options">
<div class="tree_option_banner nowrap" :key="index" @click.stop="optionClick(item)">
<div class="expand_icon_wrapper c_c" v-if="item.children">
<i
class="el-icon-arrow-down expand_icon c_c"
:style="{transform:item.expand?'rotate('+0+'deg)':'rotate('+270+'deg)'}"
></i>
</div>
{{item.label}}
</div>
<template v-if="item.expand&&item.children">
<div
class="tree_option_banner nowrap"
style="color: #606764;"
v-for="(it,i) in item.children"
@click.stop="optionClick(it)"
:class="{
'last_option_banner':i===(item.children.length-1)
}"
:key="it.label"
>{{it.label}}</div>
</template>
</template>
</div>
<!-- 是否显示操作框 -->
<div class="operate_banner row-space-between" v-if="operateVisiable">
<div class="row-flex-start">
<el-button class="operete_but c_c c_border">新建</el-button>
<el-button class="operete_but c_c c_border">删除</el-button>
</div>
<el-button type="primary" class="operete_but">保存</el-button>
</div>
</div>
</transition>
{{val}}
</div>
</template>
<script>
export default {
props: {
// 监听值
value: {
type: String
},
// 下拉项列表
optionList: {
type: Array,
default: () => {
return [];
}
},
// 是否展示操作栏
operateVisiable: {
type: Boolean,
default: false
}
},
computed: {
options() {
return this.optionList.map(x=>{
if(x.children)this.$set(x,'expand',false)
return x
})
}
},
data() {
return {
val: this.value,
optionBoxVisble: true,
};
},
// 手动双向绑定
watch:{
value(n,o){
this.val = n
}
},
methods: {
optionClick(s) {
s.expand = !s.expand;
if (s.children) return;
// 修改外部组件监听数据
this.$emit('change',s.value)
}
}
};
</script>
<style lang="scss" scoped>
.tree_select_box {
position: relative;
height: 34px;
cursor: pointer;
color: #606764;
padding: 6px 0px 6px 20px;
width: 100%;
border-radius: 4px;
border: 1px solid rgba(236, 236, 236, 1);
background: #fff;
max-width: 650px;
.arrow_icon {
position: absolute;
transform-origin: center center;
transition: transform 0.2s;
right: 15px;
top: calc(50% - 7px);
}
.triangle_up {
width: 0;
height: 0;
position: absolute;
top: calc(100% + 4px);
left: 30px;
z-index: 2009;
border-left: 8px solid transparent;
border-right: 8px solid transparent;
border-bottom: 8px solid #fff;
border-top-width: 0;
border-bottom-color: #e8e8e8;
}
.triangle_up::after {
content: "";
width: 0;
height: 0;
position: absolute;
top: 1px;
left: -7px;
z-index: 2010;
border-left: 7px solid transparent;
border-right: 7px solid transparent;
border-bottom: 7px solid #fff;
border-top-width: 0;
}
.option_box {
position: absolute;
z-index: 2000;
padding: 10px 0px;
top: calc(100% + 10px);
left: 0px;
background: #fff;
width: 100%;
border-radius: 4px;
min-height: 50px;
max-height: 300px;
overflow-y: auto;
box-shadow: 0px 2px 12px 0px rgba(0, 0, 0, 0.06);
border: 1px solid rgba(232, 232, 232, 1);
// padding-bottom: 60px;
.tree_option_banner {
font-size: 14px;
// z-index: 2002;
color: #999999;
position: relative;
width: 100%;
height: 34px;
padding: 6px 0px 6px 20px;
}
.option_wrapper {
width: 100%;
overflow-y: auto;
flex: 1;
}
.operate_banner {
position: absolute;
bottom: 0;
width: 100%;
z-index: 2002;
padding: 0 14px;
background: #fff;
height: 60px;
.operete_but {
width: 60px;
height: 32px;
}
}
.last_option_banner {
margin-bottom: 8px;
}
.last_option_banner::after {
content: "";
position: absolute;
width: calc(100% - 40px);
bottom: 0px;
height: 1px;
background: #e8e8e8;
right: 20px;
}
.tree_option_banner:hover {
background: #f5f7fa;
}
.expand_icon_wrapper {
position: absolute;
z-index: 2003;
font-size: 12px;
transform: scale(0.75);
left: 5px;
top: 7px;
.expand_icon {
font-weight: bold;
transform-origin: center center;
transition: transform 0.3s;
}
}
}
@keyframes slideShow {
from {
top: 0px;
}
to {
top: calc(100% + 10px);
}
}
@keyframes slideHide {
from {
transform: scale(1);
}
to {
transform: scale(0);
}
}
.slide-enter-active {
animation: slideShow 0.2s;
}
.slide-leave-active {
animation: slideHide 0.2s;
}
}
</style>