说明:
本示例中的代码和截图可能和您现在手中的版本不同,但操作思路一样。

一、概述

对表单界面的字段实现设定其值,读取其值、修改其值,隐藏、显示,必填等操作如:给某个字段设置默认值,获取表单界面某个字段的值,修改表单界面某个字段显示的值。
有时候我们需要操作按钮的前后置事件 或者添加自定义按钮,点击自定义按钮时需要将列表中选中行的数据导入打开的其他表单。

二、操作及示例

2.1、通过表单属性编写表单脚本

2.2、编写脚本示例

三、表单脚本接口API

统一说明以下不再说明
form:是表单的vue实例数据,可以通过这个操作数据、dom,事件等
callback:数据回调,如果通过ajax异步提交需要回调,按钮等操作必须回调,不然操作没反应。返回true 是回调成功,false表示回调失败,阻止继续操作 【v3.1.8+支持】
$request:可以通过this.$request 来ajax请求数据【v3.1.9+支持】

3.1、加载事件

 //表单加载事件
 onLoad:function(form){

  }

3.2、按钮加载事件【v.3.2.4+】

 //表单加载按钮事件
  onLoadActions:function(form,action,button,type){

  }
参数 说明 是否必须 可选值
action 按钮别名,在使用页面传入的根据自己需求定义的值; add、edit等
button 按钮相关数据,包含位置(position)等信息 -
type 处理方式,hidden 隐藏,disabled 禁用 - hidden,disabled

3.3、 表单提交校验

 //表单提交校验
  onValidate:function(form,callback){
    callback(true);
  }

3.4、按钮的前置事件

注意:自定义按钮必须设置按钮编码

 //表单按钮前置事件
  beforeSubmit:function(form,action,postValue,callback){
    callback(true);
  }

表单按钮提交前触发,在具体的页面中调用.

参数说明:

参数 说明 是否必须 可选值
action 按钮code,在使用页面传入的根据自己需求定义的值; - close,save等
postValue 提交给后台的数据,表单数据 -
callback funtion【必须】,数据回调,一定要有回调方法,不然没反应 必须 true,false

3.5、按钮的后置事件

注意: 自定义按钮没有后置事件

 //表单按钮后置事件
  afterSubmit:function(form,action,postValue,callback){
    callback(true); 
  }

表单按钮提交后触发,在具体的页面中调用.

参数说明:

参数 说明 是否必须 可选值
action 按钮code,在使用页面传入的根据自己需求定义的值; - close,save等
postValue 提交给后台的数据,表单数据 -
callback funtion【必须】,数据回调,一定要有回调方法,不然没反应 必须 true,false

3.6、子表按钮的前置事件【v3.1.9+支持】

注意:
1、自定义按钮必须设置按钮编码
2、如果多个子表要需要判断是那个子表的按钮 tableForm.code

 //表单子表按钮前置事件
  beforeSubButton:function(tableForm, action, position, params, callback){
    callback(true);
  }

表单子表按钮提交前触发,在具体的页面中调用.

参数说明:

参数 说明 是否必须 可选值
tableForm 是表单子表的vue实例数据,可以通过这个操作数据、dom,事件等 -
action 按钮code,在使用页面传入的根据自己需求定义的值; - add、edit等
position 按钮位置 -
callback funtion【必须】,数据回调,一定要有回调方法,不然没反应,如果通过ajax异步提交需要回调 必须 true,false

3.7、子表按钮的后置事件 【v3.1.9+支持】

注意:
1、自定义按钮没有后置事件
2、如果多个子表要需要判断是那个子表的按钮 tableForm.code

 //表单按钮后置事件
  afterSubButton:function(tableForm, action, position, params, callback){
    callback(true);
  }

表单子表按钮提交后触发,在具体的页面中调用.

参数说明:

参数 说明 是否必须 可选值
tableForm 是表单子表的vue实例数据,可以通过这个操作数据、dom,事件等 -
action 按钮code,在使用页面传入的根据自己需求定义的值; - add、edit等
position 按钮位置 -
callback funtion【必须】,数据回调,一定要有回调方法,不然没反应,如果通过ajax异步提交需要回调 必须 true,false

3.8、子表的统计方法 【v3.1.10+支持】

 //子表的统计方法
  summaryMethod:function(tableForm, tableName, params){

  }

在具体的页面中调用.
参数说明:

参数 说明 是否必须 可选值
tableForm 是表单子表的vue实例数据,可以通过这个操作数据、dom,事件等 -
tableName 子表的表名区分多个子表 -
params 字段的参数{ columns, data },其中 columns 是字段的属性,data 是数据 -

