Posicionamiento automático de errores de envío de formularios (antd+vue)

Requisitos: haga clic en el botón Enviar. Si varios elementos requeridos no se completan o no pasan la verificación, el primer elemento se colocará automáticamente.

Análisis de ideas: para esta función, debemos dejar en claro que el elemento de error está relacionado con el DOM, por lo que para ubicar la ubicación del DOM correspondiente, debemos vincular una ID única a cada elemento del formulario y luego recopilarla de acuerdo con a las reglas de verificación Ordene los elementos de error, luego calcule la posición del primer elemento de error y desplácese hasta la ubicación del dom.

Código central:

Paso 1: vincular una identificación única

  <a-form-model-item ref="title" label="课程名称" prop="title">
        <a-input
          placeholder="请输入课程名称"
          id="title"
          style="width: 500px"
          v-model.trim="form.title"
          @blur="() => $refs.title.onFieldBlur()"
          :maxLength="200"
        />
  </a-form-model-item>

El segundo paso es combinar los elementos de las reglas de utilería con el dom y personalizar la clasificación para cada dom.

Consejos: los dominios son elementos para agregar, eliminar y verificar formularios dinámicos, los mostraré en el código completo a continuación.

<script>
//positionData的key是表单的prop,ele是表单的dom的id名
const positionData = {
  title: { ele: "title", sort: 1 },
  time: { ele: "time", sort: 2 },
  teacherId: { ele: "teacherId", sort: 3 },
  date: { ele: "course-date", sort: 4 },
  place: { ele: "place", sort: 5 },
  // "domains.0.date": { ele: "domains.0.date", sort: 6 },
  // "domains.1.date": { ele: "domains.1.date", sort: 7 },
  // "domains.2.date": { ele: "domains.2.date", sort: 8 },
};
export default{
    data(){
       return{
             positionData,
             noValidate: [],//存放表单报错信息的数组
        }
    }
}
</script>

Paso 3: envíe el formulario, recopile los elementos de error, ordene y desplácese hasta la posición

<script>
import { sortBy } from "lodash-es";
    export default{
        methods:{
           handleSubmit() {
             console.log("保存");
             this.noValidate = [];
             this.$refs.form.validate(async (valid, ...args) => {
                 if (!valid) {
                     this.handleErrorPosition(args);
                     return false;
                 }
             });
           },
        }
    }
</script>
  //处理错误定位
    handleErrorPosition(args) {
      //获取错误的key
      const errorDataKey = Object.keys(args[0]);
      // 整理错误信息项
      errorDataKey.forEach((key) => {
        this.noValidate.push(this.positionData[key]);
      });
      //错误信息排序
      this.noValidate = sortBy(this.noValidate, [(val) => val.sort]);
      this.handleJump();
   },
   // 报错定位
    handleJump() {
      const box = document.getElementById(this.noValidate[0]["ele"]);
      const container = document.getElementById("upgrad-offline-course-form");
      const { top } = box.getBoundingClientRect();
      const parentTop = container.getBoundingClientRect().top;
      const scrollTop = container.scrollTop;
      //realTop 每一项距离父级盒子顶部的距离
      const realTop = top - parentTop;
      const positionY = realTop + scrollTop;
      container.scroll(0, positionY);
   },

Consejos: El contenedor aquí es el área de desplazamiento configurada, lo cual es muy importante. El estilo debe configurarse correctamente, de lo contrario el desplazamiento no tendrá efecto.

CSS central:

#upgrad-offline-course-form {
  height: 83vh;
  overflow-y: scroll;
  padding-bottom: 30px;
}

Código completo:

Consejos: El siguiente código utiliza el estilo de nombre de clase taiwindcss. Si el proyecto asociado al que hace referencia tiene taiwindcss, puede copiarlo y modificarlo en su totalidad.

El formulario es relativamente completo: cuadro de entrada, cuadro desplegable, selector de tiempo, cuadro de entrada de números, formulario dinámico de adición y eliminación

El código del proyecto real es demasiado complicado, así que optimicé este código fuente en una demostración para que todos puedan copiarlo y usarlo directamente.

