|
@@ -0,0 +1,117 @@
|
|
|
|
+<template>
|
|
|
|
+ <div class="accordion">
|
|
|
|
+ <div class="item" v-for="(item, index) in _arr">
|
|
|
|
+ <div
|
|
|
|
+ class="head flex items-center justify-between"
|
|
|
|
+ @click="toggleOpen(item, $event)"
|
|
|
|
+ >
|
|
|
|
+ <div class="title-box">
|
|
|
|
+ <span class="title">{{ item.title }}</span>
|
|
|
|
+ </div>
|
|
|
|
+ <DownOutlined class="collapse-icon" :class="{ rotate: item.open }" />
|
|
|
|
+ </div>
|
|
|
|
+ <div class="body" :style="{ height: item.height }">
|
|
|
|
+ <slot :name="item.name" />
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+</template>
|
|
|
|
+<script name="Accordion" lang="ts" setup>
|
|
|
|
+import { ref, watch, nextTick } from "vue";
|
|
|
|
+import { DownOutlined } from "@ant-design/icons-vue";
|
|
|
|
+interface AccordionItem {
|
|
|
|
+ title: string;
|
|
|
|
+ name: string;
|
|
|
|
+ open?: boolean;
|
|
|
|
+}
|
|
|
|
+const props = withDefaults(
|
|
|
|
+ defineProps<{
|
|
|
|
+ arr: AccordionItem[];
|
|
|
|
+ }>(),
|
|
|
|
+ {
|
|
|
|
+ arr: () => [],
|
|
|
|
+ }
|
|
|
|
+);
|
|
|
|
+const _arr = ref<any>([]);
|
|
|
|
+_arr.value = props.arr.map((item: AccordionItem) => {
|
|
|
|
+ return {
|
|
|
|
+ ...item,
|
|
|
|
+ height: item.open ? "auto" : "0px",
|
|
|
|
+ open: item.open || true,
|
|
|
|
+ };
|
|
|
|
+});
|
|
|
|
+
|
|
|
|
+const toggleOpen = async (
|
|
|
|
+ item: AccordionItem & { height?: any; open?: boolean },
|
|
|
|
+ e: any
|
|
|
|
+) => {
|
|
|
|
+ const body = e.target.nextSibling;
|
|
|
|
+ if (item.open && item.height === "auto") {
|
|
|
|
+ let h = getComputedStyle(body).height;
|
|
|
|
+ item.height = h;
|
|
|
|
+ await new Promise((rs: any) => {
|
|
|
|
+ setTimeout(() => {
|
|
|
|
+ rs();
|
|
|
|
+ }, 0);
|
|
|
|
+ });
|
|
|
|
+ }
|
|
|
|
+ item.open = !item.open;
|
|
|
|
+ if (item.open) {
|
|
|
|
+ item.height = "auto";
|
|
|
|
+ await nextTick();
|
|
|
|
+ let comHeight = getComputedStyle(body).height;
|
|
|
|
+ item.height = "0px";
|
|
|
|
+ setTimeout(() => {
|
|
|
|
+ item.height = comHeight;
|
|
|
|
+ }, 0);
|
|
|
|
+ } else {
|
|
|
|
+ item.height = "0px";
|
|
|
|
+ }
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+watch(
|
|
|
|
+ () => props.arr,
|
|
|
|
+ () => {
|
|
|
|
+ _arr.value = props.arr.map((item: AccordionItem) => {
|
|
|
|
+ return {
|
|
|
|
+ ...item,
|
|
|
|
+ height: item.open ? "auto" : "0px",
|
|
|
|
+ open: item.open || true,
|
|
|
|
+ };
|
|
|
|
+ });
|
|
|
|
+ }
|
|
|
|
+);
|
|
|
|
+</script>
|
|
|
|
+<style lang="less" scoped>
|
|
|
|
+.accordion {
|
|
|
|
+ .item {
|
|
|
|
+ .head {
|
|
|
|
+ height: 32px;
|
|
|
|
+ border-top: 1px solid #e5e5e5;
|
|
|
|
+ border-bottom: 1px solid #e5e5e5;
|
|
|
|
+ background: linear-gradient(180deg, #ffffff 0%, #f2f3f5 100%);
|
|
|
|
+ padding: 0 16px;
|
|
|
|
+ .title-box {
|
|
|
|
+ .title {
|
|
|
|
+ color: @text-color1;
|
|
|
|
+ font-size: 14px;
|
|
|
|
+ font-weight: 500;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ .collapse-icon {
|
|
|
|
+ color: #8c8c8c;
|
|
|
|
+ font-size: 12px;
|
|
|
|
+ transition: all 0.2s;
|
|
|
|
+ &.rotate {
|
|
|
|
+ rotate: 180deg;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ .body {
|
|
|
|
+ background-color: red;
|
|
|
|
+ transition: all 0.2s;
|
|
|
|
+ overflow: hidden;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+</style>
|