表单提交报错自动定位(antd+vue)

需求:点击提交按钮,多项必填项未填写或者未通过校验,则自动定位至第一项

思路分析:对于这个功能,我们要明确报错项和dom是相关联的,所以为了能定位到对应的dom位置,我们需要给每一个表单项都绑定唯一的id,然后根据校验规则,收集报错项,进行排序,然后计算第一个报错项的位置,scroll滚动到该dom所在的位置。

核心代码:

第一步:绑定唯一的id

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

第二步,将prop的规则项和dom进行结合绑定,并且给每个dom自定义排序

tips:domains是动态表单增删校验的项,在下方我会在完整代码里进行展示

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

第三步:提交表单,收集报错项,进行排序,滚动定位

<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);
   },

tips:这里的container是设置的滚动区域,十分重要,样式上一定要设置好,否则滚动不生效

核心css:

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

完整代码:

tips:下面的代码使用了taiwindcss的类名样式,如果参考的小伙伴项目里有taiwindcss这个东西,就可以整个复制过去修改

表单比较全面:输入框,下拉框,时间选择器,数字输入框,动态增删表单

真正的项目代码过于复杂,所以我把这个源码优化成了一个demo,方便大家直接复制使用

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

效果图:

未校验表单(正常表单):

课程讲师未填写定位

 动态表单第二次签到未填写定位

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
要搭建一个基于Vite、Ant DesignVue 3的项目,你可以按照以下步骤进行操作: 1. 首先,你需要安装Vite。你可以通过运行以下命令来安装最新版本的Vite: ``` npm init vite@latest my-project cd my-project npm install ``` 这将创建一个新的Vite项目,并安装所有必要的依赖项。 2. 接下来,你需要安装Ant Design Vue。运行以下命令来安装Ant Design Vue库: ``` npm install ant-design-vue@next ``` 这将安装最新版本的Ant Design Vue,并将其添加到你的项目中。 3. 配置路由。你可以通过以下步骤来配置Vue Router: - 首先,运行以下命令来安装Vue Router: ``` npm install vue-router@next ``` - 在你的项目的src目录下创建一个名为"router"的文件夹。 - 在"router"文件夹中创建一个名为"index.js"的文件,并配置你的路由信息。 - 最后,在项目的"main.js"文件中引入并使用Vue Router。 以上是使用Vite、Ant Design VueVue 3搭建项目的基本步骤。你可以根据需要进一步添加其他功能和组件。祝你搭建项目成功!<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [vue3+vite+antd 后台管理系统基础模板](https://download.csdn.net/download/yehaocheng520/87420798)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* *3* [vite + vue3 + Antd 搭建后台管理系统](https://blog.csdn.net/m0_58094704/article/details/127850749)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

零凌林

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值