Skip to content

ContactList 聯絡人清單 - Vant 4

📋 ContactList 聯絡人清單

📇 介紹

ContactList 元件就像你的私人通訊錄管家,優雅地展示著每一位聯絡人的資訊!✨ 它不僅能讓你輕鬆瀏覽聯絡人清單,還貼心地提供了選擇、編輯和新增功能。就像翻閱一本精美的通訊錄,每一頁都整齊有序,讓聯絡人管理變得如此簡單而愉悅!無論是商務場合還是日常使用,它都能讓你的聯絡人管理井井有條,一目了然。

📦 引入

透過以下方式來全域註冊元件,更多註冊方式請參考元件註冊

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

🎯 程式碼演示

🔧 基礎用法 - 打造你的專屬通訊錄

透過簡單的設定,就能建立一個功能完整的聯絡人清單!支援選擇聯絡人、編輯資訊和新增聯絡人,讓通訊錄管理變得輕鬆愉快。

html
js
import { ref } from'vue'; 
import { showToast } from'vant'; 
export default { 
  setup() { 
    const chosenContactId = ref('1'); 
    const list = ref([ { id: '1', name: '張三', tel: '13000000000', isDefault: true, }, { id: '2', name: '李四', tel: '1310000000', }, ]); const onAdd = () => showToast('新增'); 
    const onEdit = (contact) => showToast('編輯' + contact.id); 
    const onSelect = (contact) => showToast('選擇' + contact.id); 
    return { list, onAdd, onEdit, onSelect, chosenContactId, }; 
  }, 
};

📖 API

Props

參數說明類型預設值
v-model目前選中聯絡人的 id*numberstring*
list聯絡人清單ContactListItem[][]
add-text新建按鈕文案string新建聯絡人
default-tag-text預設聯絡人標籤文案string-

Events

事件名說明回呼參數
add點擊新增按鈕時觸發-
edit點擊編輯按鈕時觸發contact: ContactListItem,index: number
select切換選中的聯絡人時觸發contact: ContactListItem,index: number

ContactListItem 資料結構

鍵名說明類型
id每位聯絡人的唯一識別*number
name聯絡人姓名string
tel聯絡人手機號*number
isDefault是否為預設聯絡人*boolean

類型定義

元件匯出以下類型定義:

ts
import type { ContactListItem, ContactListProps } from'vant';

🎨 主題定製

樣式變數

元件提供了下列 CSS 變數,可用於自訂樣式,使用方法請參考 ConfigProvider 元件

名稱預設值描述
--van-contact-list-paddingvar(--van-padding-sm) var(--van-padding-sm) 80px-
--van-contact-list-edit-icon-size16px-
--van-contact-list-add-button-z-index999-
--van-contact-list-radio-colorvar(--van-primary-color)-
--van-contact-list-item-paddingvar(--van-padding-md)-

🎯 最佳實踐

📱 行動端最佳化建議

  1. 清單效能最佳化 - 使用虛擬滾動處理大量聯絡人
  2. 搜尋功能整合 - 提供快速查找聯絡人的能力
  3. 分組顯示 - 按首字母或分類組織聯絡人
  4. 快捷操作 - 支援滑動刪除、長按多選等手勢

🔍 進階搜尋功能

vue
<template>
  <div class="contact-list-with-search">
    <!-- 搜尋列 -->
    <Search
      v-model="searchKeyword"
      placeholder="搜尋聯絡人姓名或手機號"
      @search="handleSearch"
      @clear="handleClear"
    />
    
    <!-- 聯絡人清單 -->
    <ContactList
      v-model="chosenContactId"
      :list="filteredContacts"
      :add-text="addButtonText"
      @add="handleAdd"
      @edit="handleEdit"
      @select="handleSelect"
    />
    
    <!-- 空狀態 -->
    <Empty
      v-if="filteredContacts.length === 0 && searchKeyword"
      description="未找到相關聯絡人"
      image="search"
    />
  </div>
</template>

<script setup>
import { ref, computed, watch } from 'vue'
import { showToast } from 'vant'

