用slot和component實現表單共用
阿新 • • 發佈:2018-12-14
業務需求
在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就可以完成。
原文地址:https://segmentfault.com/a/1190000017306664