2023年2月

在我Boostrap框架中,很多地方需要使用bootstrapTable表格插件和jstree树形列表插件来共同构建一个比较常见的查询界面,bootstrapTable表格插件主要用来实现数据的分页和表格展示,而jstree树形列表插件则是用来展示树形列表,以便快速分类查询的,在很多场合下结合它们两者,可以实现较好的用户体验效果。本篇随笔介绍在Bootstrap开发框架中使用bootstrapTable表格插件和jstree树形列表插件时候,对树列表条件和查询条件的处理,是指在快速展示数据的时候,分页条件信息也能够通过更新。

1、bootstrapTable表格插件和jstree树形列表插件的使用

关于Boostrap table表格插件的使用,我在随笔《
基于Metronic的Bootstrap开发框架经验总结(16)-- 使用插件bootstrap-table实现表格记录的查询、分页、排序等处理
》和《
基于Metronic的Bootstrap开发框架经验总结(18)-- 在代码生成工具Database2Sharp中集成对Bootstrap-table插件的分页及排序支持
》都有介绍,可谓居家旅行必备良药。

bootstrapTable表格插件和jstree树形列表插件结合起来展示数据的界面也是经常看到的,如下所示。

以及在选择用户信息页面的时候,也需要根据条件进行筛选用户。

从界面的展示来看,结合两者确实可以带来很多便利,不过使用的时候,需要特别注意相关属性的处理,否则分页就会显示全部的记录了。

默认分页查询的代码如下所示。

默认属性列表的绑定操作代码如下所示。

       //绑定左侧树形列表
        //如果update为True,则重新更新缓存
        functioninitJsTree(update) {var baseUrl = "/Apply/GetMyApplyJson?r=" +Math.random();var url = update ? baseUrl + "&update=true": baseUrl;
bindJsTree(
"jstree_div", url);//树控件的变化事件处理 $('#jstree_div').on("changed.jstree", function(e, data) {var icon =data.node.icon;
loadData(data.selected);
});
}

默认情况下,通过树形列表触发的条件,或根据条件进行重新更新分页查询控件,如下所示。

        //加载指定的对象数据
        var clickId = "";functionloadData(id) {var condition = { CustomedCondition: id + ''};//修改条件后需要重新刷新
            $table.bootstrapTable('refresh', { url: queryUrl, query: condition, pageNumber:1});
clickId
=id;
}

不过如果仅仅是这样的处理,那么数据分页的时候,单击下一页则会没有记录刚才的树形列表条件,那么我们需要记录这个选择的树形条件,从而在更新条件的时候加入所需的条件,那么修改上面代码为以下代码。

        //加载指定的对象数据
        var clickId = "";var where = {};//树列表条件
        functionloadData(id) {var condition = { CustomedCondition: id + ''};
where
= {};//清空
where["CustomedCondition"] = id + '';//使用自定义条件
//修改条件后需要重新刷新 $table.bootstrapTable('refresh', { url: queryUrl, query: condition, pageNumber:1});
clickId
=id;
}

这样处理后,我们在bootstrapTable表格插件的条件处理部分代码里面,可以增加对这个条件的处理即可。

增加了红色方框里面的条件后,我们选择分页会得到正确的结果,这样也不会导致两个条件的不兼容,同时我们在切换条件的时候,恢复到第一页的页码。

而其中where里面存储的是我们属性列表的条件,以JSON方式存储起来的,可以根据需要添加自己所需的分页条件,如我的另一个选择用户界面的条件,可以如下代码所示。

例如流程模板的分页展示和条件分类树形展示如下所示。

例如其中一个菜单的树形列表以及数据展示界面如下所示。

在Winform开发的时候,我们很多时候可以利用表格控件来直接录入数据,不过在Web上较少看到,其实也可以利用dataTable对象处理直接录入表格行数据,这个可以提高数据的录入方便,特别是在一些简单业务的明细数据的时候,看起来会比弹出窗口录入方便一些,也高大上一点。本篇主要介绍在Bootstrap开发框架中使用dataTable直接录入表格行数据。

1、基于表格直接录入数据和Winform的界面回顾

