可自定义设置以下属性:
-
折叠面板数据(collapseData),必传,类型:Array<{key?: string, header?: string, text?: string}>,默认值[]
-
当前激活 tab 面板的 key(activeKey),类型:number[] | number | string[] | string | null,默认值null
-
是否可复制面板内容(copyable),默认值false
-
面板右上角固定内容,例如标识language(lang),类型:string | slot,默认值''
-
面板标题和内容,字体大小(fontSize),默认值0
-
面板标题字体大小,优先级高于fontSize(headerFontSize),默认值14
-
面板内容字体大小,优先级高于fontSize(textFontSize),默认值14
-
是否展示面板上的箭头(showArrow),默认值true
效果如下图:
①创建折叠面板组件Collapse.vue:
<script setup lang="ts">
import { onMounted, ref } from 'vue'
// 使用 requestAnimationFrame 实现的等效 setTimeout
import { rafTimeout } from '../index'
interface Collapse {
key?: string|number, // 对应activeKey,如果没有传入key属性,则默认使用数字索引(0,1,2...)绑定
header?: string, // 面板标特
text?: string // 面板内容
}
interface Props {
collapseData: Collapse[], // 折叠面板数据
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
})
onMounted(() => {
getCollapseHeight() // 获取各个面板内容高度
})
const collapseHeight = ref<any[]>([])
function getCollapseHeight () {
const len = props.collapseData.length
for (let n = 0; n < len; n++) {
const el = document.getElementById(`${n}`)
collapseHeight.value.push(el?.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) {
const el = document.getElementById(`${index}`)
navigator.clipboard.writeText(el?.innerHTML || '').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" :index="index">{
{ lang }}</slot>
</div>
<Button size="small" class="u-copy" type="primary" @click="onCopy(index)">{
{ copyTxt }}</Button>
<p class="u-content" :style="`font-size: ${fontSize || textFontSize}px;`" :id="`${index}`" v-html="data.text"></p>
</div>
</div>
</div>
</template>
<style lang="less" scoped>
.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-content {
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, watch } 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])
watch(activeKey, (to) => {
console.log('p to:', to)
})
const key = ref(1)
watch(key, (to) => {
console.log('p to:', to)
})
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">
<template #header="{ header, index }">
<span v-if="index===1" style="color: burlywood;">burlywood color header (index = {
{ index }})</span>
</template>
<template #lang>typescript</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>