用slot和component实现表单共用
业务需求
在oa开发中,有许多流程,每个流程里都会有很多字段,比如流程标题、拉下选择,附件等等,有些是每个流程都会有的,有些是特有的,按常规的方法开发,就为为一个流程写一个表单,校验,提交。如果新来流程,就复制一个表达,修改需要变更的地方。这样开发会导致很多重复的代码,而且比较凌乱
简化实现
- 将每一个输入框写成共用的,将必填校验判断也一并写入,比如:流程标题组件: iProcessTitle,使用详情看下方注释
<template>
<div>
<div v-if="!isShow">
<Row>
<Col :xs="6" :md='mdModelLeft'><span class="t-span">{{config.title}}:</span></Col>
<Col :xs="18" :md='mdModelRight'>
<FormItem :prop="config.key" :rules="rules">
<Input
v-model="postData[config.key]"
:placeholder="placeholder"
:maxlength="config.maxLength"
:disabled="config.disabled || false"
></Input>
</FormItem>
</Col>
</Row>
</div>
<div v-else>
<div class="cont-i" v-if="config.title">
<span class="gray gray-f">{{ config.title }}</span>
<div class="attachment-i">{{ postData[config.key] }}</div>
</div>
</div>
</div>
</template>
<script>
import { mapState } from 'vuex'
var validateData = {}
export default {
name: "i-process-title",
computed: {
...mapState([
'postData'
]),
placeholder: function () {
// 更具传入标题显示placeholder
let placeholder = '请选择输入' + this.config.title
if (this.config.maxLength) {
placeholder += '(' + this.config.maxLength +'个字以内)'
}
return placeholder
},
rules: function () {
return {
validator: validateData,
trigger: 'blur'
}
},
isShow: function () {
return this.config.isShow
}
},
props: {
// 当前输入框配置
config: {
default(){
return {
title: '流程标题', // 输入框标题
key: 'processTitle', // 要提交的字段
required: false, // 是否必填
disabled: false, // 是否禁止编辑
isShow: true, // 是否是流程发起状态 true:流程发起,展示输入框; false: 审批过程/打印,展示结果
}
},
type: Object
}
},
data() {
// 输入校验
validateData = (rule, value, callback) => {
let reg = /^[0-9]*$/;
// 是否必填
if (this.config.required) {
if (value === '' || value === undefined) {
callback(new Error(this.config.title + '必填'));
return
}
}
// 纯数字校验
if (this.config.type && this.config.type === 'Number') {
if (!reg.test(value) && value !== '' && value !== undefined) {
callback(new Error('格式不符合'));
return
}
}
callback();
}
return {
}
},
methods: {
},
mounted(){
this.postData.department = this.$store.state.department
}
}
</script>
<style scoped>
</style>
- 选择框组件: iSelectType
<template>
<Row>
<Col :xs="6" :md='mdModelLeft'><span class="t-span">{{config.title}}:</span></Col>
<Col :xs="18" :md='mdModelRight'>
<FormItem :prop="config.key" :rules="rules">
<Select v-model="postData[config.key]">
<Option v-for="(item, key) in config.list" :value="item" :key="item">{{ key }}</Option>
</Select>
</FormItem>
</Col>
</Row>
</template>
<script>
import UImodel from '../../assets/js/UIModel'
import {mapState, mapMutations} from 'vuex'
export default {
name: 'i-select-type',
props: {
config: {
default(){
return {
title: '是否超标', // 默认标题
key: 'excessive', // 默认字段
list: { // 默认列表
'是': 'true',
'否': 'false'
}
}
},
type: Object
}
},
computed: {
...mapState([
'postData'
]),
rules: function () {
// 必填校验
if (this.config.required) {
return {
required: true,
message: '选择' + this.config.title,
trigger: 'change'
}
}
}
},
data () {
return {
mdModelLeft: UImodel.mdModelLeft,
mdModelRight: UImodel.mdModelRight
}
}
}
</script>
- 时间选择组件:iDate
<template>
<div>
<Row>
<Col :xs="6" :md='mdModelLeft'><span class="t-span">{{config.title}}</span></Col>
<Col :xs="18" :md='mdModelRight'>
<FormItem prop="startTimeFlag" :rules="startRules">
<DatePicker
:type="type"
v-model="postData.startTimeFlag"
:format="format"
placeholder="请选择时间"
@on-change="sdateChange"
style="width: 200px">
</DatePicker>
</FormItem>
</Col>
</Row>
<Row v-if="config.endate">
<Col :xs="6" :md='mdModelLeft'><span class="t-span">结束时间:</span></Col>
<Col :xs="18" :md='mdModelRight'>
<FormItem prop="endTimeFlag" :rules="endRules">
<DatePicker
:type="type"
v-model="postData.endTimeFlag"
:format="format"
:options="endDateOptions"
placeholder="请选择时间"
@on-change="edateChange"
style="width: 200px">
</DatePicker>
</FormItem>
</Col>
</Row>
</div>
</template>
<script>
import UImodel from '../../assets/js/UIModel'
import {mapState, mapMutations} from 'vuex'
var datePassCheck = {}
export default {
name: 'i-date',
props: {
config: {
default () {
return {
title: '开始时间'
}
},
type: Object
}
},
computed: {
...mapState([
'postData'
]),
// 开始时间校验
startRules: function () {
//是否必填
if (this.config.required) {
return {
type: 'date',
required: true,
message: this.config.title + '不能为空',
trigger: 'change'
}
}
},
// 结束时间校验
endRules: function () {
// 是否必填
if (this.config.endate && this.config.endrequired) {
return {
validator: datePassCheck, trigger: 'change'
}
}
},
// 时间显示格式
format: function () {
if (this.config.type === 'datetime') {
this.type = 'datetime'
return 'yyyy-MM-dd HH:mm'
}
return 'yyyy-MM-dd'
}
},
methods: {
...mapMutations([
'SET_POSTDATAKEY'
]),
sdateChange: function (val) {
this.$set(this.postData, this.config.key, val)
this.$set(this.postData, 'startTime', val)
},
edateChange: function (val) {
this.postData.endTime = val
}
},
watch: {
// 开始时间改变,需清空结束时间
'postData.startTime': function (val) {
let _this = this
let v = this.postData.startTimeFlag
let date = new Date(v)
let time = date.getFullYear() + '-' +
(date.getMonth() + 1) + '-' +
date.getDate()
this.endDateOptions.disabledDate = function (date) {
return _this.config.isYesterday ? date.valueOf() < (new Date(time) - 23 * 60 * 60 * 1000) : date.valueOf() < new Date(time)
// return date.valueOf() < new Date(time)
}
// 清空后面的日期
this.postData.endTimeFlag = ''
this.postData.endTime = ''
this.showError = true
}
},
data () {
// 结束时间校验
datePassCheck = (rule, value, callback) => {
if (value === '') {
callback(new Error('结束时间不能为空'))
} else if (this.postData.endTime < this.postData.startTime) {
callback(new Error('结束时间需大于开始时间'))
} else {
callback()
}
}
return {
mdModelLeft: UImodel.mdModelLeft,
mdModelRight: UImodel.mdModelRight,
// 结束日期的 起点规则
endDateOptions: {
disabledDate (date) { }
},
type: 'date'
}
},
mounted () {
}
}
</script>
<style></style>
- 如果还需要其他组件,按照上述方法添加即可,下面写申请界面的公共部分:apply
<template>
<Form ref="formValidate" :model="postData" :rules="ruleValidate" class="leave">
<div class="disabledBox">
<!-- 这里是每个流程的表单部分 -->
<slot></slot>
<!-- 附件组件 -->
<uploadAttachments
:processKey="processKey"
:fileData="fileData"
:fileAry="temporary.file"
@deleteFileAry="deleteFileAry">
</uploadAttachments>
<div class="disabled" ref="disabled" v-if="submitAfret"></div>
</div>
<Row v-if="!submitAfret">
<Col :span="6" :offset="18">
<Button type="info" @click="submitData('formValidate')">转下一步</Button>
</Col>
</Row>
</Form>
</template>
<script>
import {mapState, mapMutations} from 'vuex'
import uploadAttachments from './../process/common/uploadAttachments.vue'
import tools from 'static/js/tools.js'
export default {
components: {
uploadAttachments
},
props: {
ruleValidate: {
default(){
return {}
},
type: Object
},
processKey: {
type: String
},
candidate: {
type: Array
}
},
data () {
return {
processStart: true,
// 提交之后显示推荐人
submitAfret: false,
// 转下一步数据
nextStep: {},
temporary: {
},
fileData: []
}
},
computed: {
...mapState([
'postData', 'processData'
])
},
methods: {
...mapMutations([
'SET_POSTDATA'
]),
submitData: function () {
// console.log(this.postData)
console.log(this.processStart)
// 验证
this.$refs.formValidate.validate(res => {
//验证通过,则提交
if (res) {
// 这里执行提交操作
}
this.$Message.error("请根据页面提示填写内容!");
})
}
}
}
</script>
如上:<slot></slot>
是每个流程的表单部分,其他则为每个流程共有的,比如附件、提交操作等。
- 用上面的资源写一个休假流程:leave
<template>
<apply :processKey="processKey" :candidate="candidate">
<!-- apply的slot部分,即为每个流程的表单部分 -->
<component :is="item.component" v-for="(item, index) in items" :config="item" :key="index">
</component>
</apply>
</template>
<script>
import apply from './../comm/apply.vue'
import {mapState, mapMutations} from 'vuex'
const getComponent = name => {
return resolve => require([`./../comm/${name}.vue`], resolve)
}
export default {
components: {
apply
},
props: {
candidate: {
type: Array
},
processKey: {
type: String
}
},
data () {
return {
//表单配置
items: [
{
component: getComponent('iProcessTitle'),
title: '流程标题',
key: 'processTitle',
required: true
},
{
component: getComponent('iSelectType'),
title: '休假类别',
key: 'leave',
required: true,
list: {
'事假': 'busy',
'病假': 'sick',
'婚假': 'marriage',
'产假': 'maternity',
'丧假': 'funeral',
'陪产假': 'paternity',
'姨妈假': 'menstruation',
'年假': 'annual'
}
},
/**
* @author Liangyuhong
* @date 2018/9/21 10:33
* @Description: 精确到分钟
*/
{
component: getComponent('iDate'),
title: '开始时间',
type: 'datetime',
required: true,
endate: true, // 需要显示结束时间
endrequired: true, // 结束时间必填
isYesterday: true // 是否可以选择当天
},
{
component: getComponent('iDays'),
title: '休假天数',
key: 'day',
required: true
},
{
component: getComponent('iRemarks'),
title: '请假理由',
key: 'state',
required: true
}
]
}
},
methods: {
...mapMutations([
'SET_POSTDATA'
]),
init: function (data) {
this.SET_POSTDATA(data)
this.$root.Bus.$emit('initPostData', data)
this.postData = data
this.postData.processInstanceId = data.processInstanceId
}
},
mounted () {
this.SET_POSTDATA({})
}
}
</script>
<style lang="less" scoped>
@import './../../../static/css/process/process.less';
</style>
这样再开发新流程的过程中就不用去重写template部分了,只需要配置好data里的items,这里指明了当前流程所需要的字段,每个字段的各种属性,其他的都基本不用动
注:以上为不完全代码,依赖于ivew,提交的数据为postData 。存的全局变量
总结
- 将每一个输入框都写成单独的,可配置的组件,借助ivew,把校验都放在单个表单组件内部完成
- 写一个公共组件apply,利用slot提供可变部分传入,把不变的,比如附件,提交这些写入这个组件,提供的便利在于不用在每一个流程去关注提交,校验等等一系列每个流程都需要的操作
- 具体到某一个流程时只需关注data的items,也就是开发一个流程,只写items就可以完成。