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';
export default {
setup() {
const activeNames = ref(['1']);
return { activeNames };
},
};手風琴
啟用 accordion 手風琴模式,就像一位優雅的鋼琴家 🎹,同一時間只專注演奏一個美妙的旋律。此時 activeName 變身為字串格式的獨奏者,確保舞台上永遠只有一個主角在閃耀。這種專一的展示方式讓使用者的注意力更加集中,避免了資訊過載的困擾,營造出簡約而不簡單的視覺體驗。
import { ref } from'vue';
export default {
setup() {
const activeName = ref('1');
return { activeName };
},
};禁用狀態
透過 disabled 屬性為特定面板戴上「請勿打擾」的標識牌 🚫,讓它們安靜地待在那裡,既保持存在感又不會被意外觸發。這種貼心的保護機制確保了重要內容的安全性,就像給珍貴的藝術品加上了防護罩。
自訂標題內容
透過 title 插槽這位創意設計師 🎨,可以為標題欄注入個性化的靈魂。不再局限於單調的文字,你可以添加圖示、徽章、甚至是動畫效果,讓每個面板的標題都成為獨特的藝術品,吸引使用者的目光並傳達更豐富的資訊。
import { ref } from'vue';
export default {
setup() {
const activeNames = ref(['1']);
return { activeNames };
},
};全部展開與全部切換
透過 Collapse 實例上的 toggleAll 這位全能指揮家 🎭,可以一鍵實現所有面板的集體表演。想要所有內容一覽無餘?還是希望回歸簡潔狀態?只需輕輕一揮指揮棒,所有面板都會整齊劃一地響應你的號令,就像訓練有素的合唱團,展現出完美的協調性。
import { ref } from'vue';
export default {
setup() {
const activeNames = ref(['1']);
const collapse = ref(null);
const openAll = () => {
collapse.value.toggleAll(true);
};
const toggleAll = () => {
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';
import type { 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 動作面板 - 底部動作選擇