首页 > 代码库 > MVC使用Gantt Chart实现甘特图,管理事情进度

MVC使用Gantt Chart实现甘特图,管理事情进度

借助"甘特图",可以直观地了解任务、活动、工作的进度。dhtmlxGantt是一个开源的Javacirpt库,能帮助我们快速创建"甘特图",本篇体验在MVC中的实现。主要包括:

 

  • 认识"甘特图"
  • 下载dhtmlxGantt包
  • 把dhtmlxGantt相关CSS、JS、样式引入到_Layout.cshtml中
  • 初始化dhtmlxGantt
  • 通过EF Code First创建初始数据
  • 显示数据
  • 保存数据

 

  认识"甘特图"

2


 

  下载dhtmlxGantt包

通过NuGet,输入关键字"Gantt Chart",下载dhtmlxGantt包。

1

 

  把dhtmlxGantt相关CSS、JS、样式引入到_Layout.cshtml中

<head>    <meta charset="utf-8" />    <meta name="viewport" content="width=device-width" />    <title>@ViewBag.Title</title>    @Styles.Render("~/Content/css")    <script src=http://www.mamicode.com/"~/Scripts/dhtmlxgantt/dhtmlxgantt.js"></script>    <link href=http://www.mamicode.com/"~/Content/dhtmlxgantt/dhtmlxgantt.css" rel="stylesheet" />    <script src=http://www.mamicode.com/"~/Scripts/dhtmlxgantt/locale/locale_cn.js"></script>    <style type="text/css">        html, body {            height: 100%;            padding: 0px;            margin: 0px;            overflow: hidden;        }    </style></head><body>    @RenderBody()    <script src=http://www.mamicode.com/"~/Scripts/main.js"></script></body>

以上,locale_cn.js用来汉化,main.js用来初始化配置。

 

  初始化dhtmlxGantt

在Home/Index.cshtml中,创建一个id为ganttContainer的div,dhtmlxGantt将被加载到此div中。

@{    ViewBag.Title = "Index";    Layout = "~/Views/Shared/_Layout.cshtml";}<div id="ganttContainer" style="width: 100%; height: 100%;"></div>

 

main.js中的配置如下:

