vue+element大型表单解决方案(5)--校验标识

2,573 阅读3分钟

代码地址:gitee.com/wyh-19/supe…
本篇代码分支:essay-5

系列文章:

前言

之前花了两篇的篇幅完成了锚点组件,最开始设计锚点组件的目的只是为了辅助滚动大型表单的页面内容。由于是自主实现,进一步驱动我挖掘这个表单解决方案更多的可能性。首先我想到的是如果子表单校验失败,能在锚点上标记出来就太方便了。现在就朝这个目标一步步实现吧。

准备工作

去除上篇中添加的占位符代码,主表单的template代码如下:

<div ref="pageBlock" class="form-wrapper">
    <el-button type="primary" @click="handleSave">保存</el-button>
    <div data-section="基础信息" data-ismain></div>
    <div data-section="个人信息"></div>
    <form1 ref="form1" :data="formDataMap.form1" />
    <div data-section="高级信息" data-ismain></div>
    <div data-section="公司信息"></div>
    <form2 ref="form2" :data="formDataMap.form2" />
    <div class="anchor-wrapper">
      <anchor :page-block="pageBlock" />
    </div>
</div>

现在相当于回到表单拆分文末的状态,只是多了anchor的组件使用。

实现思路

由于锚点组件的数据全部是通过dom结构解析出来的,所以如果要给锚点打上标记,需要将信息写入dom中。当表单出现校验失败时,给相应的section挂上data-tip属性,再通知锚点更新。为了让section和具体表单绑定,给section上增加data-for=表单名属性,用于指明具体的表单。下面看具体实现。

具体实现

修改dom

根据实现思路,先给secton增加data-for属性,用于指明表单。修改的代码如下:

...
<div data-section="个人信息" data-for="form1"></div>
...
<div data-section="公司信息" data-for="form2"></div>
...

修改handleSave方法,给校验失败的逻辑部分增加处理代码,遍历所有的formKeys,依次去validResults里查校验结果,如果校验失败,则通过[data-for=${formKey}]查找相应的章节,并给其增加data-tip属性,否则则去除该属性,具体代码如下:

this.$message.warning('校验未通过')
// 标记出相应的失败表单
formKeys.map((formKey, index) => {
  const section = this.pageBlock.querySelector(`[data-for=${formKey}]`)
  if (!validResults[index]) {
    section?.setAttribute('data-tip', '')
  } else {
    section?.removeAttribute('data-tip')
  }
})

此时点击保存,校验效果以及相应的属性如下图所示:

image.png

更新锚点

上面给section增加了data-tip,下面要在锚点想办法取出来并进行绘制。回到anchor组件中,首先给getSectionsData增加一项数据输出,代码如下:

return {
  // 如果有data-tip属性,则为true,反之为false
  tip: 'tip' in item.dataset,
  ismain,
  index: ismain ? mainIndex : `${mainIndex}.${subIndex}`,
  label: item.dataset.section,
  top: item.offsetTop
}

在template中,每个{{ node.label }}后面增加<span v-if="node.tip" class="highlight-tag">!</span>,相应的样式如下:

.highlight-tag {
  display: inline-block;
  text-align: center;
  margin-left: 8px;
  width: 16px;
  height: 16px;
  border-radius: 16px;
  background: crimson;
  color: #efefef;
}

下面只剩如何通知锚点组件更新了。自然地,我们会想到能不能deep模式watchpageBlock属性,当dom变化时,pageBlock会产生变化,但是遗憾的是pageBlock里的内容太多了,会报如下错误:

image.png

既然这条路走不通,那就回归最基础的做法,给anchor增加一个重绘接口,当校验时,外界主动去调用这个重绘接口。在anchor组件内,增加reRender方法,代码如下:

reRender() {
  this.sections = this.getSectionsData(this.pageBlock)
  this.currentSection = this.getCurrentSection()
}

由于anchor组件做了层包装,还需要修改包装层,由包装层输出真正的reRender,包装层代码如下:

<template>
  <anchor v-if="pageBlock" ref="anchor" :page-block="pageBlock" />
</template>
<script>
import Anchor from './anchor'
export default {
  components: {
    Anchor
  },
  props: {
    pageBlock: HTMLElement
  },
  methods: {
    reRender() {
      this.$nextTick(() => {
        this.$refs['anchor'].reRender()
      })
    }
  }
}
</script>

再回到表单组件内,给anchor组件增加ref="anchor",这时候可以在handleSave的最后增加this.$refs['anchor'].reRender()执行语句。此时再点击保存,得到下面的效果:

image.png

不错,基本达到了我要的效果。不过到这里还差一步工作要做,那就是当校验成功后,锚点就不能再显示红色警告了,下面给组装数据的逻辑中,增加去除所有红色警告的代码,代码如下:

formKeys.map(formKey => {
  // 增加去除警告的代码
  const section = this.pageBlock.querySelector(`[data-for=${formKey}]`)
  section?.removeAttribute('data-tip')
  const partFormData = this.$refs[formKey].formData
  Object.assign(formData, partFormData)
})

现在再测试,就完全正常了。但是这样的效果依然不够惊艳,必须到最后点击保存时才定位出问题所在,能不能实时定位呢?由于时间因素,这个问题留到下一篇中解决。谢谢您的阅读,欢迎提出指正意见!