65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 C# MVC 和 jQuery.Fullcalendar 为货运/航运创建一个事件日历

starIconstarIconstarIconstarIconstarIcon

5.00/5 (5投票s)

2017年3月5日

CPOL

8分钟阅读

viewsIcon

46402

一个完整的事件日历,以货运/航运为例,使用 Visual Studio Community 2015、C# MVC 和 jQuery.Fullcalendar 构建。

我们公司为客户种植林木幼苗,然后交付给他们。交付过程称为货运或航运,当我们为大量客户提供大量幼苗时,这是一个非常复杂的过程。因此,我在 2015 年使用免费的 Microsoft Visual Studio Community 2015 和 Adam Shaw 的 jQuery.Fullcalendar 制作了一个事件日历,用于在线管理此过程,并供我们的客户在线查看其幼苗的运输状态。它最近投入使用。现在,我想分享我的经验和一些代码,通过从头开始,一步一步地构建一个简单的新事件日历,以货运或航运为例,使其成为一个可用的日历。

AJSON 有一篇很棒的文章 Full calendar – 一个完整的 jQuery 和 C# MVC 网络日记系统,介绍了如何使用 jQuery 插件。但他做得太复杂了,并没有完全遵守 MVC 规则。我到时会解释这些。

以下是常规步骤

  • 首先,让我们使用 Visual Studio Community 2015,创建一个新的 ASP.NET Web 应用程序,并将其命名为“TruckingCalendar”。选择 MVC 并点击“确定”。一个空白的 Trucking Calendar 应用程序就创建好了。

  • 我们需要一个数据库来存储货运事件。因此,使用免费的 Microsoft SQL Server 2014 Express 创建数据库,名为“Trucking”。
  • 在数据库中创建一个表,也命名为“Trucking”。添加列:TruckID (int, 主键, Identity Specification = Yes), TruckDate (smalldatetime), LoadMinutes (smallint), TruckStatus (tinyint)。不允许 null 值。将 LoadMinutes 默认为 60 (或任何数字),将 TruckStatus 默认为 0

  • 在项目中,右键单击模型文件夹 -> 添加新项 -> 数据 -> ADO.NET 实体数据模型,并将其命名为“TruckingModel”。点击添加。
  • 然后选择“来自数据库的 EF 设计器”。点击“下一步”。然后在“服务器名称”中输入“./SQLEXPRESS”,在“数据库名称”中选择我们刚刚创建的“Trucking”数据库。测试连接以显示已连接。
  • 点击下一步。它将创建“TruckingEntities”。点击下一步选择您的数据库对象和设置:选择 Trucking 表,并保留默认设置。
  • 点击“完成”,我们就得到了一个 TruckingModel.edmx

  • 现在我们需要 jQuery.Fullcalendar 包。点击项目 -> 管理 Nuget 包...,并更新所有已有的包。
  • 然后浏览并安装 jQuery.FullcalendarMoment.js,因为 Fullcalendar 使用 Moment.js 进行 DateTime 设置。
  • 打开 App_Start 文件夹中的 BundleConfig.cs。在最后一个 bundles(CSS bundle)中,添加 "~/Content/themes/base/all.css"。检查您的文件夹,看看文件是否在那里。这些 CSS 主题将由 Calendar 使用。
  • 现在我们开始有趣的 EF 脚手架工作。但首先要重建项目。然后右键点击 Controllers 文件夹 -> 添加 -> 新建脚手架项...,选择“带视图的 MVC 5 控制器,使用 Entity Framework”。点击添加。
  • 在“Model class:”中,选择 Trucking;在“data context class:”中,选择 TruckingEntities,保持默认设置。然后点击“添加”。

各位,这就是整个设置。呼!即使是简单的部分,也花费了大量工作。现在才是困难的部分。

现在 Trucking Controller 已打开,我们将替换以下内容

        // GET: Truckings
        public ActionResult Index()
        {
            return View(db.Truckings.ToList());
        }    

