概述
Vue2弹框-动态表单渲染
开头废话
该动态表单弹框组件是公司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 | {} |
formList参数说明
类名 | 参数说明 | 类型 | 是否必传 | 例子 | 说明 |
---|---|---|---|---|---|
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
效果图
只有一开始设计时保留的图片,后面做了一些弹框整体的样式修改修改新的到时候再截图补充;但是弹框布局就只有下面两种,是定制化的,截图时涉及到隐私信息已经截取掉了,只保留了实现结果
组件修改原则
如果要修改记得考虑向下兼容以前的使用,非必要不更改。这是我在公司自己思考封装的一个很小小小小的想法
最后
以上就是传统煎蛋为你收集整理的动态弹框组件--配置化--低代码的全部内容,希望文章能够帮你解决动态弹框组件--配置化--低代码所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
发表评论 取消回复