1.新建collapseTransition.vue文件,代码如下:
<template>
<transition
@before-enter="beforeEnter"
@enter="enter"
@leave="leave"
@after-leave="afterLeave"
@after-enter="afterEnter"
@before-leave="beforeLeave"
>
<slot></slot>
</transition>
</template>
<script setup lang="ts">
const beforeEnter = (el: any) => {
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.maxHeight = "0";
el.style.paddingTop = 0;
el.style.paddingBottom = 0;
};
const enter = (el: any) => {
el.style.maxHeight = el.scrollHeight + "px";
el.style.paddingTop = el.dataset.oldPaddingTop;
el.style.paddingBottom = el.dataset.oldPaddingBottom;
};
const afterEnter = (el: any) => {
el.classList.remove("collapse-transition");
el.style.maxHeight = "";
el.style.overflow = el.dataset.oldOverflow;
};
const beforeLeave = (el: any) => {
el.dataset.oldPaddingTop = el.style.paddingTop;
el.dataset.oldPaddingBottom = el.style.paddingBottom;
el.dataset.oldOverflow = el.style.overflow;
el.style.maxHeight = el.scrollHeight + "px";
el.style.overflow = "hidden";
};
const leave = (el: any) => {
el.classList.add("collapse-transition");
el.style.maxHeight = 0;
el.style.paddingTop = 0;
el.style.paddingBottom = 0;
};
const afterLeave = (el: any) => {
el.classList.remove("collapse-transition");
el.style.maxHeight = "";
el.style.overflow = el.dataset.oldOverflow;
el.style.paddingTop = el.dataset.oldPaddingTop;
el.style.paddingBottom = el.dataset.oldPaddingBottom;
};
</script>
<style scoped lang="scss">
.collapse-transition {
transition: all 0.4s ease-in-out;
}
</style>
2.新建外层容器文件collapseItem.vue,代码如下
<template>
<div class="collapse-item">
<div class="collapse-head">
<div class="collapse-head-right">
<span v-if="!slots.title" class="collapse-title">{{ attrs.title }}</span>
<slot name="title"></slot>
</div>
<div class="right-arrow flx-align-center ml-10px" @click.stop="handlePanelItemClick">
<span class="mr-6px font-size-14px">明细</span>
<el-icon class="caret-down" :class="{ 'caret-open': isCollapse }">
<ArrowRight />
</el-icon>
</div>
</div>
<CollapseTransition>
<div v-show="isCollapse" class="collapse-content">
<slot name="content"></slot>
</div>
</CollapseTransition>
</div>
</template>
<script setup lang="ts">
import { useSlots, useAttrs, inject, computed } from "vue";
import CollapseTransition from "./collapseTransition.vue";
const slots = useSlots();
const attrs = useAttrs();
const activeNames: any = inject("activeNames");
const handleToggle: any = inject("toggle");
const isCollapse = computed(() => {
return activeNames["value"].includes(attrs.name);
});
const handlePanelItemClick = () => {
handleToggle(attrs.name);
};
</script>
<style scoped lang="scss">
.collapse-item {
display: flex;
flex-flow: column;
.collapse-head {
display: flex;
flex-flow: row nowrap;
align-items: center;
overflow: hidden;
border-bottom: none;
border-radius: 4px 4px 0 0;
.right-arrow {
color: #2578f2;
cursor: pointer;
user-select: none;
.caret-down {
margin-right: 6px;
font-size: 16px;
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;
}
}
}
}
</style>
3.再新建最外层容器collapsePanel.vue,代码如下
<template>
<div class="collapse-panel">
<slot></slot>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, provide } from "vue";
const props = defineProps({
modelValue: {
type: [String, Array, Number]
}, // 数据绑定
accordion: {
type: Boolean
} // 是否开启手风琴模式,默认不开启
});
const emits = defineEmits(["update:modelValue", "change"]);
const activeNames = ref<Array<any>>([]);
onMounted(() => {
setValueLists();
});
// 初始化设置激活项
const setValueLists = () => {
if (!Array.isArray(props.modelValue)) {
activeNames["value"] = [props.modelValue];
} else {
activeNames["value"] = props.modelValue;
}
};
// 点击每项处理函数
const toggle = (name: any) => {
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="scss" scoped></style>
4.最后代码使用:
<template>
<div class="table-list">
<CollapsePanel v-model="activeName" @change="change" :accordion="true">
<collapse-item :name="1">
<template #title>
<!-- 自定义title -->
</template>
<template #content>
<!-- 自定义内容 -->
</template>
</collapse-item>
</CollapsePanel>
</div>
</template>
<script setup lang="ts">
import { ref } from "vue";
import CollapsePanel from "./collapsePanel.vue";
import CollapseItem from "./collapseItem.vue";
const activeName = ref([1]);
// 点击触发
const change = (value: any) => {
console.log(value);
};
</script>
<style lang="scss" scoped>
.table-list {
padding: 20px;
.info-wrap {
padding: 10px 0;
.img {
flex-shrink: 0;
width: 36px;
height: 36px;
font-size: 18px;
line-height: 36px;
color: var(--el-color-white);
text-align: center;
background: #2578f2;
border-radius: 10px;
}
.info-name {
.name {
font-size: 14px;
}
.num {
font-size: 14px;
font-weight: 400;
color: #9b9b9b;
}
}
.responsibility-list {
display: flex;
flex-grow: 1;
align-items: center;
justify-content: space-between;
width: 300px;
max-width: 400px;
.res-info {
.title {
font-size: 14px;
color: #9b9b9b;
}
.num {
font-size: 14px;
}
}
}
.progress-bar {
width: 400px;
}
}
}
</style>