摘要: 实现 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 绘制拓扑图的通用方法的更多相关文章

  1. 使用JsPlumb绘制拓扑图的通用方法

    转自:http://www.it165.net/pro/html/201311/7616.html 使用JsPlumb绘制拓扑图的通用方法 一. 实现目标 绘制拓扑图, 实际上是个数据结构和算法的问题 ...

  2. 使用 jsPlumb 绘制拓扑图 —— 异步加载与绘制的实现

    本文实现的方法可以边异步加载数据边绘制拓扑图. 有若干点需要说明一下: 1.  一次性获取所有数据并绘制拓扑图, 请参见文章: <使用 JsPlumb 绘制拓扑图的通用方法> ; 本文实现 ...

  3. 使用 jsPlumb 绘制拓扑图 —— 异步载入与绘制的实现

    本文实现的方法能够边异步载入数据边绘制拓扑图. 有若干点须要说明一下: 1.  一次性获取全部数据并绘制拓扑图. 请參见文章: <使用 JsPlumb 绘制拓扑图的通用方法> ; 本文实现 ...

  4. 使用 highchart 绘制柱状图的通用方法与接口

    本文给出使用 highchart 绘制柱状图的通用方法与接口, 只要指定相应的数据结构和配置, 就可以直接拿来使用. 一.  数据结构与基本接口   一般绘制图形, 会涉及到较复杂的数据结构, 比如使 ...

  5. 使用java泛型设计通用方法

    泛型是Java SE 1.5的新特性, 泛型的本质是参数化类型, 也就是说所操作的数据类型被指定为一个参数. 因此我们可以利用泛型和反射来设计一些通用方法. 现在有2张表, 一张user表和一张stu ...

  6. .NET基础架构方法—DataTableToExcel通用方法

    p { display: block; margin: 3px 0 0 0; } --> .NET架构基础方法—DataTableToExcel通用方法(NPOI) 今天封装DataTaleTo ...

  7. .NET基础架构方法—DataTableToList通用方法

    p { display: block; margin: 3px 0 0 0; } --> .NET架构基础方法—DataTableToList通用方法   我们经常需要将从数据库中所读取的数据以 ...

  8. DataTable数据赋值给Model通用方法

    注:该文属本人原创,今后项目中发现该方法存在BUG会实时更新,转载记得附上原文出处,方便大家获得最新代码. 相信大家在做项目中,经常会根据不同的表new各种不同的Model,当需要对Model进行实例 ...

  9. 带毫秒的字符转换成时间(DateTime)格式的通用方法

    C#自身有更好的方式,Net任意String格式转换为DateTime类型 ====================================================== 原文 ==== ...

随机推荐

  1. css水平垂直居中(绝对定位居中)

    使用绝对定位有个限制就是父集必须设置一个固定的高度. 首先HTML <div id="box"> <div class="child"> ...

  2. VMvare克隆复制多个操作系统

    目的:完成linux的双机和集群实验 2016-12-06 在网上查找了一些资料现整理如下,以供之后查看和帮助他人. 注意事项: 1.关闭源虚拟机的电源: 操作很简单 选择完整创建 输入源克隆机的用户 ...

  3. HTTP头的Expires与Cache-control

    HTTP头的Expires与Cache-control 1.概念 Cache-control用于控制HTTP缓存(在HTTP/1.0中可能部分没实现,仅仅实现了Pragma: no-cache) 数据 ...

  4. java的debug和release编译方式

    以前没有注意过,其实Java编译成.class 有两种方式 使用javac,默认使用的release方式,而我们经常使用的MyEclipse工具,用的是debug模式. 区别暂时了解如下: relea ...

  5. EasyUEFI

    ---------------------------------http://www.easyuefi.com/downloads/EasyUEFI_Setup.exe--------------- ...

  6. 有向图的邻接矩阵表示法(创建,DFS,BFS)

    package shiyan; import java.util.LinkedList; import java.util.Queue; import java.util.Scanner; publi ...

  7. BZOJ 1293: [SCOI2009]生日礼物【单调队列】

    1293: [SCOI2009]生日礼物 Time Limit: 10 Sec  Memory Limit: 162 MBSubmit: 2534  Solved: 1383[Submit][Stat ...

  8. 利用Python进行数据分析——pandas入门

    利用Python进行数据分析--pandas入门 基于NumPy建立的 from pandas importSeries,DataFrame,import pandas as pd 一.两种数据结构 ...

  9. python———day04

    一.tuple (元组) 元组被称为制度列表,可以被查询,但是不能被修改. 元组写在小括号里(),元素之间用逗号隔开. tup1 = () #空元组 tup2 = (20,) #只有一个元素的元组 记 ...

  10. springboot面试专题及答案

    声明:此文章非本人所 原创,是别人分享所得,如有知道原作者是谁可以联系本人,如有转载请加上此段话 问题一 什么是 Spring Boot? 多年来,随着新功能的增加,spring 变得越来越复杂.只需 ...