<template>
  <div id="upgrad-offline-course-form">
    <a-form-model
      ref="form"
      :model="form"
      :rules="rules"
      :label-col="{ span: 3 }"
      :wrapper-col="{ span: 14 }"
      :colon="false"
    >
      <a-form-model-item ref="title" label="课程名称" prop="title">
        <a-input
          placeholder="请输入课程名称"
          id="title"
          style="width: 500px"
          v-model.trim="form.title"
          @blur="() => $refs.title.onFieldBlur()"
          :maxLength="200"
        />
      </a-form-model-item>

      <a-form-model-item ref="time" label="课程学时" prop="time">
        <a-input-number
          placeholder="请输入"
          id="time"
          v-model="form.time"
          :min="0"
          :max="99"
          :precision="1"
        />
        <span class="ml-12">小时</span>
        <div class="gray-hint">支持0-99正的整数,1位小数</div>
      </a-form-model-item>

      <a-form-model-item ref="teacherId" label="课程讲师" prop="teacherId">
        <a-select
          id="teacherId"
          show-search
          v-model="form.teacherId"
          placeholder="请选择课程讲师"
          :default-active-first-option="false"
          :filter-option="false"
          @search="handleTeacherSearch"
          @change="$refs.ownerId.onFieldChange()"
          @blur="searchTeacher('')"
          style="width: 400px"
        >
          <a-select-option
            :value="item.id"
            v-for="item in teacherList"
            :key="item.id"
          >
            {
   
   { item.truename }}
          </a-select-option>
        </a-select>
      </a-form-model-item>

      <a-form-model-item label="课程时间" prop="date">
        <a-range-picker
          valueFormat="YYYY-MM-DD HH:mm"
          style="width: 400px"
          :show-time="{ format: 'HH:mm' }"
          format="YYYY-MM-DD HH:mm"
          @change="onTimeChange"
          v-model="form.date"
          id="course-date"
          :placeholder="['开始日期', '结束日期']"
        />
      </a-form-model-item>

      <a-form-model-item ref="place" label="课程地点" prop="place">
        <a-input
          placeholder="请输入课程地点"
          id="place"
          style="width: 400px"
          v-model.trim="form.place"
          @blur="() => $refs.place.onFieldBlur()"
          :maxLength="60"
        />
      </a-form-model-item>

      <div
        class="gray-area"
        v-for="(domain, index) in form.domains"
        :key="index"
        :id="'domains.' + index + '.date'"
      >
        <div class="flex items-center justify-between mb-16">
          <div class="flex items-center">
            <div class="point"></div>
            <div class="font-500">第 {
   
   { index + 1 }} 次签到</div>
          </div>
          <a-tooltip>
            <template slot="title"> 删除 </template>
            <img
              src="../assets/img/delete-bin-line.svg"
              alt=""
              @click="removeDomain(domain)"
            />
          </a-tooltip>
        </div>
        <a-form-model-item
          :prop="'domains.' + index + '.date'"
          :rules="{
            required: true,
            message: '请输入签到时间',
            trigger: 'change',
          }"
          label="签到时间"
          :label-col="{ span: 5 }"
          :wrapper-col="{ span: 14 }"
          style="margin-bottom: 0"
        >
          <a-range-picker
            valueFormat="YYYY-MM-DD HH:mm"
            :show-time="{ format: 'HH:mm' }"
            format="YYYY-MM-DD HH:mm"
            :placeholder="['开始时间', '结束时间']"
            v-model="domain.date"
          />
        </a-form-model-item>
      </div>
      <div
        class="add-btn"
        @click="addDomain"
        :class="{ active: form.domains.length === 30 }"
      >
        <a-icon type="plus" class="mr-6" />
        {
   
   { `添加签到(${form.domains.length}/30)` }}
      </div>
    </a-form-model>

    <div class="flex justify-end">
      <a-button type="primary" ghost class="mr-4" @click="resetForm">
        取消
      </a-button>
      <a-button type="primary" @click="handleSubmit">提交</a-button>
    </div>
  </div>
</template>
<script>
import { sortBy } from "lodash-es";

