Vue3折叠面板(Collapse)

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

效果如下图:

Vue3折叠面板(Collapse)

Vue3折叠面板(Collapse)Vue3折叠面板(Collapse)

Vue3折叠面板(Collapse)Vue3折叠面板(Collapse)

 注:组件引用方法 import { rafTimeout } from '../index' 请参考以下博客:

使用requestAnimationFrame模拟实现setTimeout和setInterval_theMuseCatcher的博客-CSDN博客使用requestAnimationFrame模拟实现setTimeout和setInterval!Vue3折叠面板(Collapse)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>
声明:本站所有文章,如无特殊说明或标注,均为爬虫抓取以及网友投稿,版权归原作者所有。
0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