用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

&lt;template&gt;
    &lt;apply :processKey="processKey" :candidate="candidate"&gt;
        &lt;!-- apply的slot部分,即为每个流程的表单部分 --&gt;
        &lt;component :is="item.component" v-for="(item, index) in items" :config="item" :key="index"&gt;
        &lt;/component&gt;
    &lt;/apply&gt;
&lt;/template&gt;
&lt;script&gt;
  import apply from './../comm/apply.vue'
  import {mapState, mapMutations} from 'vuex'

  const getComponent = name =&gt; {
    return resolve =&gt; 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({})
    }
  }
&lt;/script&gt;
&lt;style lang="less" scoped&gt;
    @import './../../../static/css/process/process.less';
&lt;/style&gt;

这样再开发新流程的过程中就不用去重写template部分了,只需要配置好data里的items,这里指明了当前流程所需要的字段,每个字段的各种属性,其他的都基本不用动

注:以上为不完全代码,依赖于ivew,提交的数据为postData 。存的全局变量

总结

  • 将每一个输入框都写成单独的,可配置的组件,借助ivew,把校验都放在单个表单组件内部完成
  • 写一个公共组件apply,利用slot提供可变部分传入,把不变的,比如附件,提交这些写入这个组件,提供的便利在于不用在每一个流程去关注提交,校验等等一系列每个流程都需要的操作
  • 具体到某一个流程时只需关注data的items,也就是开发一个流程,只写items就可以完成。

原文地址:https://segmentfault.com/a/1190000017306664

原文地址:https://www.cnblogs.com/datiangou/p/10121663.html

时间: 2024-10-11 12:34:31

用slot和component实现表单共用的相关文章

ASP.NET MVC 搜索+保存搜索结果.2个按钮共用一个表单

想要实现的效果,1.搜索功能 2.搜索结果保存到text功能 前台代码 一个表单2个按钮,通过JS代码来修改form的action,来实现调用不同的后台代码 <form name="form1" action="~/Card/List" method="get" > <span class="label label-success">卡号:</span> <input class=&q

数据表格,查询、导出共用一个form表单,实现文件流方式下载

在开发中遇到问题是这样的: 在维护老的管理系统的过程中,老板说让加导出功能:项目中,查询的筛选条件是用的表单提交的方式写的. 解决方案有两种: 一.用ajax方式导出 var array = $('#frmSearch').serialize(); 获得表单数据后,用post方式提交给服务器,服务器返回文件所存在的网络地址,然后用windows.open()的方式下载文件 但是我希望文件下载后,能够把文件删除了:用上边方式就不太合适了,不能及时删除旧文件,于是想出下面的方式: 二.文件流的方式下

自定义laravel表单请求验证类(FormRequest共用一个rules())

我们可以利用Form Request来封装表单验证代码,从而精简Controller中的代码逻辑,使其专注于业务.而独立出去的表单验证逻辑可以复用到其它请求中,看过几篇文章,大多都是讲怎么创建Request,表面看起来是将逻辑与业务分离了,但是没有做到复用,一个业务就得新建一个Request类实在太累,索性这里我将项目全部的表单验证放在一个Request类里,实现高度可复用,下面是具体实现. 首先创建Request php artisan make:request CreateUserReque

vue的表单编辑删除,保存取消功能

过年回来第一篇博客,可能说的不是很清楚,而且心情可能也不是特别的high,虽然今天是元宵,我还在办公室11.30在加班,但就是想把写过的代码记下来,怕以后可能真的忘了.(心将塞未塞,欲塞未满) VUE+ElementUI 的表单编辑,删除,保存,取消功能 VUE的表单 <el-form :label-position="labelPosition" label-width="120px" :model="form" ref="fo

Struts2中UI标签之非表单标签

1.非表单标签主要用于在页面生成一些非表单的可视化元素,例如Tab页面,输出HTML页面的树形结构等.当然,非表单标签也包含在页面显示Action里封装的信息,非表单标签主要有如下几个: a:生成一个超级连接(link). actionerror:如果Action实例的getActionError()方法返回不为null,则该标签负责输出该方法返回的系列错误. actionmessage:如果Action实例的getActionMessage()方法返回不为null,则该标签负责输出该方法返回的

Hibernate表单操作

1.单一主键 assigned(由java程序生成标识符) native(由数据库自动生成标识符,例如MySQL就是increment.Oracle就是sequence) 2.基本类型 hibernate映射类型 java类型 sql类型 大小 integer/int  java.lang.Integer/int INTEGER  4 btye long  java.lang.Long BIGENT 8 short java.lang.Short  SMALLINT 2 float java.l

群里分享的react的收藏一下!今日周末,改了个表单验证然后无所事事了!

今日周末,改了个表单验证然后无所事事了,然后把昨天群里分享的react的收藏一下尽管现在还在研究angular和nodeJs毕竟刚刚开始用有点不熟...没准以后会研究一下react毕竟看着下面这张图还是觉得有点欢乐的 现在最热门的前端框架,毫无疑问是 React . 上周,基于 React 的 React Native 发布,结果一天之内,就获得了 5000 颗星,受瞩目程度可见一斑. React 起源于 Facebook 的内部项目,因为该公司对市场上所有 JavaScript MVC 框架,

四种表单验证方法的分析和比较

前言 任何可以交互的站点都有输入表单,只要有可能,就应该对用户输入的数据进行验证.无论服务器后端是什么样的系统,都不愿意把时间浪费在一些无效的信息上,必须对表单数据进行校验,若有不符合规定的表单输入,应及时返回并给出相应的提示信息.本文将列举四种不同原理的表单验证方法,并给出各方法在 PHP 服务器上的实现. 回页首 浏览器端验证 传统上,表单数据一般都通过浏览器端的 Javascript 验证.浏览器端的验证速度快,若有不符合要求的输入,响应信息快速的返回给用户.由于验证数据不需要提交给服务器

deirective写form表单组件

directive 在使用隔离 scope 的时候,提供了三种方法同隔离之外的地方交互.这三种分别是 @ 绑定一个局部 scope 属性到当前 dom 节点的属性值.结果总是一个字符串,因为 dom 属性是字符串.& 提供一种方式执行一个表达式在父 scope 的上下文中.如果没有指定 attr 名称,则属性名称为相同的本地名称.= 通过 directive 的 attr 属性的值在局部 scope 的属性和父 scope 属性名之间建立双向绑定 但是当我们不使用隔离scope的时候,我们要能够