vue项目开发中,经常会用到下拉菜单功能,我们经常会使用element这里ui组件来实现,但是在实际开发中经常要自定义样式,这里会遇到一些问题,比如下拉菜单的样式自定义困难以及一些选中白边处理问题,所以这里想着自己实现一个下拉,这样自己想怎么实现样式就可以怎么实现。
下拉动画
要实现下拉以及收缩时,为了增加视觉效果,我们需要给下拉收缩增加一个动画,这里我们仍然使用element的动画组件来实现:el-collapse-transition
图标旋转
我们在展开以及收缩时,还需要对下拉三角进行处理,比如下拉后,图标向上,收缩后图标向下。根据show的状态来判断图标的展开收缩,我们还需要一个样式来让图标有一个变化的过程transition: transform 0.3s ease-in-out;
。
<img class="changeSkinIcon" :style="{ transform: show ? 'rotate(180deg)' : 'rotate(0deg)' }"
src="./assets/icon_down.png"
alt="">
父组件使用
vue2写法
<selected :active.sync="active" :list="selectedList"></selected>
active: '1',
selectedList: [{
value: '1',
name: '吉林门站'
}, {
value: '2',
name: '吉林门站2'
}, {
value: '3',
name: '吉林门站3'
}, {
value: '4',
name: '吉林门站4'
}],
vue3写法
<selected v-model:active="active" :list="selectedList"></selected>
active: '1',
selectedList: [{
value: '1',
name: '吉林门站'
}, {
value: '2',
name: '吉林门站2'
}, {
value: '3',
name: '吉林门站3'
}, {
value: '4',
name: '吉林门站4'
}],
实例代码
<template>
<div class="changeSkins">
<div class="changeSkin" @click="show = !show">
<span>{{ activeLabel ? activeLabel : placeholder }}</span>
<img class="changeSkinIcon" :style="{ transform: show ? 'rotate(180deg)' : 'rotate(0deg)' }"
src="./assets/icon_down.png"
alt="">
</div>
<el-collapse-transition>
<div class="dropdown-menu" v-if="show">
<div class="dropdownMenuItem"
v-for="(item,index) in list" :key="index"
:class="{highlight: active === item.value}"
@click="changeTheme(item.value)"><span>{{ item.name }}</span></div>
</div>
</el-collapse-transition>
</div>
</template>
<script>
export default {
name: "changeSkin",
components: {},
props: {
list: {
type: Array,
default() {
return [];
}
},
placeholder: {
type: String,
default() {
return '请选择';
}
},
active: {
type: [Number, String],
default() {
return 0;
}
},
},
data() {
return {
value: 0,
show: false,
}
},
computed: {
activeLabel: function () {
const selectedOption = this.list.find(option => option.value === this.active);
return selectedOption ? selectedOption?.name : '';
}
},
mounted() {
// 添加全局点击事件监听器
document.addEventListener('click', this.handleOutsideClick);
},
beforeUnmount() {
// 组件销毁前移除事件监听器
document.removeEventListener('click', this.handleOutsideClick);
},
beforeDestroy() {
// 组件销毁前移除事件监听器
document.removeEventListener('click', this.handleOutsideClick);
},
methods: {
handleOutsideClick(e) {
// 检查点击的元素是否是菜单或触发菜单的按钮
if (!this.$el.contains(e.target)) {
this.show = false; // 如果不是,则关闭菜单
}
},
changeTheme(theme) {
this.$emit('update:active', theme)
this.show = false;
}
},
}
</script>
<style lang="scss" scoped>
.changeSkins {
display: flex;
justify-content: center;
align-items: center;
flex-wrap: nowrap;
flex-direction: column;
align-content: flex-start;
position: relative;
height: 40px;
width: 100%;
z-index: 100;
.changeSkin {
display: flex;
justify-content: space-between;
align-items: center;
flex-wrap: nowrap;
flex-direction: row;
align-content: flex-start;
cursor: pointer;
height: 40px;
width: 100%;
span {
margin-left: 12px;
font-size: 14px;
font-family: MicrosoftYaHei;
font-weight: 400;
color: #CEEEFE;
}
img {
margin-right: 12px;
}
}
.dropdown-menu {
opacity: 1;
top: 40px;
width: 100%;
background: rgba(1, 23, 63, 0.66);
border: 1px solid #02639D;
position: absolute;
cursor: pointer;
.dropdownMenuItem {
height: 40px;
border-bottom: 1px solid rgba(0, 163, 218, 0.1);
display: flex;
justify-content: center;
align-items: center;
flex-wrap: nowrap;
flex-direction: row;
align-content: flex-start;
font-size: 14px;
font-family: MicrosoftYaHei;
font-weight: 400;
color: #CCECFF;
}
.dropdownMenuItem.highlight, .dropdownMenuItem:hover {
color: rgba(164, 222, 255, 1);
}
}
}
.fade-enter-active, .fade-leave-active {
transition: transform 0.3s ease-in-out;
}
.fade-enter, .fade-leave-to {
transform: translateY(-100%);
}
.changeSkinIcon {
width: 12px;
height: 7px;
transition: transform 0.3s ease-in-out;
}
</style>
更新日志
2024年01月20日
添加点击空白区域收回菜单
增加placeholder添加默认显示内容