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

禁用JavaScript时在前端展示Hapi服务端校验结果的方案

实现方案(无客户端JavaScript)

1. 核心思路

完全依赖服务端渲染(SSR)完成校验反馈:表单提交后由Hapi接收请求,通过Joi完成服务端校验;校验失败时,将字段级错误信息、错误汇总与用户提交的原始数据一起传入模板引擎,由模板根据错误信息渲染带样式的表单页面,实现红色边框、字段提示、错误汇总的效果。

2. 具体实现步骤

(1)配置Hapi模板引擎

以Handlebars为例,先安装依赖并注册模板插件:

const Hapi = require('@hapi/hapi');
const Vision = require('@hapi/vision');
const Handlebars = require('handlebars');

const init = async () => {
  const server = Hapi.server({ port: 3000 });

  // 注册模板插件
  await server.register(Vision);

  // 配置Handlebars模板引擎
  server.views({
    engines: { html: Handlebars },
    relativeTo: __dirname,
    path: 'templates' // 模板文件存放目录
  });

  // 后续路由逻辑...
};

init();

(2)Joi校验与错误格式化

在表单提交路由中,用Joi校验并整理错误信息(需开启abortEarly: false收集所有字段错误):

const Joi = require('joi');

server.route({
  method: 'POST',
  path: '/submit',
  handler: async (request, h) => {
    // 定义校验规则,配置自定义错误信息
    const schema = Joi.object({
      username: Joi.string().min(3).max(20).required().messages({
        'string.empty': '用户名不能为空',
        'string.min': '用户名至少3个字符',
        'any.required': '用户名是必填项'
      }),
      email: Joi.string().email().required().messages({
        'string.email': '请输入有效的邮箱地址',
        'any.required': '邮箱是必填项'
      })
    });

    // 校验表单数据,收集所有错误
    const { error, value } = schema.validate(request.payload, { abortEarly: false });

    if (error) {
      // 整理字段错误为{字段名: 错误信息}格式
      const fieldErrors = {};
      error.details.forEach(detail => {
        const field = detail.path[0];
        fieldErrors[field] = detail.message;
      });
      // 生成错误汇总文本
      const errorSummary = error.details.map(d => d.message).join(';');

      // 返回带错误信息的表单页面,同时回显用户输入
      return h.view('form', {
        data: request.payload,
        fieldErrors,
        errorSummary
      });
    }

    // 校验通过,处理业务逻辑(如存数据库)
    return h.view('success', { data: value });
  }
});

(3)模板中渲染错误样式与提示

在Handlebars模板templates/form.html中,根据传入的错误信息渲染样式:

<!DOCTYPE html>
<html>
<head>
  <style>
    .error-field {
      border: 2px solid red;
    }
    .error-message {
      color: red;
      font-size: 0.8em;
      margin-left: 8px;
    }
    .error-summary {
      color: red;
      margin-bottom: 1em;
      padding: 0.5em;
      border: 1px solid red;
      background-color: #ffebee;
    }
  </style>
</head>
<body>
  <!-- 错误汇总 -->
  {{#if errorSummary}}
    <div class="error-summary">
      请修正以下问题:{{errorSummary}}
    </div>
  {{/if}}

  <form method="POST" action="/submit">
    <!-- 用户名字段 -->
    <div style="margin: 1em 0;">
      <label>用户名:</label>
      <input 
        type="text" 
        name="username" 
        value="{{data.username}}"
        class="{{#if fieldErrors.username}}error-field{{/if}}"
      >
      {{#if fieldErrors.username}}
        <span class="error-message">{{fieldErrors.username}}</span>
      {{/if}}
    </div>

    <!-- 邮箱字段 -->
    <div style="margin: 1em 0;">
      <label>邮箱:</label>
      <input 
        type="text" 
        name="email" 
        value="{{data.email}}"
        class="{{#if fieldErrors.email}}error-field{{/if}}"
      >
      {{#if fieldErrors.email}}
        <span class="error-message">{{fieldErrors.email}}</span>
      {{/if}}
    </div>

    <button type="submit">提交</button>
  </form>
</body>
</html>

3. 简化错误处理的小技巧

可以封装一个Joi错误格式化工具函数,减少路由中的重复代码:

const formatJoiErrors = (error) => {
  const fieldErrors = {};
  const errorSummary = [];
  if (!error) return { fieldErrors, errorSummary };
  
  error.details.forEach(detail => {
    const field = detail.path[0];
    fieldErrors[field] = detail.message;
    errorSummary.push(detail.message);
  });
  return {
    fieldErrors,
    errorSummary: errorSummary.join(';')
  };
};

之后在路由中直接调用:const { fieldErrors, errorSummary } = formatJoiErrors(error);


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.17 19:45:40