四、常用脚本示例

4.1、获取表单所有数据

// 获取表单所有数据
form.getFormData();

4.2、获取表单某个字段值

// 获取主表值(key为字段名)
form.getData('zd1');
// 子表数据(key 为子表的name)
form.getData('zbzd');
// 子表第几行的数据,先获取子表字段数据,然后找到数组的第几行的字段的数据,下面[1] 表示第2行【索引开始是0】,xxx:表示字段名
form.getData('zbzd')[1].xxx;

4.3、修改表单某个字段值或者赋值

// 设置主表字段【key为字段属性,value:为字段值】
form.setData('zd1','广州流辰');
// 设置子表数据 如果是一对多是个`数组`。一对一是个`对象`
form.setData('bdjbzb',[{
"leiXing": "",
"mingCheng": "测试",
"shuLiang": "",
"danJia": "",
"zhuangTai": "",
"xiaoJi": ""}]);

4.4、子表默认添加一行

//子表默认添加一行
form.setData('bdjbzb',[{
"leiXing": "",
"mingCheng": "测试",
"shuLiang": "",
"danJia": "",
"zhuangTai": "",
"xiaoJi": ""}]);

4.5、设置字段隐藏,只读,必填

快捷方式(推荐):

权限(隐藏【h】,编辑【e】,只读【r】,必填【b】,子表或只读字段显示【s】)

// ①设置字段隐藏,只读,必填
// 字段名 ,权限(隐藏【h】,编辑【e】,只读【r】,必填【b】)
 form.setFormRights('ziDuan2','h')

 //② 设置子表权限
 // 建议先获取表单该字段的权限,然后在赋值 v3.2.4+支持
 const rights =  form.getFormRights('zb1') ||{}
 //可能是如下格式:
 //   const rights= {
  //      rights:'r',//整个子表权限
  //    buttons:{//子表按钮权限
  //        'add':'s',
  //        'remove':'h'
  //    },
  //    columns:{//子表字段权限
  //         'id':'h',
  //         'name':'e'        
  //    }
  // }

  // 如果没定义请先定义columns
  //    if(!rights.columns){
  //      rights.columns ={}
  //  }
  //

   //设置子表字段 权限
   rights.columns['name'] = 'r'
   form.setFormRights('zb1',rights)

如果必填需重新加载下验证规则

 setTimeout(()=>{
           form.validate(()=>{})
      },10)

也可以操作dom方式处理:
//TODO:需要编写

4.6、字段事件

//字段事件 目前只支持 change blur focus 事件
// 其中 `formItemziDuan1` 由formItem+字段名组成
form.$refs.dynamicForm.$refs.formItemziDuan1[0].$on('change',(data)=>{
    if(data==='1'){
        form.setFormRights('ziDuan2','h')
    }else{
        form.setFormRights('ziDuan2','e')
    }
})

如果是栅格布局、标签页、步骤条等嵌套布局需要知道布局key

//如下代码
//formItemgrid_19i6jxc 是先找到栅格布局字段,然后找到字段进行操作
form.$refs.dynamicForm.$refs.formItemgrid_19i6jxc[0].$refs.formItemlanguage[0]

具体事件参考文件:
https://cn.vuejs.org/v2/guide/components-edge-cases.html#%E8%AE%BF%E9%97%AE%E5%AD%90%E7%BB%84%E4%BB%B6%E5%AE%9E%E4%BE%8B%E6%88%96%E5%AD%90%E5%85%83%E7%B4%A0

4.7、设置表单背景,字体,字体颜色

//设置表单背景,字体,字体颜色
  const id =  'formStyle' //保证唯一建议时间戳
  let styleTag = document.getElementById(id)
      if (!styleTag) {
        styleTag = document.createElement('style')
        styleTag.setAttribute('type', 'text/css')
        styleTag.setAttribute('id', id)
        document.head.appendChild(styleTag)
      }
  //添加的样式
  const newStyle ='' //这里编写你要添加的样式
  styleTag.innerText = newStyle

4.8、form DOM操作

form可以操作表单vue的方法(methods)、属性、DOM元素和事件

4.9、子表合计自定义方法