const searchKeyword = ref('')
const chosenContactId = ref('1')
const addButtonText = ref('新增聯絡人')

const contacts = ref([
  { id: '1', name: '張三', tel: '13000000000', isDefault: true, group: 'Z' },
  { id: '2', name: '李四', tel: '13100000000', group: 'L' },
  { id: '3', name: '王五', tel: '13200000000', group: 'W' },
  { id: '4', name: 'Alice', tel: '13300000000', group: 'A' },
  { id: '5', name: 'Bob', tel: '13400000000', group: 'B' }
])

// 過濾聯絡人
const filteredContacts = computed(() => {
  if (!searchKeyword.value) return contacts.value
  
  const keyword = searchKeyword.value.toLowerCase()
  return contacts.value.filter(contact => 
    contact.name.toLowerCase().includes(keyword) ||
    contact.tel.includes(keyword)
  )
})

// 搜尋處理
const handleSearch = (value) => {
  console.log('搜尋:', value)
}

// 清除搜尋
const handleClear = () => {
  searchKeyword.value = ''
}

// 新增聯絡人
const handleAdd = () => {
  showToast('跳轉到新增聯絡人頁面')
  // router.push('/contact/add')
}

// 編輯聯絡人
const handleEdit = (contact, index) => {
  showToast(`編輯聯絡人: ${contact.name}`)
  // router.push(`/contact/edit/${contact.id}`)
}

// 選擇聯絡人
const handleSelect = (contact, index) => {
  showToast(`選擇了聯絡人: ${contact.name}`)
  chosenContactId.value = contact.id
}
</script>

📊 分組顯示功能

vue
<template>
  <div class="grouped-contact-list">
    <!-- 字母索引 -->
    <IndexBar :sticky="false">
      <IndexAnchor
        v-for="group in groupedContacts"
        :key="group.letter"
        :index="group.letter"
      >
        <ContactList
          v-model="chosenContactId"
          :list="group.contacts"
          :add-text="false"
          @edit="handleEdit"
          @select="handleSelect"
        />
      </IndexAnchor>
    </IndexBar>
    
    <!-- 懸浮新增按鈕 -->
    <FloatingBubble
      axis="xy"
      icon="plus"
      @click="handleAdd"
    />
  </div>
</template>

<script setup>
import { computed } from 'vue'

// 按首字母分組
const groupedContacts = computed(() => {
  const groups = {}
  
  contacts.value.forEach(contact => {
    const firstLetter = getFirstLetter(contact.name)
    if (!groups[firstLetter]) {
      groups[firstLetter] = []
    }
    groups[firstLetter].push(contact)
  })
  
  return Object.keys(groups)
    .sort()
    .map(letter => ({
      letter,
      contacts: groups[letter].sort((a, b) => a.name.localeCompare(b.name))
    }))
})

// 取得首字母
const getFirstLetter = (name) => {
  const firstChar = name.charAt(0).toUpperCase()
  return /[A-Z]/.test(firstChar) ? firstChar : '#'
}
</script>

🎨 自訂樣式主題

vue
<template>
  <div class="custom-contact-list">
    <ContactList
      v-model="chosenContactId"
      :list="contacts"
      class="themed-list"
      @add="handleAdd"
      @edit="handleEdit"
      @select="handleSelect"
    />
  </div>
</template>

<style>
.themed-list {
  --van-contact-list-padding: 16px 16px 100px;
  --van-contact-list-item-padding: 20px;
  --van-contact-list-radio-color: #ff6b6b;
  --van-contact-list-edit-icon-size: 18px;
}

.custom-contact-list {
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  min-height: 100vh;
}

/* 自定义联系人项样式 */
.themed-list .van-contact-list__item {
  background: rgba(255, 255, 255, 0.95);
  border-radius: 12px;
  margin-bottom: 8px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
  transition: all 0.3s ease;
}

.themed-list .van-contact-list__item:hover {
  transform: translateY(-2px);
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
}