用这个:

        // GET: Calendar
        public ActionResult Calendar(DateTime? truckDate)
        {
            ViewBag.TruckDate = truckDate ?? DateTime.Now;
            return View();
        }    

ViewBag.TruckDate 用于获取可为空的 truckDate。如果为 null,则将使用 "Now"。其目的稍后解释。

现在我们必须根据 Controller 的更改,将 Truckings 文件夹中的 Index.cshtml 重命名为 Calendar.cshtml。在 View 中,将 ViewBag.Title 更改为 "Trucking Calendar",并将其下方所有内容替换为以下 4 个代码块(因为代码太长,无法放入一个块中)

    <link rel='stylesheet' href='~/Content/fullcalendar.css' />
    <script src='~/Scripts/moment.js'></script>
    <script src='~/Scripts/fullcalendar.js'></script>

我们可以看到,上面是已安装的包(FullcalendarMoments)的使用之处。代码继续...

    <script>
    $(document).ready(function () {
        $('#calendar').fullCalendar({
            header: {
                right: 'prev,next today',
                center: 'title',
                left: 'month,agendaWeek,agendaDay'
            },
            theme: true,
            timezone: 'local',
            editable: true,
            defaultDate: '@ViewBag.TruckDate',
            allDaySlot: false,
            selectable: true,
            slotMinutes: 15,
            events: '@Url.Action("GetEvents")',

以上是设置和自动操作。代码继续...

            eventClick: function(calEvent) {
                window.location='/Truckings/Details/' + calEvent.id;
            },

            dayClick: function (date, jsEvent, view) {
                if(view.name != 'month') {
                    if (date > moment()) {
                        if (confirm("Add a new trucking?")) {
                            window.location = '@Url.Action("Create")' + '?truckDate=' + date.format();
                        }
                    }
                }
                else {
                    $('#calendar').fullCalendar('changeView', 'agendaDay');
                    $('#calendar').fullCalendar('gotoDate', date);
                }
            },

            eventDrop: function(event, delta, revertFunc) {
                if (confirm("Confirm move?")) {
                    UpdateEvent(event.id, event.start, event.end);
                }
                else {
                    revertFunc();
                }
            },

            eventResize: function (event, delta, revertFunc) {
                if (confirm("Confirm change loading time?")) {
                    UpdateEvent(event.id, event.start, event.end);
                }
                else {
                    revertFunc();
                }
            }
        });
    });

以上是人机交互。代码继续...

    function UpdateEvent(id, start, end) {
        var obj = { id: id, start: start, end: end }
        $.ajax({
            type: 'POST',
            url: '@Url.Action("UpdateEvent")',
            dataType: "json",
            contentType: "application/json",
            data: JSON.stringify(obj)
        });
    }
    </script>

在脚本部分之后,添加:<div id='calendar'></div> 来容纳日历。至此,Calendar.cshtml 中的代码就结束了。

要链接到 Calendar,请打开 Home 文件夹中的 Index.cshtml,保留“Home Page”标题,但将其余所有内容替换为以下操作链接

    <div class="jumbotron h3 text-center">
        @Html.ActionLink("Trucking Calendar", "Calendar", "Truckings", null, null)
    </div>

现在运行项目,点击主页上的“Trucking Calendar”链接,我们将得到一个空白的 Calendar

耶!如果你走到这一步,恭喜你自己!

现在我们需要使用数据库来输入和输出事件。

首先,我们来创建一个 ViewModel。右键点击 Models 文件夹,添加一个 Class,并将其命名为 TruckingVMs.cs。打开后,将 public class TruckingVMs 替换为以下代码

    public class Events
    {
        public int id;
        public string title;
        public DateTime start;
        public DateTime end;
        public string color;
    }

我们还需要一个用于 TruckStatusEnum,请记住其数据类型为 tinyint。因此,右键单击 Models 文件夹,添加一个 Class,并将其命名为 Enums.cs。打开后,用以下代码替换生成的代码

