EmailVerify LogoEmailVerify

Vue.js

Email checker for Vue.js. Real-time email verification in Vue components and forms.

将 EmailVerify 整合到您的 Vue 3 應用中以实現即時電子郵件驗證。本指南涵盖 Composition API 和 Options API 实現,從基础設定到高级模式。

安裝

安裝 EmailVerify 包并設定您的項目。

npm install emailverify
yarn add emailverify
pnpm add emailverify

快速開始

Composition API

在您的元件中使用 Composition API 進行反应式電子郵件驗證。

<template>
  <div class="email-verification">
    <input
      v-model="email"
      type="email"
      placeholder="輸入電子郵件地址"
      @blur="verifyEmail"
      class="email-input"
    />

    <div v-if="isLoading" class="status loading">
      驗證中...
    </div>
    <div v-else-if="result" :class="['status', result.status]">
      <span v-if="result.status === 'valid'">✓ 有效的電子郵件</span>
      <span v-else-if="result.status === 'invalid'">✗ 無效的電子郵件</span>
      <span v-else>? 无法驗證</span>
    </div>
  </div>
</template>

<script setup lang="ts">
import { ref } from 'vue';
import { emailverify } from 'emailverify';

const email = ref('');
const isLoading = ref(false);
const result = ref<any>(null);

const verifyEmail = async () => {
  if (!email.value) return;

  isLoading.value = true;
  try {
    result.value = await emailverify.verify({
      email: email.value,
      apiKey: import.meta.env.VITE_EMAILVERIFY_API_KEY
    });
  } catch (error) {
    console.error('驗證失敗:', error);
    result.value = null;
  } finally {
    isLoading.value = false;
  }
};
</script>

<style scoped>
.email-input {
  padding: 8px 12px;
  border: 1px solid #ccc;
  border-radius: 4px;
  font-size: 16px;
  width: 100%;
}

.status {
  margin-top: 8px;
  padding: 8px 12px;
  border-radius: 4px;
  font-size: 14px;
}

.status.valid {
  background-color: #d4edda;
  color: #155724;
  border: 1px solid #c3e6cb;
}

.status.invalid {
  background-color: #f8d7da;
  color: #721c24;
  border: 1px solid #f5c6cb;
}

.status.loading {
  background-color: #d1ecf1;
  color: #0c5460;
  border: 1px solid #bee5eb;
}
</style>

Options API

对於使用 Options API 模式的項目:

<template>
  <div class="email-verification">
    <input
      v-model="email"
      type="email"
      placeholder="輸入電子郵件地址"
      @blur="verifyEmail"
      class="email-input"
    />

    <div v-if="isLoading" class="status loading">
      驗證中...
    </div>
    <div v-else-if="result" :class="['status', result.status]">
      {{ statusMessage }}
    </div>
  </div>
</template>

<script lang="ts">
import { defineComponent } from 'vue';
import { emailverify } from 'emailverify';

export default defineComponent({
  name: 'EmailVerification',
  data() {
    return {
      email: '',
      isLoading: false,
      result: null
    };
  },
  computed: {
    statusMessage(): string {
      if (this.result?.status === 'valid') return '✓ 有效的電子郵件';
      if (this.result?.status === 'invalid') return '✗ 無效的電子郵件';
      return '? 无法驗證';
    }
  },
  methods: {
    async verifyEmail() {
      if (!this.email) return;

      this.isLoading = true;
      try {
        this.result = await emailverify.verify({
          email: this.email,
          apiKey: import.meta.env.VITE_EMAILVERIFY_API_KEY
        });
      } catch (error) {
        console.error('驗證失敗:', error);
        this.result = null;
      } finally {
        this.isLoading = false;
      }
    }
  }
});
</script>

Composables 參考

useEmailVerification

用於管理電子郵件驗證状态和逻辑的自定義 Composable。

import { ref, computed } from 'vue';
import { emailverify } from 'emailverify';

export function useEmailVerification(apiKey: string) {
  const email = ref('');
  const isLoading = ref(false);
  const error = ref<string | null>(null);
  const result = ref<any>(null);

  const isValid = computed(() => result.value?.status === 'valid');
  const isInvalid = computed(() => result.value?.status === 'invalid');
  const isDisposable = computed(() => result.value?.result?.disposable || false);

  const verify = async (emailToVerify?: string) => {
    const emailAddress = emailToVerify || email.value;
    if (!emailAddress) {
      error.value = '電子郵件地址是必需的';
      return;
    }

    isLoading.value = true;
    error.value = null;

    try {
      result.value = await emailverify.verify({
        email: emailAddress,
        apiKey
      });
    } catch (err) {
      error.value = err instanceof Error ? err.message : '驗證失敗';
      result.value = null;
    } finally {
      isLoading.value = false;
    }
  };

  const reset = () => {
    email.value = '';
    result.value = null;
    error.value = null;
    isLoading.value = false;
  };

  return {
    email,
    isLoading,
    error,
    result,
    isValid,
    isInvalid,
    isDisposable,
    verify,
    reset
  };
}

