前言

通过结合 HTML5 和 OpenLayers 可以组合成非常棒的一个电信地图网络拓扑图的应用,形成的效果可以用来作为电信资源管理系统,美食定位分享软件,片区找房,绘制铁轨线路等等,各个领域都能够涉及的一款应用。虽然这个 Demo 是结合 OpenLayers3 的,其实还可推广到与 ArcGIS、百度地图以及 GoogleMap 等众多 GIS 地图引擎融合。

http://www.hightopo.com/demo/openlayers/

代码生成

创建地图

OpenLayers 是一个用于开发 WebGIS 客户端的 JavaScript 包。OpenLayers 支持的地图来源包括 Google Maps、Yahoo、 Map、微软 Virtual Earth 等多种离线在线地图,这里用到的是比较大众化的谷歌地图 Google Map 的在线地图,使用 OpenLayers 前只需要引入相关的类库以及 css 文件:

<link rel="stylesheet" href="css/ol.css" type="text/css">
<script src="lib/ol.js"></script>

初始化地图的操作则是将 Map 放进一个 div 元素中,初始化一个 ol.Map 地图类,这在整个电信资源管理系统中必不可少,然后设置这个类中的各个参数:

var mapDiv = document.getElementById('mapDiv');
map = new ol.Map({target: 'mapDiv', // 地图容器controls: ol.control.defaults().extend([graphViewControl, // 自定义拓扑控件new ol.control.OverviewMap(), // 地图全局视图控件new ol.control.ScaleLine(), // 比例尺控件new ol.control.ZoomSlider(), // 缩放刻度控件new ol.control.ZoomToExtent() // 缩放到全局控件]),layers: [ // 图层new ol.layer.Tile({source: new ol.source.XYZ({ // 谷歌地图 url:'http://www.google.cn/maps/vt/pb=!1m4!1m3!1i{z}!2i{x}!3i{y}!2m3!1e0!2sm!3i345013117!3m8!2szh-CN!3scn!5e1105!12m4!1e68!2m2!1sset!2sRoadmap!4e0'  })  })],view: new ol.View({ // 地图视图projection: 'EPSG:3857', // 投影center: ol.proj.fromLonLat([106, 35]), // 视图的初始中心 中心的坐标系由projection选项指定    zoom: 4 // 缩放级别 用于计算视图的初始分辨率})
});

上面的代码根据每行的代码注释加上官方 API 解释应该没有什么难度。细心的朋友可能注意到了一个非官方的控件:graphViewControl 控件,这个控件是我自定义出来,用来在这个控件上绘制拓扑图形的,声明和定义部分在 GraphViewControl.js 文件中。

自定义控件


自定义 OpenLayers 的控件,无非就是将某个类继承于 ol.control.Control 类,然后针对不同的需求重写父类方法或者增加方法。

我在声明类的时候传了一个 options 参数,通过在定义类的时候设置控件的容器元素并且将控件渲染到 GIS 地图的 viewport 之外:

var view = graphView.getView(); // 获取拓扑组件的 div
ol.control.Control.call(this, {element: view, // 控件的容器元素
target: options.target // 将控件渲染到地图的视口之外
});

