赞
踩
该动态表单弹框组件是公司UI规范和目前涉及到的场景进行编写的,也是为了减少样式和代码量。当我写这篇文章的时候其实已经能完成大部分需求了。在此也只是记录以下,下面整体说明在公司文档里也有记录的,以方便后人维护使用。
FormDialog.vue代码如下
<template> <el-dialog :close-on-click-modal="closeOnClickModal" :title="title" :visible.sync="dialogVisible" :before-close="handleClose" :width="width || dialogWidth" custom-class="center-dialog" > <el-form ref="form" :model="form" :rules="rules" :label-width="labelWidth" label-position="left" class="form-cnt" :class="formClass" > <!-- 预留插槽 --> <slot name="first-form-item"></slot> <!-- 渲染表单 --> <template v-for="(formItem, index) in formList"> <el-form-item :key="formItem.bindVal" :label="formItem.label" :prop="formItem.bindVal" :required="formItem.required" :class="[ {'upload-form-item': formItem.eleTag === 'el-upload'}, {'fill-width': formItem.fillWidth} ]" > <!-- 自定义label --> <template v-if="formItem.customFormLabel" slot="label"> <slot :name="`${formItem.bindVal}-label`"></slot> </template> <!-- label超长省略时使用-能不能自动判断呢?? --> <template v-else-if="formItem.labelTooltip" slot="label"> <el-tooltip effect="dark" placement="top" popper-class="atooltip" :content="formItem.label" > <span>{{ formItem.label }}</span> </el-tooltip> </template> <!-- 上传组件 --> <div v-if="formItem.eleTag === 'el-upload'" class="upload-area"> <!-- 上传图片-卡片式 --> <template v-if="!formItem.uploadOptions || formItem.uploadOptions.type === 'image'"> <upload-img v-model="form[formItem.bindVal]" class="upload" /> <div v-if="formItem.uploadOptions && formItem.uploadOptions.tips" class="upload-tips" >{{ formItem.uploadOptions.tips }}</div> </template> <!-- 上传文件列表 --> <template v-else> <upload v-bind="formItem.uploadOptions" @update="uploadUpdate($event, formItem.bindVal)" /> </template> </div> <!-- 富文本 --> <div v-else-if="formItem.eleTag === 'wang-edit'" id="editor" class="form-editor" /> <!-- 正常表单项 --> <component v-else :is="formItem.eleTag" v-model="form[formItem.bindVal]" v-bind="formItem.propsOptions" > <template v-if="formItem.eleChildTag"> <component v-for="optionItem in formItem.childOptions" :key="optionItem.value" :is="formItem.eleChildTag" :label="optionItem.label" :value="optionItem.value" :name="optionItem.name" @change="$emit(`${formItem.bindVal}Change`, $event)" > <span v-if="optionItem.customOptionLabel">{{ optionItem.customOptionLabel }}</span> </component> </template> </component> </el-form-item> <!-- 预留任意位置插入的插槽 --> <slot :name="('form-item-' + index)" /> </template> </el-form> <!-- 弹框底部 --> <div slot="footer"> <el-button class="common_white_btn" @click="handleClose">取 消</el-button> <el-button class="common_red_btn" type="primary" @click="handleConfirm">确 定</el-button> </div> </el-dialog> </template> <script> import E from 'wangeditor' export default { name: 'FormDialog', components: { UploadImg: () => import('@/components/upload-img'), Upload: () => import('@/components/upload') }, props: { dialogVisible: { type: Boolean, required: true }, // 弹框标题 title: { type: String, required: true }, // 是否可以通过点击 modal 关闭 Dialog closeOnClickModal: { type: Boolean, required: true }, /* *表单容器类名,决定单列还是多列 *取值: * block-cnt单列布局 * flex-cnt两列布局 */ formClass: { type: String, default: 'block-cnt', validator(value) { return ['block-cnt', 'flex-cnt'].includes(value) } }, // 标签宽度 labelWidth: { type: String, default: '80px' }, // 表单json数组 formList: { type: Array, required: true }, // 表单props对象 form: { type: Object, required: true }, // 表单校验规则 rules: { type: Object, default: () => {} }, // 自定义弹框宽度 width: { type: String, default: '' }, // 是否初始化富文本 isInitEditor: { type: Boolean, default: false }, // 富文本对应表单字段名 editorPropName: { type: String, default: 'details' } }, data () { return { // formData: {}, editor: null } }, computed: { // 454px为单行时的宽度,704px为多行 dialogWidth() { let width = '704px' const labelWidthNum = parseInt(this.labelWidth, 10) if (this.formClass === 'block-cnt') { width = '454px' } if (labelWidthNum > 80) { width = `${(labelWidthNum - 80) * 2 + 704}px` } return width } }, // watch: { // form(newVal) { // this.formData = JSON.parse(JSON.stringify(newVal)) // } // }, mounted() { this.$nextTick(() => { if (this.isInitEditor && document.getElementById('editor')) this.initEditor() }) }, beforeDestroy() { if (!this.editor) return this.editor.destroy() this.editor = null }, methods: { handleClose() { this.$refs['form'].clearValidate() this.$emit('close') }, // 确认 handleConfirm() { this.$refs['form'].validate((valid) => { if (valid) { // 校验成功 if (this.editor) this.form[this.editorPropName] = this.editor.txt.html() this.$emit('success', this.form) this.$refs['form'].clearValidate() } }) }, uploadUpdate({ fileList }, prop) { let res = fileList.map((file) => ({ fileName: file.name, url: file.response?.data || 'No response' })) this.form[prop] = res }, // 初始化富文本 initEditor() { this.editor = new E('#editor') this.editor.config.menus = [ 'image', 'table', 'fontSize', 'foreColor', 'bold', 'italic', 'underline' ] this.editor.config.height = 150 this.editor.config.placeholder = '请输入产品详情' this.editor.config.showFullScreen = true this.editor.config.showLinkImg = false this.editor.config.customUploadImg = (resultFiles, insertImgFn) => { let editorFormData = new FormData() editorFormData.append('file', resultFiles[0]) this.$api.uploadPublic(editorFormData) .then(({ data }) => { insertImgFn(data) }) } this.editor.config.uploadImgMaxSize = 5 * 1024 * 1024 this.editor.config.uploadImgAccept = ['jpg', 'jpeg', 'png'] this.editor.config.uploadImgMaxLength = 5 this.editor.create() if (this.form[this.editorPropName]) this.editor.txt.html(this.form[this.editorPropName]) } } } </script> <style lang="scss" scoped> @mixin innerWidth($width) { /deep/.el-input__inner, /deep/.el-textarea__inner, /deep/.el-date-editor.el-input, /deep/.el-date-editor.el-input__inner { width: $width; } } .flex-cnt { @include innerWidth(240px); display: flex; flex-wrap: wrap; justify-content: space-between; .el-checkbox-group, .el-radio-group { min-width: 240px; max-width: 240px; } .upload-form-item { width: 100%; } } .block-cnt { @include innerWidth(330px); .el-checkbox-group, .el-radio-group { min-width: 330px; max-width: 330px; } } .fill-width { @include innerWidth(100%); width: 100%; } .el-dialog__wrapper.el-dialog__wrapper { display: flex; margin: 20px 0; } /deep/.center-dialog { margin: auto !important; } .form-cnt { .el-form-item { font-size: 12px; } /deep/.el-input__inner { height: 32px; } // 普通输入框 /deep/.el-input__inner, /deep/.el-textarea__inner { font-size: 12px; border: 1px solid #d7dbe8; &::placeholder { color: #c5cad5; } } /deep/.el-textarea .el-input__count { bottom: 0; background: transparent; } /deep/.el-input__count-inner { color: #c5cad5; } /deep/.el-form-item__label, /deep/.el-radio__label { font-size: 12px; color: #323e58; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .upload-tips { margin-top: 8px; font-size: 12px; color: #85889c; line-height: 17px; } } .form-editor { position: relative; width: 100%; z-index: 0; /deep/.w-e-toolbar { border-radius: 4px 4px 0 0; } /deep/.w-e-text-container { min-height: 150px; height: 100% !important; border-radius: 0 0 4px 4px; } /deep/.w-e-text { max-height: 450px; } /deep/.placeholder { color: #c5cad5; font-size: 12px; } /deep/ .w-e-toolbar p, /deep/ .w-e-text-container p, /deep/ .w-e-menu-panel p { font-size: 12px !important; color: #323e58; } } </style>
<template> <!-- 测试弹框 --> <FormDialog v-if="showEditDialog" :dialogVisible="showEditDialog" title="编辑合伙人" form-class="flex-cnt" :form="curEditItem" :form-list="formList" :rules="rules" @success="handleUpdate" @close="showEditDialog = false" /> </template> <script> import FormDialog from '@/components/layoutComponet/FormDialog' export default { components: { FormDialog }, data () { return { formList: [ { label: '姓名', eleTag: 'el-input', bindVal: 'name', propsOptions: { placeholder: '请输入姓名' } }, { label: '花名', eleTag: 'el-input', bindVal: 'stageName', propsOptions: { placeholder: '请输入花名' } }, { label: '性别', eleTag: 'el-radio-group', bindVal: 'sex', eleChildTag: 'el-radio', childOptions: [ { label: '男' }, { label: '女' } ] }, { label: '手机号码', eleTag: 'el-input', bindVal: 'phone', propsOptions: { placeholder: '请输入手机号码', disabled: true } }, { label: '工号', eleTag: 'el-input', bindVal: 'employNum', propsOptions: { placeholder: '请输入工号' } }, { label: '所属部门', eleTag: 'el-select', bindVal: 'department', eleChildTag: 'el-option', propsOptions: { placeholder: '请选择所属部门' }, childOptions: [] }, { label: '职位', eleTag: 'el-input', bindVal: 'position', propsOptions: { placeholder: '请输入职位' } } ], rules: { name: { required: true, message: '请输入姓名', trigger: 'blur' }, department: { required: true, message: '请选择所属部门', trigger: 'change' } }, } } } </script>
参数名 | 参数说明 | 类型 | 是否必传 | 默认 |
---|---|---|---|---|
dialogVisible | 是否显示弹框 | Boolean | true | |
title | 弹框标题 | String | true | |
width | 弹框的宽度 | String | false | 为’'空字符串,需要自定义弹框宽度时可传 |
formClass | block-cnt单行布局 flex-cnt两行布局 | String | false | 默认值block-cnt,可选flex-cnt |
labelWidth | 最好不要超过100px | String | false | 80px |
isInitEditor | 是否初始化富文本,使用弹框默认富文本,如果功能不满足请使用slot自定义,自定义时最好不要传该属性 | Boolean | false | false |
editorPropName | 富文本对应表单字段名 | String | false | ‘details’ |
formList | 需要渲染的表单json数组,详细参数说明见2-2 | Array | true | |
form | 表单对象 | Object | true | |
rules | 表单校验规则 | Object | false | {} |
类名 | 参数说明 | 类型 | 是否必传 | 例子 | 说明 |
---|---|---|---|---|---|
label | el-form-item的label名称 | String | true | ||
eleTag | 需要渲染的表单元素名,eg: el-input/el-select | String | true | ||
bindVal | 绑定的props值,必须对应form对象的某个属性 | String | true | ||
eleChildTag | 需要渲染的表单元素的子元素名,eg: el-select的子元素为el-option | String | false | ||
fillWidth | 是否占满一行 | Boolean | false | ||
labelTooltip | label超长省略的时候通过tooltip提示 | Boolean | false | 能不能自动判断然后添加el-tooltip??? | |
customFormLabel | 允许自定义label,用于标签过长的时候省略之后悬浮显示label的全称 | Boolean | false | ||
propsOptions | Element UI 表单元素的的可接受属性 | Object | false | { type: ‘textarea’, placeholder: ‘请输入文本’ } | |
childOptions | 渲染表单元素的子元素的项数组,options:[{value|name: ‘’, label: ‘’, customOptionLabel: ‘’}],value|name按表单组件传就行, customOptionLabel为可选项(自定义label) | Array | false | 具体的options查看element对应组件 | |
uploadOptions | 当eleTag为el-upload时可选项,当el-upload不是上传y图片时可选对象参数见’@/components/upload.vue’组件 (样式自己改.jpg) 可以换掉这个上传文件的,毕竟是某人写的 | Object || 不传任何值 | false | 默认为undefined(uploadOptions, uploadOptions.type为image时同理),为图片上传组件(做上层兼容) |
项目留有多个具名插槽
first-form-item
:第一项el-form-item
的位置
form-item-${index}
:index
为每一项表单元素后都留有一个具名插槽,可在表单之间随意插入
${bindVal}-label
: 各个表单项可完全自定义label,需要和customFormLabel
配合使用
现已支持element UI 所有表单的可接收属性,所有属性通过
propsOptions
对象接收
:is="component"
动态组件有性能问题,如果有需要可以加keep-alive
只有一开始设计时保留的图片,后面做了一些弹框整体的样式修改修改新的到时候再截图补充;但是弹框布局就只有下面两种,是定制化的,截图时涉及到隐私信息已经截取掉了,只保留了实现结果
如果要修改记得考虑向下兼容以前的使用,非必要不更改。这是我在公司自己思考封装的一个很小小小小的想法
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。