2023-08-04 TForm表单可以集成
自己封装含有下拉框组件
的使用
HTML 一行代码,可实现表单输入框/日期选择/下拉选择/复选框选中等及规则校验功能
<t-form
:ref-obj.sync="formOpts.ref"
:formOpts="formOpts"
:widthSize="2"
@handleEvent="handleEvent"
/>
<--注意:ref-obj(form校验规则相当于ref)必须要‘.sync’修饰符--!>
参数 | 说明 | 类型 | 默认值 |
---|---|---|---|
refObj | form 表单校验规则方法 (可以参考 elementUI Form 表单方法中的 validate) | obj | - |
className | 自定义类名 | String | - |
labelPosition | 改变表单项 label 与输入框的布局方式(默认:right) /top | String | ‘right’ |
widthSize | 每行显示几个输入项(默认两项) 最大值 4 | Number | 2 |
isDynamic | 是否开启动态新增表单项 | Boolean | false |
isTrim | 全局是否开启清除前后空格(comp 为 el-input 且 type 不等于’password’) | Boolean | true |
formOpts | 表单配置项 | Object | {} |
—listTypeInfo | 下拉选择数据源(type:'select’有效) | Object | {} |
—fieldList | form 表单每项 list | Array | [] |
------isHideItem | 某一项不显示 | Boolean | false |
------slotName | 自定义表单某一项输入框 | slot | - |
------childSlotName | 自定义表单某一下拉选择项子组件插槽(el-option) | slot | - |
------comp | form 表单每一项组件是输入框还是下拉选择等(可使用第三方 UI 如 el-select/el-input 也可以使用自定义组件) | String | - |
------bind | 表单每一项属性(继承第三方 UI 的 Attributes,如 el-input 中的 clearable 清空功能)默认清空及下拉过滤 | Object | {} |
------isTrim | 是否不清除前后空格(comp 为 el-input 且 type 不等于’password’) | Boolean | false |
------type | form 表单每一项类型 | String | 是 |
------widthSize | form 表单某一项所占比例(如果占一整行则设置 1) | Number | 2 |
------width | form 表单某一项所占实际宽度 | String | 100% |
------arrLabel | type=select-arr 时,每个下拉显示的中文 | String | ‘dictLabel’ |
------arrKey | type=select-arr 时,每个下拉显示的中文传后台的数字 | String | ‘dictValue’ |
------label | form 表单每一项 title | String | - |
------labelRender | 自定义某一项 title | function | - |
------value | form 表单每一项传给后台的参数 | String | - |
------rules | 每一项输入框的表单校验规则 | Object/Array | - |
------list | 下拉选择数据源(仅仅对 type:'select’有效) | String | - |
------event | 表单每一项事件标志(handleEvent 事件) | String | - |
------eventHandle | 继承 comp 组件的事件(返回两个参数,第一个自己自带,第二个 formOpts) | Object | - |
------isSelfCom | 是否使用自己封装的组件(TSelect等—含有下拉框) | Boolean | false |
—formData | 表单提交数据(对应 fieldList 每一项的 value 值) | Object | - |
—labelWidth | label 宽度 | String | 120px |
—rules | 规则(可依据 elementUI el-form 配置————对应 formData 的值) | Object/Array | - |
—operatorList | 操作按钮 list | Array | - |
事件名 | 说明 | 返回值 |
---|---|---|
handleEvent | 单个查询条件触发事件 | fieldList 中 type/查询条件输入的值/fieldList 中 event 值 |
事件名 | 说明 | 参数 |
---|---|---|
resetFields | 重置表单 | - |
clearValidate | 清空校验 | - |
<template>
<el-form
class="t-form"
ref="form"
:class="className"
:model="formOpts.formData"
:rules="formOpts.rules"
:label-width="formOpts.labelWidth||'120px'"
:label-position="formOpts.labelPosition||'right'"
v-bind="$attrs"
v-on="$listeners"
>
<template v-for="(item, index) in fieldList">
<el-form-item
v-if="!item.isHideItem"
:key="index"
:prop="item.value"
:label="item.label"
:class="[item.className,{'render_label':item.labelRender},{'slot_label':item.slotName},{'render_laber_position':formOpts.labelPosition},{'is_dynamic':isDynamic}]"
:rules="item.rules"
:style="getChildWidth(item)"
v-bind="{...item.formItemBind}"
>
<template #label v-if="item.labelRender">
<render-comp :createElementFunc="item.labelRender" />
template>
<template v-if="item.slotName">
<slot :name="item.slotName">slot>
template>
<template v-if="item.textShow">
<span>{{item.textValue||formOpts.formData[item.value]}}span>
template>
<template v-if="item.isSelfCom">
<component
:is="item.comp"
v-model="formOpts.formData[item.value]"
:placeholder="item.placeholder||getPlaceholder(item)"
v-bind="{clearable:true,filterable:true,...item.bind}"
:style="{width: item.width||'100%'}"
v-on="cEvent(item)"
/>
template>
<component
v-if="!item.slotName&&!item.textShow&&!item.isSelfCom"
:is="item.comp"
v-model="formOpts.formData[item.value]"
:type="item.comp==='el-input'?item.type||'input':item.type||item.bind.type"
:placeholder="item.placeholder||getPlaceholder(item)"
@change="handleEvent(item.event, formOpts.formData[item.value],item)"
v-bind="{clearable:true,filterable:true,...item.bind}"
:style="{width: item.width||'100%'}"
v-on="cEvent(item)"
>
<template #prepend v-if="item.prepend">{{ item.prepend }}template>
<template #append v-if="item.append">{{ item.append }}template>
<template v-if="item.childSlotName">
<slot :name="item.childSlotName">slot>
template>
<component
v-else
:is="compChildName(item)"
v-for="(value, key, index) in selectListType(item)"
:key="index"
:disabled="value.disabled"
:label="compChildLabel(item,value)"
:value="compChildValue(item,value,key)"
>{{compChildShowLabel(item,value)}}component>
component>
<i :key="index+'icon'" v-if="isDynamic" class="el-icon-delete" @click="dynamicDel(index)">i>
el-form-item>
template>
<div class="footer_btn flex-box flex-ver t-margin-top-5">
<template v-if="formOpts.btnSlotName">
<slot :name="formOpts.btnSlotName">slot>
template>
<template v-if="!formOpts.btnSlotName&&formOpts.operatorList&&formOpts.operatorList.length>0">
<el-button
v-for="(val,index) in formOpts.operatorList"
:key="index"
@click="val.fun(val)"
:type="val.type||'primary'"
:icon="val.icon"
:size="val.size || 'small'"
:disabled="val.disabled"
>{{ val.label }}el-button>
template>
div>
el-form>
template>
<script>
import RenderComp from './renderComp'
export default {
name: 'TForm',
components: {
RenderComp
},
props: {
// 自定义类名
className: {
type: String
},
/** 表单配置项说明
* formData object 表单提交数据
* rules object 验证规则
* fieldList Array 表单渲染数据
* operatorList Array 操作按钮list
* listTypeInfo object 下拉选项数据
* labelWidth String label宽度
*/
formOpts: {
type: Object,
default: () => ({})
},
// 一行显示几个输入项;最大值4
widthSize: {
type: Number,
default: 2,
validator: (value) => {
return value <= 4
}
},
// 是否开启动态新增表单项
isDynamic: {
type: Boolean,
default: false
},
// 全局是否开启清除前后空格
isTrim: {
type: Boolean,
default: true
},
// ref
refObj: {
type: Object
}
},
computed: {
cEvent() {
return ({ eventHandle }) => {
let event = { ...eventHandle }
let changeEvent = {}
Object.keys(event).forEach(v => {
changeEvent[v] = (e) => {
if ((typeof e === 'number' && e === 0) || e) {
event[v] && event[v](e, this.formOpts, arguments)
} else {
event[v] && event[v](this.formOpts, arguments)
}
}
})
return { ...changeEvent }
}
},
selectListType() {
return ({ list }) => {
if (this.formOpts.listTypeInfo) {
return this.formOpts.listTypeInfo[list]
} else {
return []
}
}
},
// 子组件名称
compChildName() {
return ({ type }) => {
switch (type) {
case 'checkbox':
return 'el-checkbox'
case 'radio':
return 'el-radio'
case 'select-arr':
case 'select-obj':
return 'el-option'
}
}
},
// 子子组件label
compChildLabel() {
return ({ type, arrLabel }, value) => {
switch (type) {
case 'radio':
case 'checkbox':
return value.value
case 'el-select-multiple':
case 'select-arr':
return value[arrLabel || 'dictLabel']
case 'select-obj':
return value
}
}
},
// 子子组件value
compChildValue() {
return ({ type, arrKey }, value, key) => {
switch (type) {
case 'radio':
case 'checkbox':
return value.value
case 'el-select-multiple':
case 'select-arr':
return value[arrKey || 'dictValue']
case 'select-obj':
return key
}
}
},
// 子子组件文字展示
compChildShowLabel() {
return ({ type, arrLabel }, value) => {
switch (type) {
case 'radio':
case 'checkbox':
return value.label
case 'el-select-multiple':
case 'select-arr':
return value[arrLabel || 'dictLabel']
case 'select-obj':
return value
}
}
}
},
data() {
return {
colSize: this.widthSize,
fieldList: this.formOpts.fieldList
}
},
watch: {
'formOpts.formData': {
handler(val) {
// 将form实例返回到父级
this.$emit('update:refObj', this.$refs.form)
},
deep: true // 深度监听
},
widthSize(val) {
if (val > 4) {
this.$message.warning('widthSize值不能大于4!')
this.colSize = 4
} else {
this.colSize = val
}
}
},
mounted() {
// 将form实例返回到父级
this.$emit('update:refObj', this.$refs.form)
},
methods: {
// label与输入框的布局方式
getChildWidth(item) {
if (this.formOpts.labelPosition === 'top') {
return `flex: 0 1 calc((${100 / (item.widthSize || this.colSize)}% - 10px));margin-right:10px;`
} else {
return `flex: 0 1 ${100 / (item.widthSize || this.colSize)}%;`
}
},
// 得到placeholder的显示
getPlaceholder(row) {
let placeholder
if (typeof row.comp === 'string' && row.comp) {
if (row.comp.includes('input')) {
placeholder = '请输入' + row.label
} else if (row.comp.includes('select') || row.comp.includes('date') || row.comp.includes('cascader')) {
placeholder = '请选择' + row.label
} else {
placeholder = row.label
}
} else {
placeholder = row.label
}
return placeholder
},
dynamicDel(index) {
this.$emit('del', index)
},
// 绑定的相关事件
handleEvent(type, val, item) {
console.log('组件', type, val, item)
// 去除前后空格
if (this.isTrim && !item.isTrim && item.comp.includes('el-input') && item.type !== 'password' && item.type !== 'inputNumber') {
this.formOpts.formData[item.value] = this.formOpts.formData[item.value].trim()
}
this.$emit('handleEvent', type, val)
},
// 校验
validate() {
return new Promise((resolve, reject) => {
this.$refs.form.validate(valid => {
if (valid) {
resolve({
valid,
formData: this.formOpts.formData
})
} else {
// eslint-disable-next-line prefer-promise-reject-errors
reject({
valid,
formData: null
})
}
})
})
},
// 重置表单
resetFields() {
return this.$refs.form.resetFields()
},
// 清空校验
clearValidate() {
return this.$refs.form.clearValidate()
}
}
}
script>
<style lang="scss">
.t-form {
display: flex;
flex-wrap: wrap;
.el-form-item {
display: inline-block;
width: 50%;
.el-form-item__content {
.el-input,
.el-select,
.el-date-editor,
.el-textarea {
width: 100%;
}
.el-input-number {
.el-input {
width: inherit;
}
}
}
}
.is_dynamic {
.el-form-item__content {
display: flex;
align-items: center;
.el-icon-delete {
margin-left: 5px;
cursor: pointer;
}
}
}
.t-margin-top-5 {
margin-top: calc(5px);
}
.el-input-number {
.el-input {
.el-input__inner {
text-align: left;
}
}
}
.render_label {
.el-form-item__label {
display: flex;
align-items: center;
justify-content: flex-end;
&::before {
margin-top: 1px;
}
}
}
.render_laber_position {
.el-form-item__label {
justify-content: flex-start;
}
}
.label_render {
display: flex;
align-items: center;
justify-content: flex-end;
}
.slot_label {
// margin-bottom: 0 !important;
.el-form-item__content {
// margin-left: 0 !important;
label {
min-width: 108px;
color: #606266;
text-align: right;
margin-right: 12px;
}
}
}
.footer_btn {
width: 100%;
}
}
style>
gitHub组件地址
gitee码云组件地址
基于ElementUi再次封装基础组件文档
vue+element-ui的table组件二次封装