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'; exportdefault { setup() { const chosenContactId = ref('1'); const list = ref([ { id: '1', name: '张三', tel: '13000000000', isDefault: true, }, { id: '2', name: '李四', tel: '1310000000', }, ]); constonAdd = () => showToast('新增'); constonEdit = (contact) => showToast('编辑' + contact.id); constonSelect = (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
importtype { 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 官方文档