若依表单构建--Vue3避坑版(最后代码模版)

893 阅读3分钟

接着上一篇juejin.cn/post/742412…

这一篇内容就稍微简单点,基本上就是把我们formData数据,转成script、html、css然后通过js-beautify@1.15.1。然后你可能会选择复制代码而不是每次都下载代码,所以需要用到copy依赖clipboard@^2.0.11

clipboard vue3中使用

若依vue2构建中也是用了这个库,但是是vue2的,所以这里介绍下vue3中我是如何使用的。 最基础的一种用法就是通过拿到input中的value。如下 通过button中 data-clipboard-target="#foo"

<input id="foo" value="https://github.com/zenorocha/clipboard.js.git" />
<button class="btn" data-clipboard-target="#foo">
  <img src="assets/clippy.svg" alt="Copy to clipboard" />
</button>

本文中使用的是另外一种方式,通过new ClipboardJs动态设置text,然后return相关复制内容。如下

<template>
    <input id="copyNode" type="hidden">
</template>
onMounted(() => {
  const clipboard = new ClipboardJS('#copyNode', {
    text: trigger => {
      const codeStr = generateCode()
      ElNotification({
        title: '成功',
        type: 'success',
        message: h('i', { style: 'color: teal' }, '代码已复制到剪切板,可粘贴。'),
      })
      return codeStr
    }
  })
  clipboard.on('error', e => {
    ElNotification({
      title: '失败',
      type: 'error',
      message: h('i', { style: 'color: teal' }, '代码失败,请重试')
    })
  })
})

然后generateCode()就是生成代码的方法。所有关于生成代码的处理方法都在这里了。

function generateCode() {
  const { type } = generateConfOjb.generateConf;
  AssembleFormData();
  const script = vueScript(makeUpJsVue3(generateForm.formData, type))
  const html = vueTemplate(makeUpHtmlVue3(generateForm.formData, type))
  const css = cssStyle(makeUpCss(generateForm.formData));
  return beautifier.html(html + script + css, beautifierConf.html)
}

script

makeUpJsVue3方法

export function makeUpJsVue3(conf, type) {
  confGlobal = conf = JSON.parse(JSON.stringify(conf))
  const dataList = []
  const ruleList = []
  const optionsList = []
  const propsList = []
  const methodList = mixinMethodVue3(type)
  const uploadVarList = []

  conf.fields.forEach(el => {
    buildAttributesVue3(el, dataList, ruleList, optionsList, methodList, propsList, uploadVarList)
  })

  const script = buildExportVue3(
    conf,
    type,
    dataList.join('\n'),
    ruleList.join('\n'),
    optionsList.join('\n'),
    uploadVarList.join('\n'),
    propsList.join('\n'),
    methodList.join('\n')
  )
  confGlobal = null
  return script
}

mixinMethodVue3

image.png' 因为选择生成表单的时候可以选择是弹窗还是页面,所以这个方法的type就是用来识别的。最后把所有的方法(其实就也没多少方法了,就是表单提交,重置方法)作为数组返回

function mixinMethodVue3(type) {
  const list = []; const
    minxins = {
      file: confGlobal.formBtns ? {
        submitForm: `function submitForm() {
        ${confGlobal.formRef}.value.validate(valid => {
          if(!valid) return
          // TODO 提交表单
        })
      };`,
        resetForm: `function resetForm() {
        ${confGlobal.formRef}.value.resetFields()
      };`
      } : null,
      dialog: {
        onOpen: 'function onOpen() {}',
        onClose: `function onClose() {
        ${confGlobal.formRef}.value.resetFields()
      }`,
        close: `function close() {
        dialogVisible.value = false;
      }`,
        handleConfirm: `function handleConfirm() {
        ${confGlobal.formRef}.value.validate(valid => {
          if(!valid) return
          close()
        })
      }`
      }
    }

  const methods = minxins[type]
  if (methods) {
    Object.keys(methods).forEach(key => {
      list.push(methods[key])
    })
  }

  return list
}

buildAttributesVue3

