HTML

<form class="contact-form" id="contactForm" novalidate>
  <div class="form-group">
    <label for="name">姓名 *</label>
    <input type="text" id="name" name="name" required minlength="2"
      placeholder="请输入您的姓名">
    <span class="error-message" id="nameError"></span>
  </div>

  <div class="form-group">
    <label for="email">邮箱 *</label>
    <input type="email" id="email" name="email" required
      placeholder="请输入您的邮箱">
    <span class="error-message" id="emailError"></span>
  </div>

  <div class="form-group">
    <label for="message">留言 *</label>
    <textarea id="message" name="message" required minlength="10"
      rows="5" placeholder="请输入留言内容(至少 10 个字符)"></textarea>
    <span class="error-message" id="messageError"></span>
  </div>

  <!-- 蜜罐字段 -->
  <input type="text" name="website" class="honeypot" tabindex="-1" autocomplete="off">

  <button type="submit" class="submit-btn" id="submitBtn">
    <span class="btn-text">发送留言</span>
    <span class="btn-loading" style="display:none">发送中...</span>
  </button>
</form>

<div id="formSuccess" class="form-success" style="display:none">
  <h3>✓ 感谢您的留言!</h3>
  <p>我们会尽快回复您。</p>
</div>

CSS

.contact-form {
  max-width: 600px;
  margin: 0 auto;
  padding: 32px;
  background: #f9fafb;
  border-radius: 12px;
}

.form-group {
  margin-bottom: 20px;
}

.form-group label {
  display: block;
  margin-bottom: 6px;
  font-weight: 500;
  color: #374151;
}

.form-group input,
.form-group textarea {
  width: 100%;
  padding: 10px 14px;
  border: 1px solid #d1d5db;
  border-radius: 8px;
  font-size: 16px;
  transition: border-color .2s;
  box-sizing: border-box;
}

.form-group input:focus,
.form-group textarea:focus {
  outline: none;
  border-color: #4F46E5;
  box-shadow: 0 0 0 3px rgba(79,70,229,.1);
}

.form-group input.error,
.form-group textarea.error {
  border-color: #ef4444;
}

.error-message {
  display: block;
  margin-top: 4px;
  font-size: 14px;
  color: #ef4444;
  min-height: 20px;
}

.honeypot {
  display: none !important;
}

.submit-btn {
  width: 100%;
  padding: 12px 24px;
  background: #4F46E5;
  color: #fff;
  border: none;
  border-radius: 8px;
  font-size: 16px;
  font-weight: 500;
  cursor: pointer;
  transition: background .2s;
}

.submit-btn:hover { background: #4338CA; }
.submit-btn:disabled { opacity: .6; cursor: not-allowed; }

.form-success {
  text-align: center;
  padding: 32px;
  background: #f0fdf4;
  border-radius: 12px;
  color: #166534;
}

JavaScript

document.addEventListener('DOMContentLoaded', () => {
  const form = document.getElementById('contactForm');
  const submitBtn = document.getElementById('submitBtn');
  const btnText = submitBtn.querySelector('.btn-text');
  const btnLoading = submitBtn.querySelector('.btn-loading');

  // 实时验证
  const fields = ['name', 'email', 'message'];
  fields.forEach(field => {
    const input = document.getElementById(field);
    input.addEventListener('blur', () => validateField(field));
    input.addEventListener('input', () => {
      const error = document.getElementById(field + 'Error');
      if (error.textContent) validateField(field);
    });
  });

  function validateField(field) {
    const input = document.getElementById(field);
    const error = document.getElementById(field + 'Error');
    let message = '';

    switch (field) {
      case 'name':
        if (!input.value.trim()) message = '请输入姓名';
        else if (input.value.trim().length < 2) message = '姓名至少 2 个字符';
        break;
      case 'email':
        if (!input.value.trim()) message = '请输入邮箱';
        else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(input.value))
          message = '邮箱格式不正确';
        break;
      case 'message':
        if (!input.value.trim()) message = '请输入留言';
        else if (input.value.trim().length < 10) message = '留言至少 10 个字符';
        break;
    }

    error.textContent = message;
    input.classList.toggle('error', !!message);
    return !message;
  }

  // 提交表单
  form.addEventListener('submit', async (e) => {
    e.preventDefault();

    // 验证所有字段
    const valid = fields.every(validateField);
    if (!valid) return;

    // 显示加载状态
    submitBtn.disabled = true;
    btnText.style.display = 'none';
    btnLoading.style.display = 'inline';

    try {
      const formData = new FormData(form);
      const data = Object.fromEntries(formData);

      const response = await fetch('/api/contact', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(data)
      });

      const result = await response.json();

      if (response.ok) {
        form.style.display = 'none';
        document.getElementById('formSuccess').style.display = 'block';
      } else {
        alert(result.error || '提交失败,请稍后重试');
      }
    } catch (error) {
      alert('网络错误,请检查连接后重试');
    } finally {
      submitBtn.disabled = false;
      btnText.style.display = 'inline';
      btnLoading.style.display = 'none';
    }
  });
});

功能特性

  • ✅ 实时输入验证(失焦 + 输入时)
  • ✅ 异步提交(无页面刷新)
  • ✅ 加载状态(按钮禁用 + 加载动画)
  • ✅ 错误提示(字段级别 + 全局)
  • ✅ 成功反馈(替换表单为成功消息)
  • ✅ 蜜罐反垃圾保护
  • ✅ 配合后端 PHP/Node.js/Flask 使用