useEmailInput

用於處理帶防抖和快取的電子郵件輸入的 Composable。

import { ref, computed, watch } from 'vue';
import { useEmailVerification } from './useEmailVerification';

const CACHE_DURATION = 30 * 60 * 1000; // 30 分钟

export function useEmailInput(apiKey: string) {
  const email = ref('');
  const cache = new Map<string, { result: any; timestamp: number }>();
  const { verify: verifyWithAPI, isLoading, error, result } = useEmailVerification(apiKey);

  const verify = async () => {
    if (!email.value) return;

    // 先檢查快取
    const cached = cache.get(email.value);
    if (cached && Date.now() - cached.timestamp < CACHE_DURATION) {
      result.value = cached.result;
      return;
    }

    // 使用 API 驗證
    await verifyWithAPI(email.value);

    // 快取結果
    if (result.value) {
      cache.set(email.value, {
        result: result.value,
        timestamp: Date.now()
      });
    }
  };

  const clearCache = () => cache.clear();

  return {
    email,
    isLoading,
    error,
    result,
    verify,
    clearCache
  };
}

元件整合

VeeValidate 整合

使用 VeeValidate 将電子郵件驗證與表單驗證整合。

<template>
  <Form @submit="onSubmit" class="form">
    <div class="form-group">
      <label>電子郵件地址</label>
      <Field
        name="email"
        type="email"
        placeholder="your@email.com"
        v-slot="{ field, meta }"
        :rules="validateEmail"
      >
        <input
          v-bind="field"
          :class="{ 'input-error': meta.touched && !meta.valid }"
          @blur="verifyEmail(field.value)"
        />
      </Field>

      <div v-if="verificationResult" :class="['verification-status', verificationResult.status]">
        {{ getStatusMessage(verificationResult) }}
      </div>

      <ErrorMessage name="email" class="error-message" />
    </div>

    <button type="submit" :disabled="isLoading">
      {{ isLoading ? '驗證中...' : '提交' }}
    </button>
  </Form>
</template>

<script setup lang="ts">
import { Form, Field, ErrorMessage } from 'vee-validate';
import * as yup from 'yup';
import { ref } from 'vue';
import { useEmailVerification } from './composables/useEmailVerification';

const schema = yup.object({
  email: yup.string().email('無效的電子郵件').required('電子郵件是必需的')
});

const { verify, result: verificationResult, isLoading } = useEmailVerification(
  import.meta.env.VITE_EMAILVERIFY_API_KEY
);

const validateEmail = async (value: string) => {
  if (!value) return '電子郵件是必需的';

  try {
    await schema.validate({ email: value });
    return true;
  } catch {
    return '電子郵件格式無效';
  }
};

const verifyEmail = async (email: string) => {
  if (email) {
    await verify(email);
  }
};

const getStatusMessage = (result: any) => {
  switch (result.status) {
    case 'valid':
      return '✓ 有效的電子郵件地址';
    case 'invalid':
      return '✗ 無效的電子郵件地址';
    case 'unknown':
      return '? 无法驗證此電子郵件';
    default:
      return '未知状态';
  }
};

const onSubmit = (values: any) => {
  if (verificationResult.value?.status === 'valid') {
    console.log('表單已提交:', values);
  }
};
</script>

<style scoped>
.form-group {
  margin-bottom: 16px;
}

label {
  display: block;
  margin-bottom: 8px;
  font-weight: 500;
}

input {
  width: 100%;
  padding: 8px 12px;
  border: 1px solid #ccc;
  border-radius: 4px;
  font-size: 14px;
}

input.input-error {
  border-color: #dc3545;
  background-color: #fff5f5;
}

.error-message {
  color: #dc3545;
  font-size: 12px;
  margin-top: 4px;
}

.verification-status {
  margin-top: 8px;
  padding: 8px 12px;
  border-radius: 4px;
  font-size: 13px;
}

.verification-status.valid {
  background-color: #d4edda;
  color: #155724;
}

.verification-status.invalid {
  background-color: #f8d7da;
  color: #721c24;
}
</style>

Element Plus 整合

使用 EmailVerify 與 Element Plus 表單元件。

