表单字段A失焦异步获取B,输完A之后,快速点保存,如何保证提交正确的B呢?

web后台的实现离不开表格和表单,在表单中,数据联动是我们常常遇到的功能。当产生复杂的异步联动时,我们提交表单时,如何保证数据的完整性和准确性呢?相信很多人都遇到过这种问题,在这里记录一下我的探索之旅...核心实现逻辑可以从方案三开始看

问题的产生

如下表单,存在字段A,字段B,以及一个保存按钮,当A失去焦点之后异步获取B,点击保存按钮,为了方便看到效果,将要提交的数据以JSON字符串的形式展示出来。

js 复制代码
<template>
  <el-form label-width="80px" :model="formData">
    <el-form-item label="字段A">
      <el-input v-model="formData.fieldA" @change="handleAChange"></el-input>
    </el-form-item>
    <el-form-item label="字段B">
      <el-input v-model="formData.fieldB"></el-input>
    </el-form-item>
    <el-form-item>
      <el-button type="primary" @click="handleSave">保存</el-button>
    </el-form-item>
    <el-form-item> 此时保存的数据:{{ submitDataJSON }} </el-form-item>
  </el-form>
</template>

<script>
export default {
  data() {
    return {
      submitDataJSON: "",
      formData: {
        fieldA: "",
        fieldB: "",
      },
    };
  },
  methods: {
    handleAChange() {
      this.getFieldB();
    },
    getFieldB() {
      setTimeout(() => {
        this.formData.fieldB = this.formData.fieldA + "fieldB";
      }, 2000);
    },
    handleSave() {
      this.submitDataJSON = JSON.stringify(this.formData);
    },
  },
};
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped></style>

由于字段B异步获取,所以当我们输入字段A,马上点保存,保存的数据就会是上一次的字段B,如下图1所示:

图1

显然,这不是我们想要的结果,我们希望,点保存可以等到字段B获取到新值之后再触发保存操作

方案一:字段B为必填时,使用必填项校验拦截

考虑到字段A变化引发字段B联动变化,那么字段A每次变化时,我们可以先把字段B清空。如果字段B必填,那保存时,我们会进行必填项校验,使保存操作被中断,从而避免提交错误的数据到后台。代码如下:

js 复制代码
<template>
  <el-form label-width="80px" :model="formData">
    <el-form-item label="字段A">
      <el-input v-model="formData.fieldA" @change="handleAChange"></el-input>
    </el-form-item>
    <el-form-item label="字段B" required>
      <el-input v-model="formData.fieldB"></el-input>
    </el-form-item>
    <el-form-item>
      <el-button type="primary" @click="handleSave">保存</el-button>
    </el-form-item>
    <el-form-item> 此时保存的数据:{{ submitDataJSON }} </el-form-item>
  </el-form>
</template>

<script>
export default {
  data() {
    return {
      submitDataJSON: "",
      formData: {
        fieldA: "",
        fieldB: "",
      },
    };
  },
  methods: {
    handleAChange() {
      this.formData.fieldB = "";
      this.getFieldB();
    },
    getFieldB() {
      setTimeout(() => {
        this.formData.fieldB = this.formData.fieldA + "fieldB";
      }, 2000);
    },
    handleSave() {
      if (!this.formData.fieldB) {
        this.$message.warning("字段B必填");
        return;
      }
      this.submitDataJSON = JSON.stringify(this.formData);
    },
  },
};
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped></style>

但是如果字段B非必填呢?前端就无法通过必填项校验来拦截错误数据的提交了。而且,必填项校验的限制,会导致保存动作中断,需要等字段B获取到之后再次手动触发保存才可实现保存操作。

���案二:既然保存的数据不对,那就让保存按钮禁用

字段A的变化触发字段B的查询,在字段B查询的过程中,我们将保存按钮禁用,如下代码:

js 复制代码
...
<el-button type="primary" :disabled="disabled" @click="handleSave">保存</el-button>
...

...
getFieldB() {
  this.disabled = true;
  setTimeout(() => {
    this.formData.fieldB = this.formData.fieldA + "fieldB";
    this.disabled = false;
  }, 2000);
},
...

同方案一:输入A之后迅速点保存,保存动作不会生效,需要等到字段B获取到之后二次手动触发保存操作

方案三:引入Promise监听获取字段B的状态

上面两个方案都需要两步操作,即获取字段B和保存需要单独触发。那我们能不能输入字段A后点一次保存,等到最新的字段B获取到后主动提交 呢?问题的关键在于我们需要知道字段B的获取状态,如下代码,我们引入Promise实现:

js 复制代码
...
<script>
let promiseStatus = Promise.resolve();
export default {
  data() {
    return {
      submitDataJSON: "",
      formData: {
        fieldA: "",
        fieldB: "",
      },
    };
  },
  methods: {
    handleAChange() {
      this.formData.fieldB = "";
      this.getFieldB();
    },
    getFieldB() {
      promiseStatus = new Promise((resolve) => {
        setTimeout(() => {
          this.formData.fieldB = this.formData.fieldA + "fieldB";
          resolve();
        }, 2000);
      });
    },
    handleSave() {
      promiseStatus.finally(() => {
        this.submitDataJSON = JSON.stringify(this.formData);
      });
    },
  },
};
</script>

如上:我们将获取字段B的操作包装成一个promise,并把它赋值给一个变量,那它的状态就会变的可跟踪。我们点保存之后就可以保证在获取到最新的B之后执行保存操作了。

将方案三通用化