el原生的方法

 summaryMethod:function(tableForm,tableName,param) {
      const { columns, data } = param
      const sums = []

      columns.forEach((column, index) => {
        if (index === 0) {
          sums[index] = this.sumText
          return
        }
        const property = column.property

        const values = data.map(item => Number(item[property]))
        const precisions = []
        let notNumber = true
        values.forEach(value => {
          if (!isNaN(value)) {
            notNumber = false
            const decimal = ('' + value).split('.')[1]
            precisions.push(decimal ? decimal.length : 0)
          }
        })
        const precision = Math.max.apply(null, precisions)
        if (!notNumber) {
          sums[index] = values.reduce((prev, curr) => {
            const value = Number(curr)
            if (!isNaN(value)) {
              return parseFloat((prev + curr).toFixed(Math.min(precision, 20)))
            } else {
              return prev
            }
          }, 0)
        } else {
          sums[index] = ''
        }
      })
      return sums
        }

官网的例子:

 summaryMethod:function(tableForm,tableName,param) {
    const { columns, data } = param;
    const sums = [];
    columns.forEach((column, index) => {
      if (index === 0) {
        sums[index] = '总价';
        return;
      }
      const values = data.map(item => Number(item[column.property]));
      if (!values.every(value => isNaN(value))) {
        sums[index] = values.reduce((prev, curr) => {
          const value = Number(curr);
          if (!isNaN(value)) {
            return prev + curr;
          } else {
            return prev;
          }
        }, 0);
        sums[index] += ' 元';
      } else {
        sums[index] = 'N/A';
      }
    });

    return sums;
  }

4.10 监听表单的值变化

//表单所有字段,如果需要细节,采用键路径
form.$refs.dynamicForm.$watch('models',function(data){
          console.info(data)
        })
//子表的值改变
form.$refs.dynamicForm.$refs.formItembmsyjemxb[0].$watch('dataModel',function(data){
          console.info(data)
        })

https://cn.vuejs.org/v2/api/#vm-watch

为了发现对象内部值的变化,可以在选项参数中指定 deep: true。注意监听数组的变更不需要这么做。
在选项参数中指定 immediate: true 将立即以表达式的当前值触发回调。

移动端不采用 键路径

4.11 动态改变下拉选项的值

Object.assign(JForm, {
  // 加载事件
  onLoad: function(form) {
    const defField = JSON.parse(JSON.stringify(form.formDefData.fields[2]))
    form.$refs.dynamicForm.$watch('models.tenantId', function(data) {
      if (data == '1') {
        const field = JSON.parse(JSON.stringify(defField))
        field.field_options.options = [{
          val: '3',
          label: '测试'
        }]
        form.formDefData.fields.splice(2, 1, field)
      } else {
        form.formDefData.fields.splice(2, 1, defField)
      }
    })
  }
})

4.12 常用工具类

  1. this.$request :可以通过this.$request 来ajax请求数据

    //例如
    this.$request({
         url:'/business/v3/form/def/getScriptValue',
         method: 'post',
         data:  { 
          'script': `脚本代码`
          }
       }).then(response => {
           const { state } = response
           if (state === 200) {
             console.log("ok")
           } else {
             const err = new Error(response.message)
             err.state = state
             err.cause = response.cause
             console.log(err)
           }
         }).catch(error => {
             console.log(error)
         })
  2. this._ (注意是下划线): Lodash是一个一致性、模块化、高性能的 JavaScript 实用工具库。
    一些常用的就不需要我们自己写了
    具体API:https://www.lodashjs.com/

  3. this.$dialog 【v3.3.0支持】: 简单封装element-ui的弹窗

//自己写vue的模版和窗口
this.$dialog({
    template:'<xxx>xx</xxx>'
},[options],[callback])

//<xxx></xxx> 这个是组件或html元素
// [options] 是element ui的 dialog的属性

具体用例:

this.$dialog({
          data(){
              return {
             readonly:form.readonly,
             data:data
          }
          },
           template:'<multi-table-supplier  :data="data" :readonly="readonly" />'
      },{
        dialog:{
          appendToBody:true,
          width: '90%',
          top: '5vh',
          center: true,
          title: '供应商明细',
          'custom-class':'ibps-dialog-90' 
       }
      },(tpl)=>{
        form.dialogTemplate =tpl 
      }).catch((_this)=>{ 
           _this.visible = false
        form.dialogTemplate = null 
      })

注意:在数据模版中使用‘template’ 不是‘form’。修改为template.dialogTemplate =tpl

4.13 自定义提示弹窗

实现示例

