ContactEdit 联系人编辑 - Vant 4
✏️ ContactEdit 联系人编辑
📝 介绍
ContactEdit 就像是你的专属联系人管理助手!✨ 它不仅提供了完整的联系人编辑功能,更像是一个贴心的数字管家,让新增和编辑联系人信息变得如丝般顺滑。界面设计简洁而不失优雅,每一个操作都经过精心雕琢,让你在管理联系人时享受到如艺术般的流畅体验!🎨
📦 引入
通过以下方式来全局注册组件,更多注册方式请参考组件注册。
js
import { createApp } from'vue'; import { ContactEdit } from'vant'; const app = createApp(); app.use(ContactEdit);🎯 代码演示
🔧 基础用法 - 联系人信息的完美编辑体验
ContactEdit 为你打造了一个功能齐全的联系人编辑界面!📱 无论是添加新朋友的信息,还是更新老朋友的联系方式,都能让你在优雅的界面中轻松完成。每一个输入框都经过精心设计,让信息录入变得既高效又愉悦。
html
js
import { ref } from'vue'; import { showToast } from'vant'; exportdefault { setup() { const editingContact = ref({ tel: '', name: '', }); constonSave = (contactInfo) => showToast('保存'); constonDelete = (contactInfo) => showToast('删除'); return { onSave, onDelete, editingContact, }; }, };📖 API
Props
| 参数 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| contact-info | 联系人信息 | ContactEditInfo | {} |
| is-edit | 是否为编辑联系人 | boolean | false |
| is-saving | 是否显示保存按钮加载动画 | boolean | false |
| is-deleting | 是否显示删除按钮加载动画 | boolean | false |
| tel-validator | 手机号格式校验函数 | (tel: string) => boolean | - |
| show-set-default | 是否显示默认联系人栏 | boolean | false |
| set-default-label | 默认联系人栏文案 | string | - |
Events
| 事件名 | 说明 | 回调参数 |
|---|---|---|
| save | 点击保存按钮时触发 | content:表单内容 |
| delete | 点击删除按钮时触发 | content:表单内容 |
| change-default | 切换是否为默认联系人时触发 | checked:是否默认 |
ContactEditInfo 数据结构
| 键名 | 说明 | 类型 |
|---|---|---|
| name | 联系人姓名 | string |
| tel | 联系人手机号 | string |
| isDefault | 是否默认 | *boolean |
类型定义
组件导出以下类型定义:
ts
importtype { ContactEditInfo, ContactEditProps } from'vant';🎨 主题定制
样式变量
组件提供了下列 CSS 变量,可用于自定义样式,使用方法请参考 ConfigProvider 组件。
| 名称 | 默认值 | 描述 |
|---|---|---|
| --van-contact-edit-padding | var(--van-padding-md) | - |
| --van-contact-edit-fields-radius | var(--van-radius-md) | - |
| --van-contact-edit-buttons-padding | var(--van-padding-xl) 0 | - |
| --van-contact-edit-button-margin-bottom | var(--van-padding-sm) | - |
| --van-contact-edit-button-font-size | var(--van-font-size-lg) | - |
| --van-contact-edit-field-label-width | 4.1em | - |
🎯 最佳实践
📝 表单验证策略
- 实时验证 - 在用户输入时提供即时反馈
- 友好提示 - 使用清晰易懂的错误信息
- 防重复提交 - 避免用户多次点击保存按钮
- 数据持久化 - 自动保存草稿,防止数据丢失
🔍 高级表单验证
vue
<template>
<ContactEdit
:contact-info="contactInfo"
:is-edit="isEditMode"
:is-saving="isSaving"
:tel-validator="validatePhone"
@save="handleSave"
@delete="handleDelete"
/>
</template>
<script setup>
import { ref, reactive } from 'vue'
import { showToast, showDialog } from 'vant'
const contactInfo = reactive({
name: '',
tel: '',
isDefault: false
})
const isSaving = ref(false)
const isEditMode = ref(false)
// 手机号验证
const validatePhone = (tel) => {
const phoneRegex = /^1[3-9]\d{9}$/
return phoneRegex.test(tel)
}
// 姓名验证
const validateName = (name) => {
if (!name.trim()) {
showToast('请输入联系人姓名')
return false
}
if (name.length > 20) {
showToast('姓名长度不能超过20个字符')
return false
}
return true
}
// 保存联系人
const handleSave = async (formData) => {
// 表单验证
if (!validateName(formData.name)) return
if (!validatePhone(formData.tel)) {
showToast('请输入正确的手机号码')
return
}
isSaving.value = true
try {
await saveContactAPI(formData)
showToast('保存成功')
// 返回上一页或刷新列表
router.back()
} catch (error) {
showToast('保存失败,请重试')
} finally {
isSaving.value = false
}
}
// 删除联系人
const handleDelete = async (formData) => {
const result = await showDialog({
title: '确认删除',
message: '删除后无法恢复,确定要删除这个联系人吗?',
confirmButtonText: '删除',
confirmButtonColor: '#ee0a24'
})
if (result === 'confirm') {
try {
await deleteContactAPI(formData.id)
showToast('删除成功')
router.back()
} catch (error) {
showToast('删除失败,请重试')
}
}
}
</script>💾 数据持久化方案
javascript
// 自动保存草稿
const draftKey = 'contact-edit-draft'
// 保存草稿到本地存储
const saveDraft = (formData) => {
localStorage.setItem(draftKey, JSON.stringify(formData))
}
// 恢复草稿数据
const restoreDraft = () => {
const draft = localStorage.getItem(draftKey)
if (draft) {
return JSON.parse(draft)
}
return null
}
// 清除草稿
const clearDraft = () => {
localStorage.removeItem(draftKey)
}
// 监听表单变化,自动保存草稿
watch(contactInfo, (newValue) => {
if (newValue.name || newValue.tel) {
saveDraft(newValue)
}
}, { deep: true })
// 页面加载时恢复草稿
onMounted(() => {
const draft = restoreDraft()
if (draft && !isEditMode.value) {
Object.assign(contactInfo, draft)
showToast('已恢复上次编辑的内容')
}
})💡 使用技巧
🎨 自定义表单布局
vue
<template>
<div class="custom-contact-edit">
<ContactEdit
:contact-info="contactInfo"
:show-set-default="showDefaultOption"
:set-default-label="defaultLabel"
@save="handleSave"
class="enhanced-form"
>
<!-- 可以通过插槽自定义额外字段 -->
<template #extra-fields>
<Field
v-model="contactInfo.email"
label="邮箱"
placeholder="请输入邮箱地址"
type="email"
/>
<Field
v-model="contactInfo.company"
label="公司"
placeholder="请输入公司名称"
/>
</template>
</ContactEdit>
</div>
</template>
<style>
.enhanced-form {
--van-contact-edit-padding: 20px;
--van-contact-edit-fields-radius: 12px;
}
.custom-contact-edit {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
padding: 20px;
}
</style>📱 移动端优化
vue
<template>
<div class="mobile-contact-edit">
<!-- 顶部导航栏 -->
<NavBar
:title="isEditMode ? '编辑联系人' : '新增联系人'"
left-arrow
@click-left="handleBack"
>
<template #right>
<Button
type="primary"
size="small"
:loading="isSaving"
@click="quickSave"
>
保存
</Button>
</template>
</NavBar>
<!-- 联系人头像 -->
<div class="avatar-section">
<Uploader
v-model="avatarList"
:max-count="1"
:preview-size="80"
upload-text="上传头像"
/>
</div>
<!-- 表单内容 -->
<ContactEdit
:contact-info="contactInfo"
:is-saving="isSaving"
@save="handleSave"
@delete="handleDelete"
/>
</div>
</template>
<script setup>
// 处理返回操作
const handleBack = () => {
if (hasUnsavedChanges.value) {
showDialog({
title: '确认离开',
message: '当前有未保存的更改,确定要离开吗?',
confirmButtonText: '离开',
cancelButtonText: '继续编辑'
}).then(() => {
router.back()
}).catch(() => {
// 用户选择继续编辑
})
} else {
router.back()
}
}
// 快速保存
const quickSave = () => {
// 触发表单保存
handleSave(contactInfo)
}
</script>🔄 批量操作支持
vue
<template>
<div class="batch-contact-edit">
<div class="batch-header">
<Checkbox v-model="selectAll" @change="handleSelectAll">
全选 ({{ selectedContacts.length }}/{{ contacts.length }})
</Checkbox>
<div class="batch-actions" v-if="selectedContacts.length > 0">
<Button size="small" @click="batchDelete">
批量删除
</Button>
<Button size="small" type="primary" @click="batchExport">
批量导出
</Button>
</div>
</div>
<div class="contact-list">
<div
v-for="contact in contacts"
:key="contact.id"
class="contact-item"
>
<Checkbox
:model-value="selectedContacts.includes(contact.id)"
@update:model-value="toggleSelect(contact.id)"
/>
<ContactEdit
:contact-info="contact"
is-edit
@save="updateContact"
@delete="deleteContact"
/>
</div>
</div>
</div>
</template>
<script setup>
const contacts = ref([])
const selectedContacts = ref([])
const selectAll = ref(false)
// 切换选择状态
const toggleSelect = (contactId) => {
const index = selectedContacts.value.indexOf(contactId)
if (index > -1) {
selectedContacts.value.splice(index, 1)
} else {
selectedContacts.value.push(contactId)
}
}
// 全选/取消全选
const handleSelectAll = (checked) => {
if (checked) {
selectedContacts.value = contacts.value.map(c => c.id)
} else {
selectedContacts.value = []
}
}
// 批量删除
const batchDelete = async () => {
const result = await showDialog({
title: '批量删除',
message: `确定要删除选中的 ${selectedContacts.value.length} 个联系人吗?`
})
if (result === 'confirm') {
try {
await batchDeleteAPI(selectedContacts.value)
showToast('删除成功')
// 刷新列表
loadContacts()
selectedContacts.value = []
} catch (error) {
showToast('删除失败')
}
}
}
</script>❓ 常见问题解决
🔧 表单验证不生效?
问题:自定义验证函数没有被调用 解决方案:
- 确保
tel-validator属性正确绑定 - 验证函数必须返回布尔值
- 检查函数是否在正确的作用域内
📱 移动端键盘遮挡问题
问题:软键盘弹起时遮挡输入框 解决方案:
css
/* 添加视口适配 */
.van-contact-edit {
padding-bottom: env(keyboard-inset-height, 0);
}
/* 或使用 JavaScript 动态调整 */
const adjustForKeyboard = () => {
const viewport = window.visualViewport
if (viewport) {
document.documentElement.style.setProperty(
'--keyboard-height',
`${window.innerHeight - viewport.height}px`
)
}
}💾 数据保存失败处理
问题:网络异常导致保存失败 解决方案:
javascript
const saveWithRetry = async (data, maxRetries = 3) => {
for (let i = 0; i < maxRetries; i++) {
try {
await saveContactAPI(data)
return true
} catch (error) {
if (i === maxRetries - 1) {
// 最后一次重试失败,保存到本地
saveDraft(data)
showToast('网络异常,已保存为草稿')
return false
}
// 等待一段时间后重试
await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1)))
}
}
}🎨 设计灵感
🌈 主题风格定制
商务风格 - 专业简洁
css--van-contact-edit-padding: 24px; --van-contact-edit-fields-radius: 8px; --van-contact-edit-button-font-size: 16px;时尚风格 - 现代活泼
css--van-contact-edit-padding: 20px; --van-contact-edit-fields-radius: 16px; --van-contact-edit-button-font-size: 18px;极简风格 - 纯净优雅
css--van-contact-edit-padding: 32px; --van-contact-edit-fields-radius: 4px; --van-contact-edit-button-font-size: 14px;
🎯 交互体验优化
- 智能输入 - 根据输入内容自动格式化
- 快捷操作 - 支持键盘快捷键
- 手势支持 - 滑动切换编辑模式
- 语音输入 - 集成语音识别功能
📚 相关文档
- ContactCard 联系人卡片 - 联系人卡片展示组件
- Contact 联系人 - 联系人选择组件
- Field 输入框 - 表单输入组件
- Button 按钮 - 按钮组件
🔗 延伸阅读
- 表单设计最佳实践 - UX Design 表单设计指南
- 移动端表单优化 - Google 开发者文档
- 无障碍表单设计 - WebAIM 无障碍指南
- Vue 3 表单处理 - Vue 3 官方文档