vue实现折叠面板(Vue3折叠面板(Collapse))
可自定义设置以下属性:
折叠面板数据 ,可使用 slot 替换对应索引的 header 和 text(collapseData) ,必传 ,类型:Array<{key?: string, header?: string | slot, text?: string | slot}> ,默认 []
当前激活 tab 面板的 key(activeKey) ,类型:number[] | number | string[] | string | null ,默认 null
是否可复制面板内容(copyable) ,类型:number ,默认 false
面板右上角固定内容 ,例如标识language(lang) ,类型:string | slot ,默认
面板标题和内容 ,字体大小(fontSize),类型:number ,单位px ,默认 0
面板标题字体大小,优先级高于fontSize(headerFontSize) ,类型:number ,单位px ,默认 14
面板内容字体大小 ,优先级高于fontSize(textFontSize) ,类型:number ,单位px ,默认 14
是否展示面板上的箭头(showArrow) ,类型:boolean ,默认 true
效果如下图:
注:组件引用方法 import { rafTimeout } from ../index 请参考以下博客:
使用requestAnimationFrame模拟实现setTimeout和setInterval_theMuseCatcher的博客-CSDN博客使用requestAnimationFrame模拟实现setTimeout和setInterval!https://blog.csdn.net/Dandrose/article/details/130167061
①创建折叠面板组件Collapse.vue:
<script setup lang="ts"> import { ref, watchEffect } from vue // 使用 requestAnimationFrame 实现的等效 setTimeout import { rafTimeout } from ../index interface Collapse { key?: string|number // 对应activeKey ,如果没有传入key属性 ,则默认使用数据索引(0,1,2...)绑定 header?: string // 面板标题 string | slot text?: string // 面板内容 string | slot } interface Props { collapseData: Collapse[] // 折叠面板数据 ,可使用 slot 替换对应索引的 header 和 text activeKey?: number[] | number | string[] | string | null // 当前激活 tab 面板的 key copyable?: boolean // 是否可复制面板内容 lang?: string // 面板右上角固定内容,例如标识language string | slot fontSize?: number // 面板标题和内容 ,字体大小 headerFontSize?: number // 面板标题字体大小 ,优先级高于fontSize textFontSize?: number // 面板内容字体大小,优先级高于fontSize showArrow?: boolean // 是否展示面板上的箭头 } const props = withDefaults(defineProps<Props>(), { collapseData: () => [], activeKey: null, copyable: false, lang: , fontSize: 0, headerFontSize: 14, textFontSize: 14, showArrow: true }) watchEffect(() => { const len = props.collapseData.length if (len) { getCollapseHeight(len) // 获取各个面板内容高度 } }, { flush: post }) const text = ref() const collapseHeight = ref<any[]>([]) function getCollapseHeight (len: number) { for (let n = 0; n < len; n++) { collapseHeight.value.push(text.value[n].offsetHeight) } } const emits = defineEmits([update:activeKey, change]) function dealEmit (value: any) { emits(update:activeKey, value) emits(change, value) } function onClick (key: number|string) { if (activeJudge(key)) { if (Array.isArray(props.activeKey)) { const res = (props.activeKey as any[]).filter(actKey => actKey!== key) dealEmit(res) } else { dealEmit(null) } } else { if (Array.isArray(props.activeKey)) { dealEmit([...props.activeKey, key]) } else { dealEmit(key) } } } function activeJudge (key: number|string): boolean { if (Array.isArray(props.activeKey)) { return (props.activeKey as any[]).includes(key) } else { return props.activeKey === key } } const copyTxt = ref(Copy) function onCopy (index: number) { navigator.clipboard.writeText(text.value[index].innerText || ).then(() => { /* clipboard successfully set */ copyTxt.value = Copied rafTimeout(() => { copyTxt.value = Copy }, 3000) }, (err) => { /* clipboard write failed */ copyTxt.value = err }) } </script> <template> <div class="m-collapse"> <div class="m-collapse-item" :class="{u-collapse-item-active: activeJudge(data.key || index)}" v-for="(data, index) in collapseData" :key="index"> <div class="u-collapse-header" @click="onClick(data.key || index)"> <svg focusable="false" v-if="showArrow" class="u-arrow" data-icon="right" aria-hidden="true" viewBox="64 64 896 896"><path d="M765.7 486.8L314.9 134.7A7.97 7.97 0 00302 141v77.3c0 4.9 2.3 9.6 6.1 12.6l360 281.1-360 281.1c-3.9 3-6.1 7.7-6.1 12.6V883c0 6.7 7.7 10.4 12.9 6.3l450.8-352.1a31.96 31.96 0 000-50.4z"></path></svg> <div class="u-header" :class="{ml24: showArrow}" :style="`font-size: ${fontSize || headerFontSize}px;`"> <slot name="header" :header="data.header" :index="index">{{ data.header || -- }}</slot> </div> </div> <div class="u-collapse-content" :class="{u-collapse-copyable: copyable}" :style="`height: ${activeJudge(data.key || index) ? collapseHeight[index]:0}px;`"> <div class="u-lang"> <slot name="lang" :lang="lang" :key="data.key || index">{{ lang }}</slot> </div> <Button size="small" class="u-copy" type="primary" @click="onCopy(index)">{{ copyTxt }}</Button> <div ref="text" class="u-text" :style="`font-size: ${fontSize || textFontSize}px;`"> <slot name="text" :text="data.text" :key="data.key || index">{{ data.text }}</slot> </div> </div> </div> </div> </template> <style lang="less" scoped> * { box-sizing: border-box; margin: 0; padding: 0; } .m-collapse { background-color: rgba(0, 0, 0, 0.02); border: 1px solid #d9d9d9; border-bottom: 0; border-radius: 8px; .m-collapse-item { border-bottom: 1px solid #d9d9d9; &:last-child { border-radius: 0 0 8px 8px; .u-collapse-content { border-radius: 0 0 8px 8px; } } .u-collapse-header { position: relative; padding: 12px 16px; cursor: pointer; transition: all 0.3s; .u-arrow { position: absolute; width: 12px; height: 12px; top: 0; bottom: 0; margin: auto 0; fill: rgba(0,0,0,.88); transition: transform 0.3s; } .u-header { display: inline-block; color: rgba(0, 0, 0, 0.88); line-height: 1.5714285714285714; } .ml24 { margin-left: 24px; } } .u-collapse-content { position: relative; height: 0; overflow: hidden; background-color: #fff; transition: height .3s; .u-lang { position: absolute; right: 10px; top: 6px; font-size: 14px; color: rgba(0, 0, 0, .38); opacity: 1; transition: opacity .3s; } .u-copy { position: absolute; right: 8px; top: 8px; opacity: 0; pointer-events: none; transition: opacity .3s; } .u-text { padding: 16px; color: rgba(0, 0, 0, 0.88); white-space: pre-wrap; } } .u-collapse-copyable { &:hover { .u-lang { opacity: 0; pointer-events: none; } .u-copy { opacity: 1; pointer-events: auto; } } } } .u-collapse-item-active { .u-arrow { transform: rotate(90deg); } .u-collapse-content { border-top: 1px solid #d9d9d9; } } } </style>②在要使用的页面引入:
<script setup lang="ts"> import { Collapse } from ./Collapse.vue import { ref, watchEffect } from vue const collapseData = ref([ { key: 1, header: This is panel header 1, text: A dog is a type of domesticated animal.Known for its loyalty and faithfulness,it can be found as a welcome guest in many households across the world. }, { key: 2, header: This is panel header 2, text: ` A dog is a type of domesticated animal.Known for its loyalty and faithfulness,it can be found as a welcome guest in many households across the world. A dog is a type of domesticated animal.Known for its loyalty and faithfulness,it can be found as a welcome guest in many households across the world.` }, { key: 3, header: This is panel header 3, text: A dog is a type of domesticated animal.Known for its loyalty and faithfulness,it can be found as a welcome guest in many households across the world. } ]) const activeKey = ref([1]) watchEffect(() => { console.log(activeKey:, activeKey.value) }) const key = ref(1) watchEffect(() => { console.log(key:, key.value) }) function onChange (key: any) { console.log(change:, key) } </script> <template> <div> <h2 class="mb10">Collapse 折叠面板基本使用 (activeKey 传入 number[] | string[] ,所有面板可同时展开)</h2> <Collapse :collapseData="collapseData" v-model:activeKey="activeKey" @change="onChange" /> <h2 class="mt30 mb10">手风琴 (只允许单个内容区域展开 ,只需 activeKey 传入 number | string 即可)</h2> <Collapse :collapseData="collapseData" v-model:activeKey="key" @change="onChange" /> <h2 class="mt30 mb10">可复制面板内容 (copyable)</h2> <Collapse lang="template" copyable :collapseData="collapseData" v-model:activeKey="activeKey" @change="onChange" /> <h2 class="mt30 mb10">使用插槽 slot 自定义 header 、lang 、text 内容</h2> <Collapse copyable :collapseData="collapseData" v-model:activeKey="activeKey" @change="onChange"> <template #header="{ header, key }"> <span v-if="key===1" style="color: burlywood;">burlywood color {{ header }} (key = {{ key }})</span> </template> <template #lang>typescript</template> <template #text="{ text, key }"> <span v-if="key===1" style="color: burlywood;">burlywood color {{ text }} (key = {{ key }})</span> </template> </Collapse> <h2 class="mt30 mb10">折叠面板 ,隐藏箭头图标 (showArrow: false)</h2> <Collapse :show-arrow="false" :collapseData="collapseData" v-model:activeKey="activeKey" @change="onChange"/> </div> </template> <style lang="less" scoped> </style>创心域SEO版权声明:以上内容作者已申请原创保护,未经允许不得转载,侵权必究!授权事宜、对本内容有异议或投诉,敬请联系网站管理员,我们将尽快回复您,谢谢合作!