系列文章:
- vue+element大型表单解决方案(1)–概览
- vue+element大型表单解决方案(2)–表单拆分
- vue+element大型表单解决方案(3)–锚点组件(上)
- vue+element大型表单解决方案(4)–锚点组件(下)
前言
之前花了两篇的篇幅完成了锚点组件,最开始设计锚点组件的目的只是为了辅助滚动大型表单的页面内容。由于是自主实现,进一步驱动我挖掘这个表单解决方案更多的可能性。首先我想到的是如果子表单校验失败,能在锚点上标记出来就太方便了。现在就朝这个目标一步步实现吧。
准备工作
去除上篇中添加的占位符代码,主表单的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')
}
})
复制代码
此时点击保存,校验效果以及相应的属性如下图所示:
更新锚点
上面给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 && !noTip" 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
里的内容太多了,会报如下错误:
既然这条路走不通,那就回归最基础的做法,给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()
执行语句。此时再点击保存,得到下面的效果:
不错,基本达到了我要的效果。不过到这里还差一步工作要做,那就是当校验成功后,锚点就不能再显示红色警告了,下面给组装数据的逻辑中,增加去除所有红色警告的代码,代码如下:
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)
})
复制代码
现在再测试,就完全正常了。但是这样的效果依然不够惊艳,必须到最后点击保存时才定位出问题所在,能不能实时定位呢?由于时间因素,这个问题留到下一篇中解决。谢谢您的阅读,欢迎提出指正意见!