Vue 3 后端错误消息处理范例
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 后端错误消息处理范例的更多相关文章
- ASP.NET WebApi+Vue前后端分离之允许启用跨域请求
前言: 这段时间接手了一个新需求,将一个ASP.NET MVC项目改成前后端分离项目.前端使用Vue,后端则是使用ASP.NET WebApi.在搭建完成前后端框架后,进行接口测试时发现了一个前后端分 ...
- Vue之前后端交互
Vue之前后端交互 一.前后端交互模式 接口调用方式 原生ajax 基于jQuery的ajax fetch axios 异步 JavaScript的执行环境是「单线程」 所谓单线程,是指JS引擎中负责 ...
- 三、vue前后端交互(轻松入门vue)
轻松入门vue系列 Vue前后端交互 六.Vue前后端交互 1. 前后端交互模式 2. Promise的相关概念和用法 Promise基本用法 then参数中的函数返回值 基于Promise处理多个A ...
- vue根据后端菜单自动生成路由(动态路由)
vue根据后端菜单自动生成路由(动态路由) router.js import Vue from 'vue' import Router from 'vue-router' import store f ...
- 解决Django+Vue前后端分离的跨域问题及关闭csrf验证
前后端分离难免要接触到跨域问题,跨域的相关知识请参:跨域问题,解决之道 在Django和Vue前后端分离的时候也会遇到跨域的问题,因为刚刚接触Django还不太了解,今天花了好长的时间,查阅了 ...
- Flask + vue 前后端分离的 二手书App
一个Flask + vue 前后端分离的 二手书App 效果展示: https://blog.csdn.net/qq_42239520/article/details/88534955 所用技术清单 ...
- 喜大普奔,两个开源的 Spring Boot + Vue 前后端分离项目可以在线体验了
折腾了一周的域名备案昨天终于搞定了. 松哥第一时间想到赶紧把微人事和 V 部落部署上去,我知道很多小伙伴已经等不及了. 1. 也曾经上过线 其实这两个项目当时刚做好的时候,我就把它们部署到服务器上了, ...
- 基于Spring Boot+Spring Security+JWT+Vue前后端分离的开源项目
一.前言 最近整合Spring Boot+Spring Security+JWT+Vue 完成了一套前后端分离的基础项目,这里把它开源出来分享给有需要的小伙伴们 功能很简单,单点登录,前后端动态权限配 ...
- 两个开源的 Spring Boot + Vue 前后端分离项目
折腾了一周的域名备案昨天终于搞定了. 松哥第一时间想到赶紧把微人事和 V 部落部署上去,我知道很多小伙伴已经等不及了. 1. 也曾经上过线 其实这两个项目当时刚做好的时候,我就把它们部署到服务器上了, ...
- beego-vue URL重定向(beego和vue前后端分离开发,beego承载vue前端分离页面部署)
具体过程就不说,是搞这个的自然会动,只把关键代码贴出来. beego和vue前后端分离开发,beego承载vue前端分离页面部署 // landv.cnblogs.com //没有授权转载我的内容,再 ...
随机推荐
- Istio(十二):Istio问题排查
目录 一.模块概览 二.Envoy基础 2.1 Envoy 基础 三.Envoy示例 四.调试备忘录 4.1 配置 4.2 运行时 4.3 检查 Envoy 日志 4.4 配置 istiod 日志 一 ...
- MyBatis数据源模块源码分析
数据源对象是比较复杂的对象,其创建过程相对比较复杂,对于 MyBatis 创建数据源,具体来讲有如下难点: MyBatis 不但要能集成第三方的数据源组件,自身也提供了数据源的实现: 数据源的初始化参 ...
- 5分钟明白LangChain 的输出解析器和链
本文介绍 LangChain 的输出解析器OutputParser的使用,和基于LangChain的LCEL构建链. 1. 输出解析器OutputParser 1.1.为什么需要OutputParse ...
- Nginx 修饰符 Location 详解
概述 location 指令可以用在虚拟服务器 server 部分,并且意味着提供来自客户端的 URI 或者内部重定向访问. location 的定义如下: location [modifier] u ...
- CSS操作——display属性
display可以指定元素的显示模式,它可以把行内元素修改成块状元素,也可以把别的模式的元素改成行内元素.diisplay常用的值有四个. 语法: /* display: block; // 声明当前 ...
- Java 集合的概念
目录 集合 单列集合(Collection) Collection中的一些方法 public static < T > boolean addAll(Collection<? sup ...
- JavaSE 数据类型以及基本转化与包装
目录 数据类型. 1.基本类型(八个) 数值型 整型类型 byte型:1字节 8bit位 第一位是符号位 null short型:2字节 int 型:4字节 long型:8字节 浮点类型 float ...
- Manim使用心得
Manim 使用心得 manim 做视频还是挺方便的. 当然,如果你每一次都从 0 开始写,那么你会崩溃. 所以需要找到自己做视频的风格,以此总结出一套通用的 python 模板代码,然后调用. 例如 ...
- 西数 WD SATA SSD 固态 蓝盘 复制和剪切速度慢
现象:速度只有4,5M,活动时间100%.用AS SSD 测试速度正常. 问题:冷数据掉速.冷数据门. 解决方法:用DiskFresh,刷新下. 刷新时间,要看你存储数据的多少.我的1T 蓝盘,用了3 ...
- 关于 ulimit 的两个天坑
稍微有点 Linux 经验的人一定会遇到过 "Too many open files" 错误,这个错误本质是 ulimit 设置不合理导致的.关于 ulimit 设置,有哪些需要注 ...