Python 自研审批流引擎,可根据封装的审批引擎接口对接各类表单!!
本帖最后由 Summer000 于 2024-1-10 21:29 编辑这两个月给部门使用Django框架开发了一套内部人员使用的工作平台,想着可能很快就结束了,临到结束,领导要求增加两个调整申请审批单,需要有审批流,并且附带会签。可以自定义审批节点,本来想着找个开源的审批流引擎,发现市场上都是Activiti、Airflow、Zope之类的审批流引擎,引用起来还比较麻烦,不能很好的通过接口直接调用,比较烦人,索性自己开发了一套审批流引擎,暂且叫它MapFlow吧,为啥是Map呢,主要是配置很灵活,开箱即用。
好了,言归正传,开始讲思路与功能点:
首先,我需要使用一个类似于绘制前端动态图的方式绘制一张审批流程图,我需要保存当前绘制审批流程图的相关点位,对当前节点进行分类,是属于开始结束类还是步骤类,以及使用不同代码区分当前步骤节点,便于后期封装接口,这个后期再讲,上图:
呃,图片咋贴出来这么chou,先将就着看吧。。。
整个审批流引擎一共使用了12张表,包括两个完全不同的申请单,两个审批流审核,审批节点记录表,审批记录表等;
这个代码粘贴的太丑了,为啥不能直接上小方块呢?
function init() {
var $ = go.GraphObject.make;// for conciseness in defining template
myDiagram =
$(go.Diagram, "myDiagramDiv",// must name or refer to the DIV HTML element
{
"LinkDrawn": showLinkLabel,// this DiagramEvent listener is defined below
"LinkRelinked": showLinkLabel,
"undoManager.isEnabled": true// enable undo & redo
});
myDiagram.addDiagramListener("Modified", function(e) {
var button = document.getElementById("SaveButton");
if (button) button.disabled = !myDiagram.isModified;
var idx = document.title.indexOf("*");
if (myDiagram.isModified) {
if (idx < 0) document.title += "*";
} else {
if (idx >= 0) document.title = document.title.substr(0, idx);
}
});
function nodeStyle() {
return [
new go.Binding("location", "loc", go.Point.parse).makeTwoWay(go.Point.stringify),
{
locationSpot: go.Spot.Center
}
];
}
function makePort(name, align, spot, output, input) {
var horizontal = align.equals(go.Spot.Top) || align.equals(go.Spot.Bottom);
return $(go.Shape,
{
fill: "transparent",// changed to a color in the mouseEnter event handler
strokeWidth: 0,// no stroke
width: horizontal ? NaN : 8,// if not stretching horizontally, just 8 wide
height: !horizontal ? NaN : 8,// if not stretching vertically, just 8 tall
alignment: align,// align the port on the main Shape
stretch: (horizontal ? go.GraphObject.Horizontal : go.GraphObject.Vertical),
portId: name,// declare this object to be a "port"
fromSpot: spot,// declare where links may connect at this port
fromLinkable: output,// declare whether the user may draw links from here
toSpot: spot,// declare where links may connect at this port
toLinkable: input,// declare whether the user may draw links to here
cursor: "pointer",// show a different cursor to indicate potential link point
mouseEnter: function(e, port) {// the PORT argument will be this Shape
if (!e.diagram.isReadOnly) port.fill = "rgba(255,0,255,0.5)";
},
mouseLeave: function(e, port) {
port.fill = "transparent";
}
});
}
function textStyle() {
return {
font: "bold 11pt Lato, Helvetica, Arial, sans-serif",
stroke: "#F8F8F8"
}
}
function makeButton(text, action, visiblePredicate) {
return $("ContextMenuButton",
$(go.TextBlock, text),
{ click: action },
visiblePredicate ? new go.Binding("visible", "", function(o, e) { return o.diagram ? visiblePredicate(o, e) : false; }).ofObject() : {});
}
var myContextMenu =$("ContextMenu",
makeButton("✰配置✰",
function(e, obj) {// OBJ is this Button
var contextmenu = obj.part;// the Button is in the context menu Adornment
var part = contextmenu.adornedPart;// the adornedPart is the Part that the context menu adorns
if (part instanceof go.Link) alert(linkInfo(part.data));
else if (part instanceof go.Group) alert(groupInfo(contextmenu));
else nodeInfo(part.data);
}),
function isInArray(arr,value){
for(var i = 0; i < arr.length; i++){
if(value === arr){
return true;
}
}
return false;
}
function nodeInfo(d) {
var re_url = "{{url_data|safe}}";
var pro_id = "{{process_id|safe}}";
var node_key = JSON.parse('{{chart_exist|safe}}');
if (isInArray(node_key,d.key) == true){
layui.use('layer', function(){
parent.layer.open({
type: 2 //Page层类型
,area: ['40%', '70%']
,title: '节点配置'
,shade: 0.6 //遮罩透明度
,anim: 4 //0-6的动画形式,-1不开启
,offset: '8%'
,content:['/Flow/flow_chart_setting.html/'] + "?node_key=" + d.key + "&node_name=" + d.text + "&type=" + re_url + "&process_id=" + pro_id
});
})
}
else {
layui.use('layer', function(){
parent.layer.msg('请先保存节点数据!', {icon: 2});
})
}
}
myDiagram.nodeTemplateMap.add("",// the default category
$(go.Node, "Table", nodeStyle(),
{ contextMenu: myContextMenu },
$(go.Panel, "Auto",
$(go.Shape, "Rectangle",
{ fill: "#282c34", stroke: "#00A9C9", strokeWidth: 3.5 },
new go.Binding("figure", "figure")),
$(go.TextBlock, textStyle(),
{
margin: 8,
maxSize: new go.Size(160, NaN),
wrap: go.TextBlock.WrapFit,
editable: true
},
new go.Binding("text").makeTwoWay())
),
makePort("T", go.Spot.Top, go.Spot.TopSide, false, true),
makePort("L", go.Spot.Left, go.Spot.LeftSide, true, true),
makePort("R", go.Spot.Right, go.Spot.RightSide, true, true),
makePort("B", go.Spot.Bottom, go.Spot.BottomSide, true, false)
));
myDiagram.nodeTemplateMap.add("Conditional",
$(go.Node, "Table", nodeStyle(),
{ contextMenu: myContextMenu },
$(go.Panel, "Auto",
$(go.Shape, "Diamond",
{ fill: "#282c34", stroke: "#00A9C9", strokeWidth: 3.5 },
new go.Binding("figure", "figure")),
$(go.TextBlock, textStyle(),
{
margin: 8,
maxSize: new go.Size(160, NaN),
wrap: go.TextBlock.WrapFit,
editable: true
},
new go.Binding("text").makeTwoWay())
),
makePort("T", go.Spot.Top, go.Spot.Top, false, true),
makePort("L", go.Spot.Left, go.Spot.Left, true, true),
makePort("R", go.Spot.Right, go.Spot.Right, true, true),
makePort("B", go.Spot.Bottom, go.Spot.Bottom, true, false)
));
myDiagram.nodeTemplateMap.add("Start",
$(go.Node, "Table", nodeStyle(),
// { contextMenu: myContextMenu },
$(go.Panel, "Spot",
$(go.Shape, "Circle",
{ desiredSize: new go.Size(70, 70), fill: "#282c34", stroke: "#09d3ac", strokeWidth: 3.5 }),
$(go.TextBlock, "Start", textStyle(),
new go.Binding("text"))
),
makePort("L", go.Spot.Left, go.Spot.Left, true, false),
makePort("R", go.Spot.Right, go.Spot.Right, true, false),
makePort("B", go.Spot.Bottom, go.Spot.Bottom, true, false)
));
myDiagram.nodeTemplateMap.add("End",
$(go.Node, "Table", nodeStyle(),
// { contextMenu: myContextMenu },
$(go.Panel, "Spot",
$(go.Shape, "Circle",
{ desiredSize: new go.Size(60, 60), fill: "#282c34", stroke: "#DC3C00", strokeWidth: 3.5 }),
$(go.TextBlock, "End", textStyle(),
new go.Binding("text"))
),
makePort("T", go.Spot.Top, go.Spot.Top, false, true),
makePort("L", go.Spot.Left, go.Spot.Left, false, true),
makePort("R", go.Spot.Right, go.Spot.Right, false, true)
));
go.Shape.defineFigureGenerator("File", function(shape, w, h) {
var geo = new go.Geometry();
var fig = new go.PathFigure(0, 0, true); // starting point
geo.add(fig);
fig.add(new go.PathSegment(go.PathSegment.Line, .75 * w, 0));
fig.add(new go.PathSegment(go.PathSegment.Line, w, .25 * h));
fig.add(new go.PathSegment(go.PathSegment.Line, w, h));
fig.add(new go.PathSegment(go.PathSegment.Line, 0, h).close());
var fig2 = new go.PathFigure(.75 * w, 0, false);
geo.add(fig2);
// The Fold
fig2.add(new go.PathSegment(go.PathSegment.Line, .75 * w, .25 * h));
fig2.add(new go.PathSegment(go.PathSegment.Line, w, .25 * h));
geo.spot1 = new go.Spot(0, .25);
geo.spot2 = go.Spot.BottomRight;
return geo;
});
myDiagram.nodeTemplateMap.add("Comment",
$(go.Node, "Auto", nodeStyle(),
{ contextMenu: myContextMenu },
$(go.Shape, "File",
{ fill: "#282c34", stroke: "#DEE0A3", strokeWidth: 3 }),
$(go.TextBlock, textStyle(),
{
margin: 8,
maxSize: new go.Size(200, NaN),
wrap: go.TextBlock.WrapFit,
textAlign: "center",
editable: true
},
new go.Binding("text").makeTwoWay())
));
myDiagram.linkTemplate =
$(go.Link,// the whole link panel
{
routing: go.Link.AvoidsNodes,
curve: go.Link.JumpOver,
corner: 5, toShortLength: 4,
relinkableFrom: true,
relinkableTo: true,
reshapable: true,
resegmentable: true,
mouseEnter: function(e, link) { link.findObject("HIGHLIGHT").stroke = "rgba(30,144,255,0.2)"; },
mouseLeave: function(e, link) { link.findObject("HIGHLIGHT").stroke = "transparent"; },
selectionAdorned: false
},
new go.Binding("points").makeTwoWay(),
$(go.Shape,// the highlight shape, normally transparent
{ isPanelMain: true, strokeWidth: 8, stroke: "transparent", name: "HIGHLIGHT" }),
$(go.Shape,// the link path shape
{ isPanelMain: true, stroke: "gray", strokeWidth: 2 },
new go.Binding("stroke", "isSelected", function(sel) { return sel ? "dodgerblue" : "gray"; }).ofObject()),
$(go.Shape,// the arrowhead
{ toArrow: "standard", strokeWidth: 0, fill: "gray" }),
$(go.Panel, "Auto",// the link label, normally not visible
{ visible: false, name: "LABEL", segmentIndex: 2, segmentFraction: 0.5 },
new go.Binding("visible", "visible").makeTwoWay(),
$(go.Shape, "RoundedRectangle",// the label shape
{ fill: "#F8F8F8", strokeWidth: 0 }),
$(go.TextBlock, "Yes",// the label
{
textAlign: "center",
font: "10pt helvetica, arial, sans-serif",
stroke: "#333333",
editable: true
},
new go.Binding("text").makeTwoWay())
)
);
function showLinkLabel(e) {
var label = e.subject.findObject("LABEL");
if (label !== null) label.visible = (e.subject.fromNode.data.category === "Conditional");
}
myDiagram.toolManager.linkingTool.temporaryLink.routing = go.Link.Orthogonal;
myDiagram.toolManager.relinkingTool.temporaryLink.routing = go.Link.Orthogonal;
load();// load an initial diagram from some JSON text
myPalette =
$(go.Palette, "myPaletteDiv",// must name or refer to the DIV HTML element
{
"animationManager.initialAnimationStyle": go.AnimationManager.None,
"InitialAnimationStarting": animateFadeDown, // Instead, animate with this function
nodeTemplateMap: myDiagram.nodeTemplateMap,// share the templates used by myDiagram
model: new go.GraphLinksModel([// specify the contents of the Palette
{ category: "Start", text: "开始" },
{ text: "步骤" },
{ category: "Conditional", text: "判断" },
{ category: "End", text: "结束" },
])
});
function animateFadeDown(e) {
var diagram = e.diagram;
var animation = new go.Animation();
animation.isViewportUnconstrained = true;
animation.easing = go.Animation.EaseOutExpo;
animation.duration = 900;
animation.add(diagram, 'position', diagram.position.copy().offset(0, 200), diagram.position);
animation.add(diagram, 'opacity', 0, 1);
animation.start();
}
} Summer000 发表于 2024-1-10 21:24
咱论坛代码不好粘贴呀
myDiagram =
$(go.Diagram, "myDiagramDiv",// must name or refer to the DIV HTML element
{
"LinkDrawn": showLinkLabel,// this DiagramEvent listener is defined below
"LinkRelinked": showLinkLabel,
"undoManager.isEnabled": true// enable undo & redo
});
myDiagram.addDiagramListener("Modified", function(e) {
var button = document.getElementById("SaveButton");
if (button) button.disabled = !myDiagram.isModified;
var idx = document.title.indexOf("*");
if (myDiagram.isModified) {
if (idx < 0) document.title += "*";
} else {
if (idx >= 0) document.title = document.title.substr(0, idx);
}
});
这还可以吧 File "<frozen importlib._bootstrap_external>", line 936, in exec_module
File "<frozen importlib._bootstrap_external>", line 1074, in get_code
File "<frozen importlib._bootstrap_external>", line 1004, in source_to_code
File "<frozen importlib._bootstrap>", line 241, in _call_with_frames_removed
File "C:\Users\1\PycharmProjects\djangoProject\djangoProject\urls.py", line 1
function init() {
IndentationError: unexpected indent
你确定你做的程序能跑? 咱论坛代码不好粘贴呀{:1_926:} 学习学习,感谢分享 老大。有成品嘛? 主要是学习你的项目数据结构的啦 这个东西那几家收费的目前比较有优势 青蛙考拉 发表于 2024-1-10 21:43
File "", line 936, in exec_module
File "", line 1074, in get_code
File "", line 1004, in sou ...
必须能跑,代码贴的不全,抽空我会都放上去, milu1123 发表于 2024-1-11 08:47
老大。有成品嘛?
必须有!!一种是直接内部调用审批流的,一种是对接系统封装的外部接口。 大佬,有应用程序成品吗