在开始Web界面直接录入表格行数据前,我们先来看看Winform界面的处理情况,如我在流程管理里面,对于具有主从明细的报销业务表的数据处理,采用了下面的界面。

这种明细表单可以直接在表格控件Griview上进行新增、编辑处理。

而对于Web界面,如果我们要保持和这个布局类似的话,采用dataTable直接录入表格行数据也可以达到。

上面的界面处理明细数据的时候,可以直接使用新增记录,直接在录入框中输入数据,然后保存起来,保存后数据变为只读,如果需要修改,还可以单击编辑按钮进行修改。

而这些明细的数据,也仅仅存在JS的对象里面,还没有保存到后台数据库中,我们可以在最后保存(如上界面的确定按钮)处理中再获取全部添加的数据进行提交即可。

在这些数据提交之后,我们在查看界面里面可以可以Bootstrap Table插件来展示数据即可。

2、在Web上使用dataTable直接录入表格行数据的实现

上面的界面展示了在Web上使用dataTable直接录入表格行数据和数据展示,这里开始介绍它们的界面和实现代码。

界面部分主要是这个明细的处理。

界面视图的HTML代码如下所示。

<divclass="portlet light portlet-fit ">
    <divclass="portlet-title">
        <divclass="caption">
            <iclass="icon-settings font-red"></i>
            <spanclass="caption-subject font-red sbold uppercase">明细清单</span>
        </div>
    </div>
    <divclass="portlet-body">
        <divclass="table-toolbar">
            <divclass="row">
                <divclass="col-md-6">
                    <divclass="btn-group">
                        <buttonid="detail_editable_1_new"class="btn green">新增记录<iclass="fa fa-plus"></i>
                        </button>
                    </div>
                </div>
            </div>
        </div>
        <tableclass="table table-striped table-hover table-bordered"id="detail_editable_1">
            <thead>
                <tr>         
                    <th>序号</th>
                    <th> 费用类型 </th>
                    <th> 发生时间 </th>
                    <th> 费用金额(元) </th>
                    <th> 费用说明 </th>
                    <th> 编辑 </th>
                    <th> 删除 </th>
                </tr>
            </thead>
            <tbody>@*<tr>
                    <td> 1 </td>
                    <td> 交通费 </td>
                    <td> 2018-10-01 </td>
                    <td> 2000 </td>
                    <td> 备注信息 </td>
                    <td>
                        <aclass="edit"href="javascript:;"> 编辑 </a>
                    </td>
                    <td>
                        <aclass="delete"href="javascript:;"> 删除 </a>
                    </td>
                </tr>*@</tbody>
        </table>
    </div>
</div>

其中主要是ID为 detail_editable_1 的标记,这个就是承载明细信息的表格,我们可以定义我们需要的表头信息,而输入框的内容,则可以通过dataTable插件的对象进行动态添加。

            //定义dataTable对象
            var table = $('#detail_editable_1');var oTable =table.dataTable({"lengthMenu": [
[
5, 15, 20, -1],
[
5, 15, 20, "All"] //改变每页的行数 ],//使用汉化 "language": {
url:
'//cdn.datatables.net/plug-ins/3cfcc339e89/i18n/Chinese.json'},//初始化 "pageLength": 5,"columnDefs": [{ //设置默认列设置 'orderable': true,'targets': [0]
}, {
"searchable": true,"targets": [0]
}],
"order": [
[
0, "asc"]
]
//将第一列设置为asc的默认排序 });

编辑行记录,就是动态增加一些Input控件,让用户可以录入数据,如下代码所示。

                //编辑行
                functioneditRow(oTable, nRow) {var aData =oTable.fnGetData(nRow);var jqTds = $('>td', nRow);
jqTds[
0].innerHTML = '<input type="text" class="form-control input-small" value="' + aData[0] + '" readonly>';
jqTds[
1].innerHTML = '<input type="text" class="form-control input-small" value="' + aData[1] + '">';
jqTds[
2].innerHTML = '<input type="date" class="form-control input-small" value="' + aData[2] + '">';
jqTds[
3].innerHTML = '<input type="number" class="form-control input-small" value="' + aData[3] + '">';
jqTds[
4].innerHTML = '<input type="text" class="form-control input-small" value="' + aData[4] + '">';
jqTds[
5].innerHTML = '<a class="edit" href="">保存</a>';
jqTds[
6].innerHTML = '<a class="cancel" href="">取消</a>';
}