using System.ComponentModel.DataAnnotations;
namespace TruckingCalendar.Models
{
    public enum TruckStatus : byte
    {
        [Display(Name = "Planned")]
        orange,
        [Display(Name = "Confirmed")]
        green,
        [Display(Name = "Changed")]
        red,
        [Display(Name = "Loaded")]
        darkcyan
    }
}

因此,TruckStatus 将有 4 种状态,对应 4 种不同的颜色,但数据库中仍为 0, 1, 2, 3。

然后点击模型 EvergreenModel.edmx,数据库图表将显示。右键点击 Trucking 表中的 TruckStatus -> 转换为 Enum。接下来,在 Enum 类型 Name: 中,输入 TruckStatus。点击“引用外部类型”,输入 TruckingModel.TruckStatus。点击“确定”进行转换。现在检查 Trucking.cs 部分类,它将显示:public TruckingModel.TruckStatus TruckStatus { get; set; }。不知何故,这里不允许使用“TruckingModel.”,尽管在 edmx 中是必需的。这是 Visual Studio 2015 的一个小故障。所以我们必须从 Trucking.cs 中删除“TruckingModel.”。

现在我们还需要一个显示模板和一个编辑器模板来显示和编辑 enum。Shahriar Hossain 有一篇关于如何制作它们的优秀文章:在 MVC 5.1 中处理枚举。所以按照他所示制作这两个模板。

同时在 EditorTemplates 文件夹中添加一个 DateTime.cshtml,并用以下代码替换生成的代码

    @model Nullable
    @{
        DateTime dt = DateTime.Now;
        if (Model != null)
        {
            dt = (System.DateTime)Model;
        }
        @Html.TextBox("", String.Format("{0:d}", dt.ToShortDateString() + ' ' + 
        dt.ToShortTimeString()), new { @class = "form-control datecontrol", type = "datetime" })
    }

当我们添加新事件时,这会将 DateTime 设置为带时区(带有 Calendar.cshtml 中的 timezone: 'local' 指令)的本地时间。

最终,我们准备进行一些输入和输出。首先,输入。打开 TruckingsController,转到 public ActionResult Create(),将生成的代码更改为

    public ActionResult Create(DateTime truckDate)
        {
            ViewBag.TruckDate = truckDate;
            return View();
        }

这里的 truckDate 来自于 Calendar.cshtml 的以下代码,如前所示

    dayClick: function (date, jsEvent, view) {
                if(view.name != 'month') {
                    if (date > moment()) {
                        if (confirm("Add a new trucking?")) {
                            window.location = '@Url.Action("Create")' + '?truckDate=' + date.format();
                        }
                    }
                }
                else {
                    $('#calendar').fullCalendar('changeView', 'agendaDay');
                    $('#calendar').fullCalendar('gotoDate', date);
                }
            }

当您在 Calendar 中点击未来时间时,Calendar 会询问您是否要添加新的货运事件。如果您点击“确定”,Calendar 会将您发送到 Controller 的 Create 动作,并带上您刚刚点击的 truckDate (DateTime)。因此,ViewBag.TruckDate 现在有一个选定的值,我们想将选定的值添加到 Create.cshtml

    @Html.EditorFor(model => model.TruckDate, 
    new { htmlAttributes = new { @class = "form-control", @Value = ViewBag.TruckDate } })

然后返回 TruckingControllerPOST: Truckings/Create 部分,更改

    return RedirectToAction("Index");

to

    return RedirectToAction("Calendar", new { truckDate = trucking.TruckDate });

这个 truckDate 被重定向到 Calendar 动作,并作为 defaultDate: '@ViewBag.TruckDate' 发送到 Calendar 视图,以便 Calendar 返回到它来自的月份。许多开发人员问如何返回他们离开的 Calendar 月份,这就是这样做的方法。

