milestone相关前端控件的设计方案
原来的需求是模拟visio里面做出来的时间线的效果,所以我就做成了下面那种方案;这几天闲逛,看到别人做的不错的设计,我这里记下来;
我初步看了一下前端代码绘制部分基于svg d3.js;
下面我来说说我认为他的这种设计到底好在哪里,从用户视角:
采用明显的颜色来区分,已完成迭代,当前迭代,未开始迭代
虽然我那个设计中也采用了颜色进行状态区分,但是线条小,视觉上不如这个明显;
环形的进度显示相对于单纯的百分比数值显示更能聚焦人的注意力,
不仅记录进度还加入了相关的代码量和工作量记录,将用户可能关注的相关要素集中呈现,避免二次对照的过程;
当区段的长度相对小时,隐藏了代码量部分的显示,改成了鼠标浮动时显示完整信息;
鼠标浮动于当前时间点是,提示总体的一些进度和汇总信息;
还可以注意到一些这样细节,同一段的的斜杠前后的字体大小或者颜色的变化的设计,都一样或者过于整齐的设计容易让人产生视觉疲劳;
其他的一些点,状态图的信息提示栏提供了更详细的描述信息;
采用了更多的进度和趋势的图形辅助,更直观明了;
还有一个我认为不错的特点,右上角的播放循环展板的功能;
看了一下这个项目的前端公共依赖:
echarts.js
EventEmitter.min.js//EventEmitter v5.2.5
jquery-3.3.1.min.js//jQuery v3.3.1
jquery-ui.min.js//jQuery UI - v1.11.0
jquery.gridly.js//1.2.9
bootstrap.min.js//Bootstrap v3.3.7
Bootstrap: popover.js v3.2.0
Bootstrap: tooltip.js v3.2.0
bootstrap-slider.min.js//10.0.2
pnotify.js
bootstrap-table.js//1.12.1
jquery.dataTables.js//DataTables 1.10.4
dataTables.bootstrap.js
dataTables.rowsGroup.js//RowsGroup for DataTables v2.0.0
ColVis.min.js//1.0.8
DT_bootstrap.js
ckeditor.js//2003-2018
ckeditor/styles.js
ckeditor/lang/zh-cn.js
d3.js//3.2.8
jqPaginator.js
select2.min.js
bootstrap-multiselect.js//Bootstrap Multiselect v0.9.8
基本上是使用jquery+bootstrap的前端结构,并没有使用一些当前流行的前端框架,但是代码逻辑还是清晰的;
/*-------------------------------------------------------------------------------------------------*/
less教程
https://www.ibm.com/developerworks/cn/web/1207_zhaoch_lesscss/
交互设计文档设计的一些查询记录
https://cloud.tencent.com/developer/article/1165820
https://www.cnblogs.com/JoannaQ/p/3900463.html
https://www.ctolib.com/topics-36574.html
https://zhuanlan.zhihu.com/p/21577848
http://www.yzsekj.com/cn_asp/m_newsview.asp%3Ftypeid%3D60%26id%3D83
https://wemp.app/posts/7b00dafd-a935-4c9a-a6cb-0bc0587ce6de
/*--------------------------------------------------------------------------------------------------*/
/*------------------------------------分割线-----------------------------------------------------*/
/*-------------------------------------------------------------------------------------------------*/
基于angularjs和mxgraph的里程碑计划控件
控件效果:
控件实现:
首先下载 :https://github.com/jgraph/mxgraph 最新的版本
需要其中的mxgraph-master\javascript\examples\grapheditor\www目录中的所有和src中的mxClient.js文件,
控件模板:
<head> <meta charset="UTF-8"> <link rel="stylesheet" type="text/css" href="/resources/mx/styles/grapheditor.css"> <script type="text/javascript"> mxBasePath = '/resources/mx/'; STYLE_PATH = '/resources/mx/styles'; RESOURCES_PATH = '/resources/mx/resources'; STENCIL_PATH = '/resources/mx/stencils'; </script> <!--mxGraph编辑器的相关js依赖--> <script type="text/javascript"> var mile_ui = null; function drawInit(xml){ var editorUiInit = EditorUi.prototype.init; EditorUi.prototype.init = function(){ .... }; mxResources.loadDefaultBundle = false; var bundle = mxResources.getDefaultBundle(RESOURCE_BASE, mxLanguage) || mxResources.getSpecialBundle(RESOURCE_BASE, mxLanguage); mxUtils.getAll([bundle, STYLE_PATH + '/default.xml'], function(xhr) { .... // Main mile_ui = new EditorUi(new Editor(urlParams['chrome'] == '0', themes),document.getElementById('eContainer')); updateDraw(xml); //sco["htm"]=$('#eContainer').html(); }, function() { .... }); } function updateDraw(xml){ mile_ui.editor.graph.model.beginUpdate(); try { var doc = mxUtils.parseXml(xml); mile_ui.editor.graph.resizeContainer = true; mile_ui.editor.setGraphXml(doc.documentElement); mile_ui.editor.graph.centerZoom = false; mile_ui.editor.graph.setTooltips(false); mile_ui.editor.graph.setEnabled(false); mile_ui.editor.graph.resizeContainer = false; } catch (e) { console.error(e); } finally { mile_ui.editor.graph.model.endUpdate(); } } </script> </head> <div class="mgt10 mgb40 pdl130 pdr30 position-relative"> <table class="table table-bordered table-striped table-hover"> <thead> <tr> <th id="item" scope="colgroup">年</th> <th ng-repeat="year in RangeYear" id="y_{{year}}" scope="colgroup" colspan="12">{{year}}</th> <th scope="colgroup" rowspan="2" style="width:80px;">操作</th> </tr> <tr> <th scope="col" rowspan="1">月</th> <th ng-repeat="month in RangeMonth track by $index" id="m_{{month}}">{{month}}</th> </tr> </thead> <tbody> <tr ng-repeat="tData in timelineData"> <td scope="colgroup" style="height:{{ht/timelineData.length}}px">{{tData.title}}</td> <td ng-if="$index==0" scope="colgroup" colspan="{{RangeYear.length*12}}" id="eContainer" style="height:{{ht}}px;padding:0;" rowspan="{{timelineData.length}}"></td> <td scope="colgroup"> <div class="pull-right pdt10 mgb10" ng-click="addMileStone($event,$index)"> <button class="btn btn-info btn-small ng-scope"> <i class="icon-plus"></i> <span class="ng-binding">添加</span> </button> </div> <div class="pull-right pdt10 mgb10" ng-click="updateMileStone($event,$index)"> <button class="btn btn-success btn-small ng-scope"> <i class="icon-refresh"></i> <span class="ng-binding">更新</span> </button> </div> <div class="pull-right pdt10 mgb10" ng-click="showPlan($event,$index)">//这里要注意这里$index 如果放到里面回合checkbox扩展的相关功能冲突,$index始终为用tdata为参数始终为第一个的问题 <input type="checkbox" class="new-checkbox" id="showPlan" name="showPlan" ng-model="tData.showplan"/>//同时写在外层也会有触发两次的问题使用preventdefeat处理 <label for="showPlan">显示计划</label> </div> <div class="pull-right pdt10 mgb10" ng-click="showAct($event,$index)"> <input type="checkbox" class="new-checkbox" id="showCt" name="showAct" ng-model="tData.showact"/> <label for="showCt">显示实际</label> </div> </td> </tr> </tbody> </table> </div> <div class="modal" id="addMileModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true" data-backdrop="static"> .... </div> <div class="modal" id="updateMileModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true" data-backdrop="static"> ..... </div>
控制器:
angular.module('xxx.milestone', []).component('mview', { controller: ['$scope', '$http', '$window', '$stateParams', '$timeout','$compile', 'xxxService', function($scope, $http, $window, $stateParams, $timeout,$compile, xxxService) { var tid = 0; var oldtid = tid; Object.defineProperty(this, 'tid', { get: function() { return tid; }, set: function(newVal) { if(oldtid!=0 && oldtid == newVal){ return; } oldtid = newVal; tid = newVal; console.log('tid changed:', newVal); $scope.timelineId = tid; $scope.initData(); } }); var contextpath = window.Ruban.contextPath; $scope.RangeYear = []; $scope.RangeMonth = []; $scope.timelineData = []; $scope.addMile = {}; $scope.xml = ""; $scope.timelineId = 1; $scope.initDraw = false; $scope.ht = 300; $scope.contentRestore = {}; $scope.initData = function () { var param = { "timelineId": $scope.timelineId }; $scope.ht = $scope.timelineId.split(",").length * 300; var url = contextpath + 'api/private/xxxx/getTimeline.json'; xxxService.commonPostJQlike(url, param, function (response) { if(!response.data.success){ window.location.href=contextpath +"xxxHome.html#!/error"; return; } $scope.timelineData = response.data.data; $scope.xml = response.data.xml; var bgDate = new Date($scope.timelineData[0].bg); var edDate = new Date($scope.timelineData[0].ed); $scope.RangeYear = []; $scope.RangeMonth = []; for(var i = bgDate.getFullYear();i < edDate.getFullYear();++i){ $scope.RangeYear.push(i); for(var j=1;j<=12;++j){ $scope.RangeMonth.push(j); } } if($scope.RangeMonth.indexOf(edDate.getFullYear())==-1){ $scope.RangeYear.push(edDate.getFullYear()); for(var j=1;j<=12;++j){ $scope.RangeMonth.push(j); } } $timeout(function() { if($scope.initDraw==false){//attan drawInit($scope.xml); $scope.initDraw = true; }else{ //$('#eContainer').html($scope.contentRestore["htm"]); drawInit($scope.xml);//updateDraw--pro in this scene //这里不用updateDraw是因为当控件在ng-repeat中显示的时候,angularjs刷新子元素的一些过程导致开始的mxgraph绑定失效 } date(); }); }); }; $scope.addMileStone = function(e,cid) { e.preventDefault(); $scope.currentStone = $scope.timelineData[cid]; $scope.addMile = {}; $("#addMileModal").modal(); }; $scope.submitMileAdd = function() { $("#add_mile").attr("disabled", "disabled"); var req = { "timelineId":$scope.currentStone.id, "addMile.planDesc":$scope.addMile.planDesc, "addMile.planDate":$scope.addMile.planDate }; var url=contextpath+"api/private/xxxxx/addMileView.json"; xxxService.commonPostJson(url,req,function () { .... }); }; $scope.updateMileStone = function(e,cid) { e.preventDefault(); $scope.currentStone = $scope.timelineData[cid]; $timeout(function() { date();//用于刷新新增的日期控件功能 }); $("#updateMileModal").modal(); }; $scope.submitMileUpdate = function() { $("#update_mile").attr("disabled", "disabled"); var req = { "timelineId":$scope.currentStone.id, "milestone":$scope.currentStone.milestone, "title":$scope.currentStone.title }; var url=contextpath+"api/private/xxxxx/updateMileView.json"; xxxService.commonPostJson(url,req,function () { .... }); }; $scope.showPlan = function(e,cid) { e.preventDefault(); //console.log(cid); maybe syli double $scope.currentStone = $scope.timelineData[cid]; var req = { "timelineId":$scope.currentStone.id, "showplan":!$scope.currentStone.showplan }; var url=contextpath+"api/private/xxxxx/updateMileView.json"; xxxService.commonPostJson(url,req,function () { .... }); }; $scope.showAct = function(e,cid) { e.preventDefault(); $scope.currentStone = $scope.timelineData[cid]; var req = { "timelineId":$scope.currentStone.id, "showact":!$scope.currentStone.showact }; var url=contextpath+"api/private/xxxx/updateMileView.json"; xxxService.commonPostJson(url,req,function () { .... }); }; }], controllerAs: 'MilestoneController', templateUrl: window.Ruban.contextPath+'pages/xxxx/milestone.html', bindings: { tid: '@' } });
使用形式:
<div class="bgWhite"> <mview tid="${timelineId}"></mview>//timelineId来自springcontrol绑定形式为以,分割的timelineId </div>
后端的一些实现:
主要是使用dom4j写了一个MxGraphService的类来对显示到前端的mxgraph绘制对应的xml文件,
public static String formatXmlByInfo(List<Map<String,Object>> drawParam);
//说明根据数据库中由timelineId查询到对应的里程碑信息来请求格式化绘图xml节点信息
public static List<Node> formatMileLine(int id[],int y);
//id 为用来递增为绘图图元的id,该函数用来格式化返回最基本的中间的时间线
public static List<Node> formatToday(int id[],int x,int y,int parent);
//返回指示当前日期的位置图元
public static List<Node> formatMilestone(int id[],int x,int y,Map<String,Object> info,String color,int parent);
//返回计划里程碑图元
public static List<Node> formatMilestoneD(int id[],int x,int y,Map<String,Object> info,String color,int parent);
//返回实际完成时间点图元
说明:上面的格式化过程其实很简单,具体是解析参数化过后的样本结构xml获得相应的xml节点返回,注意返回的xml Node需要clone以便添加到最终的根节点;
- 点赞
- 收藏
- 关注作者
评论(0)