01.组件封装
<template>
<!-- 自定义表单组件 -->
<el-form
:ref="formName"
class="elFormComponent"
:size="size"
:label-width="labelWidth"
:model="formData"
:rules="rules"
:inline="inline"
:style="{width:formWidth}"
>
<template v-for="(item,index) in formArr">
<!-- 根据type值显示相应框
item.width: form-item 项宽度
item.mb: form-item 项底部距离
item.childWidth:form-item内部元素宽度
-->
<el-form-item
v-if="!(item.isHide && item.isHide())"
:key="item.prop"
:label="item.label"
:prop="item.prop"
:label-width="item.labelWidth"
:style="{float:item.float,width:item.width,marginBottom:item.mb}"
>
<!-- 输入框 -->
<el-input
v-if="item.type==='input'"
:key="index"
v-model.trim.lazy="formData[item.prop]"
:size="item.size"
:placeholder="item.placeholder"
:maxlength="item.maxlength"
:type="item.inputType"
:autosize="autosize"
:disabled="item.disabled && item.disabled()"
:style="{width: item.childWidth}"
show-word-limit
clearable
@keyup.enter.native="item.handle()/* eslint-disable */"
>
<i
slot="suffix"
class="fontNormal"
>{{ item.unit }}</i>
</el-input>
<!-- 输入框textarea -->
<el-input
v-if="item.type==='textarea'"
:key="index"
v-model.lazy="formData[item.prop]"
:size="size"
:placeholder="item.placeholder"
:maxlength="item.maxlength"
type="textarea"
:autosize="autosize"
:disabled="item.disabled && item.disabled()"
:style="{width: item.childWidth}"
show-word-limit
clearable
>
<i
slot="suffix"
class="fontNormal"
>{{ item.unit }}</i>
</el-input>
<!-- 数字
max: 最大值
min:最小值
precision:保留小数点后n位数
-->
<el-input
v-if="item.type==='number'"
:key="index"
v-model.trim.lazy="formData[item.prop]"
v-only-number="{max:item.max||10,min:item.min,precision:item.precision,set:numberMethod,name:item.prop}"
:disabled="item.disabled && item.disabled()"
:placeholder="item.placeholder"
type="number"
:style="{width: item.childWidth}"
clearable
@change="item.change&&item.change($event)"
@blur="item.blur&&item.blur($event)"
@mousewheel.native.prevent="stopScrollFun($event)"
>
<i
slot="suffix"
class="fontNormal"
>{{ item.unit }}</i>
</el-input>
<!-- 数字手机号、固话-->
<el-input
v-if="item.type==='tel'"
:key="index"
v-model.trim.lazy="formData[item.prop]"
:disabled="item.disabled && item.disabled()"
:placeholder="item.placeholder"
type="text"
:maxlength="item.maxlength"
:style="{width: item.childWidth}"
show-word-limit
onkeyup="value=value.replace(/[^\d]/g,'')"
clearable
@change="item.change&&item.change($event)"
@blur="item.blur&&item.blur($event)"
>
<i
slot="suffix"
class="fontNormal"
>{{ item.unit }}</i>
</el-input>
<!-- 密码框 -->
<el-input
v-if="item.type==='password'"
:key="index"
v-model.trim.lazy="formData[item.prop]"
:size="size"
:placeholder="item.placeholder"
:maxlength="item.maxlength"
type="password"
show-password
:disabled="item.disabled && item.disabled()"
clearable
:style="{width: item.childWidth}"
/>
<!-- 下拉框 -->
<el-select
v-if="item.type==='select'"
v-model="formData[item.prop]"
:placeholder="item.placeholder"
:size="size"
:multiple="item.multiple"
:clearable="!item.closeClearable"
filterable
:style="{width: item.childWidth}"
:disabled="item.disabled && item.disabled()"
@change="item.change && item.change(formData[item.prop])"
@visible-change="item.visibleChange&&item.visibleChange($event)"
>
<el-option
v-for="op in (typeof item.options == 'function' ? item.options() : item.options)"
:key="op.value||op[item.optionValueName]"
:value="op.value||op[item.optionValueName]"
:label="op.label||op[item.optionLabelName]"
>
</el-option>
</el-select>
<!-- 单选 -->
<el-radio-group
v-if="item.type==='radio'"
v-model="formData[item.prop]"
:disabled="item.disabled&&item.disabled()"
@change="item.change && item.change(formData[item.prop])"
>
<el-radio
v-for="ra in item.radios"
:key="ra.value"
:label="ra.value"
>{{ ra.label }}</el-radio>
</el-radio-group>
<!-- 单选按钮 -->
<el-radio-group
v-if="item.type==='radioButton'"
v-model="formData[item.prop]"
@change="item.change && item.change(formData[item.prop])"
>
<el-radio-button
v-for="ra in item.radios"
:key="ra.value"
:label="ra.value"
>{{ ra.label }}</el-radio-button>
</el-radio-group>
<!-- 复选框 -->
<el-checkbox-group
v-if="item.type==='checkbox'"
v-model="formData[item.prop]"
:style="{width: item.childWidth}"
@change='item.change && item.change(formData[item.prop])'
>
<el-checkbox
v-for="(ch,i) in item.checkboxs"
:key="i"
:label="ch.label"
>{{ ch.label }}</el-checkbox>
</el-checkbox-group>
<!-- 级联选择器 -->
<el-cascader
v-if="item.type==='cascader'"
:ref="item.prop"
v-model="formData[item.prop]"
:options="item.options&&item.options()"
:props="item.props"
:placeholder="item.placeholder"
:style="{width: item.childWidth}"
:emit-path="true"
:clearable="!item.closeClearable"
:disabled="item.disabled && item.disabled()"
@change="item.change&&item.change($event)"
@visible-change="item.visibleChange&&item.visibleChange($event)"
@expand-change="item.expandChange&&item.expandChange($event)"
>
<template
v-if="item.isMask"
slot-scope="{ node, data }"
>
<span
v-if="node.level == 1 && data.children.length == 0"
class="myMask"
/>
<span>{{ data[item.props.label] || data.label }}</span>
</template>
</el-cascader>
<!-- 日期 -->
<el-date-picker
v-if="item.type==='date'"
v-model="formData[item.prop]"
:placeholder="item.placeholder"
:picker-options="item.pickerOptions"
:style="{width: item.childWidth}"
:value-format="item.valueFormat || 'timestamp'"
/>
<!-- 日期时间 -->
<!-- <el-date-picker v-if="item.type==='datetime'" v-model="formData[item.prop]" :disabled="item.disable && item.disable(formData[item.prop])" type="datetime"/> -->
<!-- 选择日期范围 -->
<el-date-picker
v-if="item.type==='daterange'"
v-model="formData[item.prop]"
type="daterange"
range-separator="至"
start-placeholder="开始日期"
end-placeholder="结束日期"
:style="{width: item.childWidth}"
:clearable="!item.closeClearable"
:default-time="['00:00:00', '23:59:59']"
:value-format="item.valueFormat || 'timestamp'"
/>
<!-- 开关 -->
<el-switch
v-if="item.type==='switch'"
v-model="formData[item.prop]"
/>
<!-- 地址选择 -->
<!-- <select-area
v-if="item.type==='selectArea'"
:isfour="item.isfour"
:citys.sync="formData[item.prop]"
clearable
@event-change="item.eventChange && item.eventChange(formData[item.prop])"
/> -->
<!-- 图片上传 -->
<img-upload
v-if="item.type==='img'"
:imgs.sync="formData[item.prop]"
:token="qiniuToken"
:limit="item.limit"
/>
<div v-if="item.remark && item.type==='img'"><span class="c606266">{{ item.remark }}</span></div>
<!-- 文件上传 -->
<file-upload
v-if="item.type==='file'"
:files.sync="formData[item.prop]"
:token="qiniuToken"
:limit="item.limit"
/>
<!-- 获取验证码 -->
<div
v-if="item.type==='getCode'"
class="display_flex"
>
<el-input
v-model="formData[item.prop]"
placeholder="请输入验证码"
class="mr_10"
/>
<el-button
icon="el-icon-mobile-phone"
class="w_130p"
type="success"
:disabled="disabled=!showGetCodeBtn"
@click="sendCode"
>
<span v-show="showGetCodeBtn">获取验证码</span>
<span
v-show="!showGetCodeBtn"
class="count"
>{{ timeCount }} s</span>
</el-button>
</div>
<!-- 富文本 -->
<div v-if="item.type==='tinymce'" >
<tinymce
v-model="formData[item.prop]"
:height="item.height||300"
/>
</div>
<!-- 标签 -->
<div v-if="item.type==='tag'">
<div class="display_flex">
<el-input
v-model.trim="item.value"
:placeholder="item.placeholder"
:maxlength="item.maxlength"
show-word-limit
class="mr_10"
clearable
@keyup.enter.native="item.addTag(item.value),item.value=''"
>
<!-- <button slot="append" @click="item.addTag(tagValue),tagValue=''"><i class="el-icon-plus" /></button> -->
</el-input>
<el-button
icon="el-icon-plus"
circle
plain
@click="item.addTag(item.value),item.value=''"
/>
</div>
<!-- <draggable
:disabled="!item.isDraggable"
:list="formData[item.prop]"
v-bind="$attrs"
>
<el-tag
v-for="(tag,tagIndex) in formData[item.prop]"
:key="tagIndex"
closable
:disable-transitions="false"
class="mr_10"
@close="item.handleClose(tag)"
>
{{ tag }}
</el-tag>
</draggable> -->
</div>
<!-- 选择按钮弹窗 -->
<div v-if="item.type==='dialog'">
<el-link
type="primary"
:underline="false"
:disabled="item.disabled && item.disabled()"
@click="item.showDialog && item.showDialog()"
>{{ item.btnText }}</el-link>
<div v-if="(item.hasSelected && item.hasSelected()).length > 0">
<el-tag
v-for="tag in item.hasSelected()"
:key="tag.userId"
class="mb_10 mr_10"
style="height: auto;"
:closable="!item.disabled || !item.disabled()"
@close="item.close(tag)"
>
<template v-if="item.dialogType === 'accountDialog'">
{{ tag.userName | noVal }}/{{ tag.accountName | noVal }}<span v-if="item.isShowIdentity">({{ tag.userType | accountType }})</span><span v-if="item.isShowAccount">(账户余额{{ tag.balance / 100 }}元)</span>
</template>
<template v-if="item.dialogType === 'projectDialog'">
<div v-if="tag.projectName">{{ tag.projectName }}</div>
<div v-if="tag.provinceName || tag.cityName || tag.areaName || tag.streetName || tag.address">{{ tag.provinceName + tag.cityName + tag.areaName + tag.streetName + tag.address }}</div>
<span class="mr_10">业主</span>{{ tag.customerName | noVal }}/{{ tag.customerMobile | noVal }}
</template>
</el-tag>
</div>
</div>
<!-- 标题栏 -->
<div
v-if="item.type==='divider'"
class="form-title mt_10"
>
<span class="form-title__icon" /> {{ item.title }}
</div>
<!-- 自定义组件插槽 -->
<slot
v-if="item.type===item.prop"
:name="item.prop"
/>
<el-tooltip v-if="item.tips" placement="top" class="ml_10">
<div slot="content">{{item.tips}}</div>
<i class="el-icon-question f20 c666"></i>
</el-tooltip>
</el-form-item>
</template>
<el-form-item v-if="btnArr.length>0">
<template v-for="item in btnArr">
<el-button
v-if="!(item.isHide && item.isHide())"
:key="item.label"
:disabled="item.disabled"
:type="item.type"
:size="item.size || size"
@click="item.handle()"
>{{ item.label }}</el-button>
</template>
</el-form-item>
</el-form>
</template>
<script>
// import SelectArea from '@/components/SelectArea'
import ImgUpload from '@/components/Upload/image'
// import FileUpload from '@/components/Upload/file'
// import { getQiniuTokenApi } from '@/api/common'
// import draggable from 'vuedraggable'
import Tinymce from '@/components/Tinymce'
export default {
name: 'FrForm',
components: {
// SelectArea,
ImgUpload,
Tinymce
// FileUpload,
// draggable
// // Tinymce
},
props: {
// 是否隐藏表单
isHide: {
type: Boolean,
default: false
},
// 是否横向显示表单
inline: {
type: Boolean,
default: false
},
// 是否有文件上传
uploade: {
type: Boolean,
default: false
},
// 表单宽度
formWidth: {
type: String,
default: '100%'
},
// 表单每项宽度
formItemWidth: {
type: String,
default: 'auto'
},
// label宽度
labelWidth: {
type: String,
default: '120px'
},
// 表单尺寸
size: {
type: String,
default: 'medium'
},
// 表单名字
formName: {
type: String,
default: ''
},
/* 要显示的表单数组
formArr字段
type: 显示的表单类型-必传, label: 左侧label, prop: 绑定值key和formName中的key对应-必传,
*/
formArr: {
type: Array,
default: () => { }
},
// 表单数据
formData: {
type: Object,
default: () => { }
},
// 表单验证
rules: {
type: Object,
default: () => { }
},
/* 按钮数组
ep:
btnArr=[{ label: '提交', type: 'primary', handle: () => this.saveForm('formData') }],
*/
btnArr: {
type: Array,
default: () => []
}
},
data () {
return {
qiniuToken: '',
autosize: { minRows: 5, maxRows: 10 },
// 显示获取验证码按钮
showGetCodeBtn: true,
// 验证码定时器
timer: null,
// 定时器时间
timeCount: 60
}
},
created () {
// 有上传文件的表单时获取七牛token
// if (this.uploade) this.getQiniuToken()
},
methods: {
// 获取七牛token
getQiniuToken () {
// getQiniuTokenApi().then(res => {
// const { code, data } = res
// if (code > 0) return
// this.qiniuToken = data.token
// })
},
// 表单验证
validate () {
return new Promise((resolve) => {
this.$refs[this.formName].validate(resolve)
})
},
// 单个字段验证
validateField (key) {
return new Promise((resolve) => {
this.$refs[this.formName].validateField(key)
})
},
// 表单重置
resetForm () {
this.$refs[this.formName].resetFields()
},
// 获取验证码
sendCode () {
if (!this.timer) {
this.showGetCodeBtn = false
this.timer = setInterval(() => {
if (this.timeCount > 0) {
this.timeCount--
} else {
this.showGetCodeBtn = true
clearInterval(this.timer) // 清除定时器
this.timeCount = 60
this.timer = null
}
}, 1000)
}
},
stopScrollFun (evt) {
evt = evt || window.event
if (evt.preventDefault) {
// Firefox
evt.preventDefault()
evt.stopPropagation()
} else {
// IE
evt.cancelBubble = true
evt.returnValue = false
}
return false
},
// number值改变
numberMethod: function (x, name) {
if (x !== 'NaN') {
this.formData[name] = x
} else {
this.formData[name] = ''
}
// console.log(this.formData[name])
}
}
}
</script>
<style lang="less" scoped>
.el-cascader-node:not(.is-disabled):hover,
.el-cascader-node:not(.is-disabled):focus {
.myMask {
background: #f5f7fa;
}
}
.c606266{
color: #606266;
}
.el-cascader-node .myMask {
position: absolute;
top: 10px;
left: 20px;
width: 14px;
height: 14px;
background: rgb(255, 255, 255);
z-index: 1;
}
.el-form-item__content{
display: flex;
align-items: center;
}
</style>
02.全局注册
import FrForm from './FrForm'
FrForm.install = Vue => {
Vue.component(FrForm.name, FrForm)
}
export default FrForm
03.main.js引入
import FrForm from '@/components/FrForm'
Vue.use(FrForm)
04.页面使用
<template>
<div class="wrap">
<el-card class="card-box">
<!--
1.formData:
说明: form 表单绑定的值
类型:Object
案例:
formData: {
name: 1,
}
2.formArr
说明: 文本框的 label
类型:Array
案例:
formArr: [{
type: 'select',
label: '筛选类型',
prop: 'type',
placeholder: '全部',
optionLabelName: 'label',
optionValueName: 'type',
closeClearable: true
}]
3.btnArr
说明: 按钮
类型:Array
案例:
btnArr: [
{ label: '查询', type: 'primary', handle: () => this.handleSearch() }
]
4.inline
说明: 是否是行内表单
类型:Boolean
案例,
:inline='true'
5.formWidth
说明:整个表单的宽度
类型:String
案例,
:formWidth='formWidth', data 中定义 formWidth:200
6.
-->
<fr-form
ref="formData"
form-name="formData"
:form-data="formData"
:form-arr="formArr"
:labelWidth="labelWidth"
:btn-arr="btnArr"
:inline='false'
:rules="formRules"
:formWidth='formWidth'
/>
</el-card>
</div>
</template>
<script>
export default {
data () {
return {
formWidth: '200',
labelWidth: '100px',
size: 'medium',
formData: {
select: '',
input: '',
textarea: '',
number: '',
tel: '',
password: '',
radio: 0,
radioButton: 0,
checkbox: ['北京'],
cascader: '',
date: '',
switch: '',
// img: '',
tinymce: ''
},
btnArr: [
{ label: '确认', type: 'primary', handle: () => this.handleSearch('formData') },
{ label: '重置', type: 'success', handle: () => this.handleReset('formData') },
{ label: '刷新', type: 'plain', handle: () => this.handleFresh() }
],
formArr: [
{
type: 'divider',
title: '标题栏'
},
{ type: 'select',
label: 'select',
prop: 'select',
placeholder: '全部',
optionLabelName: 'label',
optionValueName: 'value',
closeClearable: true,
disabled: () => this.disabled(),
change: () => this.selectChange(),
visibleChange: () => this.visibleChange(),
options: () => this.initSelectValue()
},
{ type: 'input',
label: 'input',
childWidth: '220px',
prop: 'input',
placeholder: '请输入',
handle: () => this.backEnter(),
closeClearable: true
},
{
type: 'textarea',
label: 'textarea',
prop: 'textarea',
childWidth: '420px',
placeholder: '请输入',
optionLabelName: 'label',
maxlength: '10',
closeClearable: true
},
{
type: 'number',
label: 'number',
prop: 'number',
placeholder: '请输入',
childWidth: '420px'
},
{
type: 'tel',
label: 'tel',
prop: 'tel',
placeholder: '请输入',
maxlength: 11,
change: () => this.telChange(),
childWidth: '420px'
},
{
type: 'password',
label: 'password',
prop: 'password',
placeholder: '请输入',
maxlength: 11,
childWidth: '420px'
},
{
type: 'radio',
label: 'radio',
prop: 'radio',
radios: [
{
label: '北京',
value: 0
},
{
label: '上海',
value: 1
},
{
label: '广州',
value: 2
},
{
label: '深证',
value: 3
}
],
change: (e) => this.radioChange(e)
},
{
type: 'radioButton',
label: 'radioButton',
prop: 'radioButton',
radios: [
{
label: '北京',
value: 0
},
{
label: '上海',
value: 1
},
{
label: '广州',
value: 2
},
{
label: '深证',
value: 3
}
],
change: (e) => this.radioButtonChange(e)
},
{
type: 'checkbox',
label: 'checkbox',
prop: 'checkbox',
childWidth: '420px',
change: (e) => this.CheckButtonChange(e),
checkboxs: this.checkBoxList()
},
{
type: 'cascader',
prop: 'cascader',
label: 'Cascader',
placeholder: '请选择',
childWidth: '300px',
options: () => this.CascaderOptions(),
change: () => this.cascaderChange()
},
{
type: 'date',
prop: 'date',
label: 'date',
placeholder: '请选择',
childWidth: '300px'
},
{
type: 'switch',
prop: 'switch',
label: 'switch',
placeholder: '请选择'
},
{
type: 'tinymce',
label: 'tinymce',
prop: 'tinymce',
childWidth: '300px'
}
// {
// type: 'img',
// prop: 'img',
// limit: 1,
// label: 'img',
// remark: '',
// placeholder: '请选择'
// }
],
formRules: {
select: [ { required: true, message: '请选择', trigger: 'change' } ],
input: [ { required: true, message: '请输入', trigger: 'change' } ],
textarea: [ { required: true, message: '请输入', trigger: 'change' } ],
number: [ { required: true, message: '请输入', trigger: 'change' } ],
tel: [ { required: true, message: '请输入', trigger: 'change' } ],
password: [ { required: true, message: '请输入', trigger: 'change' } ],
radio: [ { required: true, message: '请选择', trigger: 'change' } ],
radioButton: [ { required: true, message: '请选择', trigger: 'change' } ],
checkbox: [ { required: true, message: '请选择', trigger: 'change' } ],
cascader: [ { required: true, message: '请选择', trigger: 'change' } ],
date: [ { required: true, message: '请选择', trigger: 'change' } ],
tinymce: [ { required: true, message: '请输入', trigger: 'change' } ],
img: [ { required: true, message: '请选择', trigger: 'change' } ]
},
checkBox: [{
label: '北京',
value: 0
},
{
label: '上海',
value: 1
}]
}
},
methods: {
// 查询
async handleSearch (formData) {
if (await this.$refs.formData.validate(formData)) {
console.log('表单数据', this.formData)
this.$success('验证通过过')
} else {
this.$error('验证失败')
}
},
// 下拉框数据
initSelectValue () {
return [
{ label: '订阅号', value: 'subscribe' },
{ label: '服务号', value: 'server' }
]
},
// 下拉框出现/隐藏时触发,出现则为 true,隐藏则为 false
visibleChange () {
this.$success('下拉框触发')
},
// 下拉框 发生改变
selectChange () {
this.$success('下拉框改变触发')
},
// tel 发生改变
telChange () {
this.$success('手机狂变动输入')
},
// 单选
radioChange (e) {
console.log('单选按钮', e)
this.$success('单选按钮')
},
// 单选按钮
radioButtonChange (e) {
console.log('radioButtonChange', e)
},
// 复选框数据
checkBoxList () {
return [{
label: '北京',
value: 0
},
{
label: '上海',
value: 1
}]
},
// 复选框的值改变
CheckButtonChange (e) {
console.log('复选框的值改变', e)
},
// 级联数据
CascaderOptions () {
return [
{
value: 'zhinan',
label: '指南',
children: [{
value: 'shejiyuanze',
label: '设计原则',
children: [{
value: 'yizhi',
label: '一致'
}, {
value: 'fankui',
label: '反馈'
}, {
value: 'xiaolv',
label: '效率'
}, {
value: 'kekong',
label: '可控'
}]
}, {
value: 'daohang',
label: '导航',
children: [{
value: 'cexiangdaohang',
label: '侧向导航'
}, {
value: 'dingbudaohang',
label: '顶部导航'
}]
}]
}
]
},
// 级联选择器z
cascaderChange (e) {
console.log('级联选择器--cascaderChange', e)
},
// 刷新
handleFresh () {
this.$success('刷新')
/* 强制刷新
this.$router.go(0)
*/
},
// 重置
handleReset (formName) {
this.$refs[formName].resetForm()
this.$success('重置')
},
// 是否重置
disabled () {
return false
},
// 输入框回车键
backEnter () {
this.$success('你点击了 Enter 键')
}
}
}
</script>
<style scoped lang="less">
.wrap{
height: 200px;
}
</style>