保存数据后,通过把记录更新到对应TD对象里面,如下所示。

                //费用类型 发生时间 费用金额 费用说明
                var objList =[];//保存行数据,切换到普通模式
                functionsaveRow(oTable, nRow) {var jqInputs = $('input', nRow);//更新行中每个input的值
                    oTable.fnUpdate(jqInputs[0].value, nRow, 0, false);
oTable.fnUpdate(jqInputs[
1].value, nRow, 1, false);
oTable.fnUpdate(jqInputs[
2].value, nRow, 2, false);
oTable.fnUpdate(jqInputs[
3].value, nRow, 3, false);
oTable.fnUpdate(jqInputs[
4].value, nRow, 4, false);
oTable.fnUpdate(
'<a class="edit" href="">编辑</a>', nRow, 5, false);
oTable.fnUpdate(
'<a class="delete" href="">删除</a>', nRow, 6, false);
oTable.fnDraw();
}

在界面上的几个出来动作按钮,如新增、编辑、保存、删除等按钮处理事件如下所示。

                var addRow = 1;
$(
'#detail_editable_1_new').click(function(e) {
e.preventDefault();
if (nNew &&nEditing) {if (confirm("前面记录没有保存,您是否需要保存?")) {
saveRow(oTable, nEditing);
//$(nEditing).find("td:first").html("未保存"); nEditing = null;
nNew
= false;
}
else{
oTable.fnDeleteRow(nEditing);
//cancel nEditing = null;
nNew
= false;return;
}
}
//添加一条新的记录 var aiNew = oTable.fnAddData([addRow++, '', '', '', '', '', '']);var nRow = oTable.fnGetNodes(aiNew[0]);
editRow(oTable, nRow);
nEditing
=nRow;
nNew
= true;
});
//删除操作 table.on('click', '.delete', function(e) {
e.preventDefault();
if (confirm("您确认要删除该行记录吗?") == false) {return;
}
//获取上一级tr行的数据 var nRow = $(this).parents('tr')[0];var aData =oTable.fnGetData(nRow);var found = false;
$.each(objList,
function(i, item) {if (item["seq"] == aData[0]) {
found
= true;
objList.splice(i,
1);
}
});
oTable.fnDeleteRow(nRow);
});
//取消操作 table.on('click', '.cancel', function(e) {
e.preventDefault();
if(nNew) {
oTable.fnDeleteRow(nEditing);
nEditing
= null;
nNew
= false;
}
else{
restoreRow(oTable, nEditing);
nEditing
= null;
}
});
//编辑操作 table.on('click', '.edit', function(e) {
e.preventDefault();
nNew
= false;/*获取所击连接的行对象*/ var nRow = $(this).parents('tr')[0];if (nEditing !== null && nEditing !=nRow) {/*当前正在编辑 - 但不是此行 - 在继续编辑模式之前恢复旧版*/restoreRow(oTable, nEditing);
editRow(oTable, nRow);
nEditing
=nRow;
}
else if (nEditing == nRow && this.innerHTML == "保存") {/*编辑该行,并准备保存记录*/saveRow(oTable, nEditing);
nEditing
= null;

}
else{/*No edit in progress - let's start one*/editRow(oTable, nRow);
nEditing
=nRow;
}
});
}

我们在最后一步,提交数据的时候,就是遍历整个表格,获取每行的数据,并把它们放到JSON对象列表里面,在提交到后台录入即可,如下是获取列表数据的JS代码

            //获取表格的数据,并返回对象列表
            functionGetData() {var list =[];var trs =table.fnGetNodes();for (var i = 0; i < trs.length; i++) {var data = table.fnGetData(trs[i]);//获取指定行的数据

                    var obj ={};//obj["seq"] = data[0];//序号
                    obj["FeeType"] = data[1];
obj[
"OccurTime"] = data[2];
obj[
"FeeAmount"] = data[3];
obj[
"FeeDescription"] = data[4];
list.push(obj);
}
returnlist;
};

