藉助"甘特圖",能夠直觀地瞭解任務、活動、工做的進度。dhtmlxGantt是一個開源的Javacirpt庫,能幫助咱們快速建立"甘特圖",本篇體驗在MVC中的實現。主要包括:css
認識"甘特圖"html
下載dhtmlxGantt包數據庫
經過NuGet,輸入關鍵字"Gantt Chart",下載dhtmlxGantt包。express
把dhtmlxGantt相關CSS、JS、樣式引入到_Layout.cshtml中json
<head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width" /> <title>@ViewBag.Title</title> @Styles.Render("~/Content/css") <script src="~/Scripts/dhtmlxgantt/dhtmlxgantt.js"></script> <link href="~/Content/dhtmlxgantt/dhtmlxgantt.css" rel="stylesheet" /> <script src="~/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="~/Scripts/main.js"></script> </body>
以上,locale_cn.js用來漢化,main.js用來初始化配置。ide
初始化dhtmlxGanttspa
在Home/Index.cshtml中,建立一個id爲ganttContainer的div,dhtmlxGantt將被加載到此div中。3d
@{ ViewBag.Title = "Index"; Layout = "~/Views/Shared/_Layout.cshtml"; } <div id="ganttContainer" style="width: 100%; height: 100%;"></div>
main.js中的配置以下:code
(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建立初始數據orm
參照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 = 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 = jsonData, JsonRequestBehavior = JsonRequestBehavior.AllowGet }; } ...... }
在main.js中,已經對加載數據作了設置:
gantt.load("/Home/Data", "json");//加載數據
保存數據
當在"甘特圖"上進行任何的操做再保存到數據庫的時候,請求表頭信息大體以下:
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 = new XDocument(new XElement("data", actions)); data.Declaration = new XDeclaration("1.0", "utf-8", "true"); return Content(data.ToString(), "text/xml"); } } }