為何使用gooflow:1、兼容性好
2、擴展點很多可以個性化設計
3、配有api文檔
4、json格式的數據傳輸
由於最近項目需要,急需設計一個流程,考慮到時間問題,和用戶個性化的需求,沒辦法跟現在項目的后台集成,所以考慮到選擇一款jquery插件,並通過存儲過程來集成現在的業務模塊。
直接上圖了:
雙擊節點可以選擇人員

雙擊連接線可以選擇條件

使用gooflow版本為0.4的 網上可以搜到 另外當前版本有些bug需要自己改。需要提供幫助的可以加我QQ:512948935
gooflow版本為0.6



后台使用的是mvc+spring+NHibernate,主要是保存比較麻煩。
前台js
<script type="text/javascript">
var property = {
toolBtns: ["start", "end", "task"],
haveHead: true,
headBtns: ["save", "undo", "redo", "reload"], //如果haveHead=true,則定義HEAD區的按鈕
haveTool: true,
haveGroup: true,
useOperStack: true
};
var remark = {
cursor: "選擇指針",
direct: "轉換連線",
start: "開始結點",
end: "結束結點",
task: "任務結點",
group: "組織划分框編輯開關"
};
var gooFlow, focusId, flow_title, flowID;
$(function () {
gooFlow = $.createGooFlow($("#flow"), property);
flow_title = getUrlParam1("title");
flowID = getUrlParam("flowID");
if (flow_title != "") {
//自適應調整
gooFlow.reinitSize($(this).width() - 5, $(parent).height() - 25);
gooFlow.setTitle(flow_title + "·流程繪制");
parent.$('#div_layout').layout('panel', 'center').panel({
onResize: function (width, height) {
gooFlow.reinitSize(width - 5, height - 30);
}
});
}
else
gooFlow.reinitSize($(this).width() - 5, $(this).height() - 5);
if (flowID == "") flowID = 0;
gooFlow.setNodeRemarks(remark);
//新建流程
gooFlow.onBtnNewClick = function () {
gooFlow.clearData();
}
//保存流程
gooFlow.onBtnSaveClick = function () {
var h = gooFlow.$bgDiv.height();
$("<div class=\"datagrid-mask\"></div>").css({ display: "block", width: "100%", height: h }).appendTo(gooFlow.$bgDiv);
$("<div class=\"datagrid-mask-msg\"></div>").html("數據正在保存中,請稍候……").appendTo(gooFlow.$bgDiv).css({
display: "block",
left: (gooFlow.$bgDiv.width() - 200) / 2,
top: (h - 45) / 2
});
var obj = gooFlow.exportAlter();
//節點
var nodeData = "";
for (var i in obj.nodes) {
var id = gooFlow.$nodeData[i].ID == null ? 0 : gooFlow.$nodeData[i].ID;
var userID = gooFlow.$nodeData[i].userID == null ? 0 : gooFlow.$nodeData[i].userID;
nodeData += '{"ID": ' + id + ''
+ ',"FlowID": ' + flowID + ''
+ ',"NodeID": "' + i + '"'
+ ',"UserID": "' + userID + '"'
+ ',"UserName": "' + gooFlow.$nodeData[i].name + '"'
+ ',"NodeType": "' + gooFlow.$nodeData[i].type + '"'
+ ',"NodeLeft": ' + gooFlow.$nodeData[i].left + ''
+ ',"NodeTop": ' + gooFlow.$nodeData[i].top + ''
+ ',"NodeWidth": ' + gooFlow.$nodeData[i].width + ''
+ ',"NodeHeight": ' + gooFlow.$nodeData[i].height + ''
+ ',"Marked": false},';
}
if (nodeData != "") {
nodeData = "[" + $.trimend(nodeData, ',') + "]";
}
//連接線
var lineData = "";
for (var i in obj.lines) {
var id = gooFlow.$lineData[i].ID == null ? 0 : gooFlow.$lineData[i].ID;
var conditionID = gooFlow.$lineData[i].conditionID == null ? 0 : gooFlow.$lineData[i].conditionID;
var lineM = gooFlow.$lineData[i].M == null ? 0 : gooFlow.$lineData[i].M;
lineData += '{"ID": ' + id + ''
+ ',"FlowID": ' + flowID + ''
+ ',"LineID": "' + i + '"'
+ ',"ConditionID": ' + conditionID + ''
+ ',"ConditionName": "' + gooFlow.$lineData[i].name + '"'
+ ',"LineType": "' + gooFlow.$lineData[i].type + '"'
+ ',"LineFrom": "' + gooFlow.$lineData[i].from + '"'
+ ',"LineTo": "' + gooFlow.$lineData[i].to + '"'
+ ',"LineM": ' + lineM + ''
+ ',"Marked": false},';
}
if (lineData != "") {
lineData = "[" + $.trimend(lineData, ',') + "]";
}
//區域
var areaData = "";
for (var i in obj.areas) {
var id = gooFlow.$areaData[i].ID == null ? 0 : gooFlow.$areaData[i].ID;
areaData += '{"ID": ' + id + ''
+ ',"FlowID": ' + flowID + ''
+ ',"AreaID": "' + i + '"'
+ ',"AreaName": "' + gooFlow.$areaData[i].name + '"'
+ ',"AreaLeft": ' + gooFlow.$areaData[i].left + ''
+ ',"AreaTop": ' + gooFlow.$areaData[i].top + ''
+ ',"AreaWidth": ' + gooFlow.$areaData[i].width + ''
+ ',"AreaHeight": ' + gooFlow.$areaData[i].height + ''
+ ',"AreaColor": "' + gooFlow.$areaData[i].color + '"'
+ ',"Marked": false},';
}
if (areaData != "") {
areaData = "[" + $.trimend(areaData, ',') + "]";
}
if (nodeData == "" && lineData == "" && areaData == "") {
$('.datagrid-mask-msg').remove();
$('.datagrid-mask').remove();
return;
}
$.ajax({
type: "post",
url: "/HR/BacthSave",
data: { node: nodeData, line: lineData, area: areaData },
success: function (data) {
if (data.status == 1) {
jqAlert('保存成功.', 'info', "reload");
}
else
jqAlert('保存失敗:' + data, 'error')
$('.datagrid-mask-msg').remove();
$('.datagrid-mask').remove();
}
});
}
//刷新
gooFlow.onFreshClick = function () {
location.reload();
}
//單元節點雙擊事件
gooFlow.$workArea.delegate(".ico + td", "dblclick", { inthis: gooFlow }, function (e) {
var newId = $(this).parents(".GooFlow_item").attr("id");
var $frame = $("#frame_choose_aud");
if ($frame.attr("src") == undefined) {
focusId = newId;
$frame.attr("src", "/HR/BaseFlowChooseEmp");
}
else {
if (focusId != newId) {
focusId = newId;
window.frames["choose_aud"].initData();
}
}
$("#div_win_choose_aud").window('open');
});
//單元連接線雙擊事件
var tmpClk = "PolyLine";
if (GooFlow.prototype.useSVG != "")
tmpClk = "g";
$(gooFlow.$draw).delegate(tmpClk, "dblclick", { inthis: gooFlow }, function (e) {
if (GooFlow.prototype.useSVG != "") {
var $frame = $("#frame_choose_con");
if ($frame.attr("src") == undefined) {
focusId = this.id;
$frame.attr("src", "/HR/BaseFlowCondition?typeID=" + getUrlParam("typeID"));
}
else {
if (focusId != this.id) {
focusId = this.id;
window.frames["choose_con"].unselect();
}
}
$("#div_win_choose_con").window('open');
}
});
//操作單元刪除事件
gooFlow.onItemDel = function (id, type) {
var delItem = gooFlow.getItemInfo(id, type);
if (delItem.ID != null) {
uiConfirm("確定要刪除該單元嗎.", function () {
$.post("/HR/DeleteFlowItem", { "id": id, "type": type }, function (data) {
if (data.status == 1) {
delItem.ID = null;
if (type == "node")
gooFlow.delNode(id);
else if (type == "line")
gooFlow.delLine(id);
else if (type == "area")
gooFlow.delArea(id);
return true;
}
else
jqAlert('刪除失敗:' + data, 'error')
});
});
}
else
return true
}
//初始化人員選擇窗體
showMyWindow($("#div_win_choose_aud"), '選擇人員信息', 'icon-edit', '', 900, 450, true);
//初始化人員選擇窗體
showMyWindow($("#div_win_choose_con"), '選擇條件信息', 'icon-edit', '', 900, 450, true);
//加載數據
var h = gooFlow.$bgDiv.height();
$("<div class=\"datagrid-mask\"></div>").css({ display: "block", width: "100%", height: h }).appendTo(gooFlow.$bgDiv);
$("<div class=\"datagrid-mask-msg\"></div>").html("圖形正在加載中,請稍候……").appendTo(gooFlow.$bgDiv).css({
display: "block",
left: (gooFlow.$bgDiv.width() - 200) / 2,
top: (h - 45) / 2
});
var para = { "type": "get", "url": "/HR/LoadWorkArea?flowID=" + flowID, "success": onLoadSuccess, "error": onLoadError };
gooFlow.loadDataAjax(para);
});
function onLoadSuccess(msg) {
$('.datagrid-mask-msg').remove();
$('.datagrid-mask').remove();
}
function onLoadError(status, errorThrown) {
$('.datagrid-mask-msg').remove();
$('.datagrid-mask').remove();
}
function backAudChoose(row) {
gooFlow.setName(focusId, row.user_truename + "(" + row.user_no + ")", "node");
var focusNode = gooFlow.getItemInfo(focusId, "node");
focusNode.name = row.user_truename + "(" + row.user_no + ")";
focusNode.userID = row.ID;
$("#div_win_choose_aud").window('close');
}
function backConChoose(row) {
gooFlow.setName(focusId, row.ConditionName, "line")
var focusLine = gooFlow.getItemInfo(focusId, "line");
focusLine.name = row.ConditionName;
focusLine.conditionID = row.ID;
$("#div_win_choose_con").window('close');
}
</script>
后台處理
#region BaseFlowPicture
public ActionResult BaseFlowPicture()
{
return View();
}
public ActionResult BacthSave(string node = "", string line = "", string area = "")
{
try
{
if (node != "")
{
List<Q_HR_WorkFlow_Node> nodes = JSONStringToList<Q_HR_WorkFlow_Node>(node);
int s = nodes.Where(c => c.NodeType == "start").Count();
int e = nodes.Where(c => c.NodeType == "end").Count();
if (s != 1)
throw new Exception("請設置一個開始節點.");
if (e != 1)
throw new Exception("請設置一個結束節點.");
Q_HR_WorkFlow_NodeManage.BatchSave(nodes);
}
if (line != "")
{
List<Q_HR_WorkFlow_Line> lines = JSONStringToList<Q_HR_WorkFlow_Line>(line);
Q_HR_WorkFlow_LineManage.BatchSave(lines);
}
if (area != "")
{
List<Q_HR_WorkFlow_Area> areas = JSONStringToList<Q_HR_WorkFlow_Area>(area);
Q_HR_WorkFlow_AreaManage.BatchSave(areas);
}
return Json(new { status = 1 }, JsonRequestBehavior.AllowGet);
}
catch (Exception ex)
{
return Content(ex.Message);
}
}
public string LoadWorkArea(int flowID = 0)
{
DataTable nodes = Q_HR_WorkFlow_NodeManage.GetList(flowID);
DataTable lines = Q_HR_WorkFlow_LineManage.GetList(flowID);
IList<Q_HR_WorkFlow_Area> areas = Q_HR_WorkFlow_AreaManage.GetList(flowID);
string jsonStr = "{";
string alt;
//jsonStr += "\"initNum\":" + Q_HR_WorkFlow_NodeManage.GetInitNum() + ",";
if (nodes.Rows.Count > 0)
{
jsonStr += "\"nodes\":{";
foreach (DataRow row in nodes.Rows)
{
alt = "false";
if (row["NodeType"].ToString() == "start" || row["NodeType"].ToString() == "end")
alt = "true";
jsonStr += "\"" + row["NodeID"] + "\":{"
+ "\"ID\":" + row["ID"] + ""
+ ",\"name\":\"" + row["UserName"] + "(" + row["user_no"] + ")\""
+ ",\"userID\":" + row["UserID"] + ""
+ ",\"type\":\"" + row["NodeType"] + "\""
+ ",\"left\":" + row["NodeLeft"] + ""
+ ",\"top\":" + row["NodeTop"] + ""
+ ",\"width\":" + row["NodeWidth"] + ""
+ ",\"height\":" + row["NodeHeight"] + ""
+ ",\"alt\":" + alt + ""
+ ",\"mark\":" + row["Marked"].ToString().ToLower() + ""
+ "},";
}
jsonStr = jsonStr.TrimEnd(',') + "},";
}
if (lines.Rows.Count > 0)
{
jsonStr += "\"lines\":{";
foreach (DataRow row in lines.Rows)
{
jsonStr += "\"" + row["LineID"] + "\":{"
+ "\"ID\":" + row["ID"] + ""
+ ",\"name\":\"" + row["ConditionName"] + "\""
+ ",\"conditionID\":" + row["ConditionID"] + ""
+ ",\"type\":\"" + row["LineType"] + "\""
+ ",\"from\":\"" + row["LineFrom"] + "\""
+ ",\"to\":\"" + row["LineTo"] + "\""
+ ",\"M\":" + row["LineM"] + ""
+ ",\"mark\":" + row["Marked"].ToString().ToLower() + ""
+ "},";
}
jsonStr = jsonStr.TrimEnd(',') + "},";
}
if (areas.Count > 0)
{
jsonStr += "\"areas\":{";
foreach (Q_HR_WorkFlow_Area area in areas)
{
jsonStr += "\"" + area.AreaID + "\":{"
+ "\"ID\":" + area.ID + ""
+ ",\"name\":\"" + area.AreaName + "\""
+ ",\"left\":" + area.AreaLeft + ""
+ ",\"top\":" + area.AreaTop + ""
+ ",\"width\":" + area.AreaWidth + ""
+ ",\"height\":" + area.AreaHeight + ""
+ ",\"color\":\"" + area.AreaColor + "\""
+ "},";
}
jsonStr = jsonStr.TrimEnd(',') + "},";
}
jsonStr = jsonStr.TrimEnd(',') + "}";
return jsonStr;
}
public ActionResult DeleteFlowItem(string id, string type)
{
try
{
if (type.Equals("node"))
Q_HR_WorkFlow_NodeManage.DeleteFlowNode(id, type);
else if (type.Equals("line"))
Q_HR_WorkFlow_LineManage.DeleteFlowLine(id, type);
else if (type.Equals("area"))
Q_HR_WorkFlow_AreaManage.DeleteFlowArea(id, type);
return Json(new { status = 1 }, JsonRequestBehavior.AllowGet);
}
catch (Exception ex)
{
return Content(ex.Message);
}
}
#endregion
| 屬性名稱 |
作用 |
| $id |
裝載整個UI的DOM對象的ID。 |
| $bgDiv |
最父框架的DIV。 |
| $tool |
左側工具欄JQ對象。 |
| $head |
頂部欄標題標簽及工具欄按鈕。 |
| $title |
載入的流程圖的名稱。 |
| $nodeRemark |
左側工具欄中每一種結點或按鈕的說明文字,JSON格式,key為按鈕類型名,value為用戶自定義文字說明。 |
| $nowType |
當前要繪制的對象類型,開始時為“cursor”,即不繪制任何元素,只是作為鼠標指針進行元素選定。 |
| $lineData={} |
轉換線數據Map集,以id為key,value為詳細數據JSON對象。 |
| $lineCount=0 |
轉換線數據的數量。 |
| $nodeData={} |
節點數據Map集,以id為key,value為詳細數據JSON對象。 |
| $nodeCount=0 |
節點數據的數量。 |
| $areaData={} |
分組區數據Map集,以id為key,value為詳細數據JSON對象。 |
| $areaCount=0 |
分組區數據的數量。 |
| $lineDom={} |
轉換線DOM展示對象Map集,以id為key,value為詳細在DOM對象。 |
| $nodeDom={} |
節點JQ展示對象Map集,以id為key,value為詳細在JO對象。 |
| $areaDom={} |
分組區JQ展示對象Map集,以id為key,value為詳細在JO對象。 |
| $max |
計算默認ID值的起始SEQUENCE,默認不填時為1。 |
| $focus |
當前被選定的結點/轉換線ID,如果沒選中或者工作區被清空,則為""。 |
| $cursor |
鼠標指針在工作區內的樣式,初始時為default。 |
| $editable |
當前工作區是否可編輯,即是編輯模式還是僅瀏覽模式。 |
| $workArea |
裝載結點/線條/分組區域的工作區。 |
| $draw |
畫矢量線條的容器,處於工作區中。 |
| $group |
僅用來裝配分組區域DOM元素的容器,處於工作區中。 |
| $ghost |
專門用在移動、重置大小等操作時,給用戶操作的半透明浮動區。 |
| $textArea |
雙擊操作對象后出現的浮動文本域,用來寫重命名方法setName所需的新名稱傳參。 |
| $lineMove |
操作移動折線的中段時用到的浮動DIV |
| $lineOper |
選定一條轉換線后出現的浮動操作欄,有改變線的樣式和刪除線等按鈕。 |
| //以下是當初始化的參數property.useOperStack=true且$editable=true時,才存在的屬性: |
|
| $undoStack=[] |
“撤銷操作”棧。 |
| $redoStack=[] |
重做操作棧。 |
| $isUndo |
事務操作標志位,內部調用 |
| $deletedItem={} |
在流程圖的編輯操作中被刪除掉的元素ID集合,元素ID為KEY,元素類型(node,line.area)為VALUE |
[GooFlow對象供使用者調用的方法集]
| 方法名稱 |
作用 |
| setNodeRemarks(remark) |
設定左側工具欄中每一種結點或按鈕的說明文字,傳參是JSON格式,key為按鈕類型名,value為用戶自定義文字說明。 |
| switchToolBtn(type) |
切換左邊工具欄按鈕,傳參type表示切換成哪種類型的按鈕 |
| addNode(id,json) |
增加一個結點,傳參json內容結構與$nodeData的每個屬性單元一樣。 |
| getItemInfo(id,type) |
根據id這個KEY,和要獲取的數據類型type(有”node”,”line”,”area”三種取值),返回相應的結點json數據單元 |
| blurItem() |
取消所有結點/連線被選定的狀態 |
| focusItem(id,bool) |
選定某個結點/轉換線;傳參bool:TRUE決定了要觸發選中事件,FALSE則不觸發選中事件,多用在程序內部調用。 |
| moveNode(id,left,top) |
移動一個結點到一個新的位置 |
| setName(id,name,type) |
設置結點/連線/分組區域的文字信息;傳參id為序列,name為新的名稱,type為更名對象的數據類型(有”node”,”line”,”area”三種取值) |
| resizeNode(id,width,height) |
重新設置結點的尺寸,開始/結束類型的結點不支持該方法 |
| delNode(id) |
刪除結點 |
| setTitle(text) |
設置流程圖的名稱 |
| loadData(data) |
載入一組數據JSON格式的流程圖數據,傳參data中有title,nodes,lines,areas四個KEY的數據,還有一個可選屬性數據initNum:ID起始序列號最大數字+1——由於繪制的新單元的ID都是按一定序列號及規則自動生成的,為了防止新載入的數據的ID與編輯時新加入的ID值有重復,將給設計器對象對於新生成單元的ID序列一個新的起始序列號;如果傳參JSON中沒有這個屬性,也可以在調用loadData方法前修改設計器對象的$max屬性值(其實loadData方法執行時會檢查傳參中如果有initNum時,將自動給設計器對象的$max賦上此值); nodes,lines,areas都為一組{key:value}式的Map數據,內容結構分別與GooFlow對象屬性中的$nodeData,$lineData,$areaData一致. |
| loadDataAjax(para) |
用AJAX方式,遠程讀取一組數據; 參數para為JSON結構,與JQUERY中$.ajax()方法的傳參一樣 需要后台異步返回JSON格式的msg結果,其內容格式與loadData方法的傳參一樣。 |
| exportData() |
把畫好的結束導出到一個本函數返回的變量中(其實也可以直接訪問GooFlow對象的$nodeData,$lineData,$areaData這三個JSON屬性) |
| exportAlter() |
//只把本次編輯流程圖中作了變更(包括增刪改)的元素導出到一個變量中,以方便用戶每次編輯載入的流程圖后只獲取變更過的數據 |
| transNewId(oldId,newId,type) |
變更元素的ID,一般用於快速保存后,將后台返回新元素的ID更新到頁面中;type為元素類型(節點,連線,區塊) |
| clearData() |
清空工作區及已載入的數據 |
| destrory() |
銷毀自己 |
| addLine(id,json) |
增加一條線,傳參json內容結構與$lineData的每個屬性單元一樣。 |
| setLineType(id,newType) |
重新設置連線的樣式. 傳參newType的取值有:"sl"直線, "lr"中段可左右移動型折線, "tb"中段可上下移動型折線 |
| setLineM(id,M) |
設置折線中段的X坐標值(可左右移動時)或Y坐標值(可上下移動時);直線不支持此方法 |
| delLine(id) |
刪除轉換線 |
| markItem(id,type,mark) |
//用顏色標注/取消標注一個結點或轉換線,常用於顯示重點或流程的進度。 //這是一個在編輯模式中無用,但是在純瀏覽模式中非常有用的方法,實際運用中可用於跟蹤流程的進度。 //傳參:id是操作單元對象唯一序列號;type是操作單元類型(“node”或者”line”,分組區域不支持此方法);mark為布爾值,表示是否標注/取消標注某個ID值的數據單元對象 |
| addArea(id,json) |
增加一個分組區域,傳參json內容結構與$areaData的每個屬性單元一樣。 |
| moveArea(id,left,top) |
移動分組區域到新的位置上. |
| delArea(id) |
刪除分組區域 |
| setAreaColor(id,color) |
設置分組區域的顏色,傳參color為顏色樣式,只有”red”,”yellow”,”blue”,”green”四種取值 |
| resizeArea(id,width,height) |
重新設置區分組區域的尺寸
|
| reinitSize(width,height) |
重構整個流程圖設計器的寬高,在瀏覽器窗口大小改變或者父容器寬高改變時,執行這個方法能讓設計器重新適應新的寬高顯示。 |
| //以下是當初始化的參數property.useOperStack=true時,才存在的方法: |
|
| pushOper(funcName,paras) |
僅供內部方法調用的方法:把對工作區內的數據單元編輯操作(增/刪/改/重命名/移動/標注等)加入整條管理棧中,好進入撤銷/重做的控制; 注意:將為了節省瀏覽器內存空間,undo/redo中的操作緩存棧,最多只可放40步操作;超過40步時,將自動刪掉最舊的一個緩存。 |
| pushExternalOper (func,jsonPara) |
//將外部的方法加入到GooFlow對象的事務操作堆棧中,在過后的undo/redo操作中可以進行控制,一般用於對流程圖以外的附加信息進行編輯的事務撤銷/重做控制; //傳參func為要執行方法對象,jsonPara為外部方法僅有的一個面向字面的JSON傳參或者數據,由JSON對象或數組帶入所有要傳的信息; //提示:為了讓外部方法能夠被UNDO/REDO,需要在編寫這些外部方法實現時,加入對該方法執行后效果回退的另一個執行方法的pushExternalOper。 |
| undo() |
撤銷最近一次操作 |
| redo() |
重做最近一次被撤銷的操作 |
