Skip to content

Collapse 折叠面板 - Vant 4

Collapse 折叠面板

📂 介绍

Collapse 折叠面板就像一本精美的手风琴书籍 📖,每一页都承载着丰富的内容,轻轻一点便能优雅地展开或收起。它是页面空间的魔法师 ✨,将复杂的信息巧妙地隐藏在简洁的标题背后,让用户可以按需探索,既保持了界面的整洁美观,又确保了信息的完整传达。无论是FAQ问答、产品详情还是设置选项,折叠面板都能让内容呈现变得井然有序,为用户带来愉悦的浏览体验。

📦 引入

通过以下方式来全局注册组件,更多注册方式请参考组件注册

js
import { createApp } from'vue'; import { Collapse, CollapseItem } from'vant'; const app = createApp(); app.use(Collapse); app.use(CollapseItem);

🎯 代码演示

基础用法

通过 v-model 这位贴心的管家 👨‍💼,精准控制展开的面板列表,activeNames 数组就像一份VIP名单,记录着哪些面板有幸展示自己的内容。想要多个面板同时绽放?没问题!这种自由度让内容展示变得随心所欲,就像指挥一支交响乐团,每个乐章都能按需演奏。

html
js
import { ref } from'vue'; exportdefault { setup() { const activeNames = ref(['1']); return { activeNames }; }, };

手风琴

启用 accordion 手风琴模式,就像一位优雅的钢琴家 🎹,同一时间只专注演奏一个美妙的旋律。此时 activeName 变身为字符串格式的独奏者,确保舞台上永远只有一个主角在闪耀。这种专一的展示方式让用户的注意力更加集中,避免了信息过载的困扰,营造出简约而不简单的视觉体验。

html
js
import { ref } from'vue'; exportdefault { setup() { const activeName = ref('1'); return { activeName }; }, };

禁用状态

通过 disabled 属性为特定面板戴上"请勿打扰"的标识牌 🚫,让它们安静地待在那里,既保持存在感又不会被意外触发。这种贴心的保护机制确保了重要内容的安全性,就像给珍贵的艺术品加上了防护罩。

html

自定义标题内容

通过 title 插槽这位创意设计师 🎨,可以为标题栏注入个性化的灵魂。不再局限于单调的文字,你可以添加图标、徽章、甚至是动画效果,让每个面板的标题都成为独特的艺术品,吸引用户的目光并传达更丰富的信息。

html
js
import { ref } from'vue'; exportdefault { setup() { const activeNames = ref(['1']); return { activeNames }; }, };

全部展开与全部切换

通过 Collapse 实例上的 toggleAll 这位全能指挥家 🎭,可以一键实现所有面板的集体表演。想要所有内容一览无余?还是希望回归简洁状态?只需轻轻一挥指挥棒,所有面板都会整齐划一地响应你的号令,就像训练有素的合唱团,展现出完美的协调性。

html
js
import { ref } from'vue'; exportdefault { setup() { const activeNames = ref(['1']); const collapse = ref(null); constopenAll = () => { collapse.value.toggleAll(true); } consttoggleAll = () => { collapse.value.toggleAll(); }, return { activeNames, openAll, toggleAll, collapse, }; }, };

Tips: 手风琴模式下无法使用 toggleAll 方法。

API

Collapse Props