获取到表格明细的数据后,我们就是确定如何提交到MVC后台接口来处理了,下面是业务里面关于明细数据提交MVC后台的JS代码。

后台MVC控制器的C#处理逻辑代码如下所示。

        /// <summary>
        ///保存申请单主从表数据/// </summary>
        /// <returns></returns>
[HttpPost]publicActionResult SaveApply(JObject param)
{
dynamic obj =param;if (obj != null)
{
var result = newCommonResult();if (obj.info != null)
{
//获取主信息 var info = (JObject.FromObject(obj.info)).ToObject<ReimbursementInfo>();//转换为明细信息 List<ReimbursementDetailInfo> details = null;if (obj.details != null)
{
details
= (JArray.FromObject(obj.details)).ToObject<List<ReimbursementDetailInfo>>();
}
if (info != null)
{
//修改部分信息 OnBeforeInsert(info);bool succeed = BLLFactory<Reimbursement>.Instance.Insert(info);if(succeed)
{
if (details != null)
{
foreach (var detailInfo indetails)
{
//设置关键信息 detailInfo.Apply_ID =info.Apply_ID;
detailInfo.Header_ID
=info.ID;
BLLFactory
<ReimbursementDetail>.Instance.InsertUpdate(detailInfo, detailInfo.ID);
}
}
result.Success
=succeed;
}
}
}
returnToJsonContent(result);
}
else{throw new MyApiException("传递参数错误");
}
}

其中对于提交上来的数据,对象信息用JObject进行转换,而对于明细列表则使用JArray.FromObject进行转换,其他部分就是如何保存主表和明细表的接口了。

上面的处理逻辑和代码就是处理明细表的前台获取,提交处理,以及后台的接口处理,整个过程主要用来介绍在Bootstrap开发框架中使用dataTable直接录入表格行数据。

在很多开发的场景中,很多情况下我们需要考虑抽象、以及模块化等方面的内容,其目的就是为了使得开发的时候关注的变化内容更加少一些,整体开发更加简单化,从而减少开发的复杂度,在Winform开发的时候,往往可以通过定义基类模块、用户控件的方式实现这个目的,而在Web开发的时候,我们是否也可以利用这些特性呢?特别在MVC的视图模板里面的HTML,是否可以利用这些特点,实现变化部分的隔离,从而减少整个页面的复杂度,同时又可以提高模块的重用性呢?本篇随笔介绍在Asp.NET的MVC视图处理上,使用@RenderPage实现页面内容模块化的隔离,减少复杂度。

1、回顾Winform的界面处理方式

举工作流表单处理为例说明,由于在处理流程的时候,对于表单的处理,大多数情况下的规则和处理逻辑差不多,因此可以把这些不变的内容抽离到基类界面里面,对于Winform方式,我们通过继承不同的业务窗体对象就可以实现了,如下处理方式所示。

由于基类确定了,封装了大多数的处理规则,那么在子类界面的时候,需要处理的只是和业务界面有关的赋值或者读取值的操作了,我们对于不同的业务表单,做起来就很容易了,只需要把变化的部分内容放在子窗体即可。

例如对于出差申请和会议室使用申请的表单,它们的窗体界面继承关系如下所示。

这个就是Winform界面处理的方式, 对于在Bootstrap开发框架的前端视图中,如何以类似的规则处理这些界面的分离操作呢?

答案是使用@RenderPage实现页面内容模块化的隔离。

2、使用@RenderPage实现页面内容模块化的隔离

一般在开始的时候,我们注意到了,在MVC视图中使用母版的操作中,已经隔离了页面布局相同部分,子窗体只需要定义不同部分的视图代码即可。

而进一步,我们还可以在子页面中使用@RenderPage来区分隔离不同业务界面的内容的。

例如对于创建表单界面的视图内容。

上面视图分为了几个部分的内容,一个是创建表单的界面处理,其中涉及到的选择用户界面,是一个弹出的用户选择框,由于选择用户处理是一个常见的操作,我们需要把它抽离到一个视图页面里面,可以在需要的时候,直接通过使用@RenderPage实现页面内容模块化。

