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'; 
export default { 
  setup() { 
    const activeNames = ref(['1']); 
    return { activeNames }; 
  }, 
};

手風琴

啟用 accordion 手風琴模式,就像一位優雅的鋼琴家 🎹,同一時間只專注演奏一個美妙的旋律。此時 activeName 變身為字串格式的獨奏者,確保舞台上永遠只有一個主角在閃耀。這種專一的展示方式讓使用者的注意力更加集中,避免了資訊過載的困擾,營造出簡約而不簡單的視覺體驗。

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

禁用狀態

透過 disabled 屬性為特定面板戴上「請勿打擾」的標識牌 🚫,讓它們安靜地待在那裡,既保持存在感又不會被意外觸發。這種貼心的保護機制確保了重要內容的安全性,就像給珍貴的藝術品加上了防護罩。

html

自訂標題內容

透過 title 插槽這位創意設計師 🎨,可以為標題欄注入個性化的靈魂。不再局限於單調的文字,你可以添加圖示、徽章、甚至是動畫效果,讓每個面板的標題都成為獨特的藝術品,吸引使用者的目光並傳達更豐富的資訊。

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

全部展開與全部切換

透過 Collapse 實例上的 toggleAll 這位全能指揮家 🎭,可以一鍵實現所有面板的集體表演。想要所有內容一覽無餘?還是希望回歸簡潔狀態?只需輕輕一揮指揮棒,所有面板都會整齊劃一地響應你的號令,就像訓練有素的合唱團,展現出完美的協調性。

html
js
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手風琴模式:*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'; 
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-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構建的企業級移動端解決方案