该功能使用了$createElement的语法实现

  const h = form.$createElement
      let isPrint = true
      const  checkPrint =  h('el-checkbox', {
            props:{
              value: isPrint
            },
            domProps: {
              value: isPrint
            },
            on: {
              input: (val) => {
                checkPrint.componentInstance.value =isPrint= val
              }
            }}, '打印标签')

      form.$msgbox({
        title: '提示',
        message: h('div', null, [
          h('div', null, '确认所勾选数据都审核完毕,并且填写了正确的提取数据。点击【提交】将代表本次提取完成,后续将进入分装环节 '),
         checkPrint
        ]),
        showCancelButton: true,
        confirmButtonText: '提交',
        cancelButtonText: '取消'
      }).then(action => {
        if (isPrint) {
          this.toPrintData(form, pk)
        }else{
          form.callback()
        }
       }).catch(err1 => {
         form.callback()
        console.log(err1)
      })

4.14、设置子表某行单个表格的读写权限【v3.3.7+】


Object.assign(JForm,{
  //加载事件
  onLoad:function(form){
  }, 

 //表单子表按钮后置事件
  afterSubButton:function(tableForm,action,position,params,callback){
    setTimeout(()=>{ // 防止调取实例报错
    // 取到列表某行的实例,调用对应方法控制 true为自读,不用则不需要设置
      tableForm.$refs.formItemidentification[0].setDisabled(true)
    },100)
    callback(true) 
  }


});

4.15、增加快捷找到表单组件实例方法【v3.3.9+】


// 找到表单字段,表单标签和表单字段
form.getRefs(fieldName)
//找到表单字段,找到具体控件组件实例
form.getRefsField(fieldName)

fieldName 是表单字段名或者字段标识名

4.16、数据模版选择的向详请表单赋值

Object.assign(JForm, {
  // 加载事件
  onLoad: function(form) {
      setTimeout(() => {
        // fwjbxxgl 是详请表单的字段标识
           const fwjbxxgl = form.getRefsField('fwjbxxgl').$children[0]
      // 监听数据模版的行点击事件(row-click)
      fwjbxxgl.getRefs().$refs['crud'].$on('row-click', (row) => {
          // syfwxxd 是详请表单的字段标识
        const syfwxxd = form.getRefsField('syfwxxd').$children[0]
        // 向表单赋值
        syfwxxd.setData('huZhu', row.hu_zhu_)
        syfwxxd.setData('huZhuDianHua', row.hu_zhu_dian_hua_)
      })
    }, 1000)
  }
})

FAQ

1、修改数据但界面还是旧的数据

前几天有朋友给我发了一段代码,然后说Vue有bug,他明明写的没问题,为啥数据就不响应呢,一定是Vue的bug?我感觉他比尤雨溪要牛逼,高攀不起,就没有理他了。但是确实有时候我们在开发时候会遇到数据不响应的情况,那怎么办呢?比如下面这段代码:

<template>
  <div>
    <div>
      <span>用户名: {{ userInfo.name }}</span>
      <span>用户性别: {{ userInfo.sex }}</span>
      <span v-if="userInfo.officialAccount">
        公众号: {{ userInfo.officialAccount }}
      </span>
    </div>
    <button @click="handleAddOfficialAccount">添加公众号</button>
  </div>
</template>
<script>
export default {
  data() {
    return {
      userInfo: {
        name: 'hugh',
        sex: '男'
      }
    }
  },
  methods: {
    // 在这里添加用户的公众号
    handleAddOfficialAccount() {
      this.userInfo.officialAccount = '广州流辰'
    }
  }
}
</script>

在上面的代码中,我们希望给用户信息里面添加公众号属性,但是通过this.userInfo.officialAccount = '广州流辰' 添加之后,并没有生效,这是为什么呢?
这是因为在Vue内部,数据响应是通过使用Object.definePrototype监听对象的每一个键的getter,setter方法来实现的,但通过这种方法只能监听到已有属性,新增的属性是无法监听到的,但我就是想监听,下面提供了四种方式

1. 将本来要新增的属性提前在data中定义好
比如上面的公众号,我可以提前在userInfo里面定义好,这样就不是新增属性了,就像下面这样

data() {
    return {
      userInfo: {
        name: 'hugh',
        sex: '男',
        // 我先提前定义好
        officialAccount: ''
      }
    }
  }

2. 直接替换掉userInfo
虽然无法给userInfo里面添加新的属性,但是因为userInfo已经定义好了,所以我直接修改userInfo的值不就可以了么,所以也可以像下面这样写

this.userInfo = {
  // 将原来的userInfo 通过扩展运算法复制到新的对象里面
  ...this.userInfo,
  // 添加新属性
  officialAccount: '广州流辰'
}

3. 使用Vue.set
其实上面两种方法都有点取巧的嫌疑,其实对于新增属性,Vue官方专门提供了一个新的方法Vue.set用来解决新增属性无法触发数据响应。
Vue.set 方法定义

