2023年2月

在基于Vue的工作流项目模块中,我们在查看表单明细的时候,需要包含公用表单信息,特定表单信息两部分内容。前者表单数据可以统一呈现,而后者则是不同业务的表单数据不同。为了实现更好的维护性,把它们分开作为两部分处理,但是页面入口设计为统一的呈现页面,这里介绍使用动态组件的方式统一呈现不同表单数据的处理方式。

1、表单数据分类

刚才提到,表单数据按内容区分分为了两类:通用业务表单、特定业务表单

如果我们要把两者统一在一个通用页面中进行展示,就需要根据不同表单名称,动态加载属于特定表单的展示模块,也就是动态组件的实现方式,大概的业务规则如下所示。

表单查看的实际效果界面如下所示。

2、动态组件的实现

首先我们需要在前端的路由器集合中加入一个路由地址的配置,以便定位到统一查看表单页面明细的路径,如下所示。

  '_viewDetail': {
path:
'/_viewDetail',
meta: {
title:
'查看申请单信息'},
component: Layout,
children: [{
path:
'',
component: ()
=> import('@/views/workflow/system/viewdetail')
}]
},

完成后,我们开始编写页面,包含两部分,一部分是通用的表单信息

一部分是动态组件构建的特定表单信息,如下组件代码所示

    <!--实现动态组件的展示-->
    <component:is="viewType":applyid="applyid" />

在我们平时使用vue中的模板的时候,许多时候都是直接定义成一个固定的模板,但是,vue中提供了一个动态模板,可以在任意模板中切换,就是用vue中<component>用:is来挂载不同的组件。

而其中viewType 是一个属性定义,我们可以通过后台API获取对应的模块名称