@RenderPage("~/Views/Apply/SelectUser.cshtml")

当用户单击【选择流程处理人】的按钮的时候,弹出一个DIV层,这个就是我们刚才使用@RenderPage实现的选择用户界面了,这种处理方式比较弹性化,在需要的时候,包含进来即可,不用把大段大段的代码重复复制过来,方便了维护代码。

同样,对于查看表单界面来说,虽然它的界面内容比新建业务表单的界面复杂很多,但是使用的是一样的处理方式。

刚才我们看到了,在Winform界面里面,使用的是下面的视图继承的方式。

而在MVC视图界面里面,采用的是@RenderPage实现页面内容的模块化。

这样主视图和子视图虽然同名,但是它们是在不同的控制器名下,这样我们在父页面视图的ViewDetail.cshtml就可以封装一些常见的处理界面内容,而留下部分和具体业务表单相关的展示内容放在子页面的ViewDetail.cshtml里面即可。

在页面地址中,得到完整的页面访问路径是:/Reimbursement/ViewDetail?id=8f32231d-852e-9f16-6e5a-79031c8ec995,这个URL其实就是访问具体业务视图下的内容,但是业务视图已经引用了父页面共同的部分。

我们来看看具体业务表单中的视图页面代码,如下所示。

其中的不变的业务界面内容(理解为父窗体也可以),我们通过引用页面的方式把它包含进来。

<!--此处放置业务表单的数据呈现,方便隔离,减少复杂性-->@RenderPage("~/Views/Apply/ViewDetail.cshtml", new { applyId = Request["id"] })

这样就把它们分开维护了,共同的部分就在这个 Views/Apply/ViewDetail.cshtml 视图页面里面了。

而这个共同的部分,则可以封装常规的事件处理,和界面展示,如下是界面视图的截图说明。

其中我们还可以看到通用选择用户的视图层界面

@RenderPage("~/Views/Apply/SelectUser.cshtml")

最后我们来看看两个不同的视图界面效果,以烘托一下隔离界面也不影响整体效果,但是可以降低代码的维护复杂性。

新建业务流程表单如下界面所示。

查看具体流程表单明细的界面如下所示。

在前面随笔《
在Bootstrap开发框架中使用dataTable直接录入表格行数据
》中介绍了在Web页面中使用Jquery DataTable插件进行对数据直接录入操作,这种处理能够给用户提供较好的数据录入体验,本篇继续上篇的内容,继续介绍这个直接录入的处理操作,主要涉及到控件的初始化和数据源的绑定等操作内容,随着对这个处理的深入了解,我发现可以控制的内容也更加丰富,能够更好的实现各种所需的效果。

1、直接录入数据的界面分析

在之前介绍的数据直接录入处理的时候,界面效果如下所示。

上面的界面处理明细数据的时候,可以直接使用新增记录,直接在录入框中输入数据,然后保存起来,保存后数据变为只读,如果需要修改,还可以单击编辑按钮进行修改。

而这些明细的数据,也仅仅存在JS的对象里面,还没有保存到后台数据库中,我们可以在最后保存(如上界面的确定按钮)处理中再获取全部添加的数据进行提交即可。

以上的明细数据录入,只是提供了一些基本的输入控件进行输入,没有进行过多的定制处理,而往往使用的时候,我们发现,有些数据是需要下拉列表的,有些是需要使用日期选择的等等,那么我们就需要考虑更深层次的控件显示问题了。

如我们要实现更丰富的效果处理,甚至包括一些控件之间的联动的处理,那么我们应该如何操作呢?

下拉列表,动态数据界面展示

日期输入框显示

以上一些是我们常规的录入方式,对于有一些比较多样化的操作,我们尽可能为用户提供方便,提供下拉控件给用户选择,毕竟选择比录入更加方便、规范化。

如可以在复杂界面中,使用弹出层进行查询选择

如部门和用户之间的数据联动效果如下所示。

以上种种效果,能够满足我们常规的数据选择录入的方便,从而方便客户直接录入数据处理。

2、直接录入数据的控件初始化

