使用 JsPlumb 绘制拓扑图的通用方法
摘要: 实现 JsPlumb 绘制拓扑图的通用方法。 只要服务端返回一个符合指定格式的数据结构,就可以绘制相应的拓扑图。
难度: 中级
示例工程见: http://download.csdn.net/detail/shuqin1984/6488513
一、 实现目标
绘制拓扑图, 实际上是个数据结构和算法的问题。 需要设计一个合适的数据结构来表达拓扑结构,设计一个算法来计算拓扑节点的位置及连接。
二、 实现思想
1. 数据结构
首先, 从节点开始。 显然, 需要一个字段 type 表示节点类型, 一个字段 data 表示节点数据(详情), 对于连接, 则采用一个 rel 字段, 表示有哪些节点与之关联, 相当于C 里面的指针。 为了唯一标识该节点, 还需要一个字段 key 。 通过 type-key 组合来唯一标识该节点。结合Javascript 标准的数据格式 JSON, 定下数据结构如下:
a. 节点数据结构: node = { type: 'nodeType', key: 'nodeKey', rel: [], data: {'More Info'}}
b. rel, data 可选 , type-key 唯一标识该节点, rel 不存在表示该节点为叶子节点
c. 关联关系: rel: [node1, node2, ..., nodeN]
d. 节点详情: 关于节点的更多信息可放置于 data 字段中
2. 算法
在算法上, 要预先规划好各个节点类型如何布局以及如何连接。 连接方向很容易定: 根据起始节点及终止节点的类型组合, 可以规定不同的连接方向, 比如 <VIP, VM>的连线方向为<TopCenter, LeftCenter>, <VM,VIP> 的连线方向为 <TopCenter, BottomCenter>。 节点位置的确定是一个关键问题, 该算法的实现难易取决于拓扑数据结构的设计。
这里采用的方法是: 采用深度优先遍历, 下一个的节点位置通过<上一个节点位置, 上一个节点类型, 下一个节点类型> 确定, 如果上一个节点有多个相同类型的后继节点, 那么这多个后继节点的位置是重合的, 需要在后面进行调整。实际上, 这个节点位置的算法是比较笨拙的, 如果有更好的算法, 请告知。
3. JsPlumb
jsPlumb 有几个基本概念。 首先, 拓扑节点实际上是 DIV 区域,每个DIV 都必须有一个ID,用于唯一标识该节点。 连接拓扑节点的一个重要概念是EndPoint . EndPoint 是附着于节点上的连接线的端点, 简称“附着点”。 将附着点 attach 到指定拓扑节点上的方法如下:
jsPlumb.addEndpoint(toId, this.sourceEndpoint, { anchor: sourceAnchor, uuid:sourceUUID });
toId 是 拓扑节点的 DIV 区域的 ID 值, sourceEndpoint 是附着点的样式设置, 可以复用 , sourceAnchor 是附着点位置, 共有八种,也就是四方形的八个边缘:
- Top (also
aliased as TopCenter)
- TopRight - Right (also
aliased as RightMiddle)
-
BottomRight - Bottom (also
aliased as BottomCenter)
-BottomLeft - Left (also
aliased as LeftMiddle)
- TopLeft
sourceUUID 是拓扑节点与附着位置的结合, 也就是说, 要将一个 附着点附着到拓扑节点为 toId 的 sourceAnchor 指定的位置上。 每个拓扑节点都可以定义多个源附着点和目标附着点。 源附着点是连接线的起始端, 目标附着点是连接线的终止端。
两个 uuid 即可定义一条连接线:
jsPlumb.connect({uuids:[startPoint, endPoint], editable: false});
startPoint 和 endPoint 分别是连接线的起始端 Endpoint uuid 和 终止段 Endpoint uuid. 它定义了从起始拓扑节点的指定附着点连接到终止拓扑节点的指定附着点。
三、 实现代码
drawTopo.js 提供绘制拓扑图的基本方法, 只要按照指定格式将数据结构扔进去, 就可以自动绘制出拓扑图来。
/**
* 使用 jsPlumb 根据指定的拓扑数据结构绘制拓扑图
* 使用 drawTopo(topoData, nodeTypeArray) 方法
*
*/ /**
* 初始化拓扑图实例及外观设置
*/
(function() { jsPlumb.importDefaults({ DragOptions : { cursor: 'pointer', zIndex:2000 }, EndpointStyles : [{ fillStyle:'#225588' }, { fillStyle:'#558822' }], Endpoints : [ [ "Dot", { radius:2 } ], [ "Dot", { radius: 2 } ]], ConnectionOverlays : [
[ "Arrow", { location:1 } ],
[ "Label", {
location:0.1,
id:"label",
cssClass:"aLabel"
}]
]
}); var connectorPaintStyle = {
lineWidth: 1,
strokeStyle: "#096EBB",
joinstyle:"round",
outlineColor: "#096EBB",
outlineWidth: 1
}; var connectorHoverStyle = {
lineWidth: 2,
strokeStyle: "#5C96BC",
outlineWidth: 2,
outlineColor:"white"
}; var endpointHoverStyle = {
fillStyle:"#5C96BC"
}; window.topoDrawUtil = { sourceEndpoint: {
endpoint:"Dot",
paintStyle:{
strokeStyle:"#1e8151",
fillStyle:"transparent",
radius: 2,
lineWidth:2
},
isSource:true,
maxConnections:-1,
connector:[ "Flowchart", { stub:[40, 60], gap:10, cornerRadius:5, alwaysRespectStubs:true } ],
connectorStyle: connectorPaintStyle,
hoverPaintStyle: endpointHoverStyle,
connectorHoverStyle: connectorHoverStyle,
dragOptions:{},
overlays:[
[ "Label", {
location:[0.5, 1.5],
label:"",
cssClass:"endpointSourceLabel"
} ]
]
}, targetEndpoint: {
endpoint: "Dot",
paintStyle: { fillStyle:"#1e8151",radius: 2 },
hoverPaintStyle: endpointHoverStyle,
maxConnections:-1,
dropOptions:{ hoverClass:"hover", activeClass:"active" },
isTarget:true,
overlays:[
[ "Label", { location:[0.5, -0.5], label:"", cssClass:"endpointTargetLabel" } ]
]
}, initConnection: function(connection) {
connection.getOverlay("label").setLabel(connection.sourceId + "-" + connection.targetId);
connection.bind("editCompleted", function(o) {
if (typeof console != "undefined")
console.log("connection edited. path is now ", o.path);
});
}, addEndpoints: function(toId, sourceAnchors, targetAnchors) {
for (var i = 0; i < sourceAnchors.length; i++) {
var sourceUUID = toId + sourceAnchors[i];
jsPlumb.addEndpoint(toId, this.sourceEndpoint, { anchor:sourceAnchors[i], uuid:sourceUUID });
}
for (var j = 0; j < targetAnchors.length; j++) {
var targetUUID = toId + targetAnchors[j];
jsPlumb.addEndpoint(toId, this.targetEndpoint, { anchor:targetAnchors[j], uuid:targetUUID });
}
}
}; })(); /**
* drawTopo 根据给定拓扑数据绘制拓扑图
* @param topoData 拓扑数据
* @param rootPosition 拓扑图根节点的位置
* @param nodeTypeArray 节点类型数组
*
* 拓扑图的所有节点是自动生成的, DIV class = "node" , id= nodeType.toUpperCase + "-" + key
* 拓扑图的所有节点连接也是自动生成的, 可以进行算法改善与优化, 但使用者不需要关心此问题
* 需要定义节点类型数组 nodeTypeArray
*
* 拓扑数据结构:
* 1. 节点数据结构: node = { type: 'typeName', key: 'key', rel: [], data: {'More Info'}}
* rel, data 可选 , type-key 唯一标识该节点
* 2. 关联关系: rel: [node1, node2, ..., nodeN]
* 3. 更多详情: 关于节点的更多信息可放置于此属性中
* 4. 示例:
* var topoData = {
* type: 'VM', key: '110.75.188.35',
* rel: [
* { type: 'DEVICE', key: '3-120343' },
* { type: 'DEVICE', key: '3-120344' },
* { type: 'VIP', key: '223.6.250.2',
* rel: [
* { type: 'VM', key: '110.75.189.12' },
* { type: 'VM', key: '110.75.189.12' }
* ]
* },
* { type: 'NC', key: '10.242.192.2',
* rel: [
* { type: 'VM', key: '110.75.188.132' },
* { type: 'VM', key: '110.75.188.135' },
* { type: 'VM', key: '110.75.188.140' }
* ]
*
* }
* ]
* };
*
*/
function drawTopo(topoData, rootPosition, nodeTypeArray) { // 创建所有拓扑节点及连接并确定其位置
createNodes(topoData, rootPosition, nodeTypeArray); // 调整重合节点的位置, 添加节点的附着点, 即连接线的端点
adjust(topoData, nodeTypeArray); // 使所有拓扑节点均为可拉拽的
jsPlumb.draggable(jsPlumb.getSelector(".node"), { grid: [5, 5] }); // 创建所有节点连接
createConnections(topoData, nodeTypeArray); } /**
* 根据给定拓扑数据绘制拓扑节点并确定其位置, 使用深度优先遍历
* @param topoData 拓扑数据
* @param rootPosition 根节点的位置设定
* @param nodeTypeArray 拓扑节点类型
*/
function createNodes(rootData, rootPosition, nodeTypeArray) { if (rootData == null) {
return ;
} var topoRegion = $('#topoRegion');
var relData = rootData.rel;
var i=0, relLen = relLength(relData);;
var VM_TYPE = nodeTypeArray[0];
var DEVICE_TYPE = nodeTypeArray[1];
var NC_TYPE = nodeTypeArray[2];
var VIP_TYPE = nodeTypeArray[3]; // 根节点的位置, 单位: px
var rootTop = rootPosition[0];
var rootLeft = rootPosition[1]; var nextRootData = {};
var nextRootPosition = []; // 自动生成并插入根节点的 DIV
var divStr = createDiv(rootData);
var nodeDivId = obtainNodeDivId(rootData);
topoRegion.append(divStr);
//console.log(divStr); // 设置节点位置
$('#'+nodeDivId).css('top', rootTop + 'px');
$('#'+nodeDivId).css('left', rootLeft + 'px'); for (i=0; i < relLen; i++) {
nextRootData = relData[i];
nextRootPosition = obtainNextRootPosition(rootData, nextRootData, rootPosition, nodeTypeArray);
createNodes(nextRootData, nextRootPosition, nodeTypeArray);
} } /**
* 调整重合节点的位置, 并添加节点的附着点, 即连接线的端点
*/
function adjust(topoData, nodeTypeArray) { var vm_deviceOffset = 0; // 起始节点为 vm , 终止节点为 device, device div 的偏移量
var vm_vipOffset = 0; // 起始节点为 vm , 终止节点为 vip, vip div 的偏移量
var vm_ncOffset = 0; // 起始节点为 vm , 终止节点为 nc, nc div 的偏移量
var vip_vmOffset = 0; // 起始节点为 vip , 终止节点为 vm, vm div 的偏移量
var nc_vmOffset = 0; // 起始节点为nc , 终止节点为 vm, vm div 的偏移量
var verticalDistance = 120;
var horizontalDistance = 150; var VM_TYPE = nodeTypeArray[0];
var DEVICE_TYPE = nodeTypeArray[1];
var NC_TYPE = nodeTypeArray[2];
var VIP_TYPE = nodeTypeArray[3]; $('.node').each(function(index, element) {
var nodeDivId = $(element).attr('id');
var nodeType = nodeDivId.split('-')[0];
var offset = $(element).offset();
var originalTop = offset.top;
var originalLeft = offset.left;
var parentNode = $(element).parent();
var parentNodeType = parentNode.attr('id').split('-')[0];
switch (nodeType) {
case VM_TYPE:
// VM 位置水平偏移
$(element).css('left', (originalLeft + vip_vmOffset*horizontalDistance) + 'px');
vip_vmOffset++;
topoDrawUtil.addEndpoints(nodeDivId, ['Top', 'Bottom', 'Right'], []);
break;
case DEVICE_TYPE:
// DEVICE 位置垂直偏移
$(element).css('top', (originalTop + (vm_deviceOffset-1)*verticalDistance) + 'px');
vm_deviceOffset++;
topoDrawUtil.addEndpoints(nodeDivId, [], ['Left']);
break;
case VIP_TYPE:
// VIP 位置水平偏移
$(element).css('left', (originalLeft + vm_vipOffset*horizontalDistance) + 'px');
vm_vipOffset++;
topoDrawUtil.addEndpoints(nodeDivId, ['Top'], ['Bottom']);
break;
case NC_TYPE:
// NC 位置水平偏移
$(element).css('left', (originalLeft + vm_ncOffset*verticalDistance) + 'px');
vm_ncOffset++;
topoDrawUtil.addEndpoints(nodeDivId, ['Bottom'], ['Top']);
break;
default:
break;
}
});
} /**
* 获取下一个根节点的位置, 若节点类型相同, 则位置会重合, 需要后续调整一次
* @root 当前根节点
* @nextRoot 下一个根节点
* @rootPosition 当前根节点的位置
* @nodeTypeArray 节点类型数组
*/
function obtainNextRootPosition(root, nextRoot, rootPosition, nodeTypeArray) { var VM_TYPE = nodeTypeArray[0];
var DEVICE_TYPE = nodeTypeArray[1];
var NC_TYPE = nodeTypeArray[2];
var VIP_TYPE = nodeTypeArray[3]; var startNodeType = root.type;
var endNodeType = nextRoot.type;
var nextRootPosition = [];
var rootTop = rootPosition[0];
var rootLeft = rootPosition[1]; var verticalDistance = 120;
var horizontalDistance = 250;
var shortVerticalDistance = 80; switch (startNodeType) {
case VM_TYPE:
if (endNodeType == VIP_TYPE) {
nextRootPosition = [rootTop-verticalDistance, rootLeft];
}
else if (endNodeType == DEVICE_TYPE) {
nextRootPosition = [rootTop, rootLeft+horizontalDistance];
}
else if (endNodeType == NC_TYPE) {
nextRootPosition = [rootTop+verticalDistance, rootLeft];
}
break;
case VIP_TYPE:
if (endNodeType == VM_TYPE) {
nextRootPosition = [rootTop-shortVerticalDistance, rootLeft];
}
break;
case NC_TYPE:
if (endNodeType == VM_TYPE) {
nextRootPosition = [rootTop+shortVerticalDistance, rootLeft];
}
break;
default:
break;
}
return nextRootPosition;
} /**
* 根据给定拓扑数据, 绘制节点之间的连接关系, 使用深度优先遍历
* @param topoData 拓扑数据
* @param nodeTypeArray 节点类型数组
*/
function createConnections(topoData, nodeTypeArray) { if (topoData == null) {
return ;
}
var rootData = topoData;
var relData = topoData.rel;
var i=0, len = relLength(relData);;
for (i=0; i < len; i++) {
connectionNodes(rootData, relData[i], nodeTypeArray);
createConnections(relData[i], nodeTypeArray);
}
} /**
* 连接起始节点和终止节点
* @beginNode 起始节点
* @endNode 终止节点
* NOTE: 根据是起始节点与终止节点的类型
*/
function connectionNodes(beginNode, endNode, nodeTypeArray)
{
var startNodeType = beginNode.type;
var endNodeType = endNode.type;
var startDirection = '';
var endDirection = ''; var VM_TYPE = nodeTypeArray[0];
var DEVICE_TYPE = nodeTypeArray[1];
var NC_TYPE = nodeTypeArray[2];
var VIP_TYPE = nodeTypeArray[3]; switch (startNodeType) {
case VM_TYPE:
if (endNodeType == VIP_TYPE) {
// VIP 绘制于 VM 上方
startDirection = 'Top';
endDirection = 'Bottom';
}
else if (endNodeType == DEVICE_TYPE) {
// DEVICE 绘制于 VM 右方
startDirection = 'Right';
endDirection = 'Left';
}
else if (endNodeType == NC_TYPE) {
// NC 绘制于 VM 下方
startDirection = 'Bottom';
endDirection = 'Top';
}
break;
case VIP_TYPE:
if (endNodeType == VM_TYPE) {
// VM 绘制于 VIP 上方
startDirection = 'Top';
endDirection = 'Top';
}
break;
case NC_TYPE:
if (endNodeType == VM_TYPE) {
// VM 绘制于 NC 下方
startDirection = 'Bottom';
endDirection = 'Bottom';
}
break;
default:
break;
}
var startPoint = obtainNodeDivId(beginNode) + startDirection;
var endPoint = obtainNodeDivId(endNode) + endDirection;
jsPlumb.connect({uuids:[startPoint, endPoint], editable: false});
} function createDiv(metaNode) {
return '<div class="node" id="' + obtainNodeDivId(metaNode) + '"><strong>'
+ metaNode.type + '<br/><a href="http://aliyun.com">' + metaNode.key + '</a><br/></strong></div>'
} /**
* 生成节点的 DIV id
* divId = nodeType.toUpperCase + "-" + key
* key 可能为 IP , 其中的 . 将被替换成 ZZZ , 因为 jquery id 选择器中 . 属于转义字符.
* eg. {type: 'VM', key: '1.1.1.1' }, divId = 'VM-1ZZZ1ZZZ1ZZZ1'
*/
function obtainNodeDivId(metaNode) {
return metaNode.type.toUpperCase() + '-' + transferKey(metaNode.key);
} function transferKey(key) {
return key.replace(/\./g, 'ZZZ');
} function revTransferKey(value) {
return value.replace(/ZZZ/g, '.');
} /**
* 合并新的拓扑结构到原来的拓扑结构中, 新的拓扑结构中有节点与原拓扑结构中的某个节点相匹配: type-key 相等
* @param srcTopoData 原来的拓扑结构
* @param newTopoData 要添加的的拓扑结构
*/
function mergeNewTopo(srcTopoData, newTopoData) { var srcTopoData = shallowCopyTopo(srcTopoData); if (srcTopoData == null || newTopoData == null) {
return srcTopoData || newTopoData;
} var srcRoot = srcTopoData;
var newRoot = newTopoData; var newRelData = newTopoData.rel;
var i=0, newRelLen = relLength(newRelData); var matched = findMatched(srcRoot, newRoot);
if (matched == null) {
// 没有找到匹配的节点, 直接返回原有的拓扑结构
return srcTopoData;
}
matched.rel = matched.rel.concat(newRelData);
return srcTopoData;
} /**
* 在原拓扑结构中查找与新拓扑结构根节点 newRootData 匹配的节点
* @param srcRootData 原拓扑结构
* @param newRootData 新拓扑结构的根节点
* @returns 原拓扑结构中与新拓扑结构根节点匹配的节点 or null if not found
*/
function findMatched(srcRootData, newRootData) {
var srcRelData = srcRootData.rel;
var i=0, srcRelLen = relLength(srcRelData);
var matched = null;
if ((srcRootData.type == newRootData.type) && (srcRootData.key == newRootData.key)) {
return srcRootData;
}
for (i=0; i<srcRelLen; i++) {
matched = findMatched(srcRelData[i], newRootData);
if (matched != null) {
return matched;
}
}
return matched;
} function relLength(relData) {
if (isArray(relData)) {
return relData.length;
}
return 0;
} function isArray(value) {
return value && (typeof value === 'object') && (typeof value.length === 'number');
} /**
* 浅复制拓扑结构
*/
function shallowCopyTopo(srcTopoData) {
return srcTopoData;
} /**
* 深复制拓扑结构
*/
function deepCopyTopo(srcTopoData) {
//TODO identical to deep copy of js json
}
topodemo.html 绘制拓扑图的客户端接口。 只要引进相应的依赖 JS,预置一个 <div id="topoRegion"></div>
<!doctype html>
<html>
<head>
<title>jsPlumb 1.5.3 - flowchart connectors demonstration - jQuery</title>
<link rel="stylesheet" href="topo-all.css">
<link rel="stylesheet" href="topo.css"> <!-- DEP -->
<script src="../jsPlumb/jquery-1.9.0-min.js"></script>
<script src="../jsPlumb/jquery-ui-1.9.2-min.js"></script> <!-- /DEP --> <!-- JS -->
<!-- support lib for bezier stuff -->
<script src="../jsPlumb/jsBezier-0.6-min.js"></script>
<!-- jsplumb geom functions -->
<script src="../jsPlumb/jsplumb-geom-0.1.js"></script>
<!-- jsplumb util -->
<script src="../jsPlumb/util.js"></script>
<!-- base DOM adapter -->
<script src="../jsPlumb/dom-adapter.js"></script>
<!-- main jsplumb engine -->
<script src="../jsPlumb/jsPlumb.js"></script>
<!-- endpoint -->
<script src="../jsPlumb/endpoint.js"></script>
<!-- connection -->
<script src="../jsPlumb/connection.js"></script>
<!-- anchors -->
<script src="../jsPlumb/anchors.js"></script>
<!-- connectors, endpoint and overlays -->
<script src="../jsPlumb/defaults.js"></script>
<!-- connector editors -->
<script src="../jsPlumb/connector-editors.js"></script>
<!-- bezier connectors -->
<script src="../jsPlumb/connectors-bezier.js"></script>
<!-- state machine connectors -->
<script src="../jsPlumb/connectors-statemachine.js"></script>
<!-- flowchart connectors -->
<script src="../jsPlumb/connectors-flowchart.js"></script>
<!-- SVG renderer -->
<script src="../jsPlumb/renderers-svg.js"></script>
<!-- canvas renderer -->
<script src="../jsPlumb/renderers-canvas.js"></script>
<!-- vml renderer -->
<script src="../jsPlumb/renderers-vml.js"></script> <!-- jquery jsPlumb adapter -->
<script src="../jsPlumb/jquery.jsPlumb.js"></script>
<!-- /JS --> <!-- demo code -->
<script src="drawtopo.js"></script> <script type="text/javascript">
jsPlumb.bind("ready", function() { // 拓扑数据结构根节点位置设置
var rootPosition = [270, 300];
var nodeTypeArray = ['VM', 'DEVICE', 'NC', 'VIP'];
var topoData = {
type: 'VM', key: '110.75.188.35',
rel: [
{
type: 'DEVICE',
key: '3-120343'
}, {
type: 'DEVICE',
key: '3-120344'
}, {
type: 'VIP',
key: '223.6.250.2',
rel: [
{ type: 'VM', key: '110.75.189.12' },
{ type: 'VM', key: '110.75.189.13' }
]
}, {
type: 'NC',
key: '10.242.192.2',
rel: [
{ type: 'VM', key: '110.75.188.132' },
{ type: 'VM', key: '110.75.188.135' }
] }
]
}; drawTopo(topoData, rootPosition, nodeTypeArray); var newTopoData = {
type: 'NC',
key: '10.242.192.2',
rel: [
{ type: 'VM', key: '110.75.188.140' }
]
}; var mergedTopoData = mergeNewTopo(topoData, newTopoData);
$('#topoRegion').empty();
drawTopo(mergedTopoData, rootPosition, nodeTypeArray); }); </script> </head>
<body> <div id="topoRegion">
</div> </body>
</html>
样式文件及依赖JS 见工程示例。 里面已经包含绘制拓扑图的最小依赖。
四、 最终效果图
五、 小结
这里看到, 数据结构/算法与实际应用的一个很好的结合。 所以说, 绝不要忽视数据结构和算法的作用, 实际上, 只要仔细去发现, 去挖掘, 就会发现其大有用武之地。
使用 JsPlumb 绘制拓扑图的通用方法的更多相关文章
- 使用JsPlumb绘制拓扑图的通用方法
转自:http://www.it165.net/pro/html/201311/7616.html 使用JsPlumb绘制拓扑图的通用方法 一. 实现目标 绘制拓扑图, 实际上是个数据结构和算法的问题 ...
- 使用 jsPlumb 绘制拓扑图 —— 异步加载与绘制的实现
本文实现的方法可以边异步加载数据边绘制拓扑图. 有若干点需要说明一下: 1. 一次性获取所有数据并绘制拓扑图, 请参见文章: <使用 JsPlumb 绘制拓扑图的通用方法> ; 本文实现 ...
- 使用 jsPlumb 绘制拓扑图 —— 异步载入与绘制的实现
本文实现的方法能够边异步载入数据边绘制拓扑图. 有若干点须要说明一下: 1. 一次性获取全部数据并绘制拓扑图. 请參见文章: <使用 JsPlumb 绘制拓扑图的通用方法> ; 本文实现 ...
- 使用 highchart 绘制柱状图的通用方法与接口
本文给出使用 highchart 绘制柱状图的通用方法与接口, 只要指定相应的数据结构和配置, 就可以直接拿来使用. 一. 数据结构与基本接口 一般绘制图形, 会涉及到较复杂的数据结构, 比如使 ...
- 使用java泛型设计通用方法
泛型是Java SE 1.5的新特性, 泛型的本质是参数化类型, 也就是说所操作的数据类型被指定为一个参数. 因此我们可以利用泛型和反射来设计一些通用方法. 现在有2张表, 一张user表和一张stu ...
- .NET基础架构方法—DataTableToExcel通用方法
p { display: block; margin: 3px 0 0 0; } --> .NET架构基础方法—DataTableToExcel通用方法(NPOI) 今天封装DataTaleTo ...
- DataTable数据赋值给Model通用方法
注:该文属本人原创,今后项目中发现该方法存在BUG会实时更新,转载记得附上原文出处,方便大家获得最新代码. 相信大家在做项目中,经常会根据不同的表new各种不同的Model,当需要对Model进行实例 ...
- 带毫秒的字符转换成时间(DateTime)格式的通用方法
C#自身有更好的方式,Net任意String格式转换为DateTime类型 ====================================================== 原文 ==== ...
- C# 深拷贝通用方法
C#深拷贝通用方法(引用类型的拷贝) /// <summary> /// 深度COPY /// </summary> /// <typeparam name=" ...
随机推荐
- jenkins之-----------在必要的时候并发构建
jenkins支持在同一节点支持同任务并发,但存在形成报告时会覆盖相应的文件,查看官网后发现有介绍“在必要的时候并发构建” 大致意思就是:将此项勾选后,可以同时执行,执行在任务自己的workspace ...
- 无向连通图求割边(桥)hdu4738,hdu3849
点击打开链接 题目链接: hdu 4738 题目大意: 曹操有N个岛,这些岛用M座桥连接起来 每座桥有士兵把守(也可能没有) 周瑜想让这N个岛不连通,但只能炸掉一座桥 并且炸掉一座桥需要派出不 ...
- nginx 开机自动启动
接下来聊一聊nginx的开机自启吧 看了看都是用脚本启动的,我也就不扯啥犊子了,都是前人经验 我的操作系统是centos 7 nginx版本是1.10.3 首先看一下自己的nginx配置 我的是 ./ ...
- SVN Hook造成SVN提交速度慢的问题
单就个人感情来说,我其实喜欢git.但显然subversion才是更普遍的版本控制管理工具,适合用在团队开发中. 那么,有一个很常见的需求就是把工程师提交的代码,更新到htdocs目录,这时候需要用s ...
- 注意:darknet安装
参考:https://github.com/AlexeyAB/darknet # 一.安装 ## linux下安装 - 在darknet目录下执行make ## windows下安装 1. 安装vs2 ...
- 【紫书】Undraw the Trees UVA - 10562 递归,字符串
题意:给你画了一颗树,你要把它的前序输出. 题解:读进到二维数组.边解析边输出. 坑:少打了个-1. #define _CRT_SECURE_NO_WARNINGS #include<cstri ...
- SSL/TLS 握手优化详解
随着 HTTP/2 的逐渐普及,以及国内网络环境越来越糟糕(运营商劫持和篡改),HTTPS 已经开始成为主流.HTTPS 在 TCP 和 HTTP 之间增加了 TLS(Transport Layer ...
- Nginx日志切割之Logrotate篇
不管是什么日志文件,都是会越来越大的,大到一定程度就是个可怕的事情了,所以要及早的做处理,方法之一就是按时间段来存储,不过linux系统提供了Logrotate的日志管理工具,很好用,不用写计划任务脚 ...
- Count the Colors---zoj1610线段树
题目链接 题意: 求每种颜色有几段线段: 模拟数组: #include<stdio.h> #include<iostream> #include<algorithm> ...
- 【PyQt5-Qt Designer】QComboBox(下拉列表框) 使用模板
import sys from PyQt5.QtWidgets import * from PyQt5.QtGui import * from PyQt5.QtCore import * ###### ...