参数说明类型默认值
v-model当前展开面板的 name手风琴模式:*numberstring非手风琴模式:(number
accordion是否开启手风琴模式booleanfalse
border是否显示外边框booleantrue

Collapse Events

事件名说明回调参数
change切换面板时触发activeNames: 类型与 v-model 绑定的值一致

CollapseItem Props

参数说明类型默认值
name唯一标识符,默认为索引值*numberstring*
icon标题栏左侧图标名称或图片链接,等同于 Icon 组件的 name 属性string-

| size | 标题栏大小,可选值为 large | string | - | | title | 标题栏左侧内容 | number | string | - | | value | 标题栏右侧内容 | number | string | - | | label | 标题栏描述信息 | number | string | - | | border | 是否显示内边框 | boolean | true | | is-link | 是否展示标题栏右侧箭头并开启点击反馈 | boolean | true | | disabled | 是否禁用面板 | boolean | false | | readonly | 是否为只读状态,只读状态下无法操作面板 | boolean | false | | lazy-render | 是否在首次展开时才渲染面板内容 | boolean | true | | title-class | 左侧标题额外类名 | string | - | | value-class | 右侧内容额外类名 | string | - | | label-class | 描述信息额外类名 | string | - |

Collapse 方法

通过 ref 可以获取到 CollapseItem 实例并调用实例方法,详见组件实例方法

方法名说明参数返回值
toggleAll切换所有面板展开状态,传 true 为全部展开,false 为全部收起,不传参为全部切换*options?: booleanobject*

toggleAll 方法示例

js
import { ref } from'vue'; import type { CollapseInstance } from'vant'; const collapseRef = ref<CollapseInstance>(); // 全部切换 collapseRef.value?.toggleAll(); // 全部展开 collapseRef.value?.toggleAll(true); // 全部收起 collapseRef.value?.toggleAll(false); // 全部全部切换,并跳过禁用的复选框 collapseRef.value?.toggleAll({ skipDisabled: true, }); // 全部选中,并跳过禁用的复选框 collapseRef.value?.toggleAll({ expanded: true, skipDisabled: true, });

CollapseItem 方法

通过 ref 可以获取到 CollapseItem 实例并调用实例方法,详见组件实例方法

方法名说明参数返回值
toggle切换面板展开状态,传 true 为展开,false 为收起,不传参为切换expand?: boolean-

类型定义

组件导出以下类型定义:

ts
importtype { CollapseProps, CollapseItemProps, CollapseItemInstance, CollapseToggleAllOptions, } from'vant';

CollapseItemInstance 是组件实例的类型,用法如下:

ts
import { ref } from'vue'; importtype { CollapseItemInstance } from'vant'; const collapseItemRef = ref<CollapseItemInstance>(); collapseItemRef.value?.toggle();

CollapseItem Slots

名称说明
default面板内容
title自定义标题栏左侧内容
value自定义标题栏右侧内容
label自定义标题栏描述信息
icon自定义标题栏左侧图标
right-icon自定义标题栏右侧图标

主题定制

样式变量

组件提供了下列 CSS 变量,可用于自定义样式,使用方法请参考 ConfigProvider 组件

名称默认值描述
--van-collapse-item-durationvar(--van-duration-base)-
--van-collapse-item-content-paddingvar(--van-padding-sm) var(--van-padding-md)-
--van-collapse-item-content-font-sizevar(--van-font-size-md)-
--van-collapse-item-content-line-height1.5-
--van-collapse-item-content-text-colorvar(--van-text-color-2)-
--van-collapse-item-content-backgroundvar(--van-background-2)-
--van-collapse-item-title-disabled-colorvar(--van-text-color-3)-

最佳实践

内容组织策略

合理组织折叠面板的内容结构 📋:

html
<!-- ✅ 推荐:逻辑清晰的内容分组 -->
<van-collapse v-model="activeNames">
  <van-collapse-item title="基本信息" name="basic">
    <div class="info-section">
      <p>姓名:张三</p>
      <p>年龄:25岁</p>
      <p>职业:前端工程师</p>
    </div>
  </van-collapse-item>
  
  <van-collapse-item title="联系方式" name="contact">
    <div class="contact-section">
      <p>电话:138-0000-0000</p>
      <p>邮箱:zhangsan@example.com</p>
      <p>地址:北京市朝阳区</p>
    </div>
  </van-collapse-item>
  
  <van-collapse-item title="工作经历" name="experience">
    <div class="experience-section">
      <!-- 详细的工作经历内容 -->
    </div>
  </van-collapse-item>
</van-collapse>

<!-- ❌ 避免:内容过于分散或过于集中 -->
<van-collapse v-model="activeNames">
  <van-collapse-item title="所有信息" name="all">
    <!-- 把所有内容都塞在一个面板里 -->
  </van-collapse-item>
</van-collapse>

标题设计原则

html
<!-- ✅ 推荐:简洁明了的标题 -->
<van-collapse-item title="账户设置" name="account">
  <template #title>
    <div class="custom-title">
      <van-icon name="setting-o" />
      <span>账户设置</span>
      <van-tag type="primary" size="mini">重要</van-tag>
    </div>
  </template>
  <!-- 内容 -->
</van-collapse-item>

<!-- ❌ 避免:标题过长或信息不明确 -->
<van-collapse-item 
  title="这是一个非常长的标题,包含了很多不必要的描述信息,会影响用户体验" 
  name="bad"
>
  <!-- 内容 -->
</van-collapse-item>

响应式设计

html
<template>
  <van-collapse 
    v-model="activeNames" 
    :accordion="isMobile"
    :class="{ 'mobile-collapse': isMobile }"
  >
    <van-collapse-item 
      v-for="item in collapseItems" 
      :key="item.name"
      :title="item.title"
      :name="item.name"
    >
      <div :class="['content-wrapper', { 'mobile-content': isMobile }]">
        {{ item.content }}
      </div>
    </van-collapse-item>
  </van-collapse>
</template>

<script>
import { ref, computed, onMounted, onUnmounted } from 'vue';

export default {
  setup() {
    const screenWidth = ref(window.innerWidth);
    const activeNames = ref(['1']);
    
    const isMobile = computed(() => screenWidth.value < 768);
    
    const updateScreenWidth = () => {
      screenWidth.value = window.innerWidth;
    };
    
    onMounted(() => {
      window.addEventListener('resize', updateScreenWidth);
    });
    
    onUnmounted(() => {
      window.removeEventListener('resize', updateScreenWidth);
    });
    
    return { activeNames, isMobile };
  }
};
</script>

<style>
.mobile-collapse .van-collapse-item__title {
  font-size: 14px;
  padding: 12px 16px;
}

.mobile-content {
  padding: 12px 16px;
  font-size: 14px;
  line-height: 1.6;
}
</style>

性能优化小贴士

懒加载内容

利用 lazy-render 属性优化性能 🚀:

html
<!-- ✅ 推荐:对于复杂内容使用懒加载 -->
<van-collapse v-model="activeNames">
  <van-collapse-item 
    title="图表数据" 
    name="charts"
    :lazy-render="true"
  >
    <div v-if="activeNames.includes('charts')">
      <!-- 只有在展开时才渲染复杂的图表组件 -->
      <heavy-chart-component />
    </div>
  </van-collapse-item>
</van-collapse>

<!-- 动态加载内容 -->
<van-collapse-item 
  title="用户列表" 
  name="users"
  :lazy-render="true"
  @click="loadUserData"
>
  <div v-if="userDataLoaded">
    <user-list :data="userData" />
  </div>
  <van-loading v-else>加载中...</van-loading>
</van-collapse-item>

避免频繁重渲染

javascript
// ✅ 推荐:使用计算属性优化数据处理
const processedItems = computed(() => {
  return rawItems.value.map(item => ({
    ...item,
    formattedDate: formatDate(item.date),
    isHighlighted: item.priority === 'high'
  }));
});

// ❌ 避免:在模板中进行复杂计算
// <van-collapse-item :title="formatComplexTitle(item)">

合理使用 v-show 和 v-if

html
<!-- 对于频繁切换的内容使用 v-show -->
<van-collapse-item title="快速切换内容" name="quick">
  <div v-show="showQuickContent">
    <!-- 频繁显示/隐藏的内容 -->
  </div>
</van-collapse-item>

<!-- 对于条件性渲染使用 v-if -->
<van-collapse-item title="条件内容" name="conditional">
  <div v-if="shouldRenderContent">
    <!-- 根据条件决定是否渲染的内容 -->
  </div>
</van-collapse-item>

设计建议

视觉层次

  • 标题层次:使用不同的字体大小和颜色区分重要性 📊
  • 内容间距:保持一致的内边距和外边距 📏
  • 图标使用:合理使用图标增强可识别性 🎯
  • 状态反馈:提供清晰的展开/收起状态指示 💫

交互体验

  • 动画效果:使用平滑的展开/收起动画 🎬
  • 触摸友好:确保在移动设备上有足够的点击区域 📱
  • 键盘导航:支持键盘操作提升可访问性 ⌨️
  • 加载状态:为异步内容提供加载指示器 ⏳

常见问题解决

Q: 如何实现折叠面板的嵌套?

html
<van-collapse v-model="parentActive">
  <van-collapse-item title="父级面板" name="parent">
    <van-collapse v-model="childActive" class="nested-collapse">
      <van-collapse-item title="子级面板 1" name="child1">
        <p>子级内容 1</p>
      </van-collapse-item>
      <van-collapse-item title="子级面板 2" name="child2">
        <p>子级内容 2</p>
      </van-collapse-item>
    </van-collapse>
  </van-collapse-item>
</van-collapse>

<style>
.nested-collapse {
  margin: 12px 0;
  border: 1px solid #ebedf0;
  border-radius: 6px;
}

.nested-collapse .van-collapse-item__title {
  background-color: #f7f8fa;
  font-size: 14px;
}
</style>

Q: 如何实现折叠面板的搜索过滤?

html
<template>
  <div>
    <van-search 
      v-model="searchKeyword" 
      placeholder="搜索内容"
      @input="handleSearch"
    />
    
    <van-collapse v-model="activeNames">
      <van-collapse-item 
        v-for="item in filteredItems" 
        :key="item.name"
        :title="highlightTitle(item.title)"
        :name="item.name"
      >
        <div v-html="highlightContent(item.content)"></div>
      </van-collapse-item>
    </van-collapse>
    
    <van-empty 
      v-if="filteredItems.length === 0 && searchKeyword"
      description="未找到相关内容"
    />
  </div>
</template>

<script>
const searchKeyword = ref('');
const allItems = ref([
  { name: '1', title: '用户管理', content: '管理系统用户信息' },
  { name: '2', title: '权限设置', content: '配置用户权限和角色' },
  { name: '3', title: '数据统计', content: '查看系统数据报表' }
]);

const filteredItems = computed(() => {
  if (!searchKeyword.value) return allItems.value;
  
  return allItems.value.filter(item => 
    item.title.includes(searchKeyword.value) || 
    item.content.includes(searchKeyword.value)
  );
});

const highlightTitle = (title) => {
  if (!searchKeyword.value) return title;
  return title.replace(
    new RegExp(searchKeyword.value, 'gi'),
    `<mark>$&</mark>`
  );
};

const highlightContent = (content) => {
  if (!searchKeyword.value) return content;
  return content.replace(
    new RegExp(searchKeyword.value, 'gi'),
    `<mark>$&</mark>`
  );
};
</script>

Q: 如何实现折叠面板的拖拽排序?

html
<template>
  <van-collapse v-model="activeNames">
    <draggable 
      v-model="sortableItems" 
      @end="handleDragEnd"
      item-key="name"
    >
      <template #item="{ element }">
        <van-collapse-item 
          :title="element.title"
          :name="element.name"
          class="draggable-item"
        >
          <template #title>
            <div class="drag-title">
              <van-icon name="bars" class="drag-handle" />
              <span>{{ element.title }}</span>
            </div>
          </template>
          {{ element.content }}
        </van-collapse-item>
      </template>
    </draggable>
  </van-collapse>
</template>

<script>
import draggable from 'vuedraggable';

const sortableItems = ref([
  { name: '1', title: '项目 1', content: '内容 1' },
  { name: '2', title: '项目 2', content: '内容 2' },
  { name: '3', title: '项目 3', content: '内容 3' }
]);

const handleDragEnd = (event) => {
  console.log('拖拽完成', event);
  // 保存新的排序到后端
  saveSortOrder(sortableItems.value);
};
</script>

<style>
.drag-title {
  display: flex;
  align-items: center;
  gap: 8px;
}

.drag-handle {
  cursor: move;
  color: #969799;
}

.draggable-item {
  transition: all 0.3s ease;
}

.draggable-item:hover {
  background-color: #f7f8fa;
}
</style>

高级用法示例

FAQ 问答系统

html
<template>
  <div class="faq-container">
    <van-search 
      v-model="searchQuery" 
      placeholder="搜索常见问题"
      class="faq-search"
    />
    
    <div v-for="category in faqCategories" :key="category.id" class="faq-category">
      <h3 class="category-title">{{ category.name }}</h3>
      
      <van-collapse v-model="activeQuestions[category.id]">
        <van-collapse-item 
          v-for="faq in filteredFAQs(category.questions)"
          :key="faq.id"
          :name="faq.id"
          class="faq-item"
        >
          <template #title>
            <div class="faq-title">
              <van-icon name="help-o" />
              <span>{{ faq.question }}</span>
              <van-tag v-if="faq.isHot" type="danger" size="mini">热门</van-tag>
            </div>
          </template>
          
          <div class="faq-answer">
            <div v-html="faq.answer"></div>
            
            <div class="faq-actions">
              <van-button 
                size="mini" 
                type="primary" 
                plain
                @click="markHelpful(faq.id)"
              >
                有帮助 ({{ faq.helpfulCount }})
              </van-button>
              
              <van-button 
                size="mini" 
                plain
                @click="contactSupport"
              >
                联系客服
              </van-button>
            </div>
          </div>
        </van-collapse-item>
      </van-collapse>
    </div>
  </div>
</template>

产品规格展示

html
<template>
  <div class="product-specs">
    <van-collapse v-model="activeSpecs" accordion>
      <van-collapse-item 
        v-for="spec in productSpecs"
        :key="spec.category"
        :name="spec.category"
        class="spec-item"
      >
        <template #title>
          <div class="spec-title">
            <van-icon :name="spec.icon" />
            <span>{{ spec.name }}</span>
            <van-badge 
              v-if="spec.highlight" 
              content="新"
              class="spec-badge"
            />
          </div>
        </template>
        
        <div class="spec-content">
          <div 
            v-for="detail in spec.details"
            :key="detail.key"
            class="spec-detail"
          >
            <span class="detail-label">{{ detail.label }}:</span>
            <span class="detail-value">{{ detail.value }}</span>
          </div>
          
          <div v-if="spec.comparison" class="spec-comparison">
            <h4>与同类产品对比</h4>
            <van-grid :column-num="3" :border="false">
              <van-grid-item 
                v-for="item in spec.comparison"
                :key="item.name"
                :text="item.name"
                :badge="item.advantage ? '优势' : ''"
              />
            </van-grid>
          </div>
        </div>
      </van-collapse-item>
    </van-collapse>
  </div>
</template>

相关组件

延伸阅读

基於Vant構建的企業級移動端解決方案