我们从上文可以了解到,对于新增一套记录,就是动态构建一些HTML的控件,然后进行初始化即可,如对于这个下拉列表的界面效果。

它的实现主要就是在编辑或者新增的时候,对HTML控件的处理,如下代码所示。

    //编辑行
    functioneditRow(oTable, nRow) {var aData =oTable.fnGetData(nRow);var jqTds = $('>td', nRow);var i = 0;var feeType = aData[0];
jqTds[i].innerHTML
= '<select id="txtFeeType" class="form-control input-small" value="' + aData[0] + '"></select>';
i
++; jqTds[i].innerHTML = '<input id="txtOccurTime" class="form-control input-small" value="' + aData[1] + '">';
i
++; jqTds[i].innerHTML = '<input id="txtFeeAmount" type="number" class="form-control input-small" value="' + aData[2] + '">';
i
++; jqTds[i].innerHTML = '<input id="txtFeeDescription" type="text" class="form-control input-small" value="' + aData[3] + '">';
i
++; jqTds[i].innerHTML = '<a class="btn btn-xs green edit" href="" title="保存">保存</a>';
i
++; jqTds[i].innerHTML = '<a class="btn btn-xs red cancel" href="" title="取消"><span class="glyphicon glyphicon-share-alt "></span></a>';//绑定数据字典,并更新值 BindDictItem("txtFeeType", "费用类型", function() {
$(
"#txtFeeType").val(feeType).trigger("change");
});
//初始化日期控件 $("#txtOccurTime").datepicker({
language:
'zh-CN', //语言 autoclose: true, //选择后自动关闭 clearBtn: true,//清除按钮 format: "yyyy-mm-dd"//日期格式 });
}

我们可以在下拉列表的时候,使用select2插件,通过BindDictItem的通用JS函数,可以绑定数据库的字典类型,并通过记录对应列的值,可以给该控件进行赋值。

 $("#txtFeeType").val(feeType).trigger("change");

由于每个控件都有一个对应的ID,那么我们使用它们的时候,就很方便,如初始化日期插件,可以使用DateTime Picker插件进行处理。

        //初始化日期控件
        $("#txtOccurTime").datepicker({
language:
'zh-CN', //语言 autoclose: true, //选择后自动关闭 clearBtn: true,//清除按钮 format: "yyyy-mm-dd"//日期格式 });

最终达到了日期选择效果。

使用弹出层进行查询选择的操作过程也很简单。

                    jqTds[i].innerHTML = '<input id="txtAssetName" type="text" class="form-control input-small" onclick="SelectAssets(this)" value="' + aData[i] + '">';//资产名称

也就是为这个控件增加onclick函数,在选择单击输入的时候,弹出一个层进行处理即可。而这个独立的通用层,则使用一个单独的视图,在页面里面引用即可,提高重用性。

@RenderPage("~/Views/Asset/SelectAsset.cshtml")

而通过在页面里面处理返回结果,则可以实现主界面内容控件的更新。

//选择结果
functionSelectResult() {var dict ={};
addAssetKeyList.forEach(
function(key, index, array) {var display =addAssetDisplayList[index];
dict[key]
=display;
});
//转换选择为JSON字符串 var json =JSON.stringify(dict);
$(
"#selectAsset").modal("hide");//留给调用的界面实现这个函数,实现数据的返回出来 ProcAssetJson =json;
OnSelectAsset(json);
}

而选择后,可以对控件内容以及关联的数据进行动态更新。

        //选择资产后调用
        functionOnSelectAsset(json) {
ProcAssetJson
= json;//存储到ProcAssetJson,方便下次打开界面初始化数据 if (json != '') {var dict =JSON.parse(json);if (dict != null) {for (var key indict) {var display =dict[key];
assetInput.val(display);
//更新数据 $.getJSON("/Asset/FindByCode?code=" + key, function(info) {if (info != null) {
$(
"#txtAssetCode").val(info.Code);//$("#txtKeepAddr").val(info.KeepAddr); $("#txtUnit").val(info.Unit);
$(
"#txtPrice").val(info.Price);
$(
"#txtTotalQty").val(info.Qty);
$(
"#txtTotalAmount").val(info.OriginValue);
}
});
};
}
}
}