<template>
  <el-form :model="form" @submit.prevent="handleSubmit">
    <el-form-item label="電子郵件" prop="email">
      <el-input
        v-model="form.email"
        type="email"
        placeholder="輸入電子郵件"
        @blur="verifyEmail"
        :loading="isLoading"
      />

      <el-alert
        v-if="verificationResult"
        :title="getStatusTitle()"
        :type="getAlertType()"
        :closable="false"
        style="margin-top: 8px"
      />
    </el-form-item>

    <el-form-item>
      <el-button
        type="primary"
        @click="handleSubmit"
        :loading="isLoading"
      >
        提交
      </el-button>
    </el-form-item>
  </el-form>
</template>

<script setup lang="ts">
import { reactive, ref } from 'vue';
import { useEmailVerification } from './composables/useEmailVerification';

const form = reactive({
  email: ''
});

const { verify, result: verificationResult, isLoading } = useEmailVerification(
  import.meta.env.VITE_EMAILVERIFY_API_KEY
);

const verifyEmail = async () => {
  if (form.email) {
    await verify(form.email);
  }
};

const getStatusTitle = () => {
  switch (verificationResult.value?.status) {
    case 'valid':
      return '電子郵件有效';
    case 'invalid':
      return '電子郵件無效';
    default:
      return '无法驗證電子郵件';
  }
};

const getAlertType = () => {
  return verificationResult.value?.status === 'valid' ? 'success' : 'error';
};

const handleSubmit = async () => {
  await verifyEmail();
  if (verificationResult.value?.status === 'valid') {
    console.log('電子郵件已驗證,提交表單...');
  }
};
</script>

Nuxt 3 中的 SSR

伺服器端驗證

在伺服器上驗證電子郵件以获得更好的安全性和性能。

// server/api/verify-email.ts
import { emailverify } from 'emailverify';

export default defineEventHandler(async (event) => {
  const { email } = await readBody(event);

  if (!email) {
    throw createError({
      statusCode: 400,
      statusMessage: '電子郵件是必需的'
    });
  }

  try {
    const result = await emailverify.verify({
      email,
      apiKey: process.env.EMAILVERIFY_API_KEY!
    });

    return result;
  } catch (error) {
    throw createError({
      statusCode: 500,
      statusMessage: '驗證失敗'
    });
  }
});

Nuxt 中的客戶端整合

在您的 Nuxt 元件中使用伺服器端点。

<template>
  <div class="email-verification">
    <input
      v-model="email"
      type="email"
      placeholder="your@email.com"
      @blur="verifyEmail"
    />

    <div v-if="pending" class="status loading">
      驗證中...
    </div>
    <div v-else-if="result" :class="['status', result.status]">
      {{ statusMessage }}
    </div>
  </div>
</template>

<script setup lang="ts">
import { ref, computed } from 'vue';

const email = ref('');
const { data: result, pending, execute: verifyEmail } = await useFetch(
  '/api/verify-email',
  {
    method: 'POST',
    body: computed(() => ({ email: email.value })),
    immediate: false
  }
);

const statusMessage = computed(() => {
  if (!result.value) return '';
  switch (result.value.status) {
    case 'valid':
      return '✓ 有效的電子郵件';
    case 'invalid':
      return '✗ 無效的電子郵件';
    default:
      return '? 未知状态';
  }
});
</script>

高级模式

防抖驗證

通过防抖電子郵件驗證减少 API 呼叫。

import { ref, watch } from 'vue';
import { useEmailVerification } from './composables/useEmailVerification';

export function useDebouncedEmailVerification(apiKey: string, delay = 500) {
  const email = ref('');
  const debounceTimer = ref<NodeJS.Timeout | null>(null);
  const { verify, ...rest } = useEmailVerification(apiKey);

  watch(email, (newEmail) => {
    if (debounceTimer.value) {
      clearTimeout(debounceTimer.value);
    }

    debounceTimer.value = setTimeout(() => {
      if (newEmail) {
        verify(newEmail);
      }
    }, delay);
  });

  return { email, verify, ...rest };
}

電子郵件快取策略

实現智能快取以减少 API 呼叫。

const CACHE_KEY_PREFIX = 'emailverify_';
const CACHE_DURATION = 24 * 60 * 60 * 1000; // 24 小時

