用slot和component實現表單共用

賴皮喵發表於2018-12-08

業務需求

在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就可以完成。

相關文章