背景
折叠面板大家都不陌生,很多时候需要实现一些复杂的交互,就会用到它,简洁直观还美观,通常我们直接用第三方组件库就行了,不过只会用还不行,还要会写才行,下面我们一起手写一个折叠面板组件。
最终效果
实现功能
- 手风琴模式
- 自定义标题
- 自定义内容
- 数据绑定,支持string | array
实现逻辑
首先创建组件目录,如下:
index.vue 实现代码:
js
<template>
<div class="collapse-panel">
<slot></slot>
</div>
</template>
<script setup lang="ts">
import { useSlots, ref, onMounted, provide } from 'vue'
const slots = useSlots()
const props = defineProps({
modelValue: {
type: [String, Array, Number]
}, // 数据绑定
accordion: {
type: Boolean
} // 是否开启手风琴模式,默认不开启
})
const emits = defineEmits(['update:modelValue', 'change'])
const activeNames = ref([])
onMounted(() => {
setValueLists()
})
// 初始化设置激活项
const setValueLists = () => {
if (!Array.isArray(props.modelValue)) {
activeNames['value'] = [props.modelValue]
} else {
activeNames['value'] = props.modelValue
}
}
// 点击每项处理函数
const toggle = (name) => {
if (activeNames['value'].includes(name)) {
// 收起时
activeNames['value'] = activeNames['value'].filter((item) => item != name)
} else {
// 展开时
if (props.accordion) {
activeNames['value'] = [name]
} else {
activeNames['value'].push(name)
}
}
emits('update:modelValue', activeNames['value'])
emits('change', activeNames['value'])
}
// 提供父组件指定方法
provide('toggle', toggle)
provide('activeNames', activeNames)
</script>
<style lang="less" scoped></style>
CollapseItem.vue 实现代码
js
<template>
<div class="collapse-item">
<div class="collapse-head">
<el-icon class="caret-down"
:class="{ 'caret-open': isCollapse }"
@click.stop="handlePanelItemClick">
<CaretRight />
</el-icon>
<div class="collapse-head-right">
<span v-if="!slots.title"
class="collapse-title">{{ attrs.title }}</span>
<slot name="title"></slot>
</div>
</div>
<CollapseTransition>
<div v-show="isCollapse"
class="collapse-content">
<slot name="content"></slot>
</div>
</CollapseTransition>
</div>
</template>
<script setup lang="ts">
import { ref, useSlots, useAttrs, inject, computed } from 'vue'
import CollapseTransition from './CollapseTransition.vue'
const slots = useSlots()
const attrs = useAttrs()
const activeNames = inject('activeNames')
const handleToggle = inject('toggle')
const status = ref(false) // 开展状态
const isCollapse = computed(() => {
return activeNames['value'].includes(attrs.name)
})
const handlePanelItemClick = () => {
handleToggle(attrs.name)
}
</script>
<style scoped lang="less">
.collapse-item {
display: flex;
flex-flow: column;
.collapse-head {
display: flex;
flex-flow: row nowrap;
align-items: center;
height: 42px;
background: #d9d9d9;
padding: 0 14px;
border: 1px solid #cccccc;
border-bottom: none;
border-radius: 4px 4px 0px 0px;
overflow: hidden;
.caret-down {
font-size: 20px;
color: #1b1b1b;
margin-right: 6px;
cursor: pointer;
transition: transform 0.3s;
transform-origin: center center;
&.caret-open {
transform: rotate(90deg);
}
}
.collapse-head-right {
flex: 1;
width: 0;
.collapse-title {
font-size: 14px;
color: #1b1b1b;
}
}
}
.collapse-content {
}
}
</style>
CollapseTransition.vue 展开收起动画组件
js
<template>
<transition @before-enter="beforeEnter"
@enter="enter"
@leave="leave"
@after-leave="afterLeave">
<slot></slot>
</transition>
</template>
<script setup lang="ts">
const beforeEnter = (el) => {
el.classList.add('collapse-transition')
el.dataset.oldPaddingTop = el.style.paddingTop
el.dataset.oldPaddingBottom = el.style.paddingBottom
el.dataset.oldOverflow = el.style.overflow
el.style.overflow = 'hidden'
el.style.height = '0'
el.style.paddingTop = 0
el.style.paddingBottom = 0
}
const enter = (el) => {
el.style.height = el.scrollHeight + 'px'
el.style.paddingTop = el.dataset.oldPaddingTop
el.style.paddingBottom = el.dataset.oldPaddingBottom
}
const afterEnter = (el) => {
el.classList.remove('collapse-transition')
el.style.height = ''
el.style.overflow = el.dataset.oldOverflow
}
const beforeLeave = (el) => {
el.dataset.oldPaddingTop = el.style.paddingTop
el.dataset.oldPaddingBottom = el.style.paddingBottom
el.dataset.oldOverflow = el.style.overflow
el.style.height = el.scrollHeight + 'px'
el.style.overflow = 'hidden'
}
const leave = (el) => {
el.classList.add('collapse-transition')
el.style.height = 0
el.style.paddingTop = 0
el.style.paddingBottom = 0
}
const afterLeave = (el) => {
el.classList.remove('collapse-transition')
el.style.height = ''
el.style.overflow = el.dataset.oldOverflow
el.style.paddingTop = el.dataset.oldPaddingTop
el.style.paddingBottom = el.dataset.oldPaddingBottom
}
</script>
<style scoped lang="less">
.collapse-transition {
transition: all 0.3s ease-in-out;
}
</style>
组件的使用
js
// template
<CollapsePanel v-model="activeName" @change="change" :accordion="accordion">
<collapse-item :name="1">
<template #title>
<!-- 自定义title -->
</template>
<template #content>
<!-- 自定义内容 -->
</template>
</collapse-item>
<collapse-item :name="2">
<template #title>
<!-- 自定义title -->
</template>
<template #content>
<!-- 自定义内容 -->
</template>
</collapse-item>
</CollapsePanel>
//script
const activeName = ref([2])
const accordion ref(true) // 是否开启手风琴模式
// 点击触发
const change = (value) => {
console.log(value)
}
文章推荐
Vue3 + Vite 搭建企业级开发脚手架【目录篇】
Vue3 + Vite 搭建企业级开发脚手架【配置篇】
Vue3 Element-Plus Json配置一站式生成动态表单
最后
到这里一个简洁好看的折叠面板就做好了,如果本文对您有什么帮助,别忘了动动手指点个赞❤️。 本文如果有错误和不足之处,欢迎大家在评论区指出,多多提出您宝贵的意见!