基于 Web 的日租日历






3.91/5 (12投票s)
2006年5月17日
6分钟阅读

129535

1998
在线按日计费的月视图日历,支持用户自定义租赁列表。
要求
有人请我做一个在线夏季度假屋租赁日历,功能如下:
- 每页显示一个月;
- 如果某一天有一个或多个单元出租,则显示该日期的所有租赁单元;
- 能够控制租赁单元的显示顺序,并能增加/删除租赁列表;
- 颜色编码的条目在页面上对齐;
- MS Access 数据库后端;
- 密码保护的管理页面。
背景
我本想找一个共享软件来满足我的需求,结果发现了 Paul Apostolos 的精彩文章《构建一个事件日历 Web 应用程序》。这篇文章成为了我的起点。
数据库
在 tbl_place 表中,place
字段保存了在新页面和编辑/删除页面下拉列表框中使用的完整租赁单元名称。abbre
字段是用于显示的租赁标题缩写(两个字母)。在数据表视图中,请自行输入你的 place
和 abbre
值。dsplOrder
是每个日期的租赁单元显示顺序。更改这里的编号可以决定日历上的顺序。留空(null)表示该单元不显示。
使用代码
正如 Paul 的文章所指出的,日历上的每一天都是单独渲染的,所以最好将需要从数据库中获取的信息为正在显示的页面存储一次。myCollection
和 MyDates
来自 Paul 的文章。我添加了最后三个数组来跟踪显示需求。这些存储在显示日历的 Calendar1_DayRender()
中,为每一天填充和清空。
//holds MyDate structs.
//See function: void GetDBItems()
public ArrayList myCollection;
public struct MyDates
{
//tbl_events.ID
public int _ID;
//tbl_events.date
public DateTime _date;
//tbl_events.cust
public string _renter;
//tbl_events.place_ID
public long _place;
//tbl_place.dsplOrder
public long _dsplOdr;
}
//abbreviations of rental items
public ArrayList rentalHeaders;
//displayed rental item that has a renter
public ArrayList placeDisplayOrder;
//ID of place is different than display order
public ArrayList placeID;
EditDel.aspx
Calendar1_DayRender()
:首先,GetDBItems()
会用 MyDates
结构(数据库记录)填充 myCollection
数组,以显示当前月份。ORDER BY
tbl_events.date
和 tbl_place.dsplOrder
,这样 DataReader
就可以一次性获取每一天,并以正确的租赁单元显示顺序。获取两位数的租赁标题。在 foreach
循环中,我们检查日期。现在,为该日期构建一个 HTML 行。
private void Calendar1_DayRender(object sender,
System.Web.UI.WebControls.DayRenderEventArgs e)
{
DateTime dayHold;
dayHold=Calendar1.TodaysDate.AddYears(-40);
bool dayTextHasChanged = false;
temp = new StringBuilder();
//display order
placeDisplayOrder = new ArrayList();
if(myCollection==null)
{
GetDBItems();
GetRentalHeaders();
}
foreach(MyDates Item in myCollection)
{
if(dayHold.CompareTo(Item._date)==0)
{
if(dayTextHasChanged==true)
//start a new day
{
return;
}
dayHold=Item._date;
}
if(e.Day.Date.ToShortDateString()==Item._date.ToString("d"))
{
//a list of cottages with renters for the day we are on
placeDisplayOrder.Add((int)Item._dsplOdr);
//start by putting the span
temp.Append("<span style='font-family:Arial; " +
"font-weight:bold;font-size:12px;'>");
//See Display() below
Display(sequence, (int)Item._dsplOdr-1, Item._renter);
sequence++;
dayTextHasChanged = true;
}//end day compare
}//end foreach
if(dayTextHasChanged == true)
{
//finally print any remaining
//rental headers after the last booking
//See DisplayLast() below
DisplayLast(sequence, (int)placeD
isplayOrder[placeDisplayOrder.Count-1]);
temp.Append("</font>");
temp.Append("</span>");
e.Cell.Controls.Add(new LiteralControl(temp.ToString()));
//reset sequence
sequence=0;
}
}//End Calendar1_DayRender
Display()
:将内容添加到当前日期的 HTML 行中。DataReader
逐行读取数据库。当我们开始新的一天时,sequence
的值将是 0。pl
是租赁单元的显示编号。假设这个编号是 3。第一个 for
循环打印租赁标题 0、1 和 2。当 i
达到 3 时,我们打印租用者。如果这不是当前日期第一个有租用者的租赁单元,我们就进入 else
语句。在 placeDisplayOrder
数组中,如果数组中最后一个数字减去倒数第二个数字不等于 1,那么我们就必须打印这两个租赁单元之间的 rentalHeaders
。
void Display(int sq, int pl, string renter)
{
string textColor;
//first time build string up to and including the first booking
if(sq==0)
{
for(int i=sq; i<=pl; i++)
{
textColor = GetTextColor(i);
temp.Append("<font color='"+textColor+"'>");
temp.Append(rentalHeaders[i]);
if(i==pl)
{
temp.Append(renter);
}
temp.Append("</font>");
}
}
else
{
//if the index of the current place
//minus index of last place is not one
if((int)placeDisplayOrder[sq]-(int)placeDisplayOrder[sq-1]!=1)
{
//print rental headers between the two bookings
for(int i=(int)placeDisplayOrder[sq-1]; i<=pl; i++)
{
textColor = GetTextColor(i);
temp.Append("<font color='"+textColor+"'>");
temp.Append(rentalHeaders[i]);
if(i==pl)
{
temp.Append(renter);
}
temp.Append("</font>");
}
}
else
{
//otherwise just print the next booking
textColor = GetTextColor((int)placeDisplayOrder[sq-1]);
temp.Append("<font color='"+textColor+"'>");
temp.Append(rentalHeaders[(int)placeDisplayOrder[sq-1]]);
temp.Append(renter);
temp.Append("</font>");
}
}
}
当 Calendar1_DayRender()
中的 foreach
循环遇到新日期时,我们使用 DisplayLast()
来检查剩余的 rentalHeaders
,并将它们添加到该日期的 HTML 字符串中。换句话说,如果有 6 个单元出租,但第六个单元没有租用者,我们仍然想显示最后一个单元的 rentalHeader
。
//Display remaining headers, take the total number
//of rental units minus the last unit with a booking you
//printed. Use the array of rentalHeaders to print
//the headers at the end of the list you need.
void DisplayLast(int sq, int pl)
{
int remainingHeaders = (int)Session["numRentals"]-pl;
int i=0;
while(i<remainingHeaders)
{
string txtcolor = GetTextColor(pl+i);
temp.Append("<font color='"+txtcolor+"'>");
temp.Append(rentalHeaders[pl+i]);
i++;
}
}
在日历中,日期编号是超链接。当你点击一个日期时,会调用 Calendar1_SelectionChanged()
。首先,将该日期的所有预订放入 DataReader
。使用查询字符串(ID、租赁单元和租用者)构建一个超链接。使用 HtmlGenericControl
类,你可以从服务器运行常规的 HTML 标签。selectedday
<span>
标签用于显示日期。daydetail_render
<span>
标签用于显示超链接。daydetail
<div>
标签包含另外两个标签,并用于控制另外两个标签何时可见。以下是 <form>
标签内的 HTML 代码。
<div id="daydetail" runat="server" Visible="False">
<h3>Update or Delete:
<span id="selectedday" runat="server"></span>
</h3>
<span id="daydetail_render" runat="server"></span>
</div>
这是代码隐藏文件
private void Calendar1_SelectionChanged(object sender, System.EventArgs e)
{
string temp="";
//reset record updated msg
lblMsg.Text = "";
string selDate = "#"+Calendar1.SelectedDate.ToShortDateString()+"#";
string sql = "SELECT tbl_events.ID, tbl_events.date," +
" tbl_events.cust, tbl_place.place " +
"FROM tbl_events INNER JOIN tbl_place" +
" ON tbl_events.place_ID = tbl_place.ID" +
"WHERE [date] = "+selDate+" ORDER BY tbl_place.ID";
conn = new OleDbConnection(strConn);
try
{
conn.Open();
cmd = new OleDbCommand(sql, conn);
dr = cmd.ExecuteReader();
int i=0;
if(dr.HasRows)
{
while(dr.Read())
{
temp += "<a href='EditDel.aspx?ID=" +dr["ID"]+"'>" +
dr["place"]+": "+dr["cust"]+ "</a><br>";
i++;
}
daydetail.Visible = true;
daydetail_render.InnerHtml = temp;
selectedday.InnerHtml =
Calendar1.SelectedDate.ToString("MMM d, yyyy");
dr.Close();
conn.Close();
Calendar1.Attributes.Add("style", calendarPos(i));
}
}
catch
{
conn.Close();
}
}
所选日期的所有预订都显示在日历上方,作为超链接。在 Calendar1.Attributes.Add("style", calendarPos(i));
中,i
是超链接行数。calendarPos(i)
将日历的绝对位置向下移动 i
次,以便你可以看到超链接。
选择日期后的显示效果
点击超链接(School House)会告知服务器打开 EditDel.aspx(当前页面),但这次会附加一个查询字符串。Bind()
会看到查询字符串:if(Request.QueryString["ID"]!= null)
会使另一个日历控件、一个下拉框和一个文本框可见,并根据 ID 从记录中填充数据。Calendar1.SelectionMode = (CalendarSelectionMode)0;
会关闭主日历上的日期超链接,以防止用户在更新或删除当前选定的日期或使用后退按钮(这等同于取消)之前选择另一个日期。这是选择超链接后的显示效果。
点击更新按钮时,IsAvailable()
会检查是否有重复预订并显示一条消息。
bool IsAvailable(DateTime date, int place, int id)
{
bool isAvail = true;
if(myCollection==null)
{
GetDBItems();
}
foreach(MyDates Item in myCollection)
{
if(id != Item._ID)
{
if(Item._date == date && Item._place == place)
{
isAvail=false;
}
}
}
if(isAvail==false)
{
lblMsg.Text = "<span style='font-family:Arial; " +
"font-weight:bold;font-size:16px; color:red;'>" +
"This date and cottage is " +
"already booked.</span>";
return false;
}
else
{
lblMsg.Text = "<span style='font-family:Arial; " +
"font-weight:bold;font-size:16px; color:green;'>" +
"Record updated.</span>";
return true;
}
}
文件夹
这是我的 inetpub/wwwroot/ 中的应用程序。所有公共页面都位于 cottrent 根文件夹中,不需要登录。admin 文件夹包含 Add.aspx、EditDel.aspx、login.aspx 和链接页面 admin.aspx。db 文件夹包含 cottrent.mdb Access 数据库文件。
Web.config
根文件夹中的 Web.config 文件保存连接字符串,一个用于本地,一个用于远程。本地版本保留在我的计算机上,而远程版本则在 Windows 主机服务器上。这样,我就可以在本地服务器上进行测试和上传到远程服务器之间无需更改任何代码或重新编译。远程字符串被注释掉了,所以这个配置文件在我的本地机器上使用。代码位于 <configuration>
和 <system.web>
之间。
<appSettings>
<!--Local Database connection string-->
<add key="AccessConnStr" value="Provider=Microsoft.Jet.OLEDB.4.0;" +
"Data Source=C:\\inetpub\\wwwroot\
\cottrent\\db\\cottrent.mdb;" />
<!--Remote Database connection string-->
<!--<add key="AccessConnStr" value="Provider=Microsoft.Jet.OLEDB.4.0;" +
Data Source=c:\\account\\domain\\wwwRoot\\
rent\\db\\cottrent.mdb;" />-->
</appSettings>
在我的 C# 代码中,我声明了字符串。
protected static string strConn =
ConfigurationSettings.AppSettings["AccessConnStr"];
同样,在根目录的 Web.config 中有表单登录。在此设置你的用户名和密码。
<authentication mode="Forms" >
<forms loginUrl="admin/login.aspx" >
<credentials passwordFormat="Clear">
<user name="user" password="password"/>
</credentials>
</forms>
</authentication >
<authorization>
<allow users="*" /> <!-- Allow all users -->
</authorization>
admin 文件夹中的另一个 Web.config 文件允许用户名为“user”的用户访问并调用登录表单。其他人则被拒绝。
<authorization>
<allow users="user" /> <!-- Allow user -->
<deny users="*" /> <!-- deny everyone else -->
</authorization>
打印友好
这是 Visual Studio 生成的 dayrental.aspx 的 HTML。粗体文本(参见第一页)已被移除并放入 style
部分(参见第二个代码片段中的粗体文本)。
<HTML>
<HEAD>
<title>dayrental</title>
<meta name="GENERATOR" Content="Microsoft Visual Studio .NET 7.1">
<meta name="CODE_LANGUAGE" Content="C#">
<meta name="vs_defaultClientScript" content="JavaScript">
<meta name="vs_targetSchema"
content="http://schemas.microsoft.com/intellisense/ie5">
</HEAD>
<body MS_POSITIONING="GridLayout">
<form id="Form1" method="post" runat="server">
<asp:Calendar id="Calendar1"
style="Z-INDEX: 101; LEFT: 8px; POSITION: absolute; TOP: 8px"
runat="server" Width="910px"
BorderColor="Black" SelectionMode="None"
PrevMonthText="<<Back"
NextMonthText="Next>>">
<DayStyle Font-Size="12px" Font-Names="Verdana"
Font-Bold="True" Wrap="False" HorizontalAlign="Left"
Height="100px" BorderWidth="1px"
BorderStyle="Solid" Width="120px"
VerticalAlign="Top"></DayStyle>
<NextPrevStyle Font-Size="14px"></NextPrevStyle>
<DayHeaderStyle Font-Names="Verdana"></DayHeaderStyle>
<SelectedDayStyle ForeColor="Black"
BackColor="White"></SelectedDayStyle>
<TitleStyle Font-Size="18px"
Font-Names="Verdana" Font-Bold="True"></TitleStyle>
<OtherMonthDayStyle ForeColor="DarkGray">
</OtherMonthDayStyle>
</asp:Calendar>
</form>r>
</body>
</HTML>
为了使日历正确打印,请将 <form>
部分的属性移到 <head>
中的 <style>
部分。使用 CSS @media
标签,为屏幕和打印分别创建一个版本。
<HTML>
<HEAD>
<title>dayrental</title>
<meta name="GENERATOR"
Content="Microsoft Visual Studio .NET 7.1">
<meta name="CODE_LANGUAGE" Content="C#">
<meta name="vs_defaultClientScript" content="JavaScript">
<meta name="vs_targetSchema"
content="http://schemas.microsoft.com/intellisense/ie5">
<style type="text/css">
@media Screen { .dayrentcal { WIDTH: 910px; }
.caldaystyle { FONT-SIZE: 12px; Height: 100px; }}
@media Print { .dayrentcal { WIDTH: 460pt; }
.caldaystyle { FONT-SIZE: 7.5pt; Height: 90px; }}
</style>
</HEAD>
<body MS_POSITIONING="GridLayout">
<form id="Form1" method="post" runat="server">
<asp:Calendar id="Calendar1"
style="Z-INDEX: 101; LEFT: 8px; POSITION: absolute; TOP: 8px"
runat="server" BorderColor="Black" SelectionMode="None"
PrevMonthText="<<Back"
NextMonthText="Next>>" CssClass="dayrentcal">
<DayStyle Font-Names="Verdana" Font-Bold="True" Wrap="False"
HorizontalAlign="Left" BorderWidth="1px" BorderStyle="Solid"
CssClass="caldaystyle" VerticalAlign="Top"></DayStyle>
<NextPrevStyle Font-Size="14px"></NextPrevStyle>
<DayHeaderStyle Font-Names="Verdana"></DayHeaderStyle>
<SelectedDayStyle ForeColor="Black" BackColor="White">
</SelectedDayStyle>
<TitleStyle Font-Size="18px" Font-Names="Verdana" Font-Bold="True">
</TitleStyle>
<OtherMonthDayStyle ForeColor="DarkGray"></OtherMonthDayStyle>
</asp:Calendar>
</form>
</body>
</HTML>
.dayrentcal
来自于 <style>
部分中的什么地方?这在 Visual Studio 的日历属性窗口中设置。该属性名为 CssClass
。同样,.caldaystyle
在同一个属性窗口的 DayStyle
下,然后是 CssClass
。就是这样。现在,当用户点击浏览器窗口菜单或工具栏的打印按钮时,日历将适合页面。
本地安装
解压缩到 inetpub/wwwroot。在 IIS 中创建一个 虚拟目录。用 Visual Studio 2003 或更高版本打开。打开 Access 并根据需要修改 tbl_place 表。
远程安装
将演示项目下载放到你的网站上。
- 创建一个文件夹,并将其设置为“设置为应用程序目录”和“执行 ASP/ASP.NET/PHP 脚本”。
- 在 application directory/Web.config 中 - 设置数据库连接字符串、用户名和密码。
- 在 admin/Web.config 中 - 将用户名设置为与其他 Web.config 相同。
- 将 dayrental.aspx、denied.html 和 Web.config 文件上传到你的应用程序目录。
- 在你的应用程序目录中创建三个子目录:admin、bin 和 db。
- 将 Add.aspx、admin.aspx、EditDel.aspx、login.aspx 和 Web.config 放入 admin 目录。
- 将 cottrent.dll 放入 bin 目录。
- 打开 Access 并根据需要修改 tbl_place 表。
- 将 cottrent.mdb 放入 db 目录。
- 将 db 目录设置为“锁定目录不被 Web 浏览(仍允许通过网站脚本访问)”。
- 设置修改权限。