/* 默认联系人标签样式 */
.themed-list .van-contact-list__item--default::after {
  background: linear-gradient(45deg, #ff6b6b, #ffa500);
  border-radius: 8px;
  color: white;
  font-weight: bold;
}
</style>

💡 使用技巧

🔄 批次操作功能

vue
<template>
  <div class="batch-contact-list">
    <!-- 批次操作工具列 -->
    <div class="batch-toolbar" v-if="isSelectionMode">
      <div class="selection-info">
        已選擇 {{ selectedContacts.length }} 個聯絡人
      </div>
      <div class="batch-actions">
        <Button size="small" @click="batchDelete">刪除</Button>
        <Button size="small" type="primary" @click="batchExport">匯出</Button>
        <Button size="small" @click="exitSelectionMode">取消</Button>
      </div>
    </div>
    
    <!-- 聯絡人清單 -->
    <div class="contact-items">
      <div
        v-for="contact in contacts"
        :key="contact.id"
        class="contact-item-wrapper"
        @long-press="enterSelectionMode"
      >
        <!-- 多選模式下的核取方塊 -->
        <Checkbox
          v-if="isSelectionMode"
          :model-value="selectedContacts.includes(contact.id)"
          @update:model-value="toggleSelection(contact.id)"
        />
        
        <!-- 聯絡人資訊 -->
        <div class="contact-info" @click="handleContactClick(contact)">
          <div class="contact-name">{{ contact.name }}</div>
          <div class="contact-tel">{{ contact.tel }}</div>
          <Tag v-if="contact.isDefault" type="primary" size="small">
            預設
          </Tag>
        </div>
        
        <!-- 操作按鈕 -->
        <div class="contact-actions" v-if="!isSelectionMode">
          <Button
            size="small"
            type="primary"
            @click="handleEdit(contact)"
          >
            編輯
          </Button>
        </div>
      </div>
    </div>
  </div>
</template>

<script setup>
const isSelectionMode = ref(false)
const selectedContacts = ref([])

// 進入選擇模式
const enterSelectionMode = () => {
  isSelectionMode.value = true
}

// 退出選擇模式
const exitSelectionMode = () => {
  isSelectionMode.value = false
  selectedContacts.value = []
}

// 切換選擇狀態
const toggleSelection = (contactId) => {
  const index = selectedContacts.value.indexOf(contactId)
  if (index > -1) {
    selectedContacts.value.splice(index, 1)
  } else {
    selectedContacts.value.push(contactId)
  }
}

// 批次刪除
const batchDelete = async () => {
  const result = await showDialog({
    title: '批次刪除',
    message: `確定要刪除選中的 ${selectedContacts.value.length} 個聯絡人嗎?`
  })
  
  if (result === 'confirm') {
    // 執行刪除操作
    showToast('刪除成功')
    exitSelectionMode()
  }
}

// 批次匯出
const batchExport = () => {
  const selectedData = contacts.value.filter(c => 
    selectedContacts.value.includes(c.id)
  )
  
  // 匯出為 CSV 或其他格式
  exportContacts(selectedData)
  showToast('匯出成功')
}
</script>

📞 快捷撥號功能

vue
<template>
  <ContactList
    v-model="chosenContactId"
    :list="contacts"
    @select="handleSelect"
  >
    <!-- 自訂聯絡人項目 -->
    <template #contact-item="{ contact, index }">
      <div class="custom-contact-item">
        <div class="contact-info">
          <div class="contact-name">{{ contact.name }}</div>
          <div class="contact-tel">{{ contact.tel }}</div>
        </div>
        
        <div class="quick-actions">
          <Button
            icon="phone-o"
            size="small"
            type="primary"
            @click="makeCall(contact.tel)"
          />
          <Button
            icon="chat-o"
            size="small"
            @click="sendMessage(contact.tel)"
          />
        </div>
      </div>
    </template>
  </ContactList>
</template>

<script setup>
// 撥打電話
const makeCall = (phoneNumber) => {
  if (window.navigator && window.navigator.userAgent.includes('Mobile')) {
    window.location.href = `tel:${phoneNumber}`
  } else {
    showToast('請在行動裝置上使用撥號功能')
  }
}

// 發送簡訊
const sendMessage = (phoneNumber) => {
  if (window.navigator && window.navigator.userAgent.includes('Mobile')) {
    window.location.href = `sms:${phoneNumber}`
  } else {
    showToast('請在行動裝置上使用簡訊功能')
  }
}
</script>

❓ 常見問題解決

🔧 清單效能最佳化

問題:聯絡人數量過多時清單滾動卡頓 解決方案

vue
<template>
  <!-- 使用虛擬滾動最佳化大清單效能 -->
  <VirtualList
    :list="contacts"
    :item-height="80"
    :container-height="400"
  >
    <template #default="{ item, index }">
      <div class="virtual-contact-item">
        <ContactList
          :list="[item]"
          @edit="handleEdit"
          @select="handleSelect"
        />
      </div>
    </template>
  </VirtualList>
</template>

<script setup>
// 分頁載入聯絡人
const pageSize = 20
const currentPage = ref(1)
const loading = ref(false)

const loadMoreContacts = async () => {
  if (loading.value) return
  
  loading.value = true
  try {
    const newContacts = await fetchContacts(currentPage.value, pageSize)
    contacts.value.push(...newContacts)
    currentPage.value++
  } catch (error) {
    showToast('載入失敗')
  } finally {
    loading.value = false
  }
}

// 監聽滾動到底部
const handleScroll = (event) => {
  const { scrollTop, scrollHeight, clientHeight } = event.target
  if (scrollTop + clientHeight >= scrollHeight - 10) {
    loadMoreContacts()
  }
}
</script>

📱 行動端適配問題

問題:在不同螢幕尺寸下顯示異常 解決方案

css
/* 響應式設計 */
@media (max-width: 768px) {
  .van-contact-list {
    --van-contact-list-padding: 12px 12px 80px;
    --van-contact-list-item-padding: 16px;
  }
}

@media (max-width: 480px) {
  .van-contact-list {
    --van-contact-list-padding: 8px 8px 80px;
    --van-contact-list-item-padding: 12px;
    --van-contact-list-edit-icon-size: 14px;
  }
}

/* 安全區域適配 */
.van-contact-list {
  padding-bottom: calc(80px + env(safe-area-inset-bottom));
}

🔍 搜尋功能最佳化

問題:搜尋結果不準確或效能差 解決方案

javascript
// 使用防抖最佳化搜尋效能
import { debounce } from 'lodash-es'

const searchContacts = debounce((keyword) => {
  if (!keyword.trim()) {
    filteredContacts.value = contacts.value
    return
  }
  
  // 支援拼音搜尋
  filteredContacts.value = contacts.value.filter(contact => {
    const searchText = keyword.toLowerCase()
    return (
      contact.name.toLowerCase().includes(searchText) ||
      contact.tel.includes(searchText) ||
      getPinyin(contact.name).toLowerCase().includes(searchText)
    )
  })
}, 300)

// 拼音搜尋支援
const getPinyin = (text) => {
  // 使用 pinyin 函式庫或自訂拼音轉換
  return pinyinUtil.getPinyin(text, '', true, false)
}

🎨 設計靈感

🌈 主題風格建議

  1. 商務風格 - 簡潔專業

    css
    --van-contact-list-padding: 20px 16px 80px;
    --van-contact-list-item-padding: 20px;
    --van-contact-list-radio-color: #1890ff;
  2. 時尚風格 - 現代活潑

    css
    --van-contact-list-padding: 16px 16px 80px;
    --van-contact-list-item-padding: 18px;
    --van-contact-list-radio-color: #ff6b6b;
  3. 極簡風格 - 純淨優雅

    css
    --van-contact-list-padding: 24px 20px 80px;
    --van-contact-list-item-padding: 24px;
    --van-contact-list-radio-color: #52c41a;

🎯 互動設計建議

  • 滑動操作 - 左滑顯示快捷操作按鈕
  • 長按選擇 - 長按進入多選模式
  • 下拉重新整理 - 支援下拉重新整理聯絡人清單
  • 字母導航 - 右側字母索引快速定位

📚 相關文件

🔗 延伸閱讀

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