const positionData = {
  title: { ele: "title", sort: 1 },
  time: { ele: "time", sort: 2 },
  teacherId: { ele: "teacherId", sort: 3 },
  date: { ele: "course-date", sort: 4 },
  place: { ele: "place", sort: 5 },
};
export default {
  data() {
    return {
      positionData,
      noValidate: [],
      teacherList: [],
      form: {
        domains: [{ date: [] }],
        startTime: "",
        endTime: "",
        date: undefined,
        teacherId: undefined,
        time: undefined,
        title: undefined,
      },
      rules: {
        place: [{ required: true, message: "请输入课程地点", trigger: "blur" }],
        date: [
          { required: true, message: "请选择项目时间", trigger: "change" },
        ],
        teacherId: [
          { required: true, message: "请选择课程讲师", trigger: "change" },
        ],
        title: [{ required: true, message: "请输入课程名称", trigger: "blur" }],
        time: [{ required: true, message: "请输入课程学时", trigger: "blur" }],
      },
    };
  },
  created() {
    for (let i = 0; i < 30; i++) {
      const check_key = `domains.${i}.date`;
      this.positionData[check_key] = { ele: check_key, sort: 6 + i };
    }
  },
  methods: {
    //处理错误定位
    handleErrorPosition(args) {
      console.log(args[0]);
      //获取错误的key
      const errorDataKey = Object.keys(args[0]);
      // 整理错误信息项
      errorDataKey.forEach((key) => {
        this.noValidate.push(this.positionData[key]);
      });
      //错误信息排序
      this.noValidate = sortBy(this.noValidate, [(val) => val.sort]);
      this.handleJump();
    },
    // 报错定位
    handleJump() {
      const box = document.getElementById(this.noValidate[0]["ele"]);
      const container = document.getElementById("upgrad-offline-course-form");
      const { top } = box.getBoundingClientRect();
      const parentTop = container.getBoundingClientRect().top;
      const scrollTop = container.scrollTop;
      //realTop 每一项距离父级盒子顶部的距离
      const realTop = top - parentTop;
      const positionY = realTop + scrollTop;
      container.scroll(0, positionY);
    },
    removeDomain(item) {
      let index = this.form.domains.indexOf(item);
      if (index !== -1) {
        this.form.domains.splice(index, 1);
      }
    },
    addDomain() {
      if (this.form.domains.length === 30) {
        return;
      }
      this.form.domains.push({
        date: [],
      });
    },
    //时间处理
    onTimeChange(date, dateString) {
      console.log(date, dateString);
      if (!this.form.date.length) {
        this.form.date = undefined;
      }
      this.form.startTime = dateString[0];
      this.form.endTime = dateString[1];
    },
    handleSubmit() {
      console.log("保存");
      // this.isSumbit = true;
      this.noValidate = [];
      this.$refs.form.validate(async (valid, ...args) => {
        // let isValid = valid;
        if (!valid) {
          this.handleErrorPosition(args);
          return false;
        }
      });
    },
    //课程讲师搜索
    handleTeacherSearch(value) {
      this.searchUser(value);
    },
    searchTeacher(value) {
      const requestForm = {};

      if (value) {
        requestForm.q = value;
      }
    },
    resetForm() {
      this.$refs.form.resetFields();
    },
  },
};
</script>
<style lang="less" scoped>
#upgrad-offline-course-form {
  height: 83vh;
  overflow-y: scroll;
  padding-bottom: 30px;

  .gray-hint {
    font-size: 14px;
    color: #919399;
    line-height: normal;
  }

  .gray-area {
    width: 498px;
    background: #f7f8fa;
    height: auto;
    margin-bottom: 12px;
    margin-left: 28px;
    padding: 16px 20px 10px 16px;
  }
  .add-btn {
    cursor: pointer;
    margin-left: 34px;
    color: #165dff;
    &.active {
      cursor: not-allowed;
      color: #999;
      opacity: 0.6;
    }
  }
  .point {
    margin-right: 8px;
    width: 4px;
    height: 4px;
    background-color: #165dff;
  }
}
</style>

Representación:

Forma no verificada (forma normal):

El instructor del curso no completó el posicionamiento.

 El segundo inicio de sesión del formulario dinámico no completó el posicionamiento.

Supongo que te gusta

Origin blog.csdn.net/yxlyttyxlytt/article/details/132561929
Recomendado
Clasificación