You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Nuxt3中使用Nodemailer发送表单遇500错误求助

Nuxt3 + Nodemailer 表单提交500错误排查与修复

核心问题梳理

后端(contact.post.js)错误

  1. Nodemailer配置拼写错误:sendMail中的form字段应为from;且html字段引用了不存在的data.message,实际无对应字段。
  2. 邮箱验证逻辑反转:if (validator.isEmail(body.email || ''))会在邮箱格式正确时抛出“必填”错误,应改为if (!validator.isEmail(body.email || ''))。
  3. 评论字段验证逻辑错误:当前逻辑是“评论不为空就报错”,且错误信息不完整,而前端标注评论为可选,需删除该验证。
  4. Async/Await与Promise混用:嵌套then/catch导致错误捕获不清晰,改用统一try/catch更易维护。

前端(form.vue)错误

  1. 邮箱字段v-model绑定错误:输入框绑定form.mail,但form对象定义的是email,导致邮箱值无法正确提交。
  2. $fetch请求格式错误:无需手动JSON.stringify,直接传递form.value即可,否则后端readBody可能解析失败。
  3. 重复绑定提交事件:form标签和按钮同时绑定提交事件,导致重复请求,需移除按钮的@click。
  4. 参数传递冗余:submit函数无需接收form参数,直接使用定义好的ref对象即可。

修正后的代码

后端contact.post.js

import nodemailer from 'nodemailer';
import validator from 'validator';
const config = useRuntimeConfig();

const transporter = nodemailer.createTransport({
    host: config.MAILHOST,
    port: config.MAILPORT,
    auth: {
        user: config.MAILUSER,
        pass: config.MAILPASSWORD,
    },
});

export default defineEventHandler(async (event) => {
    try {
        const body = await readBody(event);
        const data = await isValid(body);
        
        const mail = await transporter.sendMail({
            from: `"${data.name}" <${data.email}>`, // 修正form为from
            to: config.CONTACTMAIL,
            subject: `来自${data.name}的咨询`, // 调整为合理主题
            text: `姓名:${data.name}\n电话:${data.phone}\n邮箱:${data.email}\n预算:${data.budget}\n评论:${data.comment}`,
            html: `<p>姓名:${data.name}</p><p>电话:${data.phone}</p><p>邮箱:${data.email}</p><p>预算:${data.budget}</p><p>评论:${data.comment || '无'}</p>`,
        });

        console.log('Message sent: %s', mail.messageId);
        console.log('Preview URL: %s', nodemailer.getTestMessageUrl(mail));
        return '发送成功!';
    } catch (error) {
        console.error('发送失败:', error);
        sendError(event, createError({ 
            statusCode: error.name === 'ValidationError' ? 400 : 500,
            statusMessage: Array.isArray(error) ? error.map(e => e.error).join(', ') : error.message 
        }));
    }
});

async function isValid(body) {
    const errors = [];

    if (validator.isEmpty(body.name || ''))
        errors.push({ field: 'name', error: '姓名为必填项。' });
    if (validator.isEmpty(body.phone || ''))
        errors.push({ field: 'phone', error: '电话为必填项。' });
    if (!validator.isEmail(body.email || '')) // 修正验证逻辑
        errors.push({ field: 'email', error: '请输入有效的邮箱地址。' });
    if (validator.isEmpty(body.budget || ''))
        errors.push({ field: 'budget', error: '预算为必填项。' });

    if (errors.length > 0) {
        const validationError = new Error('验证失败');
        validationError.name = 'ValidationError';
        validationError.details = errors;
        throw validationError;
    } else {
        return {
            email: validator.normalizeEmail(body.email),
            name: validator.escape(body.name),
            phone: validator.escape(body.phone),
            budget: validator.escape(body.budget),
            comment: validator.escape(body.comment || ''),
        };
    }
}

前端form.vue

<template>
  <div class="m-3 lg:m-1">
    <form @submit.prevent="submit">
      <div id="form" class="flex tad">
        <div class="forma w-1/3 m-8">
          <h1 class="text-volna send text-4xl mb-3">Оставь заявку</h1>
          <div>
            <p class="text-white">Ваше ФИО</p>
            <input
              v-model="form.name"
              name="name"
              class="inp"
              type="text"
              placeholder="введите ваше имя"
            />
            <p class="text-white">Ваш номер телефона</p>
            <input
              v-model="form.phone"
              name="phone"
              class="inp"
              type="text"
              placeholder="Ввведите номер для связи с вами"
            />
            <p class="text-white">Ваш email</p>
            <input
              name="email"
              v-model="form.email" <!-- 修正绑定字段 -->
              class="inp"
              type="email"
              placeholder="введите контактный email"
            />
          </div>
        </div>
        <div class="forma w-1/3 m-8">
          <div class="sec_forma">
            <div>
              <p class="text-white">Ваш бюджет</p>
              <input
                v-model="form.budget"
                name="money"
                class="inp"
                type="text"
                placeholder="введите ваш бюджет"
              />
              <p class="text-white">Введите Ваш комментарий (по желанию)</p>
              <input
                v-model="form.comment"
                name="comment"
                class="inp"
                type="text"
                placeholder=" ваш комментарий"
              />
              <button
                type="submit" <!-- 移除重复点击事件 -->
                class="button button-1 mb-3"
              >
                отправить
              </button>
            </div>
          </div>
        </div>
      </div>
    </form>
  </div>
</template>

<script setup>
const form = ref({
  name: '',
  phone: '',
  email: '',
  budget: '',
  comment: '',
});

const errors = ref(false);
const success = ref(false);
const waiting = ref(false);

async function submit() {
  waiting.value = true;
  try {
    await $fetch('/api/contact', { // 用相对路径避免硬编码
      method: 'POST',
      body: form.value, // 直接传递对象
    });
    errors.value = false;
    success.value = true;
    form.value = {
      name: '',
      phone: '',
      email: '',
      budget: '',
      comment: '',
    };
  } catch (err) {
    errors.value = true;
    success.value = false;
    console.error('提交失败:', err);
  } finally {
    waiting.value = false;
  }
}
</script>

额外排查建议

  1. 检查nuxt.config.ts中的环境变量,确保MAILHOST、MAILPORT、MAILUSER、MAILPASSWORD、CONTACTMAIL已正确配置(服务器端变量需加NUXT_前缀)。
  2. 启动Nuxt时查看终端日志,后端错误日志会明确显示具体问题(如邮箱服务器连接失败、认证错误等)。
  3. 用Postman测试时,携带完整请求体(包含name、phone、email、budget字段),查看返回的错误详情定位问题。

内容的提问来源于stack exchange,提问作者forr

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.07 15:04:50