export function useCachedEmailVerification(apiKey: string) {
  const { verify: verifyWithAPI, result, isLoading } = useEmailVerification(apiKey);

  const getCacheKey = (email: string) => `${CACHE_KEY_PREFIX}${email}`;

  const verify = async (email: string) => {
    const cacheKey = getCacheKey(email);
    const cached = localStorage.getItem(cacheKey);

    if (cached) {
      const { result: cachedResult, timestamp } = JSON.parse(cached);
      if (Date.now() - timestamp < CACHE_DURATION) {
        result.value = cachedResult;
        return;
      }
    }

    await verifyWithAPI(email);

    if (result.value) {
      localStorage.setItem(
        cacheKey,
        JSON.stringify({
          result: result.value,
          timestamp: Date.now()
        })
      );
    }
  };

  const clearCache = (email?: string) => {
    if (email) {
      localStorage.removeItem(getCacheKey(email));
    } else {
      const keys = Object.keys(localStorage);
      keys.forEach(key => {
        if (key.startsWith(CACHE_KEY_PREFIX)) {
          localStorage.removeItem(key);
        }
      });
    }
  };

  return { verify, result, isLoading, clearCache };
}

錯誤處理和重試

实現包含重試逻辑的强大錯誤處理。

export function useEmailVerificationWithRetry(apiKey: string, maxRetries = 3) {
  const { verify: verifyWithAPI, result, isLoading, error } = useEmailVerification(apiKey);

  const verifyWithRetry = async (email: string, retryCount = 0): Promise<any> => {
    try {
      await verifyWithAPI(email);
      return result.value;
    } catch (err) {
      if (retryCount < maxRetries) {
        // 重試前等待(指数退避)
        await new Promise(resolve => setTimeout(resolve, Math.pow(2, retryCount) * 1000));
        return verifyWithRetry(email, retryCount + 1);
      }
      throw err;
    }
  };

  return { verifyWithRetry, result, isLoading, error };
}

TypeScript 支持

EmailVerify SDK 包含完整的 TypeScript 支持和类型定義。

interface VerificationRequest {
  email: string;
  apiKey: string;
}

interface VerificationResult {
  status: 'valid' | 'invalid' | 'unknown' | 'accept_all';
  result: {
    disposable: boolean;
    smtp_valid: boolean;
    format_valid: boolean;
    email_provider: string;
    risk_level: 'low' | 'medium' | 'high';
  };
  score: number;
}

interface UseEmailVerificationReturn {
  email: Ref<string>;
  isLoading: Ref<boolean>;
  error: Ref<string | null>;
  result: Ref<VerificationResult | null>;
  isValid: ComputedRef<boolean>;
  isInvalid: ComputedRef<boolean>;
  isDisposable: ComputedRef<boolean>;
  verify: (emailToVerify?: string) => Promise<void>;
  reset: () => void;
}

测試

使用 Vitest 進行單元测試

使用 Vitest 测試您的電子郵件驗證 Composables。

import { describe, it, expect, vi } from 'vitest';
import { useEmailVerification } from './composables/useEmailVerification';

vi.mock('emailverify', () => ({
  emailverify: {
    verify: vi.fn()
  }
}));

describe('useEmailVerification', () => {
  it('驗證有效的電子郵件', async () => {
    const { verify, result, isValid } = useEmailVerification('test-key');

    await verify('user@example.com');

    expect(result.value?.status).toBe('valid');
    expect(isValid.value).toBe(true);
  });

  it('标记無效的電子郵件', async () => {
    const { verify, result, isInvalid } = useEmailVerification('test-key');

    await verify('invalid@');

    expect(result.value?.status).toBe('invalid');
    expect(isInvalid.value).toBe(true);
  });

  it('處理驗證錯誤', async () => {
    const { verify, error } = useEmailVerification('test-key');

    try {
      await verify('');
    } catch (err) {
      expect(error.value).toBeTruthy();
    }
  });
});

元件测試

使用 Vue Test Utils 测試您的 Vue 元件。

import { mount } from '@vue/test-utils';
import { describe, it, expect } from 'vitest';
import EmailVerification from './EmailVerification.vue';

describe('EmailVerification', () => {
  it('渲染輸入字段', () => {
    const wrapper = mount(EmailVerification);
    expect(wrapper.find('input[type="email"]').exists()).toBe(true);
  });

  it('显示驗證状态', async () => {
    const wrapper = mount(EmailVerification);
    await wrapper.find('input').setValue('user@example.com');
    await wrapper.find('input').trigger('blur');

    expect(wrapper.find('.status').exists()).toBe(true);
  });

  it('在驗證期间显示加载状态', async () => {
    const wrapper = mount(EmailVerification);
    await wrapper.find('input').setValue('user@example.com');

    expect(wrapper.find('.status.loading').exists()).toBe(true);
  });
});

相关資源

On this page