禁用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
相关产品推荐
相关产品推荐