function buildAttributesVue3(el, dataList, ruleList, optionsList, methodList, propsList, uploadVarList) {
    buildDataVue3(el, dataList)
  buildRules(el, ruleList)

  if (el.options && el.options.length) {
    buildOptionsVue3(el, optionsList)
    if (el.dataType === 'dynamic') {
      const model = `${el.vModel}Options`
      const options = titleCase(model)
      buildOptionMethodVue3(`get${options}`, model, methodList)
    }
  }

  if (el.props && el.props.props) {
    buildPropsVue3(el, propsList)
  }

  if (el.action && el.tag === 'el-upload') {
    // uploadVarList.push(
    //   `${el.vModel}Action: '${el.action}',
    //   ${el.vModel}fileList: [],`
    // )
    uploadVarList.push(
      `const ${el.vModel}Action = ref('${el.action}')`,
      `const ${el.vModel}fileList = ref([])`
    )
    methodList.push(buildBeforeUpload(el))
    if (!el['auto-upload']) {
      methodList.push(buildSubmitUpload(el))
    }
  }

  if (el.children) {
    el.children.forEach(el2 => {
        buildAttributesVue3(el2, dataList, ruleList, optionsList, methodList, propsList, uploadVarList)
    })
  }
}

buildDataVue3 生成retive数据的

function buildDataVue3(conf, dataList) {
  if (conf.vModel === undefined) return
  let defaultValue
  if (typeof (conf.defaultValue) === 'string' && !conf.multiple) {
    defaultValue = `'${conf.defaultValue}'`
  } else {
    defaultValue = `${JSON.stringify(conf.defaultValue)}`
  }
  dataList.push(`${conf.vModel}: ${defaultValue},`)
}

buildRules 生成form表单中rules的,这个和vue2的没啥区别

buildOptionsVue3 这个是下拉自定义选项生成方法

function buildOptionsVue3(conf, optionsList) {
  if (conf.vModel === undefined) return
  if (conf.dataType === 'dynamic') { conf.options = [] }
  // const str = `${conf.vModel}Options: ${JSON.stringify(conf.options)},`
  const str = `const ${conf.vModel}Options = reactive(${JSON.stringify(conf.options)})`
  optionsList.push(str)
}

buildOptionMethodVue3 生成最后submit的方法,只是个模版

function buildOptionMethodVue3(methodName, model, methodList) {
  const str = `function ${methodName}() {
    // TODO 发起请求获取数据
  }`
  methodList.push(str)
}

buildPropsVue3 props相关

function buildPropsVue3(conf, propsList) {
  if (conf.dataType === 'dynamic') {
    conf.valueKey !== 'value' && (conf.props.props.value = conf.valueKey)
    conf.labelKey !== 'label' && (conf.props.props.label = conf.labelKey)
    conf.childrenKey !== 'children' && (conf.props.props.children = conf.childrenKey)
  }
  const str = `const ${conf.vModel}Props = ${JSON.stringify(conf.props.props)}`
  propsList.push(str)
}

buildExportVue3 最后生成vue3中script模版

下面代码中inheritAttrsVue3 在buildExportVue3通过传过来的类型(页面还是弹框)判断是否不继承父组件传来的props visibleModel element plus弹框我这边弄的是v-model:dialogVisible,所以如果是dialog的话,会把这一句插入到模板中

const inheritAttrsVue3 = {
  file: '',
  dialog: 'defineOptions({inheritAttrs: false})'
}
const visibleModel = {
    file: '',
    dialog: 'const dialogVisible = defineModel("dialogVisible")'
}



function buildExportVue3(conf, type, data, rules, selectOptions,  uploadVar, props, methods) {

    const str = `
    import { reactive, ref } from 'vue';
    ${inheritAttrsVue3[type]}
    ${visibleModel[type]}
    const ${conf.formRef} = ref(null)
    const ${conf.formModel} = reactive({${data}})
    const ${conf.formRules} = reactive({${rules}})
    ${selectOptions}
    ${uploadVar}
    ${props}
    
    ${methods}
    `
  return str
}

至此vueJs模块算是搞好了,然后看到vueScript方法,看代码就知道了

export function vueScript(str) {
  return `<script setup>
    ${str}
  </script>`
}