下面就是弹出界面层,并提供用户选择内容的界面

对于部门和用户之间的数据联动的处理,也是通过Select2控件的联动更新处理实现的。以下是Select2联动处理脚本,可以实现多个控件之间的联动操作

//部门编号后,用户列表编号
$("#txtLyDept").on("change", function(e) {var deptNameId = $("#txtLyDept").val();if (deptNameId != null) {var id = deptNameId.substring(deptNameId.lastIndexOf("|") + 1);
BindSelect(
"txtUsePerson", "/User/GetUserDictJson2?deptId=" + id, '', function() {
$(
"#txtUsePerson").val(userid).trigger("change");
});
//存储位置 BindSelect("txtKeepAddr", "/StoreAddress/GetDictJson?deptId=" + id, '', function() {
$(
"#txtKeepAddr").val(keepAddr).trigger("change");
});
}
});

界面效果如下所示。

由于我们在控件的ID上约定了以txt开头,那么我们通过这个约定规则动态获取控件的值也是很方便的,这样为我们保存控件的数据提供很好的便捷处理。

//保存行数据,切换到普通模式
var inputLength = 10;//输入的字段数
functionsaveRow(oTable, nRow) {//var jqInputs = $('input', nRow);
    var jqInputs = $("[id^='txt']", nRow);//id以txt开始([id^='txt']), id以txt结束([id$='txt'])

    //更新行中每个input的值
    for (var i = 0; i < inputLength; i++) {
oTable.fnUpdate(jqInputs[i].value, nRow, i,
false);
iLen
=i;
}

oTable.fnUpdate(
'<a class="btn btn-xs green edit" href="" title="编辑"><span class="glyphicon glyphicon-edit"></span></a>', nRow, inputLength, false);
oTable.fnUpdate(
'<a class="btn btn-xs red delete" href="" title="删除"><span class="glyphicon glyphicon-remove"></span></a>', nRow, inputLength + 1, false);
oTable.fnDraw();
}

我们如果需要保存数据到数据库里面,那么就需要先构建好对应的JS数据对象,然后调用ajax进行数据的提交处理。构建JS数据对象如下代码所示(根据自己所需定制数据内容)。

            //获取表格的数据,并返回对象列表
            functionGetData() {var list =[];var trs =table.fnGetNodes();for (var i = 0; i < trs.length; i++) {var data = table.fnGetData(trs[i]);//获取指定行的数据

                    //构建对象
                    var obj ={
AssetName: data[
0],
AssetCode: data[
1],
LyDept: data[
2],
UsePerson: data[
3],
KeepAddr: data[
4],
Unit: data[
5],
Price: data[
6],
TotalQty: data[
7],
TotalAmount: data[
8],
Note: data[
9]
};
list.push(obj);
}
returnlist;
};

在很早之前的随笔里面,已经介绍了WInform框架中工作流模块的功能,不过由于工作流模块中界面处理部分比较麻烦,一直没有在Bootstrap框架中进行集成,最近由于项目的关系,花了不少精力,把工作流模块重新梳理迁移到Bootstrap框架上,本篇随笔主要介绍基于Metronic的Bootstrap开发框架的工作模块功能。

1、工作流的设计模型

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

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

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

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

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

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

在流程审批中,一般还有一种流程处理就是会签的操作,会签处理是几个审批步骤中审批人同时处理是否通过的,一般同时通过即为通过。

会签是指创建一个或多个子流程供相关人员进行审批,等待全部人员完成处理后再次回到主流程上,然后决定是否继续流转到下一个流程步骤上去,一般的申请单的主流程如下所示。

这里设置的会签处理就是其中一个步骤,一旦会签处理步骤发起会签,就会构建多个可供审批的子流程了,如下所示。

在会签发起的步骤,指定参与具体流程会签审批的人员,然后流程则会流转到不同人员进行相关的处理【待办事项】。

我在工作流中定义会签完成后,由会签发起人审核(会签结果审核),决定是否进入下一步流程,在审核过程中决定如何处理这个申请单。

2、工作流模块介绍