/**
* target 要修改的对象
* prpertyName 要添加的属性名称
* value 要添加的属性值
*/
Vue.set( target, propertyName, value )

上面的代码使用Vue.set可以修改为

import Vue from 'vue'

// 在这里添加用户的公众号
handleAddOfficialAccount() {
  Vue.set(this.userInfo,'officialAccount', '广州流辰')
}

但是每次要用到set方法的时候,还要把Vue引入进来,好麻烦,所以为了简便起见,Vue又将set方法挂载到了Vue的原型链上了,即Vue.prototype.$set = Vue.set,所以在Vue组件内部可以直接使用this.$set代替Vue.set

this.$set(this.userInfo,'officialAccount', '广州流辰')

小编发现有许多同学不知道什么时候应该用Vue.set,其实只有当你要赋值的属性还没有定义的时候需要使用Vue,set,其他时候一般不会需要使用。
4. 使用$forceUpdate
我觉得$forceUpdate的存在,让许多前端开发者不会再去注意数据双向绑定的原理,因为不论什么时候,反正我修改了data之后,调用一下$forceUpdate就会让Vue组件重新渲染,bug是不会存在的。但是实际上这个方法并不建议使用,因为它会引起许多不必要的性能消耗。

针对数组的特定方式
其实不仅仅是对象,数组也存在数据修改之后不响应的情况,比如下面这段代码

<template>
  <div>
    <ul>
      <li v-for="item in list" :key="item">
        {{ item }}
      </li>
    </ul>
    <button @click="handleChangeName">修改名称</button>
  </div>
</template>
<script>
export default {
  data() {
    return {
      list: ['张三', '李四']
    }
  },
  methods: {
    // 修改用户名称
    handleChangeName() {
      this.list[0] = '王五'
    }
  }
}
</script>

上面的代码希望将张三的名字修改为王五,实际上这个修改并不能生效,这是因为Vue不能检测到以下变动的数组:

当你利用索引直接设置一个项时,例如: this.list[index] = newValue修改数组的length属性,例如: this.list.length = 0
所以在上例中通过this.list[0] = '王五'是无法触发数据响应的,那应该怎么办呢?像上面提到的Vue.set和$forceUpdate都可以解决这个问题,比如Vue.set可以这样写

Vue.set(this.list,0,'王五')

除了那些方法之外,Vue还针对数组提供了变异方法
在操作数组的时候,我们一般会用到数据提供的许多方法,比如push,pop,splice等等,在Vue中调用数组上面提供的这些方法修改数组的值是可以触发数据响应的,比如上面的代码改为以下代码即可触发数据响应

this.list.splice(0,1,'王五')

实际上,如果Vue仅仅依赖getter与setter,是无法做到在数组调用push,pop等方法时候触发数据响应的,因此Vue实际上是通过劫持这些方法,对这些方法进行包装变异来实现的。
Vue对数组的以下方法进行的包装变异:

push
pop
shift
unshift
splice
sort
reverse

所以在操作数组的时候,调用上面这些方法是可以保证数据可以正常响应,下面是Vue源码中包装数组方法的代码:

var original = arrayProto[method];
  def(arrayMethods, method, function mutator () {
    // 将 arguments 转换为数组
    var args = [], len = arguments.length;
    while ( len-- ) args[ len ] = arguments[ len ];
    var result = original.apply(this, args);
    // 这儿的用法同dependArray(value),就是为了取得dep
    var ob = this.__ob__;
    var inserted;
    switch (method) {
      case 'push':
      case 'unshift':
        inserted = args;
        break
      case 'splice':
        inserted = args.slice(2);
        break
    }
    // 如果有新的数据插入,则插入的数据也要进行一个响应式
    if (inserted) { ob.observeArray(inserted); }
   // 通知依赖进行更新
    ob.dep.notify();
    return result
  });

2、如果在脚本获取token

form.$utils.cookies.get('token')

3、获取当前用户信息

form.$store.getters.userInfo

获取更多的信息
获取当前组织id

form.$store.getters.userInfo.org.id

4、获取当前流程相关信息

流程节点

form.$parent.formParams.nodeId

5、表单刷新数据模版的页面

form.callback()

5、移动端和PC脚本的区别

大部分基本一样,就是调用的控件、控件属性和控件的事件不一样,pc的控件是element-ui的控件,移动端是vant的控件
比如监听事件改变
移动端用“input”,没有“change”

pc端

文档更新时间: 2021-04-08 11:39   作者:hugh