使用 C# MVC 和 jQuery.Fullcalendar 为货运/航运创建一个事件日历
一个完整的事件日历,以货运/航运为例,使用 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.Fullcalendar
和 Moment.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>
我们可以看到,上面是已安装的包(Fullcalendar
和 Moments
)的使用之处。代码继续...
<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;
}
我们还需要一个用于 TruckStatus
的 Enum
,请记住其数据类型为 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 } })
然后返回 TruckingController
的 POST
: Truckings/Create 部分,更改
return RedirectToAction("Index");
to
return RedirectToAction("Calendar", new { truckDate = trucking.TruckDate });
这个 truckDate
被重定向到 Calendar
动作,并作为 defaultDate
: '@ViewBag.TruckDate
' 发送到 Calendar 视图,以便 Calendar
返回到它来自的月份。许多开发人员问如何返回他们离开的 Calendar
月份,这就是这样做的方法。
对 Edit
和 Delete
部分中的 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")
',这意味着,当日历打开时,它将转到 TruckingsController
的 GetEvents
动作以获取事件。
所以我们必须打开 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
又会调用 TruckingsController
的 UpdateEvent
动作。所以我们需要将该动作添加到 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 仓库:一个完整的事件日历,以货运/航运为例。
参考文献
- Adam Shaw: FullCalendar
- AJSON: Full calendar – 一个完整的 jQuery 和 C# MVC 网络日记系统
- Shahriar Hossain: 在 MVC 5.1 中处理枚举