• 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>
    

    这里我们使用

  • 标签,使用 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 互赞平台,前端按本文方式进行错误处理)。

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

  • 相关阅读:
    状态压缩dp整理
    第5章 验证码识别
    RxJava 一篇文章就够了
    【温故而知新】构建高可用Linux服务器(三)
    【SpringCloud微服务实战01】Eureka 注册中心
    CompletableFuture-FutureTask
    使用vue-cli搭建vue项目
    Python机器学习 | AI芯片调研
    【MATLAB第78期】基于MATLAB的VMD-SSA-LSTM麻雀算法优化LSTM时间序列预测模型
    关起门来搞开源,做不了开源世界的Leader
  • 原文地址:https://www.cnblogs.com/techhub/p/18301010/vue-error-handle