H3BPM二次开发及实施规范V1.0
目录
1 实施开发规范
1.1 规范目的
为统一实施开发规范,提高代码可读性、稳定性、一致性,便于阅读和管理代码及结构,提高开发效率和产品的标准化,降低系统交接及运维成本。特制定一套符合本公司开发规范及标准。
1.2 规范内容
规范内容会从项目实施的过程逐步展开深入,主要包括:产品安装、组织架构初始化、流程表单开发,功能开发,定制开发,报表开发等部分。
本规范编写基于H3 BPM V9.2.7版本。
1.3 产品安装
1.3.1 注册码申请
如果产品安装环境是正式环境需要申请正式注册码,保证产品长期运行的稳定性。如果安装环境是开发环境申请临时注册码,可授权日期期限。
1.3.2 源代码管理
对于超过两人的项目团队必须采用统一的源程序管理,使用SVN进行代码管理,保证每次代码的修改都有记录可以查询。
1.4 组织架构初始化
1.4.1 AD同步
如果客户是使用AD域对员工账号进行管理,就使用系统自带的AD同步功能。
1.4.2 自定义模板导入
OU信息模板
用户信息模板
OU属性和用户属性可以实际情况适当添加。
导入程序
Excel模板和导入程序见附件1
1.5 流程开发
1.5.1 数据模型
流程包编码和字段编码要求使用英文单词;流程包和数据项设计命名统一规范,项目内部要统一。
规范示例:
不规范示例:
1.5.2 H3表单开发规范
表单命名
l 无论是默认表单还是自定义表单命名时都以流程包编码命名,杜绝使用中文首字母简称或纯数字。
l 在新建自定义表单时,要有清晰的文件目录,文件统一存放在Sheets文件夹下,文件目录分模块储存,使用:Sheets/模块/流程编码。
规范示例:
不规范示例:
表单设计
l 需使用百分比(如98%等)为表单中的控制指定宽度,避免表单上内容参差不齐。
l 保证数据项显示名的美观性,适当使用换行,避免单字换行;避免三行显示。
l 一个流程涉及到多个表单时,表单的命名规则要显而易懂,能让客户管理员通过流程状态打开表单时,能知道每个表单的作用或应用场景。
l 表单需做到纵向内容对齐。
l 表单设计应尽可能多的使用默认表单配置
Ø H3表单开发
针对H3流程表单开发的规范说明文档,H3流程表单框架规范下,可以自适应PC端和移动端展示效果。
H3表单基于BootStrap样式,采用HTML+JavaScript模式开发,通过Ajax方式往服务器端获取数据,然后通过自定义开发的一套MvcSheet控件进行表单展示。移动端采用IONIC框架,当移动端打开表单时,H3表单渲染引擎将以移动端展现模式,重新排列表单布局,同时部分复杂交互控件会改成以移动端控件进行渲染。
考虑到H3表单同时兼容PC端和移动端的特性,在二次开发过程中,需要遵循H3表单的布局规则,以及遵循H3表单控件的使用方式,对于特殊业务场景,可参考本文档给出的解决方案。
PC标准表单和移动表单示例图如下所示:
PC表单示例图
移动表单效果图
1.5.2.1 表单程序结构
1.5.2.1.1 母版页
表单必须从母版页MvcSheet.master继承,如下图所示:
1.5.2.1.2 表单基类
表单从 OThinker.H3.WorkSheet.MvcPage 继承,如下图所示:
1.5.2.1.3 自定义表单开发
当需要开发自定义表单时,可以从默认表单中拷贝代码:
从ASPX拷贝HTML文件
从代码拷贝C#文件
1.5.2.2 表单布局概述
1.5.2.2.1 标准布局
表单布局采用Bootstrap样式进行布局,标准界面如下图所示:
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="SpurcharsApp_Self.aspx.cs" Inherits="OThinker.H3.Portal.Sheets.DefaultEngine.Sheets_Test_SpurcharsApp_Self" EnableEventValidation="false" MasterPageFile="~/MvcSheet.master" %>
<%@ OutputCache Duration="999999" VaryByParam="T" VaryByCustom="browser" %> <asp:Content ID="head" ContentPlaceHolderID="headContent" runat="Server"> <script type="text/javascript">
</script> </asp:Content> <asp:Content ID="menu" ContentPlaceHolderID="cphMenu" runat="Server"> </asp:Content> <asp:Content ID="master" ContentPlaceHolderID="masterContent" runat="Server"> <div style="text-align: center;" class="DragContainer"> <label id="lblTitle" class="panel-title">采购流程</label> </div> <div class="panel-body sheetContainer"> <div class="nav-icon fa fa-chevron-right bannerTitle"> <label id="divBasicInfo" data-en_us="Basic information">基本信息</label> </div> <div class="divContent"> <div class="row"> <div id="divFullNameTitle" class="col-md-1"> <label id="lblFullNameTitle" data-type="SheetLabel" data-datafield="Originator.UserName" data-en_us="Originator" data-bindtype="OnlyVisiable">发起人</label> </div> <div id="divFullName" class="col-md-3"> <label id="lblFullName" data-type="SheetLabel" data-datafield="Originator.UserName" data-bindtype="OnlyData"> </label> </div> <div id="divOriginateDateTitle" class="col-md-1"> <label id="lblOriginateDateTitle" data-type="SheetLabel" data-datafield="OriginateDate" data-en_us="Originate Date" data-bindtype="OnlyVisiable">发起时间</label> </div> <div id="divOriginateDate" class="col-md-3"> <label id="lblOriginateDate" data-type="SheetLabel" data-datafield="OriginateDate" data-bindtype="OnlyData"> </label> </div> <div id="divSequenceNoTitle" class="col-md-1"> <label id="lblSequenceNoTitle" data-type="SheetLabel" data-datafield="SequenceNo" data-en_us="SequenceNo" data-bindtype="OnlyVisiable">流水号</label> </div> <div id="divSequenceNo" class="col-md-3"> <label id="lblSequenceNo" data-type="SheetLabel" data-datafield="SequenceNo" data-bindtype="OnlyData"> </label> </div> </div> <div class="row"> <div id="divOriginateOUNameTitle" class="col-md-1"> <label id="lblOriginateOUNameTitle" data-type="SheetLabel" data-datafield="Originator.OUName" data-en_us="Originate OUName" data-bindtype="OnlyVisiable">所属组织</label> </div> <div id="divOriginateOUName" class="col-md-3"> <label id="lblOriginateOUName" data-type="SheetLabel" data-datafield="Originator.OUName" data-bindtype="OnlyData"> </label> </div> <div id="divOriginateAreaTitle" class="col-md-1"> <label id="lblOriginateArea" data-type="SheetLabel" data-datafield="InstanceFlag" data-en_us="Area" data-bindtype="OnlyVisiable">区域/中心</label> </div> <div id="divOriginateArea" style="margin-top: 0; main-bottom: 0; line-height: 23px; padding-left: 5px !important;" class="col-md-3"> <div data-datafield="InstanceFlag" data-type="SheetUser" id="" class="suInstanceFlag" data-orgunitvisible="true" data-uservisible="false" data-rootunit="c34e07c312404e82ad021214faf90712" data-recursive="false" data-defaultvalue="area"> </div> </div> </div> </div> <div class="nav-icon fa fa-chevron-right bannerTitle"> <label id="divSheetInfo" data-en_us="Sheet information">表单信息</label> </div> <div id="ctl00_BodyContent_divSheet" class="divContent"> <div class="row"> <div id="title1" class="col-md-2"> <span id="Label12" data-type="SheetLabel" data-datafield="title">标题</span> </div> <div id="control1" class="col-md-10" colspan="3"> <input id="Control12" type="text" data-datafield="title" data-type="SheetTextBox" class=""> </div> </div> <div class="row"> <div id="title3" class="col-md-2"> <span id="Label15" data-type="SheetLabel" data-datafield="Pur_reson" class="">采购原因</span> </div> <div id="control3" class="col-md-10" colspan="3"> <input id="Control15" type="text" data-datafield="Pur_reson" data-type="SheetTextBox" class=""> </div> </div> <div class="row tableContent"> <div id="title5" class="col-md-2"> <span id="Label16" data-type="SheetLabel" data-datafield="remark" class="">采购说明</span> </div> <div id="control5" class="col-md-10"> <textarea id="Control16" data-datafield="remark" data-type="SheetRichTextBox" class=""></textarea> </div> </div> <div class="row"> <div id="div981840" class="col-md-2"><span id="Label13" data-type="SheetLabel" data-datafield="SPR1" class="">审批人1</span></div> <div id="div518625" class="col-md-4"> <div id="Control13" data-datafield="SPR1" data-type="SheetUser" class=""> </div> </div> <div id="div911605" class="col-md-2"><span id="Label14" data-type="SheetLabel" data-datafield="SPR2" class="">审批人2</span></div> <div id="div120759" class="col-md-4"> <div id="Control14" data-datafield="SPR2" data-type="SheetUser" class=""> </div> </div> </div> <div class="row"> <div id="title7" class="col-md-2"> <span id="Label17" data-type="SheetLabel" data-datafield="fj">附件</span> </div> <div id="control7" class="col-md-10"> <div id="Control17" data-datafield="fj" data-type="SheetAttachment"> </div> </div> </div> <div class="row tableContent"> <div id="title9" class="col-md-2"> <span id="Label18" data-type="SheetLabel" data-datafield="Approvement">审批意见</span> </div> <div id="control9" class="col-md-10"> <div id="Control18" data-datafield="Approvement" data-type="SheetComment"> </div> </div> </div> </div> </div> </asp:Content> |
1.5.2.2.2 表单容器
表单所有HTML内容均需要包含在指定的DIV容器中
<div class="panel-body sheetContainer"> 这里是表单所有HTML </div> |
1.5.2.2.3 显示段落
表单默认包含2个分区段落,分别是基本信息和表单信息,HTML格式如下:
<div class="nav-icon fa fa-chevron-right bannerTitle"> <label id="divBasicInfo" data-en_us="Basic information">基本信息</label> </div> <div class="divContent"> 段落内部主体内容 </div> |
1.5.2.2.4 显示行
H3表单开发中的行元素采用Bootstrap的样式,其中.row表示行,每一行又可以划分为12个单元格,分别对应col-md-*的样式表示,其中*表示占用单元格数量。
一个标准的一行2个控件,HTML格式表示如下:
<div class="row"> <div id="divFullNameTitle" class="col-md-2"> 标题1 </div> <div id="divFullName" class="col-md-4"> 输入控件1 </div> <div id="divOriginateDate" class="col-md-2"> 标题2 </div> <div id="divSequenceNoTitle" class="col-md-4"> 输入控件2 </div> </div> |
当需要整行显示某个编辑控件时,可以采用一个row包含col-md-2和col-md-10的方式,如下所示:
<div class="row"> <div id="divFullNameTitle" class="col-md-2"> 标题1 </div> <div id="divFullName" class="col-md-10"> 输入控件1 </div> </div> |
注意:
ü 在表单内容区域中请不要使用Table来替代row显示表格;
ü 不要在一个row元素中包含多余12个单元格的元素;
ü 所有HTML元素内容,请全部包含col-md-*的容器中;
1.5.2.2.5 控件容器
所有控件,均需要在.row>.col-md-*的容器中,如下所示
<div class="row"> <div id="divFullNameTitle" class="col-md-2"> //这里防止HTML控件内容 </div> <div id="divFullName" class="col-md-10"> //这里防止HTML控件内容 </div> </div> |
请不要将自定义开发的内容防止在容器之外,例如以下方式则不可取
1.5.2.2.6 仅移动端可见
可以通过指定样式 pcHidden进行实现。
<div class="row pcHidden"> 内容区域 </div> |
1.5.2.2.7 仅PC端可见
可以通过指定样式 mobileHidden进行实现。
<div class="row mobileHidden"> 内容区域 </div> |
1.5.2.2.8 脚本和样式存放区域
所有javascript脚本和css,均放置在表单的headContent区域中,如下图所示:
1.5.2.3 控件
1.5.2.3.1 概述
H3表单提供一套完善的表单JS控件,控制数据的加载、存储、显示、隐藏、必填项控制等。表单内容的展示,请使用H3提供的标准控件进行展示,否则有可能影响PC或者移动端展示效果。
例如:展示一段文本内容,采用
<span id="Label12" data-type="SheetLabel" data-datafield="title">标题</span> |
不推荐以下方式
<span id="Label12">标题</span> |
以上2者区别在于span中增加了data-type和data-datafield元素,会被H3表单引擎进行渲染,当title不可见时,会自动被隐藏。当在移动端时,也会同样使用SheetLabel控件进行渲染效果。
注:对于非H3表单提供的控件,H3则不负责渲染逻辑和移动显示效果。
1.5.2.3.2 控件列表
H3表单提供了一套完整的表单控件,详细列表参考如下:
控件名称 | 描述 | 示例 |
SheetAttachment | 附件控件 | |
SheetFrame | Iframe窗体控件 | |
SheetCheckbox | 选择框控件 | |
SheetCheckboxList | 多选框控件 | |
SheetComment | 审批意见控件 | |
SheetDropDownList | 下拉框控件 | |
SheetGridView | 子表控件 | |
SheetHyperLink | 链接控件 | |
SheetInstancePrioritySelector | 紧急程度控件 | |
SheetLabel | 标签控件 | |
SheetOffice | 在线Office文档编辑控件 | |
SheetRadioButtonList | 单选框控件 | |
SheetRichTextBox | 富文本框控件 | |
SheetTextBox | 文本框控件 | |
SheetTime | 日期控件 | |
SheetUser | 选人控件 | |
SheetTimeSpan | 时间段控件 |
部分控件使用说明如下:
<span id="Label12" data-type="SheetLabel" data-datafield="title">标题</span> <input id="Control12" type="text" data-datafield="title" data-type="SheetTextBox" class=""> <div id="Control18" data-datafield="Approvement" data-type="SheetComment"/> <div id="Control18" data-datafield="SheetUser" data-type="ApproveUser"/> |
详细的控件请使用默认表单,查看表单ASPX,可以了解控件的HTML内容,更多的帮助说明,请参考H3在线帮助文档:http://wiki.h3yun.com/
1.5.2.3.3 控件接口说明
H3表单控件提供标准的接口进行操作,以SheetTextBox控件为例,可以通过接口对象获取到控件的实例,并且调用控件的方法,如下图所示:
var control = $("#ID").SheetUIManager(); // 获取控件的值 var txtValue = control.GetValue(); // 设置控件的值 control.SetValue("Test"); |
1.5.2.3.4 获取控件的值
请调用标准接口进行操作,如下所示:
$("#ID").SheetUIManager().GetValue(); |
1.5.2.3.5 给控件赋值
请调用标准接口进行操作,如下所示:
$("#ID").SheetUIManager().SetValue("Test"); |
1.5.2.3.6 显示控件
请调用标准接口进行操作,如下所示:
$("#ID").SheetUIManager().SetVisiable(true); |
1.5.2.3.7 隐藏控件
请调用标准接口进行操作,如下所示:
$("#ID").SheetUIManager().SetVisiable(false); |
1.5.2.3.8 隐藏行
请调用标准接口进行操作,如下所示:
$("#Control14").parents(".row").hide(); |
1.5.2.3.9 前端调用后台方法获取数据
表单提供了$.MvcSheet.Action方法访问表单C#代码,如下图所示:
前端调用方法:
1.5.2.4 常见应用场景及解决方案
1.5.2.4.1 不规范表单
以上Table没有按照规范,存放在容器col-md-*的容器中,将导致移动端展示错误。
1.5.2.4.2 标准规范示例程序
表单代码:
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="MvcDemo.aspx.cs" Inherits="OThinker.H3.Portal.Sheets.H3_XSX_1.MvcDemo" EnableEventValidation="false" MasterPageFile="~/MvcSheet.master" %>
<%@ OutputCache Duration="999999" VaryByParam="T" VaryByCustom="browser" %> <asp:Content ID="head" ContentPlaceHolderID="headContent" runat="Server"> <script type="text/javascript"> /* 全局可访问的对象:$.MvcSheetUI.SheetInfo,该属性是后台传递到前端的所有信息,但是需要在 $.MvcSheet.Loaded 方法中使用 例如: $.MvcSheetUI.SheetInfo.ActivityCode 当前活动编码 $.MvcSheetUI.SheetInfo. 获取MVC表单控件的实例:$("#控件ID").SheetUIManager() */ // 增加自定义工具栏按钮方法,触发后台事件 $.MvcSheet.AddAction({ Action: "TestAction", // 执行后台方法名称 Icon: "fa-print", // 按钮图标 Text: "后台事件", // 按钮名称 Datas: ["{selectUser}"], // 参数,多个参数 "{Param1}","Param2"... //OnAction: function () { /* 自定义按钮执行事件,如果为 null 则调用$.MvcSheet.Action 执行后台方法 如果不为 null,那么会执行这里的方法,需要自己Post到后台或写前端逻辑 */ //}, OnActionDone: function (e) { // 后台方法调用完成后触发 // 以下是将后台的值输出到前端控件中 if (e) { $.MvcSheetUI.SetControlValue("code", e.Code); $.MvcSheetUI.SetControlValue("mvcName", e.Name); } }, PostSheetInfo: true // 是否提交表单数据,如果 false,那么不返回表单的数据 });
// 增加自定义工具栏按钮方法,触发前台事件 $.MvcSheet.AddAction({ Action: "TestScript", // 按钮名称 Icon: "fa-print", // 按钮图标 Text: "前端事件", // 按钮名称 Datas: ["{selectUser}"], // 参数,多个参数 "{Param1}","Param2"... OnAction: function () { alert("这里执行前端事件"); }, OnActionDone: function (e) { // 事件执行完成 }, PostSheetInfo: true // 是否提交表单数据,如果 false,那么不返回表单的数据 });
// 所有工具栏按钮完成事件 $.MvcSheet.ActionDone = function (data) { // this.Action; // 获取当前按钮名称 }
// 保存前事件 $.MvcSheet.SaveAction.OnActionPreDo = function () { // this.Action // 获取当前按钮名称 alert(this.Action); }
// 保存后事件,保存是异步的,可能比回调函数快 $.MvcSheet.SaveAction.OnActionDone = function () { //this 当前SaveAction var mvcNum = $.MvcSheetUI.GetSheetDataItem("mvcNum"); //读取后台数据项对象,L:数据项类型,V:数据项的值,O:数据项的权限,N:数据项名称,RowNum:主表中为0,子表中表示行号 if (mvcNum && (mvcNum.L == $.MvcSheetUI.LogicType.Int || mvcNum.L == $.MvcSheetUI.LogicType.Double || mvcNum.L == $.MvcSheetUI.LogicType.Long)) { if (mvcNum.V > 100) {
} } }
// 表单验证接口 $.MvcSheet.Validate = function () { // this.Action 表示当前操作的按钮名称 var nameText = $.MvcSheetUI.GetControlValue("mvcName"); // 根据数据项编码获取页面控件的值
// 填写申请单环节,设置 mvcName 必填 if ($.MvcSheetUI.SheetInfo.ActivityCode == "Apply") { if (this.Action == "Submit") { if (!nameText) { $.MvcSheetUI.GetElement("mvcName").focus(); alert("请填写名称."); return false; } } } return true; }
// 页面初始化事件,该事件在获取MVC表单数据,并且在控件初始化之前执行 $.MvcSheet.PreInit = function () { // 将提交按钮文字改为通过 if ($.MvcSheetUI.SheetInfo.ActivityCode == "Approve" || $.MvcSheetUI.SheetInfo.IsOriginateMode) { $.MvcSheet.SubmitAction.Text = "通过"; } };
// 页面加载完成事件 $.MvcSheet.Loaded = function (sheetInfo) { // 获取选人控件 // arguments[0] 该参数包含MVC表单后台传递到前端的所有信息 /* MVC控件实例,通过 SheetUIManager() 方法获取,例如获取 txtCode 所对应的MvcSheetUI实例 */ var txtCode = $("#txtCode").SheetUIManager(); // 可以调用所有 SheetTextBox 提供的接口方法,例如 txtCode.GetValue();
// 自定义按钮调用后台方法示例 $("#btnClick").click(function () { // 执行后台事件 $.MvcSheet.Action( { Action: "TestAction", // 后台方法名称 Datas: ["输入参数"], // 输入参数,格式 ["{数据项名称}","String值","控件ID"],当包含数据项名称时 LoadControlValue必须为true LoadControlValue: true, // 是否获取表单数据 PostSheetInfo: false, // 是否获取已经改变的表单数据 OnActionDone: function (e) { // 执行完成后回调事件 $.MvcSheetUI.SetControlValue("code", e.Code); $.MvcSheetUI.SetControlValue("mvcName", e.Name); } } ) }); }
// 子表行保存事件,row当前保存所在的行, val保存的结果数据 var gridSaving = function (row, val) { if (val && val.code) { val.code += row.attr("data-row"); } return val; };
// 子表行添加事件,row当前添加的行, val加载的数据对象 function gridAddRow(row, val) { if (val) { var code = val.DataItems["mvcDetail.code"].V; row.find("input[data-datafield='mvcDetail.Spec']").val(code + "规格"); } } </script> </asp:Content> <asp:Content ID="menu" ContentPlaceHolderID="cphMenu" runat="Server"> </asp:Content> <asp:Content ID="master" ContentPlaceHolderID="masterContent" runat="Server"> <div style="text-align: center;"> <label id="lblTitle">MvcDemo</label> </div> <div> <div class="nav-icon fa fa-chevron-right bannerTitle"> <label id="divBasicInfo">基本信息</label> </div> <div> <div> <div id="divFullNameTitle"> <label id="lblFullNameTitle" data-type="SheetLabel" data-datafield="Originator.UserName" data-bindtype="OnlyVisiable">发起人</label> </div> <div id="divFullName"> <label id="lblFullName" data-type="SheetLabel" data-datafield="Originator.UserName" data-bindtype="OnlyData"></label> </div> <div id="divOriginateDateTitle"> <label id="lblOriginateDateTitle" data-type="SheetLabel" data-datafield="OriginateDate" data-bindtype="OnlyVisiable">发起时间</label> </div> <div id="divOriginateDate"> <label id="lblOriginateDate" data-type="SheetLabel" data-datafield="OriginateDate" data-bindtype="OnlyData"></label> </div> </div> <div> <div id="divOriginateOUNameTitle"> <label id="lblOriginateOUNameTitle" data-type="SheetLabel" data-datafield="Originator.OUName" data-bindtype="OnlyVisiable">所属组织</label> </div> <div id="divOriginateOUName"> <label id="lblOriginateOUName" data-type="SheetLabel" data-datafield="Originator.OUName" data-bindtype="OnlyData"></label> </div> <div id="divSequenceNoTitle"> <label id="lblSequenceNoTitle" data-type="SheetLabel" data-datafield="SequenceNo" data-bindtype="OnlyVisiable">流水号</label> </div> <div id="divSequenceNo"> <label id="lblSequenceNo" data-type="SheetLabel" data-datafield="SequenceNo" data-bindtype="OnlyData"></label> </div> </div> </div> <div class="nav-icon fa fa-chevron-right bannerTitle"> <label id="divSheetInfo">表单信息</label> </div> <div id="ctl00_BodyContent_divSheet"> <div> <div id="title1"> <span id="Label11" data-type="SheetLabel" data-datafield="code">编码</span> </div> <div id="control1"> <input id="txtCode" type="text" data-datafield="code" data-type="SheetTextBox"> <asp:Button ID="txtButton" runat="server" Text="测试按钮" OnClick="txtButton_Click" /> <input type="button" id="btnClick" value="前端调用后台方法" /> </div> <div id="title2"> <span id="Label12" data-type="SheetLabel" data-datafield="mvcName">名称</span> </div> <div id="control2"> <input id="txtName" type="text" data-datafield="mvcName" data-type="SheetTextBox"> </div> </div> <div> <div id="title3"> <span id="Label13" data-type="SheetLabel" data-datafield="radio">单选</span> </div> <div id="control3"> <div data-datafield="radio" data-type="SheetRadioButtonList" id="ctl414631" class="" data-defaultitems="A;B;C;其他"></div> </div> <div id="title4"> <span id="Label14" data-type="SheetLabel" data-datafield="mvcOther" data-displayrule="{radio}=='其他'">其他</span> </div> <div id="control4"> <input id="Control14" type="text" data-datafield="mvcOther" data-type="SheetTextBox" class="" data-displayrule="{radio}=='其他'" data-vaildationrule="{radio}=='其他'"> </div> </div> <div> <div id="title5"> <span id="Label15" data-type="SheetLabel" data-datafield="multiCheck">多选</span> </div> <div id="control5"> <div data-datafield="multiCheck" data-type="SheetCheckboxList" id="ctl732795" class="" data-defaultitems="A;B;C"></div> </div> <div id="title6"> <span id="Label16" data-type="SheetLabel" data-datafield="mvcTime">日期</span> </div> <div id="control6"> <input id="Control16" type="text" data-datafield="mvcTime" data-type="SheetTime"> </div> </div> <div> <div id="title7"> <span id="Label17" data-type="SheetLabel" data-datafield="mvcMobile">电话</span> </div> <div id="control7"> <input id="Control17" type="text" data-datafield="mvcMobile" data-type="SheetTextBox" class="" data-regularexpression="/^1[3|4|5|8][0-9]{9}$/" data-regularinvalidtext="请输入一个有效的手机号码."> </div> <div id="title8"> <span id="Label18" data-type="SheetLabel" data-datafield="dropList">下拉框</span> </div> <div id="control8"> <select data-datafield="dropList" data-type="SheetDropDownList" id="ctl352297" class="" data-defaultitems="A;B;C;D"> </select> </div> </div> <div> <div id="title11"> <span id="Label20" data-type="SheetLabel" data-datafield="selectUser">选人</span> </div> <div id="Div1"> <div id="Control20" data-datafield="selectUser" data-type="SheetUser" data-defaultvalue="{Originator}" data-mappingcontrols="Email:Email,Dept:FullName"> </div> </div> <div id="title12"> <span id="Label21" data-type="SheetLabel" data-datafield="mulitUser">多人</span> </div> <div id="Div2"> <div id="Control21" data-datafield="mulitUser" data-type="SheetUser"></div> </div> </div> <div> <div id="Div5"> <span id="Span1" data-type="SheetLabel" data-datafield="Email">邮箱</span> </div> <div id="Div6"> <input id="Text3" type="text" data-datafield="Email" data-type="SheetTextBox"> </div> <div id="Div7"> <span id="Span2" data-type="SheetLabel" data-datafield="Dept">所属组织</span> </div> <div id="Div8"> <input id="Text4" type="text" data-datafield="Dept" data-type="SheetTextBox"> </div> </div> <div> <div id="title13"> <span id="Label22" data-type="SheetLabel" data-datafield="mvcNum">子表小计</span> </div> <div id="control13"> <input id="Control22" type="text" data-datafield="mvcNum" data-type="SheetTextBox" class="" data-computationrule="SUM({mvcDetail.mvcCount})" /> </div> <div id="space14"> <span id="Span3" data-type="SheetLabel" data-datafield="InvoiceCount">有发票金额</span> </div> <div id="spaceControl14"> <input id="Text5" type="text" data-datafield="InvoiceCount" data-type="SheetTextBox" class="" data-computationrule="SUM({mvcDetail.mvcCount},if('{mvcDetail.Invoice}'=='有')return {mvcDetail.mvcCount};else return 0;)" /> </div> </div> <div> <div id="title17"> <span id="Label24" data-type="SheetLabel" data-datafield="mvcDetail">子表</span> </div> <div id="Div3"> <table id="gridDemo" data-datafield="mvcDetail" data-onadded="gridAddRow(arguments[0],arguments[1]);" data-oneditorsaving="gridSaving(arguments[0],arguments[1]);" data-type="SheetGridView"> <tbody> <tr> <td id="Control24_SerialNo" rowspan="2">序号</td> <td id="Control24_Header3" data-datafield="mvcDetail.code"> <label id="Control24_Label3" data-datafield="mvcDetail.code" data-type="SheetLabel">编码</label> </td> <td id="Td2" data-datafield="mvcDetail.Invoice"> <label id="Label3" data-datafield="mvcDetail.Invoice" data-type="SheetLabel">发票</label> </td> <td id="Control24_Header4" data-datafield="mvcDetail.mvcNum" rowspan="2"> <label id="Control24_Label4" data-datafield="mvcDetail.mvcNum" data-type="SheetLabel">数量</label> </td> <td id="Control24_Header5" data-datafield="mvcDetail.mvcPrice" rowspan="2"> <label id="Control24_Label5" data-datafield="mvcDetail.mvcPrice" data-type="SheetLabel">单价</label> </td> <td id="Control24_Header6" data-datafield="mvcDetail.mvcCount" rowspan="2"> <label id="Control24_Label6" data-datafield="mvcDetail.mvcCount" data-type="SheetLabel">小计</label> </td> <td rowspan="2">删除</td> </tr> <tr> <td id="Td3" data-datafield="mvcDetail.Spec"> <label id="Label2" data-datafield="mvcDetail.Spec" data-type="SheetLabel">规格</label> </td>
<td id="Td1" data-datafield="mvcDetail.DetailName"> <label id="Label1" data-datafield="mvcDetail.DetailName" data-type="SheetLabel">名称</label> </td> </tr> <tr> <td id="Control24_Option" rowspan="2"></td> <td data-datafield="mvcDetail.code"> <input id="Control24_ctl3" type="text" data-datafield="mvcDetail.code" data-type="SheetTextBox"> </td>
<td id="Td4" data-datafield="mvcDetail.Invoice"> <select data-datafield="mvcDetail.Invoice" data-type="SheetDropDownList" id="ctl317318" class="" data-defaultitems="有;无"></select> </td> <td data-datafield="mvcDetail.mvcNum" rowspan="2"> <input id="Control24_ctl4" type="text" data-datafield="mvcDetail.mvcNum" data-type="SheetTextBox"> </td> <td data-datafield="mvcDetail.mvcPrice" rowspan="2"> <input id="Control24_ctl5" type="text" data-datafield="mvcDetail.mvcPrice" data-type="SheetTextBox"> </td> <td data-datafield="mvcDetail.mvcCount" rowspan="2"> <input id="Control24_ctl6" type="text" data-datafield="mvcDetail.mvcCount" data-type="SheetTextBox" class="" data-computationrule="{mvcDetail.mvcNum}*{mvcDetail.mvcPrice}"> </td> <td rowspan="2"> <a> <div class="fa fa-minus"> </div> </a> <a> <div class="fa fa-arrow-down"> </div> </a> </td> </tr> <tr> <td data-datafield="mvcDetail.Spec"> <input id="Text2" type="text" data-datafield="mvcDetail.Spec" data-type="SheetTextBox"> </td> <td data-datafield="mvcDetail.DetailName"> <input id="Text1" type="text" data-datafield="mvcDetail.DetailName" data-type="SheetTextBox"> </td> </tr> <tr> <td></td> <td data-datafield="mvcDetail.code"></td> <td data-datafield="mvcDetail.DetailName"></td> <td data-datafield="mvcDetail.mvcNum"> <label id="Control24_stat4" data-datafield="mvcDetail.mvcNum" data-type="SheetCountLabel"></label> </td> <td data-datafield="mvcDetail.mvcPrice"> <label id="Control24_stat5" data-datafield="mvcDetail.mvcPrice" data-type="SheetCountLabel" class="" data-stattype="NONE"></label> </td> <td data-datafield="mvcDetail.mvcCount"> <label id="Control24_stat6" data-datafield="mvcDetail.mvcCount" data-type="SheetCountLabel"></label> </td> <td></td> </tr> </tbody> </table> </div> </div> <div> <div id="title9"> <span id="Label19" data-type="SheetLabel" data-datafield="mvcAttachment">附件</span> </div> <div id="control9"> <div id="Control19" data-datafield="mvcAttachment" data-type="SheetAttachment"></div> </div> </div> <div> <div id="title15"> <span id="Label23" data-type="SheetLabel" data-datafield="mvcHtml">Html</span> </div> <div id="control15"> <textarea id="Control23" data-datafield="mvcHtml" data-richtextbox="true" data-type="SheetRichTextBox"></textarea> </div> </div> <div> <div id="title19"> <span id="Label25" data-type="SheetLabel" data-datafield="mvcComment">审批意见</span> </div> <div id="Div4"> <div id="Control25" data-datafield="mvcComment" data-type="SheetComment"></div> </div> </div> </div> </div> </asp:Content> |
C#代码:
using System; using System.Collections; using System.Configuration; using System.Data; using System.Web; using System.Web.UI; using System.Web.UI.WebControls; using OThinker.H3.WorkSheet; using System.Collections.Generic; using OThinker.H3.DataModel; using OThinker.Data;
namespace OThinker.H3.Portal.Sheets.H3_XSX_1 { /// <summary> /// MVC自定义表单DEMO,展示 /// </summary> public partial class MvcDemo : OThinker.H3.WorkSheet.MvcPage { /// <summary> /// 页面加载事件 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> protected void Page_Load(object sender, EventArgs e) { //this.ActionContext.ActivityCode; // 当前活动节点 //this.ActionContext.BizObject; // 当前数据模型对象 //this.ActionContext.Engine; // 引擎接口 //this.ActionContext.IsOriginateMode; // 是否发起模式 //this.ActionContext.IsWorkMode; // 是否工作模式 //this.ActionContext.SchemaCode; // 当前数据模型编码 //this.ActionContext.User; // 当前用户对象 }
/// <summary> /// 自定义按钮调用的后台方法 /// </summary> /// <param name="userID"></param> /// <returns></returns> public Product TestAction(string userID) { // 该方法的返回结果会序列化为JSON格式输出到前端 string name = this.ActionContext.Engine.Organization.GetName(userID); Product p = new Product() { Name = "Name->" + name + DateTime.Now.ToString("ss"), Code = "Code->" + userID }; return p; }
/// <summary> /// 加载表单数据 /// </summary> /// <returns></returns> public override MvcViewContext LoadDataFields() { /* * 子表设置初始化值,注:子表默认会有一行空数据,并且未填数据时,不会保存 */ if (this.ActionContext.IsOriginateMode) { BizObject[] bizObjects = new BizObject[2]; BizObjectSchema childSchema = this.ActionContext.Schema.GetProperty("mvcDetail").ChildSchema; // 第一行 bizObjects[0] = new BizObject(this.ActionContext.Engine, childSchema, this.ActionContext.User.UserID); bizObjects[0]["code"] = "aa"; // 第二行 bizObjects[1] = new BizObject(this.ActionContext.Engine, childSchema, this.ActionContext.User.UserID); bizObjects[1]["code"] = "bb"; this.ActionContext.InstanceData["mvcDetail"].Value = bizObjects; } MvcViewContext sheetInfo = base.LoadDataFields(); sheetInfo.BizObject.DataItems["mvcName"].V += "Load值";
return sheetInfo; }
/// <summary> /// 保存表单数据到引擎中 /// </summary> /// <param name="Args"></param> public override void SaveDataFields(MvcPostValue MvcPost, MvcResult result) { // 以下函数可改变数据项的值 MvcPost.BizObject.DataItems.SetValue("mvcName", "Save值"); // 保存后,后台执行事件 base.SaveDataFields(MvcPost, result); }
/// <summary> /// ASP.NET按钮触发事件,不要这么直接调用 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> protected void txtButton_Click(object sender, EventArgs e) { // 如果需要执行后台方法,则需要去除缓存,注释前端的:OutputCache this.txtButton.Text = DateTime.Now.ToString("mmss"); }
/// <summary> /// 重写权限验证 /// </summary> /// <returns></returns> public override BoolMatchValue ValidateAuthorization() { return base.ValidateAuthorization(); }
/// <summary> /// 如果要征询意见/协办/传阅,那么可选的征询意见/协办/传阅的人员由这里获得 /// </summary> /// <returns></returns> public override Dictionary<string, UserOptions> GetOptionalRecipients() { Dictionary<string, UserOptions> OptionalRecipients = new Dictionary<string, UserOptions>(); OptionalRecipients.Add( OThinker.H3.WorkSheet.SelectRecipientType.Circulate.ToString(), // 传阅选人范围控制 new UserOptions() { GroupVisible = false, PlaceHolder = "传阅人", UserVisible = true, OrgUnitVisible = false, // 该属性控制可选择人员的范围 VisibleUnits = this.ActionContext.Engine.Organization.GetUnit(this.ActionContext.User.User.ParentID).Code }); OptionalRecipients.Add( OThinker.H3.WorkSheet.SelectRecipientType.Assist.ToString(), // 协办选人范围控制 new UserOptions() { GroupVisible = false, PlaceHolder = "协办人", UserVisible = true, OrgUnitVisible = false, VisibleUnits = this.ActionContext.Engine.Organization.GetUnit(this.ActionContext.User.User.ParentID).Code }); // 可继续添加转发、征询操作的选人范围控制 return OptionalRecipients; } }
/// <summary> /// 自定义输出到前端的结构 /// </summary> public class Product { public string Code { get; set; } public string Name { get; set; } } } |
1.5.3 流程图
l 流程环节等高、等宽、对齐;环节名称不可补遮蔽;避免路由线交叉。
l 当数据项仅用于业务逻辑时(不在表单上与控件进行绑定),需有效控制可见权限与移动办公权限。
l 使用中文的“是”、“否”做逻辑数据项的值;避免使用数字代替固定值(例如使用0,1,2代表某个数据项的“范围一”、“范围二”、“范围三”的取值)。
l 流程的节点编码需以节点内容进行命名,不能用默认的activity,且不要用中文。
l 能用驳回功能实现流程回退,就不要使用路由方式实现。
正确示例:
不规范示例:
1.6 功能开发
1.6.1 前端js
存放位置:
单一页面使用到的JS,在页面中<head></ head >
公共js方法
常用方法见附件2
1.6.2 后台代码
l 常用方法见附件3
l 代码规范见1.9编码规范
1.6.3 公共代码
l 项目的公共类文件存放于App_Code目录。项目至少创建两个类文件,一个数据操作类文件,用于构建数据库常用操作方法,命名为项目名称缩写+DBHelp,例如:MMSHDBHelp;一个公共方法类文件,用于构建通用方法,命名为项目名称缩写+Fuction,例如:MMSHFunction。可根据项目实际情况,按通用方法的类型、所属业务,将第二个类文件进行拆分为多个类文件,如:MMSHMOSSFunction;MMSHSAPfuntion;
l 项目的WebService类文件存放于WebServices目录。系统已提供多个默认WebService类文件,用于一般性H3 BPM数据读写及引擎调用操作。当项目需要额外增加其它WebService时,一者可以在通用WebService类文件BPMService.asmx中进行扩展。也可以仿现有类文件进行构建,命名时以项目缩写作为类文件名的前缀,例如:MMSHGCFWService.asmx。
1.6.4 业务集成
l 项目中需要实现定制化的业务模块,必须独立建相应的文件夹,以Biz_作为前缀+英文命名的业务模块名称,比如项目要实现ERP适配器,文件夹为Biz_ERPAdapter,与该业务相关的代码都放在该文夹中。
l 业务功能开发中,经常需要操作数据库,优先考虑使用Engin执行数据库操作,因为BPM是有缓存机制,若直操作数据库可能会导致缓存数据没更新,从而影响页面数据的正确性
l 业务规则、业务服务和流程设计中,根据业务模块分类建对应的文件夹。
l 业务集成查询数据库时,用数据库连接池+业务类型来定义业务服务文件夹,如H3的组织服务,命名为 H3Engine组织,业务服务则用 组织信息、人员信息等。
l 系统集成使用标准的业务集成功能,如果不能,则统一转换成Portal上的WebServcies,然后再注册和调用。
1.6.5 H3 与第三方系统集成方案
本节内容针对H3 BPM在实施过程中与业务系统做集成,将业务系统流程接入到H3提供参考。
以下分别针对每种集成方式,进行说明。
重要的事情:
充分发挥H3业务集成能力和标准接口规范,禁止每个流程集成要开发代码。
1.6.5.1 集成方式
1.6.5.2 表单在业务系统,引擎使用H3,流程结束后回写状态
此方式集成过程如下描述:
a. 业务系统调用H3接口,启动流程,H3返回流程实例ID,业务系统存储流程实例ID
b. 用户在待办中打开待办,H3转向业务系统表单,URL中带上流程ID和任务ID和单点登录信息;
c. 业务系统通过单点登录打开表单,并且根据流程ID加载数据,显示提交和驳回按钮;
d. 业务系统点提交或驳回调用H3提供接口,把任务ID传入;
e. H3接收到提交或驳回请求,驱动流程往下运行,流程任务进入下一环节待办;
Ø 此集成方式,H3和业务系统分别需要处理以下事项:
l H3提供一套标准的流程接口,该接口属于产品提供,与业务流程无关,包含:流程发起、任务提交、驳回、流程状态获取等。
l 业务系统表单需要与H3系统实现单点登录;
Ø 风险说明:
l 表单在业务系统,那么表单风格不统一,H3也无法控制自适应移动端;
1.6.5.3 表单和引擎都使用H3,流程结束后回写状态
Ø 此方式集成过程如下描述:
a. 业务系统调用H3接口,启动流程,同时一次性传入所有业务数据,包含主表、子表、附件等,H3流程启动完成后,返回流程实例ID,业务系统接收并且将自己数据状态设置为审核中状态;
b. 业务系统可以通过流程实例ID,调用H3接口获取流程当前审批状态、或者单点登录打开流程审批表单;
c. H3流程驳回到发起环节时,调用业务系统接口,将业务系统数据设置为待提交状态,同时允许用户进行业务表单数据修改,再次提交,将业务数据推送并更新到H3表单,同时驱动H3流程往下运行;
d. H3流程审核结束时,调用业务系统接口,将业务系统数据设置为审批通过状态。
Ø 此集成方式,H3和业务系统分别需要处理以下事项:
a. H3提供标准流程发起接口,支持业务系统首次发起和第二次更新提交;
b. 业务系统需要存储H3返回的流程实例ID,以供下次更新时调用H3接口;
c. 业务系统需要提供状态回写接口,以WebServices形式或者restful形式提供;
注:此集成方式中,业务系统推过来的数据,也可以保护表单URL链接,这样H3表单直接嵌套业务系统表单进行展示,此时H3仍然需要和业务系统表单实现单点登录,H3提供SheetFrame控件展示业务表单。
1.6.5.4 H3直接发起流程,结束后数据写入业务系统
不要直接调用业务系统接口回写数据,流程结束后,将业务数据写入中间库,由业务系统去抓取。
Ø 此集成方式,H3和业务系统分别需要处理以下事项:
l H3提供一个通用接口,不能每个表单进行开发,在流程结束后,将业务数据更新到中间库的业务数据表;
l 业务系统轮询从中间表获取数据;
注意:如果在H3发起流程,但是业务数据都来源于业务系统,需要改成前面2种方式。
1.6.5.5 其他方式
l 除以上三种方式外,其他方案不作为标准方案推荐给客户,否则均需要实际评估接口开发工作量和集成风险。
l 需要注意的实现
1.6.5.5.1 避免重复体力劳动
禁止每个流程开发都写接口代码。如果不可避免,需要考虑是否有其他更合适的方案。
1.6.5.5.2 避免复杂的交付方式
l 避免在流程过程中与业务系统进行数据交互;
l 避免业务系统调用H3接口后,H3仍然需要调用业务系统接口获取数据;
l 避免在H3发起流程,但是数据全部来源于业务系统,使用此方式在业务系统触发流程并传递数据。
1.6.5.5.3 H3标准接口
H3提供业务系统标准接口,只允许提供一个访问入口,禁止写成多个,可以提供WebServices或者restful协议。
1.6.5.5.4 业务系统非标准接口
请和业务系统协商,提供标准化的webservices/db/restful类型接口,如果H3业务集成无法配置,那么需要评估接口集成工作量和风险,同时向用户说明情况。
1.6.5.6 H3 与第三方系统交互方式及设计原则
l H3 提供标准流程接口,若H3 标准接口能技术实现交互需求,H3不提供单独接口开发。
l H3 与第三方系统交互遵循标准化规范设计,采用标准通用接口,原则上不允许单独开发接口处理。
l 调用第三方接口,要求第三方提供标准接口技术文档,原则上不调用非标接口。
l 交互以交互次数减免为原则,能一次性交互完成的任务,禁止拆分多次交互。
l 交互方式为:
业务系统集成协作图
① 业务系统标准集成过程如下:
l 步骤一:业务系统调用H3提供的标准接口发起流程,并且传递表单数据到H3,H3接口返回流程实例ID给业务系统;
l 步骤二:H3进行流程审批,表单根据业务系统发起时传递过来的数据进行展示数据;
l 步骤三:流程审批结束后,调用业务系统提供的接口返回数据。
② 注意:
l 如果业务系统表单数据展示有特殊要求需要业务逻辑代码实现时,需对方提供经过转换的数据格式,或者在表单开发中进行实现,参考财务类SAP流程;
l 如果业务系统传递给H3的数据包含表单链接,那么双方需要约定单点登录方式,参考软装类集成流程;
l 如果业务系统需要在业务单据中打开H3表单,那么需要存储H3发起流程接口返回的流程实例ID,并且与H3表单实现单点登录;
l 以上范围外的需求则需要双方协商其他方案,需要评估工时;
1.6.6 数据库命名规范
l 当以H3 BPM为基础,构建业务系统或子系统时。可根据用户对数据库的管理要求,独立创建数据库或集成至H3 数据库。当独立创建数据库时,需以“H3_”为数据库名的前缀。
l 系统中的表名称,以“I_”为前缀的是流程的数据表,由系统自动创建;“OT_”为前缀的是H3系统表,系统安装初始化时创建;H3 9.0版前无前缀的表为流程中子表对应的数据表,由系统在发布流程时自动创建;为项目自定义的数据表命名时规则如下:dbo.Tab_[应用中心编码(或子项目名的首字母组合)]_[模块名称缩写]_表名。例如:
l dbo.Tab_XTBG_RYH_Measure
l dbo. Tab_XTBG_RYH_Measure_Answer
l 视图、存储过程、函数、触发器、索引等的命名与自定义数据表规则相应,区别于dbo.后的类型缩写,如下:
l dbo.V_XTBG_BFBToAtt(视图)
l dbo.Proc_GC_AddInlineQuestion(存储过程)
l dbo.USF_SMS_GetSendStatus(函数)
l dbo.Trig_User_Update(触发器)注:触发器名可不注明所属应用中心及模块
l dbo.IX_UserSearch(索引)注:索引名可不注明所属应用中心及模块,但最好名称能体现索引使用的范围用途或包含的字段首字母。
1.7 定制开发
1.7.1 文件管理规范
Portal目录下包含网站相关内容文件的存储子目录。项目基于H3扩展的相关文件需按目录归类存储于指定目录,如下图:
存储要求:
l 项目所有功能页面,大部分以用户控件方式实现。 功能页面对应的aspx文件存储于Portal目录下,需根据项目模块分目录存储;
l 功能页面对应的用户控件ascx文件存储于Controls目录,需根据项目模块分目录存储。
l 项目所有表单文件,存储于Sheets目录下,需根据流程包分目录存储。
l Sheets目录中的PrintTemplate子目录,用于存储各流程所使用到的固定文件,如Excel、Word模板文件、用户手册等,需分类存储。
1.7.2 文件夹及文件命名规范
l Portal及Controls目录下,创建项目模块分类子目录时,子目录名称以模块名的首字母组合命名。
l 功能页面及对应用户控件命名时,使用所属应用中心编码(或子项目名的首字母组合)+功能的名词或名词短语+功能类型命名,如财务管理下资产管理相应功能页面:CWGL_ Asset_Edit(资产新增、编辑),CWGL_Asset_List(资产列表),CWGL_ Asset_Detail(资产明细)。
1.7.3 自定义扩展规范
l SheetPage的扩展:项目中往往需要添加一些公共特性到SheetPage中,若产品升级将影响原来的代码,所以需自定义一个BasePage继承SheetPage, BasePage的属性、方法通过Visual定义,方便表单进行重写。
l SheetMaster的扩展:为了避免升级影响原来的母版,所以需要自定义母版。若情况特殊,可以根据业务模块建立对应的母版。
l WorkSheet控件的扩展:项目中客户常要求定制化的WorkSheet控件,若在原来WorkSheet控件上进行修改,同样会被产品升级所影响。最好是拷贝一份代码,把类名改了,名称定义为”项目名称+原控件名称”, 然后再进行修改和重新编译。
l JS和CSS的扩展,与项目相关的JS脚本和CSS,最好是独立建相应的文件,避免产品升级带来的影响。
1.7.4 Portal定制化
更改Portal自带页面时,必须先对页面进行备份;
尽量不改动产品页面,使用新的页面替代产品页面,然后修改应用中心的菜单指向自定义页面。
1.7.5 Worksheet定制化
改动的程序要有注释,修改的功能要清单可追踪查询。
1.8 报表开发
在新建数据源时使用视图,保证修改时的便利性和正确性。
1.9 编码规范
1.9.1 代码规范
l 在VS中通过Ctrl+E,D编排整个前台页面(aspx)的格式,可以检查标签的完整性,提高代码的易读性。
l 在程序中使用固定数值,用常量代替。如在表单中将流动名称定义为常量:
l 多使用StringBuilder替代String。使用StringBuilder的Append实现字符串的动态追加;使用string.Format实现字符串的拼接。
l 使用object+string.Empty代替object.ToString()的使用。
l 使用 object+string.Empty =={变量}替换 object. Equals(object)方法的使用。
l 获取对象的属性或调用方法前,必须先判断对象是否为null。
l 不在代码中使用具体的路径和驱动器名,使用相对路径。
l 出现任何问题给用户一个友好和有意义的错误提示。在提示中除了说哪里出现错误之外,还应提示用户如何解决问题。永远别用象“应用程序出错”,“发现一个错误”等错误消息。而应给出象“更新数据库失败。请确保登陆id和密码正确。”的具体消息。
l 能使用接口获取数据时,就不要直接操作数据库。例如,获取文件属性或文件内容,需使用接口获取附件集合、附件对象,使用系统的ReadAttachment.aspx页面进行文件下载(避免系统采用非数据库进行文件存储)。
l 每个项目中可统一使用一种模式的翻页模式或控件,便于管理与调整。
l 每个项目的查询、新增、修改、删除功能模式要统一模式与样式。
l 每个项目中重复使用的方法需要在公共类中定义为公共方法。
l 建议方法有异常处理,并记录异常日志,方便日后系统问题跟踪。
l 在循环中不要定义新变量,可使用全局变量和局部变量代替使用。
l 每行语句至少占一行,如果语句过长(超过一屏),则该语句断为多行显示。
l 同一逻辑代码,禁止在2个地方存在,函数不超过50行。
1.9.2 注释规范
l 重要变量必须有注释;典型算法必须有注释;循环和逻辑分支地方的上行必须就近书写注释。
l 对已发布的代码变更时,注释旧代码块,并使用#region将注释的代码块折叠收起,方便阅读。并增加加变更人、变更时间及变更原因。
l 对于非常规性私用方法需要标注方法的用途;对于公共的方法(一般为公共内中的public方法),需要标注方法说明、传入参数说明、返回参数、类型说有。且在参数中,如果有枚举、范围、类型的,需要补充说明,并列出所有枚举、范围、类型的值及说明。
l 公共类必须有使用说明;公共类的属性必须有用途说明。
1.9.3 实施代码管理
l 统一采用microsoft visual studio team foundation server 进行代码版本控制。
l 开发人员必须使用自己的账号进行代码的签入签出。
l 避免代码版本混乱代码签出模式必须采用独占模式。
l 代码签入必须在本机运行没问题才能嵌入代码服务器。
l 签入代码前必须先获取服务器代码,避免某些共享签出代码出现版本冲突覆盖。
l 签入代码前必须填写注释。
1.10 扩展设计
l 业务流程图设计与程序逻辑实现,需要考虑后期推广。应首先考虑使用业务规则、业务服务或自定义可配置化的设计模式,避免因为业务的拓展而将流程或表单程序重新设计。
l 例如以下合同权限审批表,其特点是判断条件较多,而且每年更新一次。遇到这种情况,使用一个统一的业务服务去调用。即使以后审批金额发生变化,只用修改业务服务代码即可,不用重新修改流程图。
1.11 开发行为约束条件
l 为杜绝出线产品级问题,提升产品的稳定性,减少因为定制化开发引发的产品级系统稳定性问题。任何定制化开发都由产品研发中心评审确定及按照产品研发管理流程研发测试后交付并增加项目版本控制管理。
l 一键部署注意事项
ü 要注意选对文件夹路径
ü 含有子流程的后部署子流程
l 页面嵌套方式(权限控制可以由H 3提供tiket,原则上H3 不负责用户验证)
其它技术实现,需要经过H 3项目组双方负责人技术评审风险评估后执行。
1.12 附件
2 表单开发规范
2.1 概述
本文档针对H3流程表单开发的规范说明文档,H3流程表单框架规范下,可以自适应PC端和移动端展示效果。
H3表单基于BootStrap样式,采用HTML+JavaScript模式开发,通过Ajax方式往服务器端获取数据,然后通过自定义开发的一套MvcSheet控件进行表单展示。移动端采用IONIC框架,当移动端打开表单时,H3表单渲染引擎将以移动端展现模式,重新排列表单布局,同时部分复杂交互控件会改成以移动端控件进行渲染。
考虑到H3表单同时兼容PC端和移动端的特性,在二次开发过程中,需要遵循H3表单的布局规则,以及遵循H3表单控件的使用方式,对于特殊业务场景,可参考本文档给出的解决方案。
PC标准表单和移动表单示例图如下所示:
PC表单示例图
移动表单效果图
2.2 表单程序结构
2.2.1 母版页
表单必须从母版页MvcSheet.master继承,如下图所示:
2.2.2 表单基类
表单从 OThinker.H3.WorkSheet.MvcPage 继承,如下图所示:
2.2.3 自定义表单开发
当需要开发自定义表单时,可以从默认表单中拷贝代码:
从ASPX拷贝HTML文件
从代码拷贝C#文件
2.3 表单布局概述
2.3.1 标准布局
表单布局采用Bootstrap样式进行布局,标准界面如下图所示:
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="SpurcharsApp_Self.aspx.cs" Inherits="OThinker.H3.Portal.Sheets.DefaultEngine.Sheets_Test_SpurcharsApp_Self" EnableEventValidation="false" MasterPageFile="~/MvcSheet.master" %>
<%@ OutputCache Duration="999999" VaryByParam="T" VaryByCustom="browser" %> <asp:Content ID="head" ContentPlaceHolderID="headContent" runat="Server"> <script type="text/javascript">
</script> </asp:Content> <asp:Content ID="menu" ContentPlaceHolderID="cphMenu" runat="Server"> </asp:Content> <asp:Content ID="master" ContentPlaceHolderID="masterContent" runat="Server"> <div style="text-align: center;" class="DragContainer"> <label id="lblTitle" class="panel-title">采购流程</label> </div> <div class="panel-body sheetContainer"> <div class="nav-icon fa fa-chevron-right bannerTitle"> <label id="divBasicInfo" data-en_us="Basic information">基本信息</label> </div> <div class="divContent"> <div class="row"> <div id="divFullNameTitle" class="col-md-1"> <label id="lblFullNameTitle" data-type="SheetLabel" data-datafield="Originator.UserName" data-en_us="Originator" data-bindtype="OnlyVisiable">发起人</label> </div> <div id="divFullName" class="col-md-3"> <label id="lblFullName" data-type="SheetLabel" data-datafield="Originator.UserName" data-bindtype="OnlyData"> </label> </div> <div id="divOriginateDateTitle" class="col-md-1"> <label id="lblOriginateDateTitle" data-type="SheetLabel" data-datafield="OriginateDate" data-en_us="Originate Date" data-bindtype="OnlyVisiable">发起时间</label> </div> <div id="divOriginateDate" class="col-md-3"> <label id="lblOriginateDate" data-type="SheetLabel" data-datafield="OriginateDate" data-bindtype="OnlyData"> </label> </div> <div id="divSequenceNoTitle" class="col-md-1"> <label id="lblSequenceNoTitle" data-type="SheetLabel" data-datafield="SequenceNo" data-en_us="SequenceNo" data-bindtype="OnlyVisiable">流水号</label> </div> <div id="divSequenceNo" class="col-md-3"> <label id="lblSequenceNo" data-type="SheetLabel" data-datafield="SequenceNo" data-bindtype="OnlyData"> </label> </div> </div> <div class="row"> <div id="divOriginateOUNameTitle" class="col-md-1"> <label id="lblOriginateOUNameTitle" data-type="SheetLabel" data-datafield="Originator.OUName" data-en_us="Originate OUName" data-bindtype="OnlyVisiable">所属组织</label> </div> <div id="divOriginateOUName" class="col-md-3"> <label id="lblOriginateOUName" data-type="SheetLabel" data-datafield="Originator.OUName" data-bindtype="OnlyData"> </label> </div> <div id="divOriginateAreaTitle" class="col-md-1"> <label id="lblOriginateArea" data-type="SheetLabel" data-datafield="InstanceFlag" data-en_us="Area" data-bindtype="OnlyVisiable">区域/中心</label> </div> <div id="divOriginateArea" style="margin-top: 0; main-bottom: 0; line-height: 23px; padding-left: 5px !important;" class="col-md-3"> <div data-datafield="InstanceFlag" data-type="SheetUser" id="" class="suInstanceFlag" data-orgunitvisible="true" data-uservisible="false" data-rootunit="c34e07c312404e82ad021214faf90712" data-recursive="false" data-defaultvalue="area"> </div> </div> </div> </div> <div class="nav-icon fa fa-chevron-right bannerTitle"> <label id="divSheetInfo" data-en_us="Sheet information">表单信息</label> </div> <div id="ctl00_BodyContent_divSheet" class="divContent"> <div class="row"> <div id="title1" class="col-md-2"> <span id="Label12" data-type="SheetLabel" data-datafield="title">标题</span> </div> <div id="control1" class="col-md-10" colspan="3"> <input id="Control12" type="text" data-datafield="title" data-type="SheetTextBox" class=""> </div> </div> <div class="row"> <div id="title3" class="col-md-2"> <span id="Label15" data-type="SheetLabel" data-datafield="Pur_reson" class="">采购原因</span> </div> <div id="control3" class="col-md-10" colspan="3"> <input id="Control15" type="text" data-datafield="Pur_reson" data-type="SheetTextBox" class=""> </div> </div> <div class="row tableContent"> <div id="title5" class="col-md-2"> <span id="Label16" data-type="SheetLabel" data-datafield="remark" class="">采购说明</span> </div> <div id="control5" class="col-md-10"> <textarea id="Control16" data-datafield="remark" data-type="SheetRichTextBox" class=""></textarea> </div> </div> <div class="row"> <div id="div981840" class="col-md-2"><span id="Label13" data-type="SheetLabel" data-datafield="SPR1" class="">审批人1</span></div> <div id="div518625" class="col-md-4"> <div id="Control13" data-datafield="SPR1" data-type="SheetUser" class=""> </div> </div> <div id="div911605" class="col-md-2"><span id="Label14" data-type="SheetLabel" data-datafield="SPR2" class="">审批人2</span></div> <div id="div120759" class="col-md-4"> <div id="Control14" data-datafield="SPR2" data-type="SheetUser" class=""> </div> </div> </div> <div class="row"> <div id="title7" class="col-md-2"> <span id="Label17" data-type="SheetLabel" data-datafield="fj">附件</span> </div> <div id="control7" class="col-md-10"> <div id="Control17" data-datafield="fj" data-type="SheetAttachment"> </div> </div> </div> <div class="row tableContent"> <div id="title9" class="col-md-2"> <span id="Label18" data-type="SheetLabel" data-datafield="Approvement">审批意见</span> </div> <div id="control9" class="col-md-10"> <div id="Control18" data-datafield="Approvement" data-type="SheetComment"> </div> </div> </div> </div> </div> </asp:Content> |
2.3.2 表单容器
表单所有HTML内容均需要包含在指定的DIV容器中
<div class="panel-body sheetContainer"> 这里是表单所有HTML </div> |
2.3.3 显示段落
表单默认包含2个分区段落,分别是基本信息和表单信息,HTML格式如下:
<div class="nav-icon fa fa-chevron-right bannerTitle"> <label id="divBasicInfo" data-en_us="Basic information">基本信息</label> </div> <div class="divContent"> 段落内部主体内容 </div> |
2.3.4 显示行
H3表单开发中的行元素采用Bootstrap的样式,其中.row表示行,每一行又可以划分为12个单元格,分别对应col-md-*的样式表示,其中*表示占用单元格数量。
一个标准的一行2个控件,HTML格式表示如下:
<div class="row"> <div id="divFullNameTitle" class="col-md-2"> 标题1 </div> <div id="divFullName" class="col-md-4"> 输入控件1 </div> <div id="divOriginateDate" class="col-md-2"> 标题2 </div> <div id="divSequenceNoTitle" class="col-md-4"> 输入控件2 </div> </div> |
当需要整行显示某个编辑控件时,可以采用一个row包含col-md-2和col-md-10的方式,如下所示:
<div class="row"> <div id="divFullNameTitle" class="col-md-2"> 标题1 </div> <div id="divFullName" class="col-md-10"> 输入控件1 </div> </div> |
注意:
ü 在表单内容区域中请不要使用Table来替代row显示表格;
ü 不要在一个row元素中包含多余12个单元格的元素;
ü 所有HTML元素内容,请全部包含col-md-*的容器中;
2.3.5 控件容器
所有控件,均需要在.row>.col-md-*的容器中,如下所示
<div class="row"> <div id="divFullNameTitle" class="col-md-2"> //这里防止HTML控件内容 </div> <div id="divFullName" class="col-md-10"> //这里防止HTML控件内容 </div> </div> |
请不要将自定义开发的内容防止在容器之外,例如以下方式则不可取
2.3.6 仅移动端可见
可以通过指定样式 pcHidden进行实现。
<div class="row pcHidden"> 内容区域 </div> |
2.3.7 仅PC端可见
可以通过指定样式 mobileHidden进行实现。
<div class="row mobileHidden"> 内容区域 </div> |
2.3.8 脚本和样式存放区域
所有javascript脚本和css,均放置在表单的headContent区域中,如下图所示:
2.4 控件
2.4.1 概述
H3表单提供一套完善的表单JS控件,控制数据的加载、存储、显示、隐藏、必填项控制等。表单内容的展示,请尽量使用H3提供的标准控件进行展示,否则有可能影响PC或者移动端展示效果。
例如:展示一段文本内容,建议采用
<span id="Label12" data-type="SheetLabel" data-datafield="title">标题</span> |
不推荐以下方式
<span id="Label12">标题</span> |
以上2者区别在于span中增加了data-type和data-datafield元素,会被H3表单引擎进行渲染,当title不可见时,会自动被隐藏。当在移动端时,也会同样使用SheetLabel控件进行渲染效果。
注:对于非H3表单提供的控件,H3则不负责渲染逻辑和移动显示效果。
2.4.2 控件列表
H3表单提供了一套完整的表单控件,详细列表参考如下:
控件名称 | 描述 | 示例 |
SheetAttachment | 附件控件 | |
SheetFrame | Iframe窗体控件 | |
SheetCheckbox | 选择框控件 | |
SheetCheckboxList | 多选框控件 | |
SheetComment | 审批意见控件 | |
SheetDropDownList | 下拉框控件 | |
SheetGridView | 子表控件 | |
SheetHyperLink | 链接控件 | |
SheetInstancePrioritySelector | 紧急程度控件 | |
SheetLabel | 标签控件 | |
SheetOffice | 在线Office文档编辑控件 | |
SheetRadioButtonList | 单选框控件 | |
SheetRichTextBox | 富文本框控件 | |
SheetTextBox | 文本框控件 | |
SheetTime | 日期控件 | |
SheetUser | 选人控件 | |
SheetTimeSpan | 时间段控件 |
部分控件使用说明如下:
<span id="Label12" data-type="SheetLabel" data-datafield="title">标题</span> <input id="Control12" type="text" data-datafield="title" data-type="SheetTextBox" class=""> <div id="Control18" data-datafield="Approvement" data-type="SheetComment"/> <div id="Control18" data-datafield="SheetUser" data-type="ApproveUser"/> |
详细的控件请使用默认表单,查看表单ASPX,可以了解控件的HTML内容,更多的帮助说明,请参考H3在线帮助文档:http://wiki.h3yun.com/
2.4.3 控件接口说明
H3表单控件提供标准的接口进行操作,以SheetTextBox控件为例,可以通过接口对象获取到控件的实例,并且调用控件的方法,如下图所示:
var control = $("#ID").SheetUIManager(); // 获取控件的值 var txtValue = control.GetValue(); // 设置控件的值 control.SetValue("Test"); |
2.4.4 获取控件的值
请调用标准接口进行操作,如下所示:
$("#ID").SheetUIManager().GetValue(); |
2.4.5 给控件赋值
请调用标准接口进行操作,如下所示:
$("#ID").SheetUIManager().SetValue("Test"); |
2.4.6 显示控件
请调用标准接口进行操作,如下所示:
$("#ID").SheetUIManager().SetVisiable(true); |
2.4.7 隐藏控件
请调用标准接口进行操作,如下所示:
$("#ID").SheetUIManager().SetVisiable(false); |
2.4.8 隐藏行
请调用标准接口进行操作,如下所示:
$("#Control14").parents(".row").hide(); |
2.4.9 前端调用后台方法获取数据
表单提供了$.MvcSheet.Action方法访问表单C#代码,如下图所示:
前端调用方法:
2.5 常见应用场景及解决方案
2.5.1 不规范表单
以上Table没有按照规范,存放在容器col-md-*的容器中,将导致移动端展示错误。
2.6 标准规范示例程序
表单代码:
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="MvcDemo.aspx.cs" Inherits="OThinker.H3.Portal.Sheets.H3_XSX_1.MvcDemo" EnableEventValidation="false" MasterPageFile="~/MvcSheet.master" %>
<%@ OutputCache Duration="999999" VaryByParam="T" VaryByCustom="browser" %> <asp:Content ID="head" ContentPlaceHolderID="headContent" runat="Server"> <script type="text/javascript"> /* 全局可访问的对象:$.MvcSheetUI.SheetInfo,该属性是后台传递到前端的所有信息,但是需要在 $.MvcSheet.Loaded 方法中使用 例如: $.MvcSheetUI.SheetInfo.ActivityCode 当前活动编码 $.MvcSheetUI.SheetInfo. 获取MVC表单控件的实例:$("#控件ID").SheetUIManager() */ // 增加自定义工具栏按钮方法,触发后台事件 $.MvcSheet.AddAction({ Action: "TestAction", // 执行后台方法名称 Icon: "fa-print", // 按钮图标 Text: "后台事件", // 按钮名称 Datas: ["{selectUser}"], // 参数,多个参数 "{Param1}","Param2"... //OnAction: function () { /* 自定义按钮执行事件,如果为 null 则调用$.MvcSheet.Action 执行后台方法 如果不为 null,那么会执行这里的方法,需要自己Post到后台或写前端逻辑 */ //}, OnActionDone: function (e) { // 后台方法调用完成后触发 // 以下是将后台的值输出到前端控件中 if (e) { $.MvcSheetUI.SetControlValue("code", e.Code); $.MvcSheetUI.SetControlValue("mvcName", e.Name); } }, PostSheetInfo: true // 是否提交表单数据,如果 false,那么不返回表单的数据 });
// 增加自定义工具栏按钮方法,触发前台事件 $.MvcSheet.AddAction({ Action: "TestScript", // 按钮名称 Icon: "fa-print", // 按钮图标 Text: "前端事件", // 按钮名称 Datas: ["{selectUser}"], // 参数,多个参数 "{Param1}","Param2"... OnAction: function () { alert("这里执行前端事件"); }, OnActionDone: function (e) { // 事件执行完成 }, PostSheetInfo: true // 是否提交表单数据,如果 false,那么不返回表单的数据 });
// 所有工具栏按钮完成事件 $.MvcSheet.ActionDone = function (data) { // this.Action; // 获取当前按钮名称 }
// 保存前事件 $.MvcSheet.SaveAction.OnActionPreDo = function () { // this.Action // 获取当前按钮名称 alert(this.Action); }
// 保存后事件,保存是异步的,可能比回调函数快 $.MvcSheet.SaveAction.OnActionDone = function () { //this 当前SaveAction var mvcNum = $.MvcSheetUI.GetSheetDataItem("mvcNum"); //读取后台数据项对象,L:数据项类型,V:数据项的值,O:数据项的权限,N:数据项名称,RowNum:主表中为0,子表中表示行号 if (mvcNum && (mvcNum.L == $.MvcSheetUI.LogicType.Int || mvcNum.L == $.MvcSheetUI.LogicType.Double || mvcNum.L == $.MvcSheetUI.LogicType.Long)) { if (mvcNum.V > 100) {
} } }
// 表单验证接口 $.MvcSheet.Validate = function () { // this.Action 表示当前操作的按钮名称 var nameText = $.MvcSheetUI.GetControlValue("mvcName"); // 根据数据项编码获取页面控件的值
// 填写申请单环节,设置 mvcName 必填 if ($.MvcSheetUI.SheetInfo.ActivityCode == "Apply") { if (this.Action == "Submit") { if (!nameText) { $.MvcSheetUI.GetElement("mvcName").focus(); alert("请填写名称."); return false; } } } return true; }
// 页面初始化事件,该事件在获取MVC表单数据,并且在控件初始化之前执行 $.MvcSheet.PreInit = function () { // 将提交按钮文字改为通过 if ($.MvcSheetUI.SheetInfo.ActivityCode == "Approve" || $.MvcSheetUI.SheetInfo.IsOriginateMode) { $.MvcSheet.SubmitAction.Text = "通过"; } };
// 页面加载完成事件 $.MvcSheet.Loaded = function (sheetInfo) { // 获取选人控件 // arguments[0] 该参数包含MVC表单后台传递到前端的所有信息 /* MVC控件实例,通过 SheetUIManager() 方法获取,例如获取 txtCode 所对应的MvcSheetUI实例 */ var txtCode = $("#txtCode").SheetUIManager(); // 可以调用所有 SheetTextBox 提供的接口方法,例如 txtCode.GetValue();
// 自定义按钮调用后台方法示例 $("#btnClick").click(function () { // 执行后台事件 $.MvcSheet.Action( { Action: "TestAction", // 后台方法名称 Datas: ["输入参数"], // 输入参数,格式 ["{数据项名称}","String值","控件ID"],当包含数据项名称时 LoadControlValue必须为true LoadControlValue: true, // 是否获取表单数据 PostSheetInfo: false, // 是否获取已经改变的表单数据 OnActionDone: function (e) { // 执行完成后回调事件 $.MvcSheetUI.SetControlValue("code", e.Code); $.MvcSheetUI.SetControlValue("mvcName", e.Name); } } ) }); }
// 子表行保存事件,row当前保存所在的行, val保存的结果数据 var gridSaving = function (row, val) { if (val && val.code) { val.code += row.attr("data-row"); } return val; };
// 子表行添加事件,row当前添加的行, val加载的数据对象 function gridAddRow(row, val) { if (val) { var code = val.DataItems["mvcDetail.code"].V; row.find("input[data-datafield='mvcDetail.Spec']").val(code + "规格"); } } </script> </asp:Content> <asp:Content ID="menu" ContentPlaceHolderID="cphMenu" runat="Server"> </asp:Content> <asp:Content ID="master" ContentPlaceHolderID="masterContent" runat="Server"> <div style="text-align: center;"> <label id="lblTitle">MvcDemo</label> </div> <div> <div class="nav-icon fa fa-chevron-right bannerTitle"> <label id="divBasicInfo">基本信息</label> </div> <div> <div> <div id="divFullNameTitle"> <label id="lblFullNameTitle" data-type="SheetLabel" data-datafield="Originator.UserName" data-bindtype="OnlyVisiable">发起人</label> </div> <div id="divFullName"> <label id="lblFullName" data-type="SheetLabel" data-datafield="Originator.UserName" data-bindtype="OnlyData"></label> </div> <div id="divOriginateDateTitle"> <label id="lblOriginateDateTitle" data-type="SheetLabel" data-datafield="OriginateDate" data-bindtype="OnlyVisiable">发起时间</label> </div> <div id="divOriginateDate"> <label id="lblOriginateDate" data-type="SheetLabel" data-datafield="OriginateDate" data-bindtype="OnlyData"></label> </div> </div> <div> <div id="divOriginateOUNameTitle"> <label id="lblOriginateOUNameTitle" data-type="SheetLabel" data-datafield="Originator.OUName" data-bindtype="OnlyVisiable">所属组织</label> </div> <div id="divOriginateOUName"> <label id="lblOriginateOUName" data-type="SheetLabel" data-datafield="Originator.OUName" data-bindtype="OnlyData"></label> </div> <div id="divSequenceNoTitle"> <label id="lblSequenceNoTitle" data-type="SheetLabel" data-datafield="SequenceNo" data-bindtype="OnlyVisiable">流水号</label> </div> <div id="divSequenceNo"> <label id="lblSequenceNo" data-type="SheetLabel" data-datafield="SequenceNo" data-bindtype="OnlyData"></label> </div> </div> </div> <div class="nav-icon fa fa-chevron-right bannerTitle"> <label id="divSheetInfo">表单信息</label> </div> <div id="ctl00_BodyContent_divSheet"> <div> <div id="title1"> <span id="Label11" data-type="SheetLabel" data-datafield="code">编码</span> </div> <div id="control1"> <input id="txtCode" type="text" data-datafield="code" data-type="SheetTextBox"> <asp:Button ID="txtButton" runat="server" Text="测试按钮" OnClick="txtButton_Click" /> <input type="button" id="btnClick" value="前端调用后台方法" /> </div> <div id="title2"> <span id="Label12" data-type="SheetLabel" data-datafield="mvcName">名称</span> </div> <div id="control2"> <input id="txtName" type="text" data-datafield="mvcName" data-type="SheetTextBox"> </div> </div> <div> <div id="title3"> <span id="Label13" data-type="SheetLabel" data-datafield="radio">单选</span> </div> <div id="control3"> <div data-datafield="radio" data-type="SheetRadioButtonList" id="ctl414631" class="" data-defaultitems="A;B;C;其他"></div> </div> <div id="title4"> <span id="Label14" data-type="SheetLabel" data-datafield="mvcOther" data-displayrule="{radio}=='其他'">其他</span> </div> <div id="control4"> <input id="Control14" type="text" data-datafield="mvcOther" data-type="SheetTextBox" class="" data-displayrule="{radio}=='其他'" data-vaildationrule="{radio}=='其他'"> </div> </div> <div> <div id="title5"> <span id="Label15" data-type="SheetLabel" data-datafield="multiCheck">多选</span> </div> <div id="control5"> <div data-datafield="multiCheck" data-type="SheetCheckboxList" id="ctl732795" class="" data-defaultitems="A;B;C"></div> </div> <div id="title6"> <span id="Label16" data-type="SheetLabel" data-datafield="mvcTime">日期</span> </div> <div id="control6"> <input id="Control16" type="text" data-datafield="mvcTime" data-type="SheetTime"> </div> </div> <div> <div id="title7"> <span id="Label17" data-type="SheetLabel" data-datafield="mvcMobile">电话</span> </div> <div id="control7"> <input id="Control17" type="text" data-datafield="mvcMobile" data-type="SheetTextBox" class="" data-regularexpression="/^1[3|4|5|8][0-9]{9}$/" data-regularinvalidtext="请输入一个有效的手机号码."> </div> <div id="title8"> <span id="Label18" data-type="SheetLabel" data-datafield="dropList">下拉框</span> </div> <div id="control8"> <select data-datafield="dropList" data-type="SheetDropDownList" id="ctl352297" class="" data-defaultitems="A;B;C;D"> </select> </div> </div> <div> <div id="title11"> <span id="Label20" data-type="SheetLabel" data-datafield="selectUser">选人</span> </div> <div id="Div1"> <div id="Control20" data-datafield="selectUser" data-type="SheetUser" data-defaultvalue="{Originator}" data-mappingcontrols="Email:Email,Dept:FullName"> </div> </div> <div id="title12"> <span id="Label21" data-type="SheetLabel" data-datafield="mulitUser">多人</span> </div> <div id="Div2"> <div id="Control21" data-datafield="mulitUser" data-type="SheetUser"></div> </div> </div> <div> <div id="Div5"> <span id="Span1" data-type="SheetLabel" data-datafield="Email">邮箱</span> </div> <div id="Div6"> <input id="Text3" type="text" data-datafield="Email" data-type="SheetTextBox"> </div> <div id="Div7"> <span id="Span2" data-type="SheetLabel" data-datafield="Dept">所属组织</span> </div> <div id="Div8"> <input id="Text4" type="text" data-datafield="Dept" data-type="SheetTextBox"> </div> </div> <div> <div id="title13"> <span id="Label22" data-type="SheetLabel" data-datafield="mvcNum">子表小计</span> </div> <div id="control13"> <input id="Control22" type="text" data-datafield="mvcNum" data-type="SheetTextBox" class="" data-computationrule="SUM({mvcDetail.mvcCount})" /> </div> <div id="space14"> <span id="Span3" data-type="SheetLabel" data-datafield="InvoiceCount">有发票金额</span> </div> <div id="spaceControl14"> <input id="Text5" type="text" data-datafield="InvoiceCount" data-type="SheetTextBox" class="" data-computationrule="SUM({mvcDetail.mvcCount},if('{mvcDetail.Invoice}'=='有')return {mvcDetail.mvcCount};else return 0;)" /> </div> </div> <div> <div id="title17"> <span id="Label24" data-type="SheetLabel" data-datafield="mvcDetail">子表</span> </div> <div id="Div3"> <table id="gridDemo" data-datafield="mvcDetail" data-onadded="gridAddRow(arguments[0],arguments[1]);" data-oneditorsaving="gridSaving(arguments[0],arguments[1]);" data-type="SheetGridView"> <tbody> <tr> <td id="Control24_SerialNo" rowspan="2">序号</td> <td id="Control24_Header3" data-datafield="mvcDetail.code"> <label id="Control24_Label3" data-datafield="mvcDetail.code" data-type="SheetLabel">编码</label> </td> <td id="Td2" data-datafield="mvcDetail.Invoice"> <label id="Label3" data-datafield="mvcDetail.Invoice" data-type="SheetLabel">发票</label> </td> <td id="Control24_Header4" data-datafield="mvcDetail.mvcNum" rowspan="2"> <label id="Control24_Label4" data-datafield="mvcDetail.mvcNum" data-type="SheetLabel">数量</label> </td> <td id="Control24_Header5" data-datafield="mvcDetail.mvcPrice" rowspan="2"> <label id="Control24_Label5" data-datafield="mvcDetail.mvcPrice" data-type="SheetLabel">单价</label> </td> <td id="Control24_Header6" data-datafield="mvcDetail.mvcCount" rowspan="2"> <label id="Control24_Label6" data-datafield="mvcDetail.mvcCount" data-type="SheetLabel">小计</label> </td> <td rowspan="2">删除</td> </tr> <tr> <td id="Td3" data-datafield="mvcDetail.Spec"> <label id="Label2" data-datafield="mvcDetail.Spec" data-type="SheetLabel">规格</label> </td>
<td id="Td1" data-datafield="mvcDetail.DetailName"> <label id="Label1" data-datafield="mvcDetail.DetailName" data-type="SheetLabel">名称</label> </td> </tr> <tr> <td id="Control24_Option" rowspan="2"></td> <td data-datafield="mvcDetail.code"> <input id="Control24_ctl3" type="text" data-datafield="mvcDetail.code" data-type="SheetTextBox"> </td>
<td id="Td4" data-datafield="mvcDetail.Invoice"> <select data-datafield="mvcDetail.Invoice" data-type="SheetDropDownList" id="ctl317318" class="" data-defaultitems="有;无"></select> </td> <td data-datafield="mvcDetail.mvcNum" rowspan="2"> <input id="Control24_ctl4" type="text" data-datafield="mvcDetail.mvcNum" data-type="SheetTextBox"> </td> <td data-datafield="mvcDetail.mvcPrice" rowspan="2"> <input id="Control24_ctl5" type="text" data-datafield="mvcDetail.mvcPrice" data-type="SheetTextBox"> </td> <td data-datafield="mvcDetail.mvcCount" rowspan="2"> <input id="Control24_ctl6" type="text" data-datafield="mvcDetail.mvcCount" data-type="SheetTextBox" class="" data-computationrule="{mvcDetail.mvcNum}*{mvcDetail.mvcPrice}"> </td> <td rowspan="2"> <a> <div class="fa fa-minus"> </div> </a> <a> <div class="fa fa-arrow-down"> </div> </a> </td> </tr> <tr> <td data-datafield="mvcDetail.Spec"> <input id="Text2" type="text" data-datafield="mvcDetail.Spec" data-type="SheetTextBox"> </td> <td data-datafield="mvcDetail.DetailName"> <input id="Text1" type="text" data-datafield="mvcDetail.DetailName" data-type="SheetTextBox"> </td> </tr> <tr> <td></td> <td data-datafield="mvcDetail.code"></td> <td data-datafield="mvcDetail.DetailName"></td> <td data-datafield="mvcDetail.mvcNum"> <label id="Control24_stat4" data-datafield="mvcDetail.mvcNum" data-type="SheetCountLabel"></label> </td> <td data-datafield="mvcDetail.mvcPrice"> <label id="Control24_stat5" data-datafield="mvcDetail.mvcPrice" data-type="SheetCountLabel" class="" data-stattype="NONE"></label> </td> <td data-datafield="mvcDetail.mvcCount"> <label id="Control24_stat6" data-datafield="mvcDetail.mvcCount" data-type="SheetCountLabel"></label> </td> <td></td> </tr> </tbody> </table> </div> </div> <div> <div id="title9"> <span id="Label19" data-type="SheetLabel" data-datafield="mvcAttachment">附件</span> </div> <div id="control9"> <div id="Control19" data-datafield="mvcAttachment" data-type="SheetAttachment"></div> </div> </div> <div> <div id="title15"> <span id="Label23" data-type="SheetLabel" data-datafield="mvcHtml">Html</span> </div> <div id="control15"> <textarea id="Control23" data-datafield="mvcHtml" data-richtextbox="true" data-type="SheetRichTextBox"></textarea> </div> </div> <div> <div id="title19"> <span id="Label25" data-type="SheetLabel" data-datafield="mvcComment">审批意见</span> </div> <div id="Div4"> <div id="Control25" data-datafield="mvcComment" data-type="SheetComment"></div> </div> </div> </div> </div> </asp:Content> |
C#代码:
using System; using System.Collections; using System.Configuration; using System.Data; using System.Web; using System.Web.UI; using System.Web.UI.WebControls; using OThinker.H3.WorkSheet; using System.Collections.Generic; using OThinker.H3.DataModel; using OThinker.Data;
namespace OThinker.H3.Portal.Sheets.H3_XSX_1 { /// <summary> /// MVC自定义表单DEMO,展示 /// </summary> public partial class MvcDemo : OThinker.H3.WorkSheet.MvcPage { /// <summary> /// 页面加载事件 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> protected void Page_Load(object sender, EventArgs e) { //this.ActionContext.ActivityCode; // 当前活动节点 //this.ActionContext.BizObject; // 当前数据模型对象 //this.ActionContext.Engine; // 引擎接口 //this.ActionContext.IsOriginateMode; // 是否发起模式 //this.ActionContext.IsWorkMode; // 是否工作模式 //this.ActionContext.SchemaCode; // 当前数据模型编码 //this.ActionContext.User; // 当前用户对象 }
/// <summary> /// 自定义按钮调用的后台方法 /// </summary> /// <param name="userID"></param> /// <returns></returns> public Product TestAction(string userID) { // 该方法的返回结果会序列化为JSON格式输出到前端 string name = this.ActionContext.Engine.Organization.GetName(userID); Product p = new Product() { Name = "Name->" + name + DateTime.Now.ToString("ss"), Code = "Code->" + userID }; return p; }
/// <summary> /// 加载表单数据 /// </summary> /// <returns></returns> public override MvcViewContext LoadDataFields() { /* * 子表设置初始化值,注:子表默认会有一行空数据,并且未填数据时,不会保存 */ if (this.ActionContext.IsOriginateMode) { BizObject[] bizObjects = new BizObject[2]; BizObjectSchema childSchema = this.ActionContext.Schema.GetProperty("mvcDetail").ChildSchema; // 第一行 bizObjects[0] = new BizObject(this.ActionContext.Engine, childSchema, this.ActionContext.User.UserID); bizObjects[0]["code"] = "aa"; // 第二行 bizObjects[1] = new BizObject(this.ActionContext.Engine, childSchema, this.ActionContext.User.UserID); bizObjects[1]["code"] = "bb"; this.ActionContext.InstanceData["mvcDetail"].Value = bizObjects; } MvcViewContext sheetInfo = base.LoadDataFields(); sheetInfo.BizObject.DataItems["mvcName"].V += "Load值";
return sheetInfo; }
/// <summary> /// 保存表单数据到引擎中 /// </summary> /// <param name="Args"></param> public override void SaveDataFields(MvcPostValue MvcPost, MvcResult result) { // 以下函数可改变数据项的值 MvcPost.BizObject.DataItems.SetValue("mvcName", "Save值"); // 保存后,后台执行事件 base.SaveDataFields(MvcPost, result); }
/// <summary> /// ASP.NET按钮触发事件,不建议这么直接调用 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> protected void txtButton_Click(object sender, EventArgs e) { // 如果需要执行后台方法,则需要去除缓存,注释前端的:OutputCache this.txtButton.Text = DateTime.Now.ToString("mmss"); }
/// <summary> /// 重写权限验证 /// </summary> /// <returns></returns> public override BoolMatchValue ValidateAuthorization() { return base.ValidateAuthorization(); }
/// <summary> /// 如果要征询意见/协办/传阅,那么可选的征询意见/协办/传阅的人员由这里获得 /// </summary> /// <returns></returns> public override Dictionary<string, UserOptions> GetOptionalRecipients() { Dictionary<string, UserOptions> OptionalRecipients = new Dictionary<string, UserOptions>(); OptionalRecipients.Add( OThinker.H3.WorkSheet.SelectRecipientType.Circulate.ToString(), // 传阅选人范围控制 new UserOptions() { GroupVisible = false, PlaceHolder = "传阅人", UserVisible = true, OrgUnitVisible = false, // 该属性控制可选择人员的范围 VisibleUnits = this.ActionContext.Engine.Organization.GetUnit(this.ActionContext.User.User.ParentID).Code }); OptionalRecipients.Add( OThinker.H3.WorkSheet.SelectRecipientType.Assist.ToString(), // 协办选人范围控制 new UserOptions() { GroupVisible = false, PlaceHolder = "协办人", UserVisible = true, OrgUnitVisible = false, VisibleUnits = this.ActionContext.Engine.Organization.GetUnit(this.ActionContext.User.User.ParentID).Code }); // 可继续添加转发、征询操作的选人范围控制 return OptionalRecipients; } }
/// <summary> /// 自定义输出到前端的结构 /// </summary> public class Product { public string Code { get; set; } public string Name { get; set; } } } |
3 二次开发指导
3.1 目的
本文档基于H3BPM 10.5 SP2版本基础上进行(其他版本的情况可以进行参考)。
基于产品在 10.5 SP1 后对产品代码进行了封装,不再直接提供源码到项目上(10.5 SP2后Portal端的源码也打包到仓库中了),这样同以往项目上进行二次开发有比较大的出入,因此编写此文档给二次开发和定制化开发做个参考;
同时可以尽量避免在项目上进行二次开发中的定制化代码与产品的代码混合在一起,导致产品的BUG需要修复或者需要升级时不能做到无缝升级的问题。
3.2 内容
本文包括以下两大块:
Ø Portal端的后端代码开发;
Ø 引擎端事件开发;
(开发过程涉及到Spring、SpringMVC和Maven等技术,请自行了解)
3.3 Portal端的后端代码开发
3.3.1 JDK版本
请使用JDK1.8及以上的,建议1.8;
3.3.2 Maven版本
使用3.3.9及以上版本,相关配置请自行处理;
H3BPM的JAR包仓库地址:http://120.78.180.15:8081/nexus/content/groups/public/
3.3.3 开发环境部署
当拿到原始的开发项目(portal-web)后请自行解压然后导入到IDE中(以下均以Ideal 为例);如下图
配置Maven仓库地址,可以在Mavan的配置文件中配置仓库地址也可以在pom.xml里进行单独配置,如下图
<repositories>
<repository>
<id>alimaven</id>
<name>local maven</name>
<url>http://120.78.180.15:8081/nexus/content/groups/public/</url>
</repository>
</repositories>
3.3.4 更换或升级产品
当需要更换或者升级产品时,只需要修改pom.xml文件更换引入的JAR包的版本号即可,如下图
<parent>
<artifactId>h3bpm-portal-parent</artifactId>
<groupId>com.h3bpm</groupId>
<version>1.0.1.sp2-RELEASE</version>
</parent>
当产品有升级时只要修改此处版本号,然后重新编译打包就即可。
3.3.5 新增类或者属性
当产品中有些类不满足当前项目上需求时需要增加属性时,可以采用继承或者重写都是可以的。
3.3.6 新增Controller类
有两种方式,第一种方式不需要修改Spring的配置文件,此方式比较简单只需要遵循路径的规则就可以;
新增代码按照如下路径进行管理OThinker.H3.Controller.project.controller其中project按照项目名称自行定义作为整个新项目的根路径,效果如下图:
另外一种方式需要修改Spring的配置文件src\main\resources\config\mvc\springmvc-servlet.xml,在配置文件中增加扫描路径,如下图所示:
这两种方式在应对产品升级和更换版本时都可以做到与产品无缝对接,当采用第二中方式时也仅需要在同步修改Spring的配置文件src\main\resources\config\mvc\springmvc-servlet.xml即可。
3.3.7 修改Controller类
当需要修改产品的Controller类时会比较麻烦,因为此处会涉及到产品的升级和更换带来的问题,同时目前产品不直接提供源码的情况下做修改时也比较麻烦,针对这种情况,目前有两种方式可以参考。
第一种方式是直接新增新的Controller类和新的URI地址,这样前端对应访问地址也要修改指向新的URI。
第二种方式是修改SpringMVC的配置文件src\main\resources\config\mvc\springmvc-servlet.xml对扫描的类进行过滤,新增一个Controller类映射同样的URI地址然后让SpringMVC在启动的时候过滤原来的Controller就可以,这样的方式有个问题就是如果需要对多个Controller进行修改需要添加多个过滤类。
此处以第二种方式来处理,比如需要对OThinker.H3.Controller.Controllers.Organization.OrgUnitController类做修改,则我们可以在新项目的中新建一个类com.project.rebuild.organization.OrgUnitController(类名可以随便取或者和原来一致),然后把OThinker.H3.Controller.Controllers.Organization.OrgUnitController的内容全部拷贝到新的类中,然后修改我们需要修改的方法即可,如下各图所示:
新增类使用了rebuild作为开始路径可以明确在这个路径下的类都是对原产品的类做修改的。
备注:如果找不到源码,请查看对应的IDE的maven帮助或者直接使用maven的命令 dependency:sources来下载。
3.4 引擎端事件开发
引擎提供了大概9个事件的接口
² IEngineEventHandler
引擎级对象的事件处理接口,比如:新建WorkItem事件等。注意:这里的事件都是滞后的事件,并不是实时的事件,比如:流程实例创建事件,是在新建的流程实例保存进数据库中才被触发的,而且是异步触发的。如果事件处理程序耗用大量的CPU,那么会影响引擎的性能;如果事件处理程序抛出异常,那么引擎会忽略掉这个异常。
² ICirculateItemEventHandler
传阅项的事件处理接口,注意:这里的事件都是滞后的事件,并不是实时的事件,比如:工作项创建事件,是在新建的工作项保存进数据库中才被触发的,而且是异步触发的。如果事件处理程序耗用大量的CPU,那么会影响引擎的性能;如果事件处理程序抛出异常,那么引擎会忽略掉这个异常。
² ICirculateItemFinishedEventHandler
传阅项的事件处理接口,注意:这里的事件都是滞后的事件,并不是实时的事件,比如:工作项创建事件,是在新建的工作项保存进数据库中才被触发的,而且是异步触发的。如果事件处理程序耗用大量的CPU,那么会影响引擎的性能;如果事件处理程序抛出异常,那么引擎会忽略掉这个异常。
² IInstanceEventHandler
流程实例的事件处理接口,注意:这里的事件都是滞后的事件,并不是实时的事件,比如:流程实例创建事件,是在新建的流程实例保存进数据库中才被触发的,而且是异步触发的。如果事件处理程序耗用大量的CPU,那么会影响引擎的性能;如果事件处理程序抛出异常,那么引擎会忽略掉这个异常。
² IMessageEventHandler
流程引擎Message处理事件接口
² INotificationEventHandler
消息通知 EventHandler 接口
² IOrganizationEventHandler
组织的事件处理接口,注意:这里的事件都是滞后的事件,并不是实时的事件,比如:组织创建事件,是在新建的组织保存进数据库中才被触发的,而且是异步触发的。如果事件处理程序耗用大量的CPU,那么会影响引擎的性能;如果事件处理程序抛出异常,那么引擎会忽略掉这个异常。
² IWorkItemEventHandler
工作项的事件处理接口,注意:这里的事件都是滞后的事件,并不是实时的事件,比如:工作项创建事件,是在新建的工作项保存进数据库中才被触发的,而且是异步触发的。如果事件处理程序耗用大量的CPU,那么会影响引擎的性能;如果事件处理程序抛出异常,那么引擎会忽略掉这个异常。
² IWorkItemFinishedEventHandler
工作项的事件处理接口,注意:这里的事件都是滞后的事件,并不是实时的事件,比如:工作项创建事件,是在新建的工作项保存进数据库中才被触发的,而且是异步触发的。如果事件处理程序耗用大量的CPU,那么会影响引擎的性能;如果事件处理程序抛出异常,那么引擎会忽略掉这个异常。
此处以工作项事件作为示例,先自行新建一个java工程,此处以maven工程为例,在pom.xml中添加h3bpm-common包和对应的版本。如下图所示:
当在开发过程中如果有新的JAR包需要引入到开发中,部署的时候需要将额外用到的JAR包,添加到h3bpm-engine.jar里的MANIFEST.MF文件中。
实现事件接口的类必须在OThinker.H3.EventHandlers路径下,开发完成后打包成Jar包同时将class文件放到到引擎的固定目录h3bpm-engine\OThinker\H3\EventHandlers下即可,如下图