上述案例,A字段变更后只会触发B的查询,在现实需求中,我们往往会有更复杂的联动逻辑,比如A字段变更后,触发B、C、D...字段的异步查询(B、C、D...无依赖关系);或者A字段变更后,触发B字段的异步查询,查到B字段后,再触发C字段的异步查询...(B、C、D...有依赖关系)。为了方便管理和使用这种涉及到多个Promise的场景,我们实现了一个异步队列,引入Promise.all,如下:

js 复制代码
// requestQueue.js
import { Loading } from "element-ui";

export default class RequestQueue {
  constructor() {
    this.requests = [];
    this.isProcessing = false;
    // 保存在requests执行过程中添加进来的新Promise
    this.newRequests = [];
    this.loadingInstance = null;
  }

  // 默认添加进来的异步任务为Promise
  addRequest(requestFn) {
    if (!this.isProcessing) {
      this.requests.push(requestFn);
      return;
    }
    this.newRequests.push(requestFn);
  }

  clearQueue() {
    this.requests = [];
    this.newRequests = [];
  }

  requestsCompleted(saveFn) {
    this.isProcessing = true;
    if (!this.loadingInstance) {
      this.loadingInstance = Loading.service({ fullscreen: true });
    }
    Promise.all(this.requests).finally(() => {
      this.isProcessing = false;
      // 处理暂存的新请求
      if (this.newRequests.length > 0) {
        this.requests = this.newRequests;
        this.newRequests = [];
        this.requestsCompleted(saveFn);
        return;
      }
      this.loadingInstance.close();
      this.loadingInstance = null;
      saveFn();
      this.clearQueue();
    });
  }
}

使用方式如下:

js 复制代码
<template>
  <el-form label-width="80px" :model="formData">
    <el-form-item label="字段A">
      <el-input v-model="formData.fieldA" @change="handleAChange"></el-input>
    </el-form-item>
    <el-form-item label="字段B">
      <el-input v-model="formData.fieldB" @change="handleBChange"></el-input>
    </el-form-item>
    <el-form-item label="字段C">
      <el-input v-model="formData.fieldC"></el-input>
    </el-form-item>
    <el-form-item label="字段D">
      <el-input v-model="formData.fieldD"></el-input>
    </el-form-item>
    <el-form-item>
      <el-button type="primary" @click="handleSave">保存</el-button>
    </el-form-item>
    <el-form-item> 此时保存的数据:{{ submitDataJSON }} </el-form-item>
  </el-form>
</template>

<script>
import RequestQueue from "../utils/requestQueue.js";
const requestQueue = new RequestQueue();
export default {
  data() {
    return {
      submitDataJSON: "",
      formData: {
        fieldA: "",
        fieldB: "",
        fieldC: "",
        fieldD: "",
      },
    };
  },
  methods: {
    handleAChange() {
      this.getFieldB();
      this.getFieldC();
    },
    handleBChange() {
      this.getFieldD();
    },
    getFieldB() {
      const promise = new Promise((resolve) => {
        setTimeout(() => {
          this.formData.fieldB = this.formData.fieldA + ":fieldB";
          this.getFieldD();
          resolve();
        }, 2000);
      });
      requestQueue.addRequest(promise);
    },
    getFieldC() {
      const promise = new Promise((resolve) => {
        setTimeout(() => {
          this.formData.fieldC = this.formData.fieldA + ":fieldC";
          resolve();
        }, 2000);
      });
      requestQueue.addRequest(promise);
    },
    getFieldD() {
      const promise = new Promise((resolve) => {
        setTimeout(() => {
          this.formData.fieldD = this.formData.fieldB + ":fieldD";
          resolve();
        }, 2000);
      });
      requestQueue.addRequest(promise);
    },
    handleSave() {
      requestQueue.requestsCompleted(this.handleRequest);
    },
    handleRequest() {
      this.submitDataJSON = JSON.stringify(this.formData);
      // 保存数据
    },
  },
  created() {
    requestQueue.clearQueue();
  },
};
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped></style>

如下图2,输入A,马上点保存,B、C、D都获取到,才会执行保存操作,将查询到的B、C、D数据展示在页面上:

图2

详细代码见:codesandbox.io/p/sandbox/r...

相关推荐
yqcoder5 分钟前
Vue3 + Vite + Electron + TS 项目构建
前端·javascript·vue.js
ggdpzhk3 小时前
VUE:基于MVVN的前端js框架
前端·javascript·vue.js
活宝小娜7 小时前
vue不刷新浏览器更新页面的方法
前端·javascript·vue.js
程序视点7 小时前
【Vue3新工具】Pinia.js:提升开发效率,更轻量、更高效的状态管理方案!
前端·javascript·vue.js·typescript·vue·ecmascript
coldriversnow7 小时前
在Vue中,vue document.onkeydown 无效
前端·javascript·vue.js
刚刚好ā8 小时前
js作用域超全介绍--全局作用域、局部作用、块级作用域
前端·javascript·vue.js·vue
会发光的猪。10 小时前
css使用弹性盒,让每个子元素平均等分父元素的4/1大小
前端·javascript·vue.js
天下代码客10 小时前
【vue】vue中.sync修饰符如何使用--详细代码对比
前端·javascript·vue.js
周全全11 小时前
Spring Boot + Vue 基于 RSA 的用户身份认证加密机制实现
java·vue.js·spring boot·安全·php
ZwaterZ11 小时前
vue el-table表格点击某行触发事件&&操作栏点击和row-click冲突问题
前端·vue.js·elementui·c#·vue