这一篇内容就稍微简单点,基本上就是把我们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
'
因为选择生成表单的时候可以选择是弹窗还是页面,所以这个方法的
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格式化生成的代码,不然生成的代码格式不好看。
总结
这里面的改动其实不难,因为如果你把我的第一篇文章吃透的话,写这个模块应该是水到渠成。最后选择是生成文件还是直接复制代码都是可以的,这一块自己琢磨下就好了。如果需要源码帮助的,可以留言给我。
最后谈谈自己这种简单的模版生成看法。鸡肋,食之无肉,弃之有味。