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 | *number | string* |
| 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-padding | var(--van-padding-sm) var(--van-padding-sm) 80px | - |
| --van-contact-list-edit-icon-size | 16px | - |
| --van-contact-list-add-button-z-index | 999 | - |
| --van-contact-list-radio-color | var(--van-primary-color) | - |
| --van-contact-list-item-padding | var(--van-padding-md) | - |
🎯 最佳實踐
📱 行動端最佳化建議
- 清單效能最佳化 - 使用虛擬滾動處理大量聯絡人
- 搜尋功能整合 - 提供快速查找聯絡人的能力
- 分組顯示 - 按首字母或分類組織聯絡人
- 快捷操作 - 支援滑動刪除、長按多選等手勢
🔍 進階搜尋功能
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)
}🎨 設計靈感
🌈 主題風格建議
商務風格 - 簡潔專業
css--van-contact-list-padding: 20px 16px 80px; --van-contact-list-item-padding: 20px; --van-contact-list-radio-color: #1890ff;時尚風格 - 現代活潑
css--van-contact-list-padding: 16px 16px 80px; --van-contact-list-item-padding: 18px; --van-contact-list-radio-color: #ff6b6b;極簡風格 - 純淨優雅
css--van-contact-list-padding: 24px 20px 80px; --van-contact-list-item-padding: 24px; --van-contact-list-radio-color: #52c41a;
🎯 互動設計建議
- 滑動操作 - 左滑顯示快捷操作按鈕
- 長按選擇 - 長按進入多選模式
- 下拉重新整理 - 支援下拉重新整理聯絡人清單
- 字母導航 - 右側字母索引快速定位
📚 相關文件
- ContactCard 聯絡人卡片 - 聯絡人卡片展示元件
- ContactEdit 聯絡人編輯 - 聯絡人編輯元件
- Contact 聯絡人 - 聯絡人選擇元件
- Radio 單選框 - 單選框元件
🔗 延伸閱讀
- 行動端清單最佳化指南 - Web.dev 效能最佳化
- 聯絡人管理最佳實踐 - Material Design 指南
- 無障礙清單設計 - W3C 無障礙指南
- Vue 3 清單渲染最佳化 - Vue 3 官方文件