Skip to content

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'; 
export default { 
  setup() { 
    const editingContact = ref({ tel: '', name: '', }); 
    const onSave = (contactInfo) => showToast('儲存'); 
    const onDelete = (contactInfo) => showToast('刪除'); 
    return { onSave, onDelete, editingContact, }; }, };

📖 API

Props

參數說明類型預設值
contact-info聯絡人資訊ContactEditInfo{}
is-edit是否為編輯聯絡人booleanfalse
is-saving是否顯示儲存按鈕載入動畫booleanfalse
is-deleting是否顯示刪除按鈕載入動畫booleanfalse
tel-validator手機號格式校驗函數(tel: string) => boolean-
show-set-default是否顯示預設聯絡人欄booleanfalse
set-default-label預設聯絡人欄文案string-

Events

事件名說明回調參數
save點擊儲存按鈕時觸發content:表單內容
delete點擊刪除按鈕時觸發content:表單內容
change-default切換是否為預設聯絡人時觸發checked:是否預設

ContactEditInfo 資料結構

鍵名說明類型
name聯絡人姓名string
tel聯絡人手機號string
isDefault是否預設*boolean

類型定義

元件匯出以下類型定義:

ts
import type { ContactEditInfo, ContactEditProps } from'vant';

🎨 主題定製

樣式變數

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

名稱預設值描述
--van-contact-edit-paddingvar(--van-padding-md)-
--van-contact-edit-fields-radiusvar(--van-radius-md)-
--van-contact-edit-buttons-paddingvar(--van-padding-xl) 0-
--van-contact-edit-button-margin-bottomvar(--van-padding-sm)-
--van-contact-edit-button-font-sizevar(--van-font-size-lg)-
--van-contact-edit-field-label-width4.1em-

🎯 最佳實踐

📝 表單驗證策略

  1. 即時驗證 - 在使用者輸入時提供即時回饋
  2. 友好提示 - 使用清晰易懂的錯誤資訊
  3. 防重複提交 - 避免使用者多次點擊儲存按鈕
  4. 資料持久化 - 自動儲存草稿,防止資料遺失

🔍 進階表單驗證

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>

❓ 常見問題解決

🔧 表單驗證不生效?

問題:自訂驗證函數沒有被呼叫 解決方案

  1. 確保 tel-validator 屬性正確綁定
  2. 驗證函數必須回傳布林值
  3. 檢查函數是否在正確的作用域內

📱 行動端鍵盤遮擋問題

問題:軟鍵盤彈起時遮擋輸入框 解決方案

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)))
    }
  }
}

🎨 設計靈感

🌈 主題風格定製

  1. 商務風格 - 專業簡潔

    css
    --van-contact-edit-padding: 24px;
    --van-contact-edit-fields-radius: 8px;
    --van-contact-edit-button-font-size: 16px;
  2. 時尚風格 - 現代活潑

    css
    --van-contact-edit-padding: 20px;
    --van-contact-edit-fields-radius: 16px;
    --van-contact-edit-button-font-size: 18px;
  3. 極簡風格 - 純淨優雅

    css
    --van-contact-edit-padding: 32px;
    --van-contact-edit-fields-radius: 4px;
    --van-contact-edit-button-font-size: 14px;

🎯 互動體驗最佳化

  • 智慧輸入 - 根據輸入內容自動格式化
  • 快捷操作 - 支援鍵盤快捷鍵
  • 手勢支援 - 滑動切換編輯模式
  • 語音輸入 - 整合語音辨識功能

📚 相關文件

🔗 延伸閱讀

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