场景
页面中有个大表单,十几或二十几个字段。开发或者测试在验证该表单功能的时候,如果页面刷新,整个表单都需要重新填写,而实际上,每次测试,只有个别字段需要改写。如果有一个工具,可以一键保存当前表单数据到localStorage里,刷新页面之后可以一键把localStorage里的数据回填至表单。有这样一个工具,在测试大表单的时候就少了一些工作量。
场景总结
引入两个名词:
- 快照:保存表单数据
- 回填:把快照数据回写之表单
实现
以下直接分享一个Vue项目里回填ElementUI的代码: autofillform.js
怎么使用?直接在main.js里导入就行
import './autofillform'
引用之后页面会多出一个功能区,大概如下:
该功能区可以自由拖动位置。
以下为autofillform.js代码
/* eslint-disable no-new */
const config = { childList: true, subtree: true }
const zIndex = 99999
const prefix = '__AUTO_FILL_FORM__'
const style = `#${prefix} {
position: fixed;
white-space: nowrap;
top: 0;
left: 0;
z-index: ${zIndex};
background: white;
padding: 10px;
border-radius: 5px;
border: #04BE02 1px solid;
}
#${prefix} .button {
color: #FFF;
padding: 5px 15px;
background-color: #04BE02;
line-height: 1;
border-radius: 4px;
cursor: pointer;
margin-left: 5px;
}
#${prefix} .button:hover {
box-shadow: 0 0 3px green;
text-shadow: 0 0 3px brown;
}
#${prefix} .button:active {
box-shadow: 0 0 3px green;
text-shadow: 0 0 3px brown;
background-color: green;
}
#${prefix} hr {
border: none;
height: 1px;
background-color: #04BE02;
}`
class AutoFillForm {
constructor () {
this.vue = null
this.forms = []
this.bodyObserver = new MutationObserver(this.findVue.bind(this))
this.vueObserver = new MutationObserver(this.vueChange.bind(this))
this.bodyObserver.observe(document.querySelector('body'), config)
this.addStyle()
}
addStyle () {
const temp = document.createElement('style')
temp.innerHTML = style
document.head.appendChild(temp)
}
findVue () {
const map = window.__VUE_HOT_MAP__ || {}
const key = Object.keys(map).find(it => map[it].instances.length > 0)
if (key) {
this.vue = map[key].instances[0].$root
this.bodyObserver.disconnect()
this.vueObserver.observe(this.vue.$el, config)
}
}
vueChange () {
this.forms = []
this.findForms(this.vue)
this.render()
}
findForms (component) {
if (component.$el.tagName === 'FORM') {
this.forms.push(component)
} else {
component.$children.forEach(it => this.findForms(it))
}
}
render () {
this.removeAutoFillForm()
const html = document.createElement('div')
html.innerHTML = this.getButtonsByForms()
document.body.appendChild(html)
html.setAttribute('id', prefix)
html.addEventListener('click', this.clickForm.bind(this))
new Drag(html)
}
removeAutoFillForm () {
const already = document.getElementById(prefix)
if (already) {
document.body.removeChild(already)
}
}
getButtonsByForms () {
return this.forms.map(form => {
const name = form.$attrs.name
if (!name) return null
return `
<div>
<span>${name}</span>
<span class="button" title="把快照数据填充回表单" data-form-name="${name}" data-fun="fill">fill</span>
<span class="button" title="对当前表单数据进行快照" data-form-name="${name}" data-fun="snapshot">snapshot</span>
</div>
`
}).filter(it => it).join('<hr />')
}
clickForm (event) {
const target = event.target
const formName = target.getAttribute('data-form-name')
const fun = target.getAttribute('data-fun') // fill、snapshot
if (fun && this[fun]) {
this[fun](formName)
}
}
snapshot (formName) {
const form = this.forms.find(it => it.$attrs.name === formName)
localStorage.setItem(prefix + formName, JSON.stringify(form.model))
const formDom = form.$el
const time = 256
const div = document.createElement('div')
div.style.position = 'fixed'
div.style.zIndex = zIndex - 1
div.style.transition = `all ${time}ms ease-in`
const rect = formDom.getBoundingClientRect()
div.style.left = rect.left + 'px'
div.style.top = rect.top + 'px'
div.style.width = formDom.offsetWidth + 'px'
div.style.height = formDom.offsetHeight + 'px'
div.style.backgroundColor = '#04BE02'
div.style.opacity = 0.1
document.body.appendChild(div)
setTimeout(_ => {
div.style.transform = 'scale(0)'
div.style.opacity = 0.3
}, 128)
setTimeout(_ => { document.body.removeChild(div) }, time + 64)
}
fill (formName) {
const form = this.forms.find(it => it.$attrs.name === formName)
const model = JSON.parse(localStorage.getItem(prefix + formName) || '{}')
Object.keys(model).forEach(key => { form.model[key] = model[key] })
}
}
new AutoFillForm()
class Drag {
constructor (dom) {
this.dom = dom
dom.setAttribute('draggable', true)
this.mask = document.createElement('div')
this.setMask()
this.ondragstartX = 0
this.ondragstartY = 0
this.dom.style.left = localStorage.getItem(prefix + 'left') + 'px'
this.dom.style.top = localStorage.getItem(prefix + 'top') + 'px'
dom.addEventListener('dragstart', this.dragstart.bind(this))
dom.addEventListener('dragend', this.dragend.bind(this))
}
setMask () {
this.mask.style.position = 'fixed'
this.mask.style.zIndex = zIndex - 1
this.mask.style.height = '100%'
this.mask.style.width = '100%'
this.mask.style.top = 0
this.mask.style.left = 0
this.mask.ondragover = event => event.preventDefault()
}
dragstart (event) {
document.body.appendChild(this.mask)
this.ondragstartX = event.x
this.ondragstartY = event.y
event.target.style.opacity = 0.328
}
dragend (event) {
console.log(event)
const target = event.target
const offsetX = event.x - this.ondragstartX
const offsetY = event.y - this.ondragstartY
let left = target.offsetLeft + offsetX
let top = target.offsetTop + offsetY
const width = target.offsetWidth
const bodyWidth = document.body.offsetWidth
if (left + width > bodyWidth) {
left = bodyWidth - width
}
left = left < 0 ? 0 : left
const height = target.offsetHeight
const bodyHeight = document.body.offsetHeight
if (top + height > bodyHeight) {
top = bodyHeight - height
}
top = top < 0 ? 0 : top
localStorage.setItem(prefix + 'left', left)
localStorage.setItem(prefix + 'top', top)
target.style.left = left + 'px'
target.style.top = top + 'px'
event.target.style.opacity = 1
document.body.removeChild(this.mask)
}
}