如何给表单快照并使用快照数据填充表单?

686 阅读2分钟

场景

页面中有个大表单,十几或二十几个字段。开发或者测试在验证该表单功能的时候,如果页面刷新,整个表单都需要重新填写,而实际上,每次测试,只有个别字段需要改写。如果有一个工具,可以一键保存当前表单数据到localStorage里,刷新页面之后可以一键把localStorage里的数据回填至表单。有这样一个工具,在测试大表单的时候就少了一些工作量。

场景总结

引入两个名词:

  1. 快照:保存表单数据
  2. 回填:把快照数据回写之表单

实现

以下直接分享一个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)
  }
}