Collapse 折叠面板 - Vant 4
Collapse 折叠面板
📂 介绍
Collapse 折叠面板就像一本精美的手风琴书籍 📖,每一页都承载着丰富的内容,轻轻一点便能优雅地展开或收起。它是页面空间的魔法师 ✨,将复杂的信息巧妙地隐藏在简洁的标题背后,让用户可以按需探索,既保持了界面的整洁美观,又确保了信息的完整传达。无论是FAQ问答、产品详情还是设置选项,折叠面板都能让内容呈现变得井然有序,为用户带来愉悦的浏览体验。
📦 引入
通过以下方式来全局注册组件,更多注册方式请参考组件注册。
import { createApp } from'vue'; import { Collapse, CollapseItem } from'vant'; const app = createApp(); app.use(Collapse); app.use(CollapseItem);🎯 代码演示
基础用法
通过 v-model 这位贴心的管家 👨💼,精准控制展开的面板列表,activeNames 数组就像一份VIP名单,记录着哪些面板有幸展示自己的内容。想要多个面板同时绽放?没问题!这种自由度让内容展示变得随心所欲,就像指挥一支交响乐团,每个乐章都能按需演奏。
import { ref } from'vue'; exportdefault { setup() { const activeNames = ref(['1']); return { activeNames }; }, };手风琴
启用 accordion 手风琴模式,就像一位优雅的钢琴家 🎹,同一时间只专注演奏一个美妙的旋律。此时 activeName 变身为字符串格式的独奏者,确保舞台上永远只有一个主角在闪耀。这种专一的展示方式让用户的注意力更加集中,避免了信息过载的困扰,营造出简约而不简单的视觉体验。
import { ref } from'vue'; exportdefault { setup() { const activeName = ref('1'); return { activeName }; }, };禁用状态
通过 disabled 属性为特定面板戴上"请勿打扰"的标识牌 🚫,让它们安静地待在那里,既保持存在感又不会被意外触发。这种贴心的保护机制确保了重要内容的安全性,就像给珍贵的艺术品加上了防护罩。
自定义标题内容
通过 title 插槽这位创意设计师 🎨,可以为标题栏注入个性化的灵魂。不再局限于单调的文字,你可以添加图标、徽章、甚至是动画效果,让每个面板的标题都成为独特的艺术品,吸引用户的目光并传达更丰富的信息。
import { ref } from'vue'; exportdefault { setup() { const activeNames = ref(['1']); return { activeNames }; }, };全部展开与全部切换
通过 Collapse 实例上的 toggleAll 这位全能指挥家 🎭,可以一键实现所有面板的集体表演。想要所有内容一览无余?还是希望回归简洁状态?只需轻轻一挥指挥棒,所有面板都会整齐划一地响应你的号令,就像训练有素的合唱团,展现出完美的协调性。
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 | 手风琴模式:*number | string非手风琴模式:(number |
| accordion | 是否开启手风琴模式 | boolean | false |
| border | 是否显示外边框 | boolean | true |
Collapse Events
| 事件名 | 说明 | 回调参数 |
|---|---|---|
| change | 切换面板时触发 | activeNames: 类型与 v-model 绑定的值一致 |
CollapseItem Props
| 参数 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| name | 唯一标识符,默认为索引值 | *number | string* |
| 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?: boolean | object* |
toggleAll 方法示例
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 | - |
类型定义
组件导出以下类型定义:
importtype { CollapseProps, CollapseItemProps, CollapseItemInstance, CollapseToggleAllOptions, } from'vant';CollapseItemInstance 是组件实例的类型,用法如下:
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-duration | var(--van-duration-base) | - |
| --van-collapse-item-content-padding | var(--van-padding-sm) var(--van-padding-md) | - |
| --van-collapse-item-content-font-size | var(--van-font-size-md) | - |
| --van-collapse-item-content-line-height | 1.5 | - |
| --van-collapse-item-content-text-color | var(--van-text-color-2) | - |
| --van-collapse-item-content-background | var(--van-background-2) | - |
| --van-collapse-item-title-disabled-color | var(--van-text-color-3) | - |
最佳实践
内容组织策略
合理组织折叠面板的内容结构 📋:
<!-- ✅ 推荐:逻辑清晰的内容分组 -->
<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>标题设计原则
<!-- ✅ 推荐:简洁明了的标题 -->
<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>响应式设计
<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 属性优化性能 🚀:
<!-- ✅ 推荐:对于复杂内容使用懒加载 -->
<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>避免频繁重渲染
// ✅ 推荐:使用计算属性优化数据处理
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
<!-- 对于频繁切换的内容使用 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: 如何实现折叠面板的嵌套?
<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: 如何实现折叠面板的搜索过滤?
<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: 如何实现折叠面板的拖拽排序?
<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 问答系统
<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>产品规格展示
<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>相关组件
- Cell 单元格 - 可与折叠面板结合使用
- List 列表 - 展示列表数据
- Tabs 标签页 - 另一种内容组织方式
- Popup 弹出层 - 弹窗式内容展示
- ActionSheet 动作面板 - 底部动作选择