EditDelete 部分中的 RedirectToAction 进行同样的操作。

然后,进入“货运视图”,在每个视图的末尾,更改

    @Html.ActionLink("Back to List", "Index")

to

    @Html.ActionLink("Back to Calendar", "Calendar", new { truckDate = Model.TruckDate })

除了 Create.cshtml,我们必须使用 "ViewBag.TruckDate",我们已经用它的选定值。这样当点击 "Back to Calendar" 时,它们都可以返回到它们来自的地方。

好的。这是输入。输出呢?啊...,等一下,让我休息一下...

好的。我们再来一次。首先,让我们看看日历视图:events: '@Url.Action("GetEvents")',这意味着,当日历打开时,它将转到 TruckingsControllerGetEvents 动作以获取事件。

所以我们必须打开 TruckingsController 并在末尾添加 GetEvents 动作

    public JsonResult GetEvents(DateTime start, DateTime end)
    {
        var truckings = (from t in db.Truckings
                        where t.TruckDate >= start && 
                        DbFunctions.AddMinutes(t.TruckDate, t.LoadMinutes) <= end
                        select new { t.TruckID, t.TruckDate, t.LoadMinutes, t.TruckStatus }).ToList()
        .Select(x => new Events()
        {
            id = x.TruckID,
            title = "Trucking " + x.TruckID + ", " + x.LoadMinutes + "min ",
            start = x.TruckDate,
            end = x.TruckDate.AddMinutes(x.LoadMinutes),
            color = Enum.GetName(typeof(TruckStatus), x.TruckStatus)
        }).ToArray();
        return Json(truckings, JsonRequestBehavior.AllowGet);
    }

开始和结束时间来自 Calendar,根据是月份、周还是日,它将获取该时间段内的事件。然后,事件通过 x => new Events() 这一步发送到 ViewModel,再从 ViewModel 发送到 Calendar,所有这些都在一个操作和一个地方完成。获取颜色只需一行代码。砰!它就在那里。

在 AJSON 的 项目中,他将一些控制器动作放入他的 ViewModel 中,这样控制器就必须调用 ViewModel 进行计算,然后返回数据,并且数据在发送到 Calendar 之前必须转换几次。效率不高。此外,他获取颜色的方式复杂而困难。

现在,让我们再看看日历视图

    eventClick: function(calEvent) {
                window.location='/Truckings/Details/' + calEvent.id;
            },

这意味着,如果我们点击一个货运事件,我们将看到事件的详细信息。从那里,我们可以返回,或者进行一些编辑并返回。很棒。

我们还可以在 Calendar 上拖放事件,如 eventDrop:eventResize: 所示,它们都调用 UpdateEvent 函数

    function UpdateEvent(id, start, end) {
        var obj = { id: id, start: start, end: end }
        $.ajax({
            type: 'POST',
            url: '@Url.Action("UpdateEvent")',
            dataType: "json",
            contentType: "application/json",
            data: JSON.stringify(obj)
        });
    }

UpdateEvent 又会调用 TruckingsControllerUpdateEvent 动作。所以我们需要将该动作添加到 Controller 的末尾

    public void UpdateEvent(int id, DateTime start, DateTime end)
    {
        Trucking trucking = db.Truckings.Find(id);
        trucking.TruckDate = start;
        trucking.LoadMinutes = (short)(end - start).TotalMinutes;
        db.SaveChanges();
    }

各位,就是这样!我们完成了!运行您的项目,点击链接进入日历。点击未来的时间并添加一些事件;更改状态以查看颜色变化;将事件拖放到不同的时间、日期、周或月份;点击事件以查看详细信息;进行一些编辑和删除;点击“返回日历”以返回原始月份。这是我的屏幕截图

你也应该看到类似的东西。

现在是庆祝的时候了!!哦,等等...,你有问题?建议?想法?查看我的 GitHub 仓库:一个完整的事件日历,以货运/航运为例。

参考文献

© . All rights reserved.