1. 错误消息格式

前后端消息传递时,我们可以通过 json 的 errors 字段传递错误信息,一个比较好的格式范例为:

{
errors: {
global: ["网络错误"],
password: ["至少需要一个大写字母", "至少需要八位字符"]
}
}

errors 中,字段名代表出错位置(如果是输入框的话,对应错误要显示在框下面),内容为一个数组,每个字符串代表一个错误。

2. 处理函数

可以新建一个 composables 文件夹,以存储各个 components 中共用的逻辑,例如错误消息处理。这里在 composables 文件夹中新建一个 error.ts

import { ref, type Ref } from 'vue';

export interface ErrorFields {
global: string[];
[key: string]: string[];
} export function useErrorFields(fields: string[]) {
const errors: Ref<ErrorFields> = ref({ global: [], ...fields.reduce((acc, field) => ({ ...acc, [field]: [] }), {}) });
const clearErrors = () => {
for (const field in errors.value) {
errors.value[field] = [];
}
};
const hasErrors = (field?: string) => {
if (field) {
return errors.value[field].length > 0;
}
return Object.values(errors.value).some((field) => field.length > 0);
};
const addError = (field: string, message: string) => {
if (field === '') {
field = 'global';
}
const array = errors.value[field];
if (!array.includes(message)) {
array.push(message);
}
return array;
};
const removeError = (field: string, message?: string) => {
if (field === '') {
field = 'global';
}
if (message) {
errors.value[field] = errors.value[field].filter((m) => m !== message);
} else {
errors.value[field] = [];
}
};
return { errors, clearErrors, hasErrors, addError, removeError };
}

这里我们就定义了错误类及其处理函数。

3. 组件中的使用

定义的 useErrorFields 工具可以在 component 中这样使用:

<script setup lang="ts">
import axios from 'axios';
import { computed, onMounted, ref, type Ref } from 'vue';
import { useErrorFields } from '@/composables/error'; const { errors, clearErrors, addError, hasErrors } = useErrorFields(['username', 'password']); const username = ref(''); function onSubmit() {
const api = axios.create({
baseURL: import.meta.env.VITE_API_URL,
});
api.get("/user/register")
.catch((error) => {
if (error.response && error.response.data && error.response.data.errors) {
errors.value = { ...errors.value, ...error.response.data.errors };
} else if (error.response) {
addError('', '未知错误');
} else {
addError('', '网络错误');
}
})
}
</script> <template>
<div
v-if="hasErrors('global')"
class="mb-5 rounded-md border-0 shadow-sm ring-1 ring-inset ring-gray-300 dark:ring-gray-500 px-4 py-2"
>
<div class="flex text-red-700 dark:text-rose-400 space-x-2 mb-2">
<p class="text-lg font-semibold">错误</p>
</div>
<ul class="flex flex-col font-medium tracking-wide text-sm list-disc pl-6">
<li v-for="e in errors.global" v-html="e" />
</ul>
</div>
<form>
<div>
<label for="username" class="block text-sm font-medium leading-6">
用户名
<span class="text-red-700">*</span>
</label>
<div class="mt-2">
<input
v-model="username"
@focus="clearErrors"
id="username"
name="username"
type="text"
autocomplete="username"
required
class="block w-full rounded-md border-0 py-1.5 px-3 shadow-sm ring-1 ring-inset focus:ring-2 focus:ring-inset focus:ring-indigo-600 focus:outline-none sm:text-sm sm:leading-6 dark:bg-white/10 dark:ring-white/20"
:class="{ 'ring-red-500': hasErrors('username'), 'ring-gray-300': !hasErrors('username') }"
/>
</div>
<ul class="flex flex-col font-medium tracking-wide text-red-500 text-xs mt-1 ml-1">
<li v-for="e in errors.username" v-html="e" />
</ul>
</div>
<div>
<button
type="submit"
class="flex w-full justify-center rounded-md px-3 py-1.5 text-sm font-semibold leading-6 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600 text-white shadow-sm hover:bg-indigo-500"
:class="{
'cursor-default pointer-events-none': hasErrors() || processing,
'bg-gray-400': hasErrors(),
'bg-indigo-600': !hasErrors(),
}"
>
注册
</button>
</div>
</form>
</template>

接下来,我们一步步解析以上代码。

3.1 根据后端响应更新错误状态

我们首先使用 useErrorFields 定义了一个错误状态类:

const { errors, clearErrors, addError, hasErrors } = useErrorFields(['username', 'password']);

这时候,错误状态 errors 中可访问三个字段,并将绑定到页面的不同位置:

global: 全局错误 / 无具体位置的错误 => 显示在表格顶端的单独框中