html

makeUpHtmlVue3

export function makeUpHtmlVue3(conf, type) {
  const htmlList = []
  confGlobal = conf
  someSpanIsNot24 = conf.fields.some(item => item.span !== 24)
  conf.fields.forEach(el => {
    htmlList.push(layoutsVue3[el.layout](el))
  })
  const htmlStr = htmlList.join('\n')

  let temp = buildFormTemplate(conf, htmlStr, type)
  if (type === 'dialog') {
    temp = dialogWrapper(temp)
  }
  confGlobal = null
  return temp
}

layoutsVue3 关键代码了,但是没有难度,看下面代码就好

const layoutsVue3 = {
  colFormItem(element) {
    let labelWidth = ''
    if (element.labelWidth && element.labelWidth !== confGlobal.labelWidth) {
      labelWidth = `label-width="${element.labelWidth}px"`
    }
    const required = !trigger[element.tag] && element.required ? 'required' : ''
    const tagDom = tagsVue3[element.tag] ? tagsVue3[element.tag](element) : null
    let str = `<el-form-item ${labelWidth} label="${element.label}" prop="${element.vModel}" ${required}>
        ${tagDom}
      </el-form-item>`
    str = colWrapper(element, str)
    return str
  },
  rowFormItem(element) {
    const type = element.type === 'default' ? '' : `type="${element.type}"`
    const justify = element.type === 'default' ? '' : `justify="${element.justify}"`
    const align = element.type === 'default' ? '' : `align="${element.align}"`
    const gutter = element.gutter ? `:gutter="${element.gutter}"` : ''
    const children = element.children.map(el => layoutsVue3[el.layout](el))
    let str = `<el-row ${justify} ${align} ${gutter}>
      ${children.join('\n')}
    </el-row>`
    str = colWrapper(element, str)
    return str
  }
}

buildFormTemplate

function buildFormTemplate(conf, child, type) {
  let labelPosition = ''
  if (conf.labelPosition !== 'right') {
    labelPosition = `label-position="${conf.labelPosition}"`
  }
  const disabled = conf.disabled ? `:disabled="${conf.disabled}"` : ''
  let str = `<el-form ref="${conf.formRef}" :model="${conf.formModel}" :rules="${conf.formRules}" size="${conf.size}" ${disabled} label-width="${conf.labelWidth}px" ${labelPosition}>
      ${child}
      ${buildFromBtns(conf, type)}
    </el-form>`
  if (someSpanIsNot24) {
    str = `<el-row :gutter="${conf.gutter}">
        ${str}
      </el-row>`
  }
  return str
}

dialogWrapper 顾名思义

export function dialogWrapper(str) {
  return `<el-dialog v-model="dialogVisible" @open="onOpen" @close="onClose" title="Dialog Title">
    ${str}
    <template #footer>
      <el-button @click="close">取消</el-button>
      <el-button type="primary" @click="handleConfirm">确定</el-button>
    </template>
  </el-dialog>`
}

至此html模块搞好了然后看到vueTemplate方法,看代码就知道了

export function vueTemplate(str) {
  return `<template>
    <div>
      ${str}
    </div>
  </template>`
}

css

makeUpCss 这个模块其实没做啥,因为基本上没改动css

export function makeUpCss(conf) {
  const cssList = []
  conf.fields.forEach(el => addCss(cssList, el))
  return cssList.join('\n')
}

然后看到cssStyle方法

function addCss(cssList, el) {
  const css = styles[el.tag]
  css && cssList.indexOf(css) === -1 && cssList.push(css)
  if (el.children) {
    el.children.forEach(el2 => addCss(cssList, el2))
  }
}

最后使用import beautifier from 'js-beautify' beautifier.html格式化生成的代码,不然生成的代码格式不好看

总结

这里面的改动其实不难,因为如果你把我的第一篇文章吃透的话,写这个模块应该是水到渠成。最后选择是生成文件还是直接复制代码都是可以的,这一块自己琢磨下就好了。如果需要源码帮助的,可以留言给我。

最后谈谈自己这种简单的模版生成看法。鸡肋,食之无肉,弃之有味。