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)