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

如何实现POST表单无跳转提交并在页面内展示错误信息

表单无跳转提交:解决验证错误与JSON解析问题

问题描述

要实现表单提交时不跳转页面,直接在当前页面的div中显示成功/错误信息。原Express服务器能正常处理表单但会跳转页面,编写前端脚本后出现以下问题:

  • 明明填了subject和body,却一直提示「Subject is required」「Message body is required」
  • 偶尔出现JSON解析错误

问题根源

前端用JSON格式提交数据,但服务器只配置了express.urlencoded()中间件,无法解析JSON请求体,导致验证时拿不到字段值;同时服务器部分响应是纯文本,前端按JSON解析就会报错。

修复方案

前端:改用URL-encoded格式提交

把FormData转成URL-encoded字符串提交,既保留所有表单字段(包括蜜罐字段),又匹配服务器原有的解析能力:

<form id="contactForm" action="/send-email" method="POST" style="z-index: 200;">
  <div>
    <input autocomplete="off" type="text" placeholder="Subject" name="subject" required />
  </div>
  <div>
    <textarea autocomplete="off" placeholder="Your message" name="body" required></textarea>
  </div>

  <div class="oh-no-hun" >
    <input tabindex="-1" autocomplete="off" type="text" name="body2" />
  </div>
  <div>
    <button type="submit">Send a message</button>
  </div>
</form>

<div id="message" class="message"></div>

<script>
  document.getElementById('contactForm').addEventListener('submit', async function(event) {
    event.preventDefault(); // 阻止默认跳转

    const form = event.target;
    const formData = new FormData(form);
    const messageDiv = document.getElementById('message');

    // 把FormData转成URL-encoded字符串
    const urlEncodedData = new URLSearchParams(formData).toString();

    try {
      const response = await fetch(form.action, {
        method: form.method,
        headers: {
          'Content-Type': 'application/x-www-form-urlencoded', // 指定格式
        },
        body: urlEncodedData,
      });

      // 处理服务器响应
      const contentType = response.headers.get('content-type');
      let responseJson = {};

      if (contentType && contentType.includes('application/json')) {
        try {
          responseJson = await response.json();
        } catch (jsonError) {
          console.error('解析JSON失败:', jsonError);
          messageDiv.innerHTML = `<p class="error">解析服务器响应失败.</p>`;
          return;
        }
      } else {
        const responseText = await response.text();
        console.error('响应格式异常:', responseText);
        messageDiv.innerHTML = `<p class="error">响应格式异常: ${responseText}</p>`;
        return;
      }

      if (response.ok) {
        // 显示成功信息并重置表单
        messageDiv.innerHTML = `<p class="success">${responseJson.message || '邮件发送成功!'}</p>`;
        form.reset();
      } else {
        // 显示错误信息
        const errors = responseJson.errors || [{ msg: '未知错误.' }];
        messageDiv.innerHTML = `<p class="error">${errors.map(err => err.msg).join(', ')}</p>`;
      }

    } catch (error) {
      messageDiv.innerHTML = `<p class="error">发生意外错误: ${error.message}</p>`;
    }
  });
</script>

后端:兼容两种数据格式+统一JSON响应

添加JSON解析中间件,把所有响应改成JSON格式,同时优化验证和蜜罐检查逻辑:

const express = require('express');
const nodemailer = require('nodemailer');
const path = require('path');
const { body, validationResult } = require('express-validator');
require('dotenv').config();
const rateLimit = require('express-rate-limit');

const app = express();
const port = process.env.PORT;

// 复用验证函数
const validate = validations => {
  return async (req, res, next) => {
    for (const validation of validations) {
      const result = await validation.run(req);
      if (!result.isEmpty()) {
        return res.status(400).json({ errors: result.array() });
      }
    }
    next();
  };
};

// 信任代理,获取真实IP用于限流
app.set('trust proxy', true);

// 静态文件服务
app.use(express.static(path.join(__dirname, 'public')));

// 同时支持解析URL-encoded和JSON格式
app.use(express.urlencoded({ extended: true }));
app.use(express.json());

// 限流配置
const formSubmitLimiter = rateLimit({
  windowMs: 24 * 60 * 60 * 1000, // 24小时
  max: 10, // 每个IP限制10次提交
  standardHeaders: true,
  legacyHeaders: false,
  message: '提交次数过多,请稍后再试.',
});

// 表单提交路由
app.post('/send-email',
  // 蜜罐检查:只有当body2有内容时才拦截
  (req, res, next) => {
    if (req.body.body2 && req.body.body2.length > 0) {
      return res.status(400).json({ message: '表单提交失败.' });
    }
    next();
  },
  
  formSubmitLimiter,

  // 验证中间件
  validate([
    body('subject').isLength({ min: 1 }).withMessage('Subject is required'),
    body('body').isLength({ min: 1 }).withMessage('Message body is required')
  ]),

  async (req, res) => {
    const errors = validationResult(req);
    if (!errors.isEmpty()) {
      return res.status(400).json({ errors: errors.array() });
    }
    
    const { subject, body } = req.body;

    // 邮件配置
    let transporter = nodemailer.createTransport({
      service: process.env.SMTP_SERVICE,
      auth: {
        user: process.env.EMAIL_USER,
        pass: process.env.EMAIL_PASS
      }
    });

    try {
      await transporter.sendMail({
        from: `"SITENAMEREDACTED" <${process.env.EMAIL_USER}>`,
        to: process.env.RECIPIENT_EMAIL,
        subject: subject,
        text: body,
      });

      console.log('邮件发送成功');
      res.status(200).json({ message: 'Email sent successfully!' });
    } catch (error) {
      console.error('邮件发送失败:', error);
      res.status(500).json({ message: 'Error sending email' });
    }
  }
);

app.listen(port, () => {
  console.log(`服务器运行在 http://localhost:${port}`);
});

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.18 11:34:54