1)流程环节管理

从上面的基础知识介绍中,我们知道,流程环节是构成流程模板和流程实例的基本单元,我们需要定义不同类型的流程处理环节,如审批、会签、阅办等等,不同类型的流程环节,在流程步骤的处理环节中是不一样的,我们也为这些不同的环节定义不同的审批界面。

首先我们在Bootstrap框架的系统菜单中选择【工作流管理】【工作流维护】【流程环节管理】菜单,就可以进入对应的流程环节管理界面。

在流程环节管理界面中,会列出系统所有定义好的流程环节,我们也可以定义自己的流程步骤。

在系统主界面里面,我们一般已经预定义了一些常规的如审批、会签、阅办、归档等类型,我们如果需要定义特殊的审批界面,我们就可以在这里定义一些不同的流程环节,也可以对已有的环节进行一定的修改处理。

2)流程模板管理

通过第一步的流程环节定义,我们接下来就基于流程环节,定义流程模板的内容了,流程模板是我们开展一些工作流的基础,也就是说,我们先有特定流程的模板,然后才有具体的流程示例。

流程模板我们定义的时候,需要指定它的具体名称,另外有几个字段是必须注意的,就是它的对应业务表名和创建流程URL、查看流程URL这几个信息。

指定这些内容,我们在指定的视图页面中处理不同的流程信息,创建在Create视图、查看在ViewDetail视图,其中还会包含一个index视图列出该类型的申请单,这几个列表都可以使用代码生成工具快速生成,具体代码的生成过程,我会在随后的文章中及进行详细介绍。

这样的自动化,可以迅速提高我们开发工作流业务的效率,并且和整个系统风格保持统一。这个就是我们整个开发框架系列的精髓所在,以工具提高效率,统一过程。

再次回到流程模板的处理来,我们刚才只是定义了流程模板的一些基础信息,对于这个流程模板,我们还需要确定它的流程步骤,这个才是工作流的灵魂所在,动态化的流程步骤,可以满足我们大多数变化流程的需要。

流程步骤的定义,如下界面所示,可以指定流程处理人,通过选定角色、部门或者具体人员都可以,如果没有选择具体的处理人,那么默认会以当前用户部门的人员供选择。

如但用户选择人员的时候,弹出层可以根据组织机构、角色进行用户的筛选和选择。

3)业务受理列表

业务受理列表,是根据我们数据库中定义的流程模板,动态列出申请单的创建入口,提供一个统一的入口方便我们处理。

一下是我们工作流模块中定义好的一些流程模板,可以供创建业务表单。

这些业务表单入口,单击后就可以创建对应类型的申请单了,我们以刚才介绍的请假申请为例,单击后进入请假申请单的Create视图界面,创建新的申请。

其他的业务申请单也是类似,我们只需要使用代码生成工具Database2Sharp对具体业务表单的工作流模块代码进行生成后,就可以配置使用,并且可以创建对应的业务表单的了。

对于一些具有明细表单的操作,也有案例可以供参考。

4)我的审批工作

我的审批工作是列出和我相关的审批表单,包括已办、待办、 发起的几种类型的分类,其中每个种类型又可以继续细化不同表单类型,方便我们快速选择查看,如下列表所示。

通过快速定位所需要的类型申请单,我们可以很好的完成一些待办工作,以及可以查看自己参与的流程申请单的处理情况等等。

双击其中的申请单,可以查看具体的申请详细信息,包括流程审批信息和业务表单信息等。

而如果是流程处理中的申请单,我们可以在相关的处理按钮中执行我们自己的审批操作。

5)所有申请单

所有申请单是提供一个给流程管理员的一个入口,方便对一些错误或者不需要的申请单进行删除等维护操作。

这里面列出的是系统所有的申请单,这个页面一般不暴露给普通用户,而是作为后台数据管理的一个页面,对不需要的数据进行删除操作的。

6)我的草稿

我的草稿是提供一个界面维护我自己的申请单草稿的,界面效果如下所示。

在我们创建申请单的时候,有时候录入数据后暂时想存起来而不提交的话,保存为草稿就可以,下次则可以直接从草稿中继续申请单的处理。