(function () {    // add month scale    gantt.config.scale_unit = "week"; //第一个时间尺度,即X轴的单位,包括:"minute", "hour", "day", "week", "month", "year"    gantt.config.step = 1;//步进,默认为1    gantt.templates.date_scale = function (date) {//日期格式化        var dateToStr = gantt.date.date_to_str("%d %M");        var endDate = gantt.date.add(gantt.date.add(date, 1, "week"), -1, "day");        return dateToStr(date) + " - " + dateToStr(endDate);    };    gantt.config.subscales = [ //第二个时间尺度单位        { unit: "day", step: 1, date: "%D" }    ];    gantt.config.scale_height = 50; //设置时间尺度和Grid树的高度    // configure milestone description    gantt.templates.rightside_text = function (start, end, task) {//进度条右侧的提示文字        if (task.type == gantt.config.types.milestone) {            return task.text;        }        return "";    };    // add section to type selection: task, project or milestone    gantt.config.lightbox.sections = [//弹出对话框设置        { name: "description", height: 70, map_to: "text", type: "textarea", focus: true },        { name: "type", type: "typeselect", map_to: "type" },        { name: "time", height: 72, type: "duration", map_to: "auto" }    ];               gantt.config.xml_date = "%Y-%m-%d %H:%i:%s"; // XML中的日期格式    gantt.init("ganttContainer"); // 初始化dhtmlxGantt,ganttContainer为视图中div的id    gantt.load("/Home/Data", "json");//加载数据    // enable dataProcessor    var dp = new dataProcessor("/Home/Save");//dhtmlxGantt保存变化,包括添加、更新、删除    dp.init(gantt);})();

  通过EF Code First创建初始数据

参照dhtmlxGantt官方文档,我们建立这样的模型:

 

Link类体现Task间的相关性。

using System.ComponentModel.DataAnnotations;namespace MyGanttChart.Models{    public class Link    {        public int Id { get; set; }        [MaxLength(1)]        public string Type { get; set; }        public int SourceTaskId { get; set; }        public int TargetTaskId { get; set; }     }}



Task类是任务的抽象。

using System;using System.ComponentModel.DataAnnotations;namespace MyGanttChart.Models{    public class Task    {        public int Id { get; set; }        [MaxLength(255)]        public string Text { get; set; }        public DateTime StartDate { get; set; }        public int Duration { get; set; }        public decimal Progress { get; set; }        public int SortOrder { get; set; }        public string Type { get; set; }        public int? ParentId { get; set; }     }}

 

创建一个派生于DbContext的上下文类:

using System.Data.Entity;using MyGanttChart.Models;namespace MyGanttChart.DAL{    public class GanttContext : DbContext    {        public GanttContext() : base("GanttContext") { }        public DbSet<Task> Tasks { get; set; }        public DbSet<Link> Links { get; set; }    }}

 

创建一些种子数据:

using System;using System.Collections.Generic;using System.Data.Entity;using MyGanttChart.Models;namespace MyGanttChart.DAL{    public class GanttInitializer : DropCreateDatabaseIfModelChanges<GanttContext>    {        protected override void Seed(GanttContext context)        {            List<Task> tasks = new List<Task>()            {                new Task() { Id = 1, Text = "Project #2", StartDate = DateTime.Today.AddDays(-3), Duration = 18, SortOrder = 10, Progress = 0.4m, ParentId = null },                new Task() { Id = 2, Text = "Task #1", StartDate = DateTime.Today.AddDays(-2), Duration = 8, SortOrder = 10, Progress = 0.6m, ParentId = 1 },                new Task() { Id = 3, Text = "Task #2", StartDate = DateTime.Today.AddDays(-1), Duration = 8, SortOrder = 20, Progress = 0.6m, ParentId = 1 }            };            tasks.ForEach(s => context.Tasks.Add(s));            context.SaveChanges();            List<Link> links = new List<Link>()            {                new Link() { Id = 1, SourceTaskId = 1, TargetTaskId = 2, Type = "1" },                new Link() { Id = 2, SourceTaskId = 2, TargetTaskId = 3, Type = "0" }            };            links.ForEach(s => context.Links.Add(s));            context.SaveChanges();        }    }}

 

在Web.config中配置种子数据:

<entityFramework>    <contexts>      <context type="MyGanttChart.DAL.GanttContext, MyGanttChart">        <databaseInitializer type="MyGanttChart.DAL.GanttInitializer, MyGanttChart" />      </context>    </contexts>    <defaultConnectionFactory type="System.Data.Entity.Infrastructure.SqlConnectionFactory, EntityFramework" />    <providers>      <provider invariantName="System.Data.SqlClient" type="System.Data.Entity.SqlServer.SqlProviderServices, EntityFramework.SqlServer" />    </providers>  </entityFramework>

以上,type="MyGanttChart.DAL.GanttInitializer, MyGanttChart"中,MyGanttChart.DAL为种子类GanttInitializer所在的命名空间,MyGanttChart为程序集的名称。

 

在Web.config中配置连接字符串:

<connectionStrings>    <add name="GanttContex"       connectionString="Data Source=.;User=some user name;Password=some password;Initial Catalog=Gantt;Integrated Security=True"       providerName="System.Data.SqlClient"/>  </connectionStrings>

 

  显示数据

HomeController的Data()方法加载数据,以json格式返回。

using System;using System.Collections.Generic;using System.Linq;using System.Web.Mvc;using System.Xml.Linq;using MyGanttChart.DAL;using MyGanttChart.Models;namespace MyGanttChart.Controllers{    public class HomeController : Controller    {        private readonly GanttContext db = new GanttContext();        public ActionResult Index()        {            return View();        }        [HttpGet]        public JsonResult Data()        {            var jsonData = http://www.mamicode.com/new            {                data = (                    from t in db.Tasks.AsEnumerable()                    select new                    {                        id = t.Id,                        text = t.Text,                        start_date = t.StartDate.ToString("u"),                        duration = t.Duration,                        order = t.SortOrder,                        progress = t.Progress,                        open = true,                        parent = t.ParentId,                        type = (t.Type != null) ? t.Type : String.Empty                    }                ).ToArray(),                links = (                    from l in db.Links.AsEnumerable()                    select new                    {                        id = l.Id,                        source = l.SourceTaskId,                        target = l.TargetTaskId,                        type = l.Type                    }                ).ToArray()            };            return new JsonResult { Data = http://www.mamicode.com/jsonData, JsonRequestBehavior = JsonRequestBehavior.AllowGet };        }        ......}

 

在main.js中,已经对加载数据做了设置:

gantt.load("/Home/Data", "json");//加载数据

 

运行
3

 

  保存数据

当在"甘特图"上进行任何的操作再保存到数据库的时候,请求表头信息大致如下:
5


dhtmlxGantt为我们提供了一个GanttRequest类,提供了Parse(FormCollection form, string ganttMode)方法把从客户端拿到的表头信息赋值到GanttRequest类的各个属性中。

public class GanttRequest    {        public GanttMode Mode { get; set; }        public GanttAction Action { get; set; }        public Task UpdatedTask { get; set; }        public Link UpdatedLink { get; set; }        public long SourceId { get; set; }        /// <summary>        /// Create new GanttData object and populate it        /// </summary>        /// <param name="form">Form collection</param>        /// <returns>New GanttData</returns>        public static List<GanttRequest> Parse(FormCollection form, string ganttMode)        {            // save current culture and change it to InvariantCulture for data parsing            var currentCulture = Thread.CurrentThread.CurrentCulture;            Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture;            var dataActions = new List<GanttRequest>();            var prefixes = form["ids"].Split(,);            foreach (var prefix in prefixes)            {                var request = new GanttRequest();                // lambda expression for form data parsing                Func<string, string> parse = x => form[String.Format("{0}_{1}", prefix, x)];                request.Mode = (GanttMode)Enum.Parse(typeof(GanttMode), ganttMode, true);                request.Action = (GanttAction)Enum.Parse(typeof(GanttAction), parse("!nativeeditor_status"), true);                request.SourceId = Int64.Parse(parse("id"));                // parse gantt task                if (request.Action != GanttAction.Deleted && request.Mode == GanttMode.Tasks)                {                    request.UpdatedTask = new Task()                    {                        Id = (request.Action == GanttAction.Updated) ? (int)request.SourceId : 0,                        Text = parse("text"),                        StartDate = DateTime.Parse(parse("start_date")),                        Duration = Int32.Parse(parse("duration")),                        Progress = Decimal.Parse(parse("progress")),                        ParentId = (parse("parent") != "0") ? Int32.Parse(parse("parent")) : (int?)null,                        SortOrder = (parse("order") != null) ? Int32.Parse(parse("order")) : 0,                        Type = parse("type")                    };                }                // parse gantt link                else if (request.Action != GanttAction.Deleted && request.Mode == GanttMode.Links)                {                    request.UpdatedLink = new Link()                    {                        Id = (request.Action == GanttAction.Updated) ? (int)request.SourceId : 0,                        SourceTaskId = Int32.Parse(parse("source")),                        TargetTaskId = Int32.Parse(parse("target")),                        Type = parse("type")                    };                }                dataActions.Add(request);            }            // return current culture back            Thread.CurrentThread.CurrentCulture = currentCulture;            return dataActions;        }    }

 

保存数据,有可能是对Task的操作,也有可能是对Link的操作,把这2种模式封装到一个枚举中:

public enum GanttMode    {        Tasks,        Links    }

 

而所有的动作无非是添加、更新、删除等,也封装到枚举中:

public enum GanttAction    {        Inserted,        Updated,        Deleted,        Error    }

 

接下来,HomeController的Save()方法,根据请求表头的信息,借助GanttRequest类的静态方法Parse(FormCollection form, string ganttMode)把表头信息封装到GanttRequest类的属性中,然后根据这些属性采取相应的操作:

using System;using System.Collections.Generic;using System.Linq;using System.Web.Mvc;using System.Xml.Linq;using MyGanttChart.DAL;using MyGanttChart.Models;namespace MyGanttChart.Controllers{    public class HomeController : Controller    {        private readonly GanttContext db = new GanttContext();        ......        /// <summary>        /// Update Gantt tasks/links: insert/update/delete         /// </summary>        /// <param name="form">Gantt data</param>        /// <returns>XML response</returns>        [HttpPost]        public ContentResult Save(FormCollection form)        {            var dataActions = GanttRequest.Parse(form, Request.QueryString["gantt_mode"]);            try            {                foreach (var ganttData in dataActions)                {                    switch (ganttData.Mode)                    {                        case GanttMode.Tasks:                            UpdateTasks(ganttData);                            break;                        case GanttMode.Links:                            UpdateLinks(ganttData);                            break;                    }                }                db.SaveChanges();            }            catch            {                // return error to client if something went wrong                dataActions.ForEach(g => { g.Action = GanttAction.Error; });            }            return GanttRespose(dataActions);        }        /// <summary>        /// Update gantt tasks        /// </summary>        /// <param name="ganttData">GanttData object</param>        private void UpdateTasks(GanttRequest ganttData)        {            switch (ganttData.Action)            {                case GanttAction.Inserted:                    // add new gantt task entity                    db.Tasks.Add(ganttData.UpdatedTask);                    break;                case GanttAction.Deleted:                    // remove gantt tasks                    db.Tasks.Remove(db.Tasks.Find(ganttData.SourceId));                    break;                case GanttAction.Updated:                    // update gantt task                    db.Entry(db.Tasks.Find(ganttData.UpdatedTask.Id)).CurrentValues.SetValues(ganttData.UpdatedTask);                    break;                default:                    ganttData.Action = GanttAction.Error;                    break;            }        }        /// <summary>        /// Update gantt links        /// </summary>        /// <param name="ganttData">GanttData object</param>        private void UpdateLinks(GanttRequest ganttData)        {            switch (ganttData.Action)            {                case GanttAction.Inserted:                    // add new gantt link                    db.Links.Add(ganttData.UpdatedLink);                    break;                case GanttAction.Deleted:                    // remove gantt link                    db.Links.Remove(db.Links.Find(ganttData.SourceId));                    break;                case GanttAction.Updated:                    // update gantt link                    db.Entry(db.Links.Find(ganttData.UpdatedLink.Id)).CurrentValues.SetValues(ganttData.UpdatedLink);                    break;                default:                    ganttData.Action = GanttAction.Error;                    break;            }        }        /// <summary>        /// Create XML response for gantt        /// </summary>        /// <param name="ganttData">Gantt data</param>        /// <returns>XML response</returns>        private ContentResult GanttRespose(List<GanttRequest> ganttDataCollection)        {            var actions = new List<XElement>();            foreach (var ganttData in ganttDataCollection)            {                var action = new XElement("action");                action.SetAttributeValue("type", ganttData.Action.ToString().ToLower());                action.SetAttributeValue("sid", ganttData.SourceId);                action.SetAttributeValue("tid", (ganttData.Mode == GanttMode.Tasks) ? ganttData.UpdatedTask.Id : ganttData.UpdatedLink.Id);                actions.Add(action);            }            var data = http://www.mamicode.com/new XDocument(new XElement("data", actions));            data.Declaration = new XDeclaration("1.0", "utf-8", "true");            return Content(data.ToString(), "text/xml");        }    }}

 

运行,点击+添加一个新的task:
4

 

参考资料:
Gantt Chart for ASP.NET MVC with dhtmlxGantt