上面的 graphView 是通过 GraphViewControl 在父类方法上新添加的一个方法并且初始化值为 ht.graph.GraphView(https://hightopo.com/guide/guide/core/beginners/ht-beginners-guide.html#ref_graphview),HT 的拓扑图形组件:

// 获取GraphView对象
GraphViewControl.prototype.getGraphView = function() { return this._graphView; };
var graphView = this._graphView = new ht.graph.GraphView(); // 拓扑图组件

我在控件中还给 graphView 拓扑组件添加了一些事件的监听,由于 OpenLayers 和 HT 是两款不同的 js 库,有着各自的交互系统和坐标系,首先我们将某些我们需要获取在 HT 上做的交互事件并停止事件传播到 OpenLayers 上:

// 拖拽 node 时不移动地图
var stopGraphPropagation = function(e) {var data = graphView.getDataAt(e); // 获取 graphView 事件下的节点var interaction = graphView.getEditInteractor(); // 获取编辑交互器if (data || e.metaKey || e.ctrlKey || interaction && interaction.gvEditing) {e.stopPropagation(); // 不再派发事件 该方法将停止事件的传播,阻止它被分派到其他 Document 节点}
}/** pointerdown 当指针变为活动事件
*    对于鼠标,当设备从按下的按钮转换到至少一个按钮被按下时,它会被触发。
*    对于触摸,当与数字化仪进行物理接触时会被触发。
*    对于笔,当触笔与数字化仪进行物理接触时会被触发。
**/
view.addEventListener('pointerdown', stopGraphPropagation, false);
view.addEventListener('touchstart', stopGraphPropagation, false); // 当触摸点被放置在触控面板上事件
view.addEventListener('mousedown', stopGraphPropagation, false); // 鼠标点下事件

GraphViewControl 类定义部分还添加了一些关于移动和编辑节点的交互事件,主要是将节点的像素坐标转为 OpenLayers 的 ol.Cordinate 地图视图投影中的坐标并存储到节点的业务属性(HT 的一个可以存储任意值的对象)中,这样我们只需要通过获取或设置节点的业务属性 coord 就可以自由获取和设置节点在 map 上的像素坐标。

var position = data.getPosition(), // 获取选中节点的坐标x = position.x + graphView.tx(), // 节点横坐标+graphView水平平移值y = position.y + graphView.ty(); // 节点纵坐标+graphView垂直平移值var coord = map.getCoordinateFromPixel([x, y]); // 根据坐标的像素获取地图视图投影中的坐标
data.a('coord', coord);

这里我就提一些基础的功能,其他的就不作解释了,只是一些扩展。

值得注意的一点是,我们在上面对节点在电信 GIS 地图视图投影中的坐标进行了数据存储,但是这个方法对于 Shape 类型的节点来说不太合适,因为地图上一般都是用点围成区域面,勾勒出某个国家或者某个城市的轮廓,缩放的时候并不实时保持大小,而是根据地图的缩放来缩放,实时保持在电信 GIS 地图的某个位置,所以我对 Shape 类型的节点中所有的点遍历了一遍,都设置了业务属性 pointCoord,获取地图视图投影中的坐标:

// 给 shape 类型的节点的每个点位置都设置为经纬度
if (e.kind === 'endEditPoint' || e.kind === 'endEditPoints' || e.kind === 'endEditResize' || e.kind === 'endMove') {if (data instanceof ht.Shape) { // Shape 类型的节点data.getPoints().forEach(function(point, index) {var pointCoord = map.getCoordinateFromPixel([point.x, point.y]); // 获取给定像素的坐标data.a('pointCoord['+index+']', pointCoord);});}
}

图层叠加


OpenLayers 的结构比较复杂,而 HT 相对来说简单很多,所以我将 HT 叠加到 OpenLayers Map 的 viewport 中。这里我在子类 GraphViewControl 中重载了父类 ol.control.Control 的 setMap 方法,在此方法中将 HT 的拓扑组件 graphView 添加到 OpenLayers 的视图 viewport 中,我们知道,HT 的组件一般都是绝对定位的,所以我们要设置 css 中的位置和宽高属性:

var graphView = self._graphView; // = GraphViewControl.getGraphView()
var view = graphView.getView(); // 获取 graphView 组件的 div
var dataModel = graphView.getDataModel(); // 获取 graphView 的数据容器
view.style.top = '0';
view.style.left = '0';
view.style.width = '100%';
view.style.height = '100%';map.getViewport().insertBefore(view, map.getViewport().firstChild); // getViewPort 获取用作地图视口的元素 insertBefore 在指定的已有子节点(参数二)之前插入新的子节点(参数一)

并对数据容器增删变化事件进行监听,通过监听当前加入数据容器的节点类型,将当前节点的像素坐标转为地图视图投影中的坐标存储在节点的业务属性 coord 上:

dataModel.addDataModelChangeListener(function(e) { // 数据容器增删改查变化监听if (e.kind === 'add' && !(e.data instanceof ht.Edge)) { // 添加事件&&事件对象不是 ht.Edge 类型if (e.data instanceof ht.Node) {var position = e.data.getPosition();var coordPosition = map.getCoordinateFromPixel([position.x, position.y]); // 获取给定像素的坐标e.data.a('coord', coordPosition);}if (e.data instanceof ht.Shape) { // 给 shape 类型的节点上的每个点都设置经纬度e.data.getPoints().forEach(function(point, index) { // 对 shape 类型的节点则将所有点的坐标都转为经纬度var pointCoord = map.getCoordinateFromPixel([point.x, point.y]); // 获取给定像素的坐标e.data.a('pointCoord['+index+']', pointCoord);});}}
});

最后监听地图更新事件,重设拓扑:

map.on('postrender', function() { self.resetGraphView(); });

坐标转换

重设拓扑在这边的意思就是将拓扑图中节点坐标从我们一开始设置在 HT 中的像素坐标重新通过地图的缩放或者移动将地图视图投影中的坐标转为像素坐标设置到节点上,这时候前面存储的业务属性 coord 就派上用场了,记住,Shape 类型的节点是例外的,还是要对其中的每个点都重新设置坐标:

GraphViewControl.prototype.resetGraphView = function() { // 重置 graphView 组件的状态var graphView = this._graphView;graphView.tx(0); // grpahView 水平平移值graphView.ty(0); // graphView 垂直平移值graphView.dm().each(function(data) { // 遍历 graphView 中的数据容器 var coord = data.a('coord'); // 获取节点的业务属性 coordif (coord) {var position = map.getPixelFromCoordinate(coord); // 获取给定坐标的像素data.setPosition(position[0], position[1]); // 重新给节点设置像素坐标}if (data instanceof ht.Shape) {var points = data.toPoints(); // 构建一个新的 Shape 点集合并返回data.getPoints().clear(); // 清空点集合data._points = new ht.List();points.forEach(function(point, index) { // 给 shape 重新设置每一个点的像素坐标point.x = map.getPixelFromCoordinate(data.a('pointCoord['+ index +']'))[0];point.y = map.getPixelFromCoordinate(data.a('pointCoord['+ index +']'))[1];data._points.add(point);});data.setPoints(data._points);}});graphView.validate(); //刷新拓扑组件
}

场景搭建


OpenLayers 的 Map 部分做好了,接下来就是将它放进场景中了~但是从上面的截图中能看到,除了地图,顶部有工具条(但是我是用 formPane 表单组件做的),左侧有一个可供拖拽的 Palette 面板组件(https://hightopo.com/guide/guide/plugin/palette/ht-palette-guide.html),通过 HT 的 borderPane 边框面板组件(https://hightopo.com/guide/guide/core/borderpane/ht-borderpane-guide.html)将整个场景布局好:

raphViewControl = new GraphViewControl(); // 自定义控件,作为 openlayers 地图上自定义控件
graphView = graphViewControl.getGraphView(); // 获取拓扑图组件
dm = graphView.getDataModel(); // 获取拓扑图中的数据容器palette = new ht.widget.Palette(); // 创建一个组件面板
formPane = createFormPane(); // 工具条的 form 表单borderPane = new ht.widget.BorderPane(); // 边框面板组件
borderPane.setTopView(formPane); // 设置顶部组件为 formPane
borderPane.setLeftView(palette, 260); // 设置左边组件为 palette 参数二为设置 该 view 的宽度
borderPane.setCenterView(mapDiv); // 设置中间组件为 mapDivborderPane.addToDOM(); // 将面板组件添加到 body 中

这样整个场景的布局和显示就完成了,非常轻松~

工具条


本身 HT 有自带的工具条,但是因为 form 表单(https://hightopo.com/guide/guide/plugin/form/ht-form-guide.html)在排布以及样式上面可以更灵活,所以采用这个。

var fp = new ht.widget.FormPane();
fp.setVGap(0); // 设置表单组件水平间距 默认值为6
fp.setHGap(0); // 设置表单的行垂直间距 默认值为6
fp.setHPadding(4); // 设置表单左边和右边与组件内容的间距,默认值为8
fp.setVPadding(4); // 设置表单顶部和顶部与组件内容的间距,默认值为8
fp.setHeight(40); // 设置表单高度var btBgColor = '#fff',btnIconColor = 'rgb(159, 159, 159)',btnSelectColor = 'rgb(231, 231, 231)';fp.addRow([ // 添加行 首尾各加了一个'',并且占的宽度均为相对值0.1,就会将中间部分居中'', {id: 'select', // id 唯一标示属性,可通过 formPane.getItemById(id) 获取添加到对应的 item 对象button: { // ht.widget.Button 为按钮类background: btBgColor, // 设置背景颜色icon: './symbols/icon/select.json', // 设置图标iconColor: btnIconColor, // 设置图标颜色selectBackground: btnSelectColor, // 设置选中背景颜色togglable: true, // 设置按钮是否处于开关状态groupId: 't', // 设置组编号,属于同组的togglable按钮具有互斥功能toolTip: '编辑', // 设置文字提示,可通过 enableToolTip() 和 disableToolTip() 启动和关闭文字提示onClicked: function() { // 按钮点击触发函数editableFunc();}}}, {id: 'pointLine',button: {background: btBgColor,icon: './symbols/icon/line.json',iconColor: btnIconColor,selectBackground: btnSelectColor,togglable: true,groupId: 't',toolTip: '连线',onClicked: function () {/** 通过 setInteractors 组合交互器* DefaultInteractor实现Group、Edge和SubGraph图元的默认双击响应,手抓图平移,滚轮缩放,键盘响应等功能* TouchInteractor实现移动设备上的Touch交互功能* CreateEdgeInteractor 为 CreateEdgeInteractor.js 文件中自定义的连线交互器* CreateShapeInteractor 为 CreateShapeInteractor.js 文件中自定义的多边形交互器**/graphView.setInteractors([new ht.graph.DefaultInteractor(graphView), new ht.graph.TouchInteractor(graphView, {selectable: false}), new CreateEdgeInteractor(graphView)]);}}},''
], [0.1, 36, 36, 0.1]);

上面的 form 表单中添加行我只列出了两个功能,一个编辑的功能,另一个绘制连线的功能。formPane.addRow 为添加一行元素,参数一为元素数组,元素可为字符串、json 格式描述的组件参数信息、html 元素或者为 null 的空,参数二为为每个元素宽度信息数组,宽度值大于1代表固定绝对值,小于等于1代表相对值,也可为 80+0.3 的组合。

为了让我想显示的部分显示在工具栏的正中央,所以我在第一项和最后一项都设置了一个空,占 0.1 的相对宽度,并且比例相同,所以中间的部分才会显示在正中央。

上面代码通过 setInteractors 组合我们所需要的交互器。DefaultInteractor 实现 Group、Edge 和 SubGraph 图元的默认双击响应,手抓图平移,滚轮缩放,键盘响应等功能;TouchInteractor 实现移动设备上的 Touch 交互功能。至于最后面的 CreateEdgeInteractor 则是继承于 ht.graph.Interactor 交互器的创建连线的交互器。这里细细地分析一下这个部分,以后就可以修改或者自定义新的交互器。

自定义交互器


我们通过 ht.Default.def(className, superClass, methods) 定义类,并在 methods 对象中对方法和变量进行声明。

setUp 方法在对象被创建的时候被调用,根据需求在这里设置一些功能,我设置的是清除所有的选中的节点:

setUp: function () { // CreateEdgeInteractor 对象被创建的时候调用的函数CreateEdgeInteractor.superClass.setUp.call(this);this._graphView.sm().cs(); // 清除所有选中
}

tearDown 方法在对象结束调用的时候被调用,绘制连线的时候,如果未结束绘制怎么办?下一次绘制不可能连着上一次继续绘制,所以我们得在结束调用这个类的时候将之前的绘制的点都清除:

tearDown: function () { // CreateEdgeInteractor 对象结束调用的时候调用的函数CreateEdgeInteractor.superClass.tearDown.call(this);// 清除连线起点、终点以及连线中间的各个点  this._source = null;this._target = null;this._logicalPoint = null;
}

关于鼠标事件以及 touch 事件,我希望这两者在操作上相同,所以直接在鼠标事件中调用的 touch 事件的方法。

绘制连线需要鼠标左键先选中一个节点,然后拖动鼠标左键不放,移动鼠标到连线的终点节点上,此时一条连线创建完毕。

首先是 touchstart 选中一个节点:

handle_mousedown: function (e) { // 鼠标点下事件this.handle_touchstart(e);
},
handle_touchstart: function (e) { // 开始 touchthis._sourceNode = this.getNodeAt(e); // 获取事件下的节点if (this._sourceNode) {this._targetNode = null; // 初始化 targetNodethis.startDragging(e);this._graphView.addTopPainter(this); // 增加顶层 Painter 使用 Canvas 的画笔对象自由绘制任意形状,顶层Painter绘制在拓扑最上面this._graphView.sm().ss(this._sourceNode); // 设置选中}
},
getNodeAt: function(e){// 获取事件下的节点if (ht.Default.isLeftButton(e) && ht.Default.getTouchCount(e) === 1) { // 鼠标左键被按下 && 当前 Touch 手指个数为1var data = this._graphView.getDataAt(e); // 获取事件下的节点if(data instanceof ht.Node) return data; // 为 ht.Node 类型的节点} return null;
}


然后手指滑动 touchmove :

handleWindowMouseMove: function (e) {this.handleWindowTouchMove(e);
},
handleWindowTouchMove: function (e) { // 手指滑动var graphView = this._graphView; // 拓扑组件this.redraw(); // 如果不重新绘制矩形区域,那么容易造成脏矩形this._logicalPoint = graphView.getLogicalPoint(e); // 获取事件下的逻辑坐标this._targetNode = this.getNodeAt(e); // 获取事件下的 edge 的终点if (this._targetNode) graphView.sm().ss([this._sourceNode, this._targetNode]); // 设置起始和终止节点都被选中else graphView.sm().ss([this._sourceNode]); // 只选中起始节点
},
redraw: function () {var p1 = this._sourceNode.getPosition(), // 获取连线起始端的节点的坐标p2 = this._logicalPoint;if (p1 && p2) {var rect = ht.Default.unionPoint(p1, p2); // 将点组合成矩形ht.Default.grow(rect, 1); // 改变rect大小,上下左右分别扩展 extend 的大小this._graphView.redraw(rect); // 重绘拓扑,rect参数为空时重绘拓扑中的所有图元,否则重绘矩形范围内的图元}
}

最后 touchend 创建连线:

handleWindowMouseUp: function (e) {this.handleWindowTouchEnd(e);
},
handleWindowTouchEnd: function (e) { if (this._targetNode) {var edge = new ht.Edge(this._sourceNode, this._targetNode); // 创建新的连线节点if (this._edgeType) edge.s('edge.type', this._edgeType); // 设置连线的类型this._graphView.dm().add(edge); // 将节点添加进数据容器this._graphView.sm().ss(edge); // 设置选中您当前连线}editableFunc(); // 绘制结束后 工具条选中“编辑”项this._graphView.removeTopPainter(this); // 移除顶层画笔
}

至于还未创建连线之前(也就是说为选中终止节点),鼠标在拖动的过程中会创建一条连线,这里是直接用 canvas 绘制的:

draw: function (g) { // 绘制起点与鼠标移动位置的连线var p1 = this._sourceNode.getPosition(),p2 = this._logicalPoint;    if(p1 && p2){g.lineWidth = 1;g.strokeStyle = '#1ABC9C';g.beginPath();g.moveTo(p1.x, p1.y);g.lineTo(p2.x, p2.y);g.stroke();              }
}

这样,自定义连线类结束!

面板组件


左侧面板组件 ht.widget.Palette (https://hightopo.com/guide/guide/plugin/palette/ht-palette-guide.html)支持自定义样式及单选、拖拽操作,由 ht.DataModel 驱动,用 ht.Group 展示分组,ht.Node 展示按钮元素。

展示分组,首先得创建分组和组中的按钮元素:

function initPalette(palette) { // 加载 palette 面板组件中的图元var nodeArray = ['city', 'equipment'];var nameArray = ['城市', '大型']; // arrNode 中的 index 与 nameArr 中的一一对应for (var i = 0; i < nodeArray.length; i++) {var name = nameArray[i];nodeArray[i] = new ht.Group(); // palette 面板是将图元都分在“组”里面,然后向“组”中添加图元即可palette.dm().add(nodeArray[i]); // 向 palette 面板组件中添加 group 图元nodeArray[i].setExpanded(true); // 设置分组为打开的状态nodeArray[i].setName(name); // 设置组的名字var imageArray = [];switch(i){case 0:imageArray = ['symbols/5.json', 'symbols/6.json', 'symbols/叉车.json', 'symbols/公交车.json', 'symbols/人1.json', 'symbols/人2.json', 'symbols/人3.json', 'symbols/树.json', 'symbols/树2.json'];break;case 1: imageArray = ['symbols/飞机.json', 'symbols/吊机.json', 'symbols/卡车.json', 'symbols/货轮.json', 'symbols/龙门吊.json', 'symbols/公园.json'];break;default: break;}setPaletteNode(imageArray, nodeArray[i], palette);}
}function setPaletteNode(imageArray, array, palette) { // 创建 palette 上 节点及设置名称、显示图片、父子关系for (var i = 0; i < imageArray.length; i++) {var imageName = imageArray[i],name = imageName.slice(imageName.lastIndexOf('/')+1, imageName.lastIndexOf('.')); // 获取最后一个 / 和最后一个.中间的文本,作为节点的 namecreateNode(imageName, name, array, palette); // 创建节点,显示在 palette 面板上}
}function createNode(image, name, parent, palette) { // 创建 palette 面板组件上的节点var node = new ht.Node();palette.dm().add(node); // 将节点添加进 palette 的数据容器中node.setImage(image); // 设置节点的图片node.setName(name); // 设置节点名称node.setParent(parent); // 设置节点的父亲node.s({ // 设置节点的属性'draggable': true, // 如果 Node 的 draggable 设为 true,Palette 可以自动处理 dragstart,但是 dragover 和 drop 事件需要我们处理'image.stretch': 'centerUniform', // 图片的绘制方式为非失真方式});return node;
}


创建完后,我们就要启用模拟的拖拽事件 handleDragAndDrop(e, state):

palette = new ht.widget.Palette(); // 创建一个组件面板var data;
palette.handleDragAndDrop = function(e, state) { // 左侧面板组件拖拽功能if ( state === 'prepare' ) data = palette.getDataAt(e);else if( state === 'begin' || state === 'between' ) {}else {if (!ht.Default.containedInView(e, graphView)) return; // 判断交互事件所处位置是否在 graphView 组件之上var node = new ht.Node(); // 拖拽到graphView中就创建一个新的节点显示在graphView上node.setImage(data.getImage()); // 设置节点上贴图node.setName(data.getName()); // 设置名称(为了显示在属性栏中)node.s('label', ''); // 在 graphView 中节点下方不会出现setName中的值,label 优先级高于namenode.p(graphView.lp(e)); // 将节点的位置设置为 graphView 事件下的拓扑图中的逻辑坐标,即设置鼠标点下的位置为节点坐标graphView.dm().add(node); // 将节点添加进 graphView 中graphView.sm().ss(node); // 默认选中节点graphView.setFocus(node); // 设置将焦点聚集在该节点上editableFunc(); // 设置节点为可编辑状态并且选中导航栏中的“编辑”}
}

好了,先在你就可以直接从左侧的 palette 面板组件上直接拖拽节点到右侧的地图上的 graphView 拓扑图。

我们可以在 graphView 上进行绘制节点的编辑、绘制连线、绘制直角连线以及绘制多边形。

最后

在上面基于 GIS 的电信资源管理系统的基础上我尝试了增加切换地图的功能,同时还在导航栏上添加了“地铁线路图”,这个地铁线路图实现起来也是非常厉害的,下次我会再针对这个地铁线路图进行一次详解,这里就不多做解释,来看看我添加后的最终结果:

http://www.hightopo.com/demo/openlayers/

如果有什么建议或者意见,欢迎留言或者私信我,或者直接去官网 HT for Web (https://hightopo.com/)查阅相关资料。

基于 OpenLayers3 实现的 HTML5 GIS 电信资源管理系统相关推荐

  1. 基于数据库和ASP的网上教学资源管理系统的开发

    1,前言 网络技术的发展促进了网络教育的发展,随着网上教学资源和网络用户的成倍增长,如何让用户.特别是接受远程教育的学生在巨量的教学资源中快速搜索.浏览所需信息,如何为教师提供一个方便快捷的课件发布渠 ...

  2. html5 canvas图表,Chart.js基于Canvas画布的HTML5统计图表库 - 资源分享

    Chart.js 是一个简单.面向对象.为设计者和开发者准备的图表绘制工具库.可以绘制柱状图.热点图.曲线图等,使用HTML5中的Canvas画布,支持原生的和jQuery的调用方法. 特点 6种图表 ...

  3. 基于JAVA+Servlet+JSP+MYSQL的教室资源管理系统

    项目功能 管理员进入的功能模块: 1)教室基本信息:查看管理教室基本信息 2)教室借用管理:审核查看借用信息 3)用户信息管理:查看管理用户信息 4)系统用户管理:管理系统用户,修改密码 用户进入的功 ...

  4. html5 职工入职后台管理系统_ChemCMS是一款基于GO+PHP+MYSQL+HTML5构建的化学内容管理系统

    ChemCMS是一款基于GO+PHP+MYSQL+HTML5构建的化学内容管理系统,旨在提高化学类企业信息化管理水平,ChemCMS提供了行业所需的库存管理.订单管理.产品管理.客户管理.权限管理全部 ...

  5. JAVA毕业设计HTML5寿光农产品资源展示平台计算机源码+lw文档+系统+调试部署+数据库

    JAVA毕业设计HTML5寿光农产品资源展示平台计算机源码+lw文档+系统+调试部署+数据库 JAVA毕业设计HTML5寿光农产品资源展示平台计算机源码+lw文档+系统+调试部署+数据库 本源码技术栈 ...

  6. 基于 Android NDK 的学习之旅-----资源释放

    基于 Android NDK 的学习之旅-----资源释放 做上一个项目的时候因为与C引擎交互频繁,有时候会突然莫名其妙的的整个应用程序直接挂掉.因为我是学Java 开始的,所以对主动释放内存没多大概 ...

  7. 【戴嘉乐】(进阶)基于IPFS和Ngrok构建自维护资源网关

    作者简介:戴嘉乐( Mr.Maple ) | 前百度高级研发工程师 | IPFS应用实践者&布道师| 个人网站:https://www.daijiale.cn 联系方式:微信号:daijial ...

  8. 一个资源管理系统的设计--基于cgroup机制

    设计一个资源管理系统对于一个综合性的资源访问系统来说是十分必要的,而好的设计一定是不复杂的,甚至说是十分简单的,原因就是精简机构,消除冗余,或者说精兵简政在任何时候任何方面总是必要的,我们不希望管理机 ...

  9. 基于html5的学生管理系统,基于HTML5的学生信息管理系统的设计与实现

    [摘要] 目前学校依然使用人工对学生信息进行管理,没有相关的学生信息管理系统,虽然学校硬件和网络设施已经提高,但对网络的利用最多也就是用QQ来传送文件及信息,效率低下,这种状态急需改变. 基于这种现状 ...

  10. 基于Cesium使用自定义着色器的资源总结

    基于Cesium使用自定义着色器的资源总结 二维几何着色器 Cesium.js着色器的简单实现 B站:cesium着色器的介绍及使用 cesium添加自己的着色器(我没复现出来) 基于3DTile着色 ...

最新文章

  1. python install scikit-image后,报错ImportError: DLL load failed: 找不到指定的模块
  2. 【Vegas原创】本地sys登录,ORA-01031: insufficient privileges的解决方法
  3. 人活系列Streetlights (秩)
  4. BOOST_PROTO_DEFINE_OPERATORS宏使用 std::vector<> 和 std::list 非原型类型来原型化表达式的示例
  5. wamp php 升级,wamp升级PHP7.1
  6. android apk 防止反编译技术加壳技术(转)
  7. python函数和代码复用思维导图_Python语言程序---代码复用与函数递归(二)
  8. SoundMorph Dust for Mac(双耳环绕音频颗粒合成仪)
  9. android--显式跳转和隐式跳转
  10. 云计算核心技术剖析学习笔记_2
  11. 初次联系导师短信模板_申博经验分享|如何联系导师?
  12. stm32f407工程改为stm32f401的方法,并修改时钟
  13. Oracle触发器原理、创建、修改、删除
  14. Python 从底层结构聊 Beautiful Soup 4(内置豆瓣最新电影排行榜爬取案例)
  15. ZZULIOJ 1138: C语言合法标识符
  16. 多人扑克游戏:99分游戏规则介绍
  17. 未来量子计算机运算速度,中国科学家实现 “量子霸权”,计算速度比超级计算机快100万亿倍...
  18. fiddler https 抓包
  19. python 3d绘图旋转_Python和Matplotlib:在Jupyter Noteb中使3D绘图具有交互性
  20. Canvas炫酷3D线条动画背景

热门文章

  1. W25Q64 的 QSPI 模式 问题
  2. Ant Design Vue下载本地文件(其他框架也适用)
  3. ADF4351原理图PCB电路设计经验建议
  4. 华为s5500t服务器硬盘,HuaweiOceanStor1T SAS 7.2K3.5寸WD1003FBYXS5500T存储硬盘
  5. 数据分析流程(学习)
  6. TCL语言学习笔记一
  7. SSM框架讲解(史上最详细的文章)
  8. scratch3.0接苹果小游戏
  9. ae教程 (四)运动控制 (三)
  10. MCSA / Windows Server 2016各版本的功能及比较,安装需求及选项