methods: {
getViewType () {
if (this.applyid) {//通过申请单的DataTable去掉前缀,转换小写,获得模块名称,如TW_Payment => payment var param = { applyId: this.applyid }
apply.GetModuleName(param).then(data
=>{if(data.result) {this.viewType =data.result.toLowerCase()
}
})
}
},

而其中viewType就是我们组件的名称,这里能够呈现出来的内容,必须是这些组件已经注册到全局中去了,就类似我们在Vue 项目中使用Element组件一样,之所以可以使用它也是它已经全局注册了(或者以组件方式加载到页面)。

而我们定义的表单内容可能很多,如下目录所示。

手工加载这些组件,难免遗漏或者错误,因此需要考虑按一定的规则动态加载进去系统中使用。

我们编写一个js文件,用于动态获取指定目录下的vue组件,并使用Vue.component 动态安装所需组件到系统中,如下代码所示。

export default{
install(Vue) {
//从模块目录中自动载入模块内容
const viewFiles = require.context('./view', true, /\.vue$/)
const viewModules
= viewFiles.keys().reduce((viewModules, modulePath) =>{//set './app.vue' => 'app'
const moduleName = modulePath.replace(/^\.\/(.*)\.\w+$/, '$1').toLowerCase()
const value
=viewFiles(modulePath)
viewModules[moduleName]
= value.default
returnviewModules
}, {})
//console.log(viewModules)
Object.keys(viewModules).forEach(key =>{//console.log(key)
Vue.component(key, viewModules[key])
})

}
}

然后我们在main.js中调用它安装所需组件即可,如下代码所示。

//导入工作流查看、创建等模块
import workflow from './views/workflow/modules'Vue.use(workflow)

这样我们就完成了组件的动态加载安装,在通用的查看页面中,就可以通过component的 is 方式动态呈现不同页面表单的数据了。

以支付申请表单为例,我们的payment.vue组件代码如下所示。

<template>
  <divclass="app-container">
    <el-cardclass="box-card">
      <divslot="header"class="clearfix">
        <span>付款申请单-表单数据</span>
      </div>
      <el-formref="viewForm":model="viewForm"label-width="120px">
        <el-tabsvalue="basicPage"type="border-card">
          <el-tab-panename="basicPage"label="基本信息">
            <el-row>
              <el-col:span="24">
                <el-form-itemlabel="付款事由">
                  <el-inputv-model="viewForm.reason"readonly/>
                </el-form-item>
              </el-col>
              <el-col:span="12">
                <el-form-itemlabel="付款金额">
                  <el-inputv-model="viewForm.payAmount"readonly>
                    <spanslot="suffix"></span>
                  </el-input>
                </el-form-item>
              </el-col>
              <el-col:span="12">
                <el-form-itemlabel="付款方式">
                  <el-inputv-model="viewForm.payType"readonly/>
                </el-form-item>
              </el-col>
              <el-col:span="12">
                <el-form-itemlabel="付款日期">
                  <el-date-pickerv-model="viewForm.payDate"align="right"type="datetime"placeholder="选择日期"value-format="yyyy-MM-dd HH:mm:ss"readonly/>
                </el-form-item>
              </el-col>
              <el-col:span="12">
                <el-form-itemlabel="收款人全称">
                  <el-inputv-model="viewForm.payeeFullName"readonly/>
                </el-form-item>
              </el-col>
              <el-col:span="12">
                <el-form-itemlabel="银行账号">
                  <el-inputv-model="viewForm.bankAccount"readonly/>
                </el-form-item>
              </el-col>
              <el-col:span="12">
                <el-form-itemlabel="开户行">
                  <el-inputv-model="viewForm.bank"readonly/>
                </el-form-item>
              </el-col>
              <el-col:span="24">
                <el-form-itemlabel="备注信息">
                  <el-inputv-model="viewForm.note"readonly type="textarea":rows="5" />
                </el-form-item>
              </el-col>
              <el-col:span="24">
                <el-form-itemlabel="附件">
                  <my-attachmentv-model="viewForm.attachGUID" />
                </el-form-item>
              </el-col>
            </el-row>
          </el-tab-pane>
        </el-tabs>
      </el-form>
    </el-card>

    <divclass="dialog-footer">
      <el-buttontype="success"@click="goback">返回列表页面</el-button>
    </div>
  </div>
</template>

并在js代码中定义了prop属性applyid,如下所示。

props: {
applyid: {
//申请单ID, 接受外部v-model传入的值 type: [String, Number],default: ''}
},

这样我们在页面创建的时候,就可以根据applyid来获得对应的特定表单数据,然后页面内容就会正常展示了。

created () {this.getData() //显示数据
},
methods: {
getData () {
if (this.applyid) {var param = { applyId: this.applyid }
payment.FindByApplyId(param).then(data
=>{//console.log(data)
Object.assign(this.viewForm, data.result)
})
}
}

表单查看的实际效果界面如下所示。

这样就完成了我们通过统一的展示页面,展示不同表单数据的统一操作。

查看页面的操作,都通过路由名称_viewdetail实现了统一的跳转。

      <!--表格行操作按钮-->
      <el-table-columnalign="center"label="操作"width="110">
        <templatescope="scope">
          <el-row>
            <el-button-group>
              <el-tooltipeffect="light"content="查看"placement="top-start">
                <el-buttonicon="el-icon-search"type="success"circle size="mini"@click="showView(scope.row.apply_ID)" />
              </el-tooltip>
              <el-tooltipeffect="light"content="删除"placement="top-start">
                <el-buttonicon="el-icon-delete"type="danger"circle size="mini"@click="showDelete(scope.row.id)" />
              </el-tooltip>
            </el-button-group>
          </el-row>
        </template>
      </el-table-column>

JS代码如下

    showView (id) { //显示查看对话框处理
      this.$router.push(
{
path: `
/_viewDetail`, //查看申请单路径,以_view开始
query: { id: id }
})
},

以上就是在Vue的工作流项目模块中,使用动态组件的方式统一呈现不同表单数据的处理方式,希望对您有所启发,在一些特定的场景使用它,可以极大提高灵活性和开发效率。

在Vue前端项目中,我这里主要是基于Vue+Element的开发,大多数情况下,我们利用Element的表格组件就可以满足大多数情况的要求,本篇随笔针对表格的展示和编辑处理,综合性的介绍几款表格组件的展示和处理效果,其中包括Element的el-table组件,以及其他第三方类组件,如vue-easytable、vue-willtable,以及vxe-table,针对性的对比相关的差异。

1、el-table表格组件

这个是Element的表格组件,使用参考地址如下:
https://element.eleme.cn/#/zh-CN/component/table
这个表格组件也是非常好用的展示数据的组件,提供了很多属性设置,表格列的模板定义可以进行各种各样的转义和处理,是我们使用Element组件经常用到的组件之一。
简单的界面效果如下所示。

它的简单界面代码如下所示,主要就是提供列的定义。

<template>
  <el-table:data="tableData"stripe style="width: 100%">
  <el-table-columnprop="date"label="日期"width="180"> </el-table-column>
    <el-table-columnprop="name"label="姓名"width="180"> </el-table-column>
    <el-table-columnprop="address"label="地址"> </el-table-column>
  </el-table>
</template>

而数据这是一个对象集合,里面对应各种属性记录,如下所示。

<script>exportdefault{
data() {
return{
tableData: [{
date:
'2016-05-02',
name:
'王小虎',
address:
'上海市普陀区金沙江路 1518 弄'}, {
date:
'2016-05-04',
name:
'王小虎',
address:
'上海市普陀区金沙江路 1517 弄'}, {
date:
'2016-05-01',
name:
'王小虎',
address:
'上海市普陀区金沙江路 1519 弄'}, {
date:
'2016-05-03',
name:
'王小虎',
address:
'上海市普陀区金沙江路 1516 弄'}]
}
}
}
</script>

不过一般实际使用的表格比这个会复杂很多,会在列之中提供很多转义信息展示,如下界面效果所示

或者

我们来看看表格头部和表格列的相关定义来进行解析。

<el-tablev-loading="listLoading":data="list"border fit stripe highlight-current-row
:header-cell-style
="{background:'#eef1f6',color:'#606266'}"@selection-change="selectionChange"@row-dblclick="rowDbclick"@sort-change="sortChange">

listLoading用来 展示/隐藏 加载中的状态

list则是提供给表格数据处理的数据源

selectionChange这是选中/取消勾选框的事件触发

rowDbclick是双击表格行出现的事件响应

sortChange是排序表头出现的事件处理

另外,表格数据往往涉及到分页信息,那么必须和分页控件一起使用

        <!--分页部分-->
        <divclass="block"style="height:70px;">
          <el-pagination:current-page="pageinfo.pageindex":page-size="pageinfo.pagesize":total="pageinfo.total":page-sizes="[10,20,30,40]"layout="total, sizes, prev, pager, next"@size-change="sizeChange"@current-change="currentChange" />
        </div>

其中分页组件中涉及到pageinfo对象,包括当前页码和每页的数据记录数量,以及对每页数据大小变化事件sizeChange、页码变化的事件currentChange的处理。

因此我们在<script>中定义相关的变量和方法。

export default{
components: {},
//常见组件已经在main.js中挂载 data () {return{
listLoading:
false, //列表加载状态 list: [], //页面列表数据 pageinfo: {//分页条件 pageindex: 1,
pagesize:
20,
total:
0},
listSelection: [],
//选中记录 sorting: null, //排序条件

获取数据的事件getlist中,我们主要就是根据条件获取数据记录,并赋值给list对象即可。

而其他相应的事件如下定义所示

    search () { //查询列表处理
      this.pageinfo.pageindex = 1;//重置为第一页
      this.searchForm.pid = null;//重置选择类型
      this.getlist()
},
resetSeachForm (formName) {
//重置查询条件处理 this.$refs[formName].resetFields();this.search()
},
sortChange ({ column, prop, order }) {
var direction = (order === 'ascending') ? ' ASC' : ' DESC' this.sorting = !this.isEmpty(prop) ? (prop + direction) : null this.getlist()
},
sizeChange (val) {
//页面大小变化处理 //console.log(`每页 ${val} 条`); this.pageinfo.pagesize =val;this.getlist()
},
currentChange (val) {
//页码变化处理 //console.log(`当前页: ${val}`); this.pageinfo.pageindex =val;this.getlist()
},
selectionChange (val) {
//选中记录 this.listSelection =val;
},
rowDbclick (row, column) {
//列表单击处理 var id =row.idthis.showView(id);
},

这样我们就是实现了对数据的分页处理,和相关的事件处理。剩下的就是对记录列内容的转义处理了,这个通过HTML的代码进行处理即可。

常规的内容处理如下所示。

<el-table-columnalign="center"label="名称"sortable="custom"prop="name"min-width="150">
   <templateslot-scope="scope">{{ scope.row.name }}</template>
</el-table-column>

如果我们需要进行转义,在这里面处理判断就可以了,非常方便。

  <el-table-columnalign="center"label="可见"sortable="custom"prop="visible"width="80">
    <templateslot-scope="scope">
      <el-tagv-if="scope.row.visible"type="success"effect="dark">可见</el-tag>
      <el-tagv-else type="danger"effect="dark">隐藏</el-tag>
    </template>
  </el-table-column>

或者通过formater的函数进行内容的转义处理。

<el-table-columnalign="center"label="创建时间"width="160"sortable="custom"prop="createTime":formatter="timeFormat" />

也可以使用filter进行内容转义

  <el-table-columnalign="center"label="商品类型"width="120"prop="ProductType">
    <templateslot-scope="scope">
      <el-tagtype="danger">  {{ scope.row.ProductType | productTypeFilter }}</el-tag>
    </template>
  </el-table-column>

如果我们需要添加表格列的操作处理,那么添加对应的按钮即可,通过获取对应记录的标识,来进行相关记录的操作即可。

  <!--表格行操作按钮-->
  <el-table-columnalign="center"label="操作"width="110">
    <templatescope="scope">
      <el-row>
        <el-button-group>
          <el-tooltipeffect="light"content="查看"placement="top-start">
            <el-buttonicon="el-icon-search"type="success"circle size="mini"@click="showView(scope.row.id)" />
          </el-tooltip>
          <el-tooltipeffect="light"content="编辑"placement="top-start">
            <el-buttonicon="el-icon-edit"type="primary"circle size="mini"@click="showEdit(scope.row.id)" />
          </el-tooltip>
          <el-tooltipeffect="light"content="删除"placement="top-start">
            <el-buttonicon="el-icon-delete"type="danger"circle size="mini"@click="showDelete(scope.row.id)" />
          </el-tooltip>
        </el-button-group>
      </el-row>
    </template>
  </el-table-column>

Element的表格组件功能虽然很好,不过它的特点是展示处理,并不是很适合进行直接内容的编辑处理,有些人通过变量标识模板的方式控制编辑界面的展示,不过总体太复杂,不方便使用。

2、vue-easytable表格组件

vue-easytable表格组件地址:
npm安装:npm install vue-easytable -S
这个组件相对于Element的表格el-table来说,它的HTML代码比较少,适用于简单快捷的表格数据展示。
简单使用代码如下所示
 <ve-table:columns="columns":table-data="tableData" />

一般它的使用代码如下所示(配合分页数据展示)

    <ve-tablerow-key-field-name="id":checkbox-option="checkboxOption":columns="columns":table-data="list":fixed-header="true":border-around="true":border-x="true":border-y="true" />

    <!--分页部分-->
    <divclass="block"style="height:70px;">
      <el-pagination:current-page="pageinfo.pageindex":page-size="pageinfo.pagesize":total="pageinfo.total":page-sizes="[10,20,30,40]"layout="total, sizes, prev, pager, next"@size-change="sizeChange"@current-change="currentChange" />

常见的界面效果如下所示

一般我们需要在JS中定义列的信息,它的类型可以是expand、checkbox、radio几种之一,默认是字符串方式。

当前列类型: "expand":行展开;"checkbox":行多选;"radio":行单选

      columns: [//表列定义
{
field:
'',
key:
'a',
type:
'checkbox',
title:
'',
width:
50,
align:
'center'},
{
field:
'name',
key:
'name',
title:
'姓名',
width:
120,
columnAlign:
'center'},

这个列的定义除了field外,还需要一个Key的标识,不能为空,一般保留位field的值即可。

如果我们需要进行内容的转义处理,可以使用 renderBodyCell 函数进行转义处理,如下所示。

{
field:
'sex',
key:
'sex',
title:
'性别',
width:
120,
columnAlign:
'center',
renderBodyCell: ({ row, column, rowIndex }, h)
=>{
const text
=row[column.field];//return <input style="width:100%" value={text}></input>; let html = null if (text === '男') {
html
= <el-tag type='primary'>{text}</el-tag> } else{
html
= <el-tag type='success'>{text}</el-tag> }return(<div>{html}</div> )
}
},

数据的处理,和我们前面介绍的类似,就是通过分页条件的方式进行查询分页,获得数据后赋值给对应列表即可。

      //获取列表,绑定到模型上,并修改分页数量
      this.listLoading = truetestUser.GetAll(param).then(data=>{
console.log(data.result.items)
this.list =data.result.itemsthis.pageinfo.total =data.result.totalCountthis.listLoading = false})

通过 row-key-field-name="id" :checkbox-option="checkboxOption" 代码的处理,可以实现选中记录的操作。

checkboxOption: {//row select事件
    selectedRowChange: ({ row, isSelected, selectedRowKeys }) =>{
console.log(row, isSelected, selectedRowKeys);
},
//selected all事件 selectedAllChange: ({ isSelected, selectedRowKeys }) =>{
console.log(isSelected, selectedRowKeys);
}
},

这个表格组件也主要是进行展示,没有直接编辑的处理操作,不过它的特点是快速展示数据,转义的话操作起来会费劲一些,它主要就是通过jsx的方式进行转义处理的,感觉没有在HTML上直接判断的快捷明了。

3、vue-willtable表格组件(可直接编辑)

以上两个表格组件,都没有涉及到编辑的操作,有时候我们在一些表格中希望给客户直接录入数据的场景。
如果采用el-table的方式来处理,非常不直观,也不易处理,因此引入第三方组件的方式来实现数据的直接录入就比较好 了。

vue-willtable表格组件地址:
https://github.com/KevinMint55/vue-willtable

官方案例界面效果如下所示。

表格提供日期,数值、下拉列表,字符串等几种常见方式的录入。

总体上录入没有什么问题,不过对于组件的更新好像有时候有些问题,毕竟github上星星不是很多的项目。

我在这个基础上进行了案例的测试,界面效果如下所示。

主从表展示界面效果

以及表格查询分页处理和录入界面效果

    <divclass="willtable_wrapper">
      <vue-willtableref="table"v-model="list":columns="columns":row-height="38":cell-class-name="cellClassName" />
    </div>

字段定义的JS代码如下所示。

获取数据的JS代码如下所示

  //获取列表,绑定到模型上,并修改分页数量
  this.listLoading = truetestUser.GetAll(param).then(data=>{this.$refs.table.setData(data.result.items)this.pageinfo.total =data.result.totalCountthis.listLoading = false})

不过分页的话,切换页面大小会导致刷新显示有些问题 ,部分空白,首次显示倒是没有问题。

4、vxe-table表格组件(可直接编辑)

vxe-table是github上星星超过4k+的开源组件,这个表格不论是展示数据,以及编辑的处理,都堪称大能之作,非常方便强大。

vxe-table地址:

https://xuliangzhan_admin.gitee.io/vxe-table/#/table/start/install

https://github.com/x-extends/vxe-table

我们来看看它的常规使用代码,以及界面效果

如果需要录入新记录,通事件处理添加一个行即可。

vxe-table的使用HTML代码和Element的el-table类似,通过HTML声明的方式进行定义,如下所示。

<vxe-tableref="xTable"border show-overflow keep-source resizable show-overflow :data="list":edit-config="{trigger: 'click', mode: 'row', showStatus: true}">
  <vxe-columntype="seq"width="60" />
  <vxe-columnfield="name"title="姓名":edit-render="{name: 'input', attrs: {type: 'text'}}" />

  <vxe-columnfield="sex"title="性别":edit-render="{name: '$select', options: sexList}" />

  <vxe-columnfield="emailAddress"title="邮件地址":edit-render="{name: 'input', attrs: {type: 'text', placeholder: '请输入邮件地址'}}" />

  <vxe-columnfield="marriage"title="婚姻状况":edit-render="{name: '$select', options: marriageList, props: {multiple: false}}" />

  <vxe-columnfield="status"title="状态":edit-render="{name: '$select', options: StatusList, props: {multiple: false}}" />

  <vxe-columnfield="height"title="身高":edit-render="{name: '$input', props: {type: 'float', digits: 2}}" />

  <vxe-columnfield="birthDate"title="出生日期":edit-render="{name: '$input', props: {type: 'date', placeholder: '请选择日期'}}":formatter="formatDate" />
</vxe-table>

而数据的获取展示方式,和上面代码操作一样

      //获取列表,绑定到模型上,并修改分页数量
      this.listLoading = truetestUser.GetAll(param).then(data=>{
console.log(data.result.items)
this.list =data.result.itemsthis.pageinfo.total =data.result.totalCountthis.listLoading = false})

其实我们主要关注它的数据的新增,以及保存处理即可,插入或者移除记录的处理如下代码所示。

async insertEvent () {
const $table
= this.$refs.xTable
const newRecord
={}
const { row: newRow }
=await $table.insert(newRecord)
await $table.setActiveRow(newRow)
},
async removeSelectEvent () {
const $table
= this.$refs.xTable
await $table.removeCheckboxRow()
},
async removeRowEvent (row) {
const $table
= this.$refs.xTable
await $table.remove(row)
},

如果是保存,那么需要获取数据记录的更新、删除、新增几个集合的情况,然后统一通过后端进行辨别处理即可。

async saveEvent () {
const $table
= this.$refs.xTable
const { insertRecords, removeRecords, updateRecords }
=$table.getRecordset()if (insertRecords.length <= 0 && removeRecords.length <= 0 && updateRecords.length <= 0) {this.msgSuccess('数据未改动!')return}
const errMap
= await $table.validate().catch(errMap =>errMap)if(errMap) {return}this.listLoading = true try{
const body
={ insertRecords, removeRecords, updateRecords }
console.log(body)
///await XEAjax.post('https://api.xuliangzhan.com:10443/demo/api/pub/save', body) //await this.loadList() this.msgSuccess(`操作成功,新增 ${insertRecords.length} 条,更新 ${updateRecords.length} 条,删除 ${removeRecords.length} 条`)
}
catch(e) {if (e &&e.message) {this.msgSuccess(e.message)
}
}
this.listLoading = false},

通过引用对象的
getRecordset
我们可以获得相关新增、更新、删除的集合,然后通过自己的逻辑处理数据即可。

该表格组件提供了很多方便的处理案例,我们可以通过学习方式进行参考即可。

以上就是几款表格组件的效果,对比它们,一般来说,我们还是倾向于使用el-table的内置表格组件来展示数据,而且也可以通过我们的代码生成工具Database2sharp进行vue界面的快速生成,然后微调即可,而对于数据直接编辑的处理,则建议通过使用vxe-table的组件的方式进行处理,它提供了非常多强大的功能,可以满足我们直接录入数据的处理操作。

关于我在Winform框架、混合框架、Bootstrap开发框架中的简易审批性工作流模块,我写过不少文章,有兴趣可以参考《
工作流模块
》的随笔进行了解,本篇随笔在完成了Vue&Element开发框架中整合工作流内容后,对其中各个实现过程进行一些总结,希望对大家有所帮助,本篇随笔主要介绍查看申请单中整合多个处理类型的处理部分。

1、简易审批性工作流的基础

在我们开始介绍工作流模块功能之前,我们需要了解下工作流模块的设计模型,以便我们更好深入了解各个部分的功能。

在Office里面创建任何文档,都有一个模板的概念,这样我们方便利用一些现成的数据和布局,工作流也一样,有一个流程模板的概念。每个流程模板,本身会预定义了一系列的处理流程,以便在流程实例里面进行不同的处理,因此流程模板还包含了多个流程步骤对象。每个流程实例,除了他们自己的流程数据和字段信息外,它本身还有一个表单设计的问题,如费用审批,可能包含填写的费用清单数据等,所以流程实例还应该包含了流程的业务表单对象。

在工作流处理表中,首先我们区分流程模板和流程实例两个部分,这个其实就是类似模板和具体文档的概念,我们一份模板可以创建很多个类似的文档,文档样式结构类似的。同理,流程模板实例为流程实例后,就是具体的一个流程表单信息了,其中流程模板和流程实例表单都包括了各个流程步骤。在流程实例的层次上,我们运行的时候,需要记录一些日志方便跟踪,如流程步骤的处理日志,流程实例表单的处理日志等这些信息。

一旦流程实例根据模板创建后,流程先根据模板初始化后,在处理过程还可以动态增加一些审批步骤,使得我们的处理更加弹性化。

当然,为了更好的处理流程的相关信息,还需要记录流程处理人,流程会签人、流程阅办人,以及常用审批意见等相关辅助表,以便对流程的各个处理信息进行合理处理和展示。

对于一个流程处理操作,我们知道一般有审批通过、拒绝、退回到某步骤、转发到内部阅读、阅读,以及包括起草者能撤销表单呢等操作,当然如果还有一些具体的业务,可能还会有一些流程的处理才操作,不过基本上也可以归结为上面几种,只是他们每步处理的数据内容不同而已。因此审批的操作步骤分类如下所示。

2、查看申请单的模块设计处理

前面介绍到的对于申请单的审批通过、拒绝、退回到某步骤、转发到内部阅读、阅读,以及包括起草者能撤销表单呢等操作,都是基于查看申请单的模块进行处理的。

我在随笔《
基于Vue的工作流项目模块中,使用动态组件的方式统一呈现不同表单数据的处理方式
》中介绍了对于表单的动态化处理的内容。

表单数据按内容区分分为了两类:通用业务表单、特定业务表单

如果我们要把两者统一在一个通用页面中进行展示,就需要根据不同表单名称,动态加载属于特定表单的展示模块,也就是动态组件的实现方式,大概的业务规则如下所示。

表单查看的实际效果界面如下所示。

通过动态化组件的呈现处理,可以实现编辑和查看申请单页面的动态呈现,我们Vue的前端也可以只需要定义一个查看页面路由,和一个编辑界面的路由即可,极大的降低开发代码和复杂度。

对于审批,我们提供一些通过、退回、取消的申请单处理操作。

审批表单的界面

发起会签操作界面

撤销自己表单的处理界面

由于申请单的各种类型节点的处理不同,如果页面引入这些会显得很臃肿,因此我们把这些处理步骤组件化,然后再通过查看页面中整合审批、发起会签、会签、批示阅办、撤销、阅办等操作即可。

这样我们把一些常用节点的处理,单独作为组件开发,放置在组件目录中即可,方便维护。

Vue的组件化,可以简化页面的内容处理,把特定的部分放在一个组件中实现,更好的实现关注点的分离,以及可以自由组合更好的页面效果。

我在起前面的几篇随笔中,大概介绍了工作流的一些场景化处理,包括如何把具体业务表单组件化,并在查看和编辑界面中,动态加载组件内容,以及对于查看申请单的主页面,把审批、取消、发起会签、会签、批示分阅、阅办等处理过程的进行模块化,并在主页面中灵活引入集成,本篇随笔综合性的介绍Vue&Element开发框架中增加的工作流各个管理界面的内容,以供参考交流。

1、工作流模块化划分

为了更有效的维护和管理工作流的设置和申请单信息,我们把工作流的内容分为三个大部分:工作流维护、工作流业务、业务表单。

工作流维护,主要是工作流模板的管理,包括流程模板的管理、以及流程环节的管理

工作流业务,主要是根据系统模板,提供创建流程申请单的入口(业务受理列表),我的审批工作(当前用户),所有审批工作(管理员维护),我的草稿(当前用户)

业务表单,这是根据具体表单的信息,提供查询统计的处理,包括各类业务表单,如付款申请单、物品领用单等等。

这些工作流的业务菜单如下所示。

2、流程环节管理

我们要开始一项工作流的申请单,首先需要配置好这个申请单的一些基本信息,其中流程环节就是决定每个步骤执行那种处理类型(如审批、撤销)的,因此流程环节也算是最基本的步骤类型定义了,也就是决定针对这类型的步骤的统一处理界面。

里面预定义了一些如审批、归档、会签、阅办等系统性的步骤类型,还可以根据需要自定义处理类型,自定义处理类型后,我们创建对应的处理规则即可在查看详细申请的界面中进行调用处理。

3、流程模板管理

完成了流程步骤类型的定义,就可以开始流程模板的管理了,流程模板主要配置相关工作流的信息,如表单的分类,对应数据表,流程步骤定义和顺序等等。

流程步骤的界面管理如下所示,可以通过双击修改,拖动调整顺序的方式管理各个步骤的信息。

编辑界面中,实现对流程模板的一些信息进行维护,如下所示。

最终,我们根据以上界面完成了流程模板和流程实例,以及相关步骤的设置处理,如下图所示。

4、业务受理列表

我们完成了流程模板,对于启用的流程模板,我们提供给用户创建具体的流程实例申请单,也就是提供创建流程申请单的入口。

由于我们约定了模板的名称和具体申请单的关系,因此查看申请单和编辑申请单的时候,使用的是同一个路由地址页面,而根据传入参数的不同,动态展示组件信息,供查看或者编辑处理。

但我们创建具体的表单的时候,根据表单的编辑界面,录入不同的流程申请单的数据,以及附件、清单、流程用户等信息。

对于主从表单的处理,我们可以通过利用Vxe-table插件的方式直接录入数据的方式进行录入 。

5、我的审批工作

我的审批工作,主要是根据当前用户列出不同类型(我发起的、我的待办、我的已办)等不同状态的申请单,从而可以快速处理自己的工作流审批业务工作。

通过双击行记录或者单击查看按钮,可以打开详细的申请单信息进行处理。

查看申请单中,会根据申请单的状态,以及当前用户的身份等等,自动展示可以操作流程的按钮。

其中流程日志展示相关的流程日志和系统日志等信息,供参考了解。

系统日志信息,则详细记录每个步骤的创建和处理信息。如在用户完成当前步骤的时候,自动创建新的流程步骤信息的日志。

6、所有审批工作

为了方便管理员查看不同的业务表单,以便核对处理或者删除等操作,我们提供了一个管理界面,可以看到所有申请单的记录信息,同时记录提供删除操作,删除申请单的时候,会把该申请单的相关步骤信息,执行用户信息,对应表单记录等信息一并抹除。

7、我的草稿

为了便于在创建申请单的时候暂存草稿信息,我们提供了一个通用的草稿存储表,用于存储任何信息的申请单信息。

在申请单创建界面里面,在末尾都会提供一个【保存草稿】的按钮,如下所示。

草稿的管理界面如下所示。

单击编辑按钮,可以对该草稿进行编辑,以便恢复到之前的录入状态。

8、业务表单

业务表单则是提供对具体类型的申请单数据进行查看,如下菜单所示。

这样不同的表单,有不同的查询界面,可以提供更加进行的业务表单数据查询或者统计处理。

在我们基于框架开发系统的时候,往往对一些应用场景的页面对进行了归纳总结,因此对大多数情况下的页面呈现逻辑都做了清晰的分析,因此在我们基于框架的基础上,增量式开发业务功能的时候,能够事半功倍。代码生成工具Database2Sharp承载着我们各种开发框架的快速开发逻辑,包括界面代码的生成、后端代码的生成等内容,本篇随笔介绍在这个基础上,增加Vue&Element 工作流页面的快速生成,以便减轻我们实际开发工作流页面的繁琐工作。

1、工作流查看、编辑页面的组件动态化

在我的随笔《
基于Vue的工作流项目模块中,使用动态组件的方式统一呈现不同表单数据的处理方式
》中曾经介绍过,由于我们动态挂载了工作流的查看页面、编辑页面,因此我们可以根据工作流表单的属性,来动态呈现所需要的页面内容,也就是组件动态化的处理方式。也就是类似我们下图所示

除了查看表单、编辑表单,还有一个对具体业务申请单的查询页面,因此一个工作流表单,包含了三个特定功能的页面,如下所示。

申请表单查看的实际效果界面如下所示。

创建具体的表单的时候,根据表单的编辑界面,录入不同的流程申请单的数据,以及附件、清单、流程用户等信息。

不同的表单,有不同的查询界面,可以提供更加进行的业务表单数据查询或者统计处理,如对于付款申请单,我们提供一个付款申请单的分页查询页面。

这些页面的内容,我们在项目框架中存放的位置如下所示。

2、结合代码生成工具快速生成页面代码

根据上面的规则,我们可以使用代码生成工具,根据数据库的信息,结合页面的呈现需要,把查看申请单、创建/编辑申请单、列表查询几个页面的内容,通过代码生成工具Database2Sharp来快速生成,只需要根据需要定义好页面模板即可。

这里的ABP的Vue &Element界面代码,即可以用来生成基于ABP后端的Vue&Element前端界面代码,也可以基于Bootstrap&VUE框架的Web API前端界面代码,我们在这里生成包含常规页面的api+views视图代码外,同时也生成工作流模块所需的三类页面代码,生成的代码只需要简单的在VSCode中进行增量式的合并即可使用,节省了非常多的前端代码编写或者裁剪工作。

我们看到生成的工作流模块内容,已经根据edit /view / list几个目录进行了区分,如下所示。

这样我们把生成的页面,复制到对应的项目框架目录中即可。

其中对于对于主从表单的处理,我们可以通过利用Vxe-table插件的方式直接录入数据的方式进行录入 。通过代码生成工具,也是根据关联表的信息,我们同时生成所需要的从表信息展示,供裁剪录入信息处理。

<el-form-itemlabel="明细清单">
  <div>
    <vxe-toolbar>
      <template#buttons>
        <vxe-buttonstatus="primary"content="新增"@click="insertEvent" />
        <vxe-buttonstatus="warning"content="删除"@click="removeSelectEvent" />
        <vxe-buttoncontent="提交"@click="saveEvent" />
      </template>
    </vxe-toolbar>
    <vxe-tableref="xTable"border show-overflow keep-source resizable show-overflow :data="list":edit-config="{trigger: 'click', mode: 'row', showStatus: true}">
      <vxe-columntype="checkbox"width="60" />
      <vxe-columntype="seq"width="60" />
      <vxe-columnfield="feeType"title="费用类型":edit-render="{name: '$select', options: feeTypeList}" />
      <vxe-columnfield="occurTime"title="发生时间":edit-render="{name: '$input', props: {type: 'date', placeholder: '请选择日期'}}":formatter="formatDate" />
      <vxe-columnfield="feeAmount"title="费用金额(元)":edit-render="{name: '$input', props: {type: 'float', digits: 2}}" />
      <vxe-columnfield="feeDescription"title="费用说明":edit-render="{name: 'input', attrs: {type: 'text'}}" />
    </vxe-table>
  </div>
</el-form-item>

最终展示的界面效果如下所示。

利用代码生成工具Database2Sharp,我们可以非常细致的定义我们所需要生成的代码逻辑,因此在项目开发中,可以快速生成界面代码以及后端C#的框架处理代码,这样增量式的开发,往往能够做到事半功倍,并且在生成的界面代码里面,我们提供了很多控件的展示例子,供修改调整,灵活度非常好,同时也是我们快速进阶前端和后端开发的试金石。

以及API的生成代码,部分注释供参考。

代码生成工具Database2Sharp为开发效率而生,同时也是开发的好帮手,我们信奉开发中,自己所想要的东西,往往就是大多数开发者所需要的,尽可能的信手拈来。