username: 用户名上的错误 => 显示在 username 输入框下方

password: 密码上的错误 => 显示在 password 输入框下方

接下来,我们需要定义提交函数,例如这里使用 axios 进行后端访问,后端地址用环境变量提供:

function onSubmit() {
const api = axios.create({
baseURL: import.meta.env.VITE_API_URL,
});
api.get("/user/register")
.catch((error) => {
if (error.response && error.response.data && error.response.data.errors) {
errors.value = { ...errors.value, ...error.response.data.errors };
} else if (error.response) {
addError('', '未知错误');
} else {
addError('', '网络错误');
}
})
}

这样,后端返回错误信息时,错误状态会被自动更新。如果出现了网络错误或其他错误,addError类会在 global 字段上增加错误 (使用空字符串为第一个参数,默认添加到 global 字段)。

接下来,将错误状态绑定到页面。

3.2 绑定到输入框

<input
v-model="username"
@focus="clearErrors"
id="username"
name="username"
type="text"
autocomplete="username"
required
class="block w-full rounded-md border-0 py-1.5 px-3 shadow-sm ring-1 ring-inset focus:ring-2 focus:ring-inset focus:ring-indigo-600 focus:outline-none sm:text-sm sm:leading-6 dark:bg-white/10 dark:ring-white/20"
:class="{ 'ring-red-500': hasErrors('username'), 'ring-gray-300': !hasErrors('username') }"
/>

这里主要使用了两个个函数:

clearErrors: 当重新开始进行输入时,清除错误状态中的全部错误。

hasErrors: 当对应位置出现错误时,将输入框边框颜色变为红色。

将错误状态显示在输入框下:

<div>
<label for="username" class="block text-sm font-medium leading-6">
用户名
<span class="text-red-700">*</span>
</label>
<div class="mt-2">
<input
...
/>
</div>
<ul class="flex flex-col font-medium tracking-wide text-red-500 text-xs mt-1 ml-1">
<li v-for="e in errors.username" v-html="e" />
</ul>
</div>

这里我们使用 <li> 标签,使用 errors.username 将对应位置的错误消息依次显示在输入框下。

3.4 全局消息显示在表格顶端

<div
v-if="hasErrors('global')"
class="mb-5 rounded-md border-0 shadow-sm ring-1 ring-inset ring-gray-300 dark:ring-gray-500 px-4 py-2"
>
<div class="flex text-red-700 dark:text-rose-400 space-x-2 mb-2">
<p class="text-lg font-semibold">错误</p>
</div>
<ul class="flex flex-col font-medium tracking-wide text-sm list-disc pl-6">
<li v-for="e in errors.global" v-html="e" />
</ul>
</div>
<form>
...
</form>

这里使用 hasErrors('global') 来检测是否有全局错误,并在输入表顶端显示。

3.5 提交按钮在有错误时不允许点击

<button
type="submit"
class="flex w-full justify-center rounded-md px-3 py-1.5 text-sm font-semibold leading-6 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600 text-white shadow-sm hover:bg-indigo-500"
:class="{
'cursor-default pointer-events-none': hasErrors(),
'bg-gray-400': hasErrors(),
'bg-indigo-600': !hasErrors(),
}"
>
注册
</button>

这里使用 hasErrors() 来检测错误状态类中是否有任何错误,并据此启用或禁用按钮。

4. 完整案例

如果你需要一个完整案例,这里有:错误状态处理在用户注册场景的案例,前端开源,详见:Github,你也可以访问 Githubstar.pro 来查看网页的效果(一个 Github 互赞平台,前端按本文方式进行错误处理)。

感谢阅读,如果本文对你有帮助,可以订阅我的博客,我将继续分享前后端全栈开发的相关实用经验。祝你开发愉快

Vue 3 后端错误消息处理范例的更多相关文章

  1. ASP.NET WebApi+Vue前后端分离之允许启用跨域请求

    前言: 这段时间接手了一个新需求,将一个ASP.NET MVC项目改成前后端分离项目.前端使用Vue,后端则是使用ASP.NET WebApi.在搭建完成前后端框架后,进行接口测试时发现了一个前后端分 ...

  2. Vue之前后端交互

    Vue之前后端交互 一.前后端交互模式 接口调用方式 原生ajax 基于jQuery的ajax fetch axios 异步 JavaScript的执行环境是「单线程」 所谓单线程,是指JS引擎中负责 ...

  3. 三、vue前后端交互(轻松入门vue)

    轻松入门vue系列 Vue前后端交互 六.Vue前后端交互 1. 前后端交互模式 2. Promise的相关概念和用法 Promise基本用法 then参数中的函数返回值 基于Promise处理多个A ...

  4. vue根据后端菜单自动生成路由(动态路由)

    vue根据后端菜单自动生成路由(动态路由) router.js import Vue from 'vue' import Router from 'vue-router' import store f ...

  5. 解决Django+Vue前后端分离的跨域问题及关闭csrf验证

      前后端分离难免要接触到跨域问题,跨域的相关知识请参:跨域问题,解决之道   在Django和Vue前后端分离的时候也会遇到跨域的问题,因为刚刚接触Django还不太了解,今天花了好长的时间,查阅了 ...

  6. Flask + vue 前后端分离的 二手书App

    一个Flask + vue 前后端分离的 二手书App 效果展示: https://blog.csdn.net/qq_42239520/article/details/88534955 所用技术清单 ...

  7. 喜大普奔,两个开源的 Spring Boot + Vue 前后端分离项目可以在线体验了

    折腾了一周的域名备案昨天终于搞定了. 松哥第一时间想到赶紧把微人事和 V 部落部署上去,我知道很多小伙伴已经等不及了. 1. 也曾经上过线 其实这两个项目当时刚做好的时候,我就把它们部署到服务器上了, ...

  8. 基于Spring Boot+Spring Security+JWT+Vue前后端分离的开源项目

    一.前言 最近整合Spring Boot+Spring Security+JWT+Vue 完成了一套前后端分离的基础项目,这里把它开源出来分享给有需要的小伙伴们 功能很简单,单点登录,前后端动态权限配 ...

  9. 两个开源的 Spring Boot + Vue 前后端分离项目

    折腾了一周的域名备案昨天终于搞定了. 松哥第一时间想到赶紧把微人事和 V 部落部署上去,我知道很多小伙伴已经等不及了. 1. 也曾经上过线 其实这两个项目当时刚做好的时候,我就把它们部署到服务器上了, ...

  10. beego-vue URL重定向(beego和vue前后端分离开发,beego承载vue前端分离页面部署)

    具体过程就不说,是搞这个的自然会动,只把关键代码贴出来. beego和vue前后端分离开发,beego承载vue前端分离页面部署 // landv.cnblogs.com //没有授权转载我的内容,再 ...

随机推荐

  1. C 编程异常 — implicit declaration of function 'free' is invalid in C99

    环境:MAC pro 问题:在编译程序的触发异常. main.c:17:9: warning: implicit declaration of function 'free' is invalid i ...

  2. PageOffice调用本地office实现多人在线同时编辑Word文档

    说明:多人同时在线编辑文件大多数会出现文档相互覆盖的问题,后保存的文档会覆盖先保存的文档.pageoffice的这个功能可以用程序控制不同用户打开文件后,编辑Word文档中属于自己的区域,并且不会互相 ...

  3. npm包离线安装

    npm包离线安装 npm包的安装,在Internet联网机器上通过npm install轻松搞定的事情,在离线或者纯内网环境下就变得异常艰难,本文就来讲一讲离线安装npm包的方法. 通过 npm in ...

  4. python命令行传参详解,optparse模块OptionParse类的学习

    官网链接:https://docs.python.org/3/library/optparse.html https://docs.python.org/2/library/argparse.html ...

  5. vue实现的常见的动画效果

    本文包括的动画: zoom-in zoom-in-left zoom-in-right zoom-in-top zoom-in-bottom zoom-in-center-x zoom-in-cent ...

  6. mvc5接口报错:The JSON request was too large to be deserialized的一种原因

    是mvc5版本的接口,接口使用了dynamic接收数组,json对象数组只有56个,length长度不到10万,但是提交就报The JSON request was too large to be d ...

  7. MyBatis反射模块源码分析

    说明:本文参考至https://www.jianshu.com/p/baba62bbc107 MyBatis 在进行参数处理.结果映射时等操作时,会涉及大量的反射操作.为了简化这些反射相关操作,MyB ...

  8. insert into select [SQL]

    insert into `d_mx_think`.`su_article` (id,catid,title,url) select id,catid,title,url from d_mx_phpcm ...

  9. 阅读LXH《FFMPEG+SDL的视频播放器》总结

    一.原文地址 https://blog.csdn.net/leixiaohua1020/article/details/46889389 在此向雷霄骅致敬!!! 二.视频播放器实现思路 1)视频播放器 ...

  10. uniapp 小程序分享功能

    上个月在做小程序的项目时,甲方需要给小程序添加个分享的功能,查看uniapp官方文档后,发现uniapp有自带的小程序分享功能(https://uniapp.dcloud.io/api/plugins ...