milestone相关前端控件的设计方案

举报
Amrf 发表于 2019/07/30 19:56:57 2019/07/30
【摘要】 控件效果: 控件实现:首先下载 :https://github.com/jgraph/mxgraph 最新的版本需要其中的mxgraph-master\javascript\examples\grapheditor\www目录中的所有和src中的mxClient.js文件,控件模板:<head><meta charset="UTF-8"><link rel="stylesheet" type...

原来的需求是模拟visio里面做出来的时间线的效果,所以我就做成了下面那种方案;这几天闲逛,看到别人做的不错的设计,我这里记下来;desgin1.png

我初步看了一下前端代码绘制部分基于svg d3.js;

下面我来说说我认为他的这种设计到底好在哪里,从用户视角:

  • 采用明显的颜色来区分,已完成迭代,当前迭代,未开始迭代

虽然我那个设计中也采用了颜色进行状态区分,但是线条小,视觉上不如这个明显;

  • 环形的进度显示相对于单纯的百分比数值显示更能聚焦人的注意力,

  • 不仅记录进度还加入了相关的代码量和工作量记录,将用户可能关注的相关要素集中呈现,避免二次对照的过程;

  • 当区段的长度相对小时,隐藏了代码量部分的显示,改成了鼠标浮动时显示完整信息;

  • 鼠标浮动于当前时间点是,提示总体的一些进度和汇总信息;

desgin11.png

  • 还可以注意到一些这样细节,同一段的的斜杠前后的字体大小或者颜色的变化的设计,都一样或者过于整齐的设计容易让人产生视觉疲劳;

  • 其他的一些点,状态图的信息提示栏提供了更详细的描述信息;

desgin21.png

  • 采用了更多的进度和趋势的图形辅助,更直观明了;

desgin3.png


  • 还有一个我认为不错的特点,右上角的播放循环展板的功能;

看了一下这个项目的前端公共依赖:

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的里程碑计划控件

控件效果:

p1.pngp2.png

p3.png s3.png

控件实现:

首先下载 :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以便添加到最终的根节点;



【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。