From 5e7d321eda4342d84040898f7060e4db99886181 Mon Sep 17 00:00:00 2001 From: Zhou Zihao Date: Wed, 28 Jun 2023 17:58:28 +0800 Subject: [PATCH 1/3] =?UTF-8?q?=E5=AE=8C=E6=88=90=20TCalendar=20=E7=9A=84?= =?UTF-8?q?=E5=B9=B4=E4=BB=BD=E6=B8=B2=E6=9F=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- doc/TDesign.Docs.ServerSide/TDesign.xml | 88 +++++++ doc/TDesign.Docs.Shared/Layouts/NavMenu.razor | 1 + .../Pages/Components/Data/CalendarPage.razor | 20 ++ .../wwwroot/TDesign.xml | 88 +++++++ src/TDesign/Components/TCalendar.cs | 248 ++++++++++++++++++ 5 files changed, 445 insertions(+) create mode 100644 doc/TDesign.Docs.Shared/Pages/Components/Data/CalendarPage.razor create mode 100644 src/TDesign/Components/TCalendar.cs diff --git a/doc/TDesign.Docs.ServerSide/TDesign.xml b/doc/TDesign.Docs.ServerSide/TDesign.xml index b3fb43b5..4d66ac21 100644 --- a/doc/TDesign.Docs.ServerSide/TDesign.xml +++ b/doc/TDesign.Docs.ServerSide/TDesign.xml @@ -3743,6 +3743,94 @@ 表示为 reset 重置按钮,可触发 form 的重置功能。 + + + 按照日历形式展示数据或日期的容器。 + + + + + 使用卡片模式。 + + + + + 日历的模式。 + + + + + 是否显示周末。 有效。 + + + + + 日历呈现的年份。默认是当前年份。 + + + + + 日历呈现的月份。默认是当前月份。 + + + + + 小于 10 的日期,是否使用 '0' 填充。 + + + + + 周几算第一天。 + + + + + 星期对应的文本。 + + + + + 当前年。 + + + + + 当前月。 + + + + + 构建日历的头部。 + + + + + + 构建日历的主体。 + + + + + + 构建年的单元格和行 + + + + + + 日历模式。 + + + + + 按月显示。 + + + + + 按年显示。 + + 卡片组件。 diff --git a/doc/TDesign.Docs.Shared/Layouts/NavMenu.razor b/doc/TDesign.Docs.Shared/Layouts/NavMenu.razor index 88b9f1cb..165f5c04 100644 --- a/doc/TDesign.Docs.Shared/Layouts/NavMenu.razor +++ b/doc/TDesign.Docs.Shared/Layouts/NavMenu.razor @@ -42,6 +42,7 @@ Avatar 头像 Badge 徽标 + Calendar 日历 Card 卡片 Collapse 折叠面板 Comment 评论 diff --git a/doc/TDesign.Docs.Shared/Pages/Components/Data/CalendarPage.razor b/doc/TDesign.Docs.Shared/Pages/Components/Data/CalendarPage.razor new file mode 100644 index 00000000..92a04fc3 --- /dev/null +++ b/doc/TDesign.Docs.Shared/Pages/Components/Data/CalendarPage.razor @@ -0,0 +1,20 @@ +@page "/components/calendar" + + + 按照日历形式展示数据或日期的容器。 + + + + + 在日期中可显示事项的日期显示容器。常用于有足够空间,且需要承载或显示事项信息时使用。 + + + + + @Code.Create(@" +```cshtml-razor +``` +") + + + \ No newline at end of file diff --git a/doc/TDesign.Docs.WebAssembly/wwwroot/TDesign.xml b/doc/TDesign.Docs.WebAssembly/wwwroot/TDesign.xml index b3fb43b5..4d66ac21 100644 --- a/doc/TDesign.Docs.WebAssembly/wwwroot/TDesign.xml +++ b/doc/TDesign.Docs.WebAssembly/wwwroot/TDesign.xml @@ -3743,6 +3743,94 @@ 表示为 reset 重置按钮,可触发 form 的重置功能。 + + + 按照日历形式展示数据或日期的容器。 + + + + + 使用卡片模式。 + + + + + 日历的模式。 + + + + + 是否显示周末。 有效。 + + + + + 日历呈现的年份。默认是当前年份。 + + + + + 日历呈现的月份。默认是当前月份。 + + + + + 小于 10 的日期,是否使用 '0' 填充。 + + + + + 周几算第一天。 + + + + + 星期对应的文本。 + + + + + 当前年。 + + + + + 当前月。 + + + + + 构建日历的头部。 + + + + + + 构建日历的主体。 + + + + + + 构建年的单元格和行 + + + + + + 日历模式。 + + + + + 按月显示。 + + + + + 按年显示。 + + 卡片组件。 diff --git a/src/TDesign/Components/TCalendar.cs b/src/TDesign/Components/TCalendar.cs new file mode 100644 index 00000000..a9441ab0 --- /dev/null +++ b/src/TDesign/Components/TCalendar.cs @@ -0,0 +1,248 @@ +namespace TDesign; + +/// +/// 按照日历形式展示数据或日期的容器。 +/// +[CssClass("t-calendar")] +public class TCalendar : TDesignComponentBase +{ + #region Parameters + /// + /// 使用卡片模式。 + /// + [Parameter][BooleanCssClass("t-calendar--card","t-calendar--full")]public bool Card { get; set; } + /// + /// 日历的模式。 + /// + [Parameter] public CalendarMode Mode { get; set; } = CalendarMode.Year; + + /// + /// 是否显示周末。 有效。 + /// + [Parameter]public bool ShowWeekend { get; set; } + /// + /// 日历呈现的年份。默认是当前年份。 + /// + [Parameter] public int Year { get; set; } = DateTime.Now.Year; + /// + /// 日历呈现的月份。默认是当前月份。 + /// + [Parameter] public int Month { get; set; } = DateTime.Now.Month; + /// + /// 小于 10 的日期,是否使用 '0' 填充。 + /// + [Parameter]public bool FiilWithZero { get; set; } + /// + /// 周几算第一天。 + /// + [Parameter] public DayOfWeek FirstDayOfWeek { get; set; } = DayOfWeek.Monday; + #endregion + + #region Internal + /// + /// 星期对应的文本。 + /// + Dictionary DayOfWeekTextMapper { get; set; } = new() + { + [DayOfWeek.Monday] = "周一", + [DayOfWeek.Tuesday] = "周二", + [DayOfWeek.Wednesday] = "周三", + [DayOfWeek.Thursday] = "周四", + [DayOfWeek.Friday] = "周五", + [DayOfWeek.Saturday] = "周六", + [DayOfWeek.Sunday] = "周日" + }; + + List DayOfWeekList { get; set; } = new() + { + DayOfWeek.Monday, + DayOfWeek.Tuesday, + DayOfWeek.Wednesday, + DayOfWeek.Thursday, + DayOfWeek.Friday, + DayOfWeek.Saturday, + DayOfWeek.Sunday + }; + + + /// + /// 当前年。 + /// + int CurrentYear { get; set; } + /// + /// 当前月。 + /// + int CurrentMonth { get; set; } + #endregion + + protected override void OnParametersSet() + { + base.OnParametersSet(); + + CurrentYear = Year; + CurrentMonth = Month; + + #region 调整第一天是周几的顺序 + var theFirstDayIndex = DayOfWeekList.FindIndex(m => m == FirstDayOfWeek); + var orderRange = DayOfWeekList.GetRange(theFirstDayIndex, DayOfWeekList.Count - theFirstDayIndex); + DayOfWeekList.RemoveRange(theFirstDayIndex, DayOfWeekList.Count - theFirstDayIndex); + DayOfWeekList.InsertRange(0, orderRange); + #endregion + } + + protected override void AddContent(RenderTreeBuilder builder, int sequence) + { + builder.Div("t-calendar__panel") + .Class(Mode.GetCssClass("t-calendar__panel--")) + .Content(BuildCalendarTable) + .Close(); + } + + void BuildCalendarTable(RenderTreeBuilder builder) + { + builder.Element("table", "t-calendar__table") + .Content(table => + { + BuildCalendarHeader(table); + BuildCalendarBody(table); + }) + .Close(); + } + + /// + /// 构建日历的头部。 + /// + /// + void BuildCalendarHeader(RenderTreeBuilder builder) + { + if(Mode!= CalendarMode.Mounth ) + { + return; + } + builder.Element("thead") + .Content(head => + { + head.Element("tr", "t-calendar__table-head-row") + .Content(tr => + { + foreach ( var item in DayOfWeekList ) + { + //不显示周末 + if ( !ShowWeekend && new[] { DayOfWeek.Saturday, DayOfWeek.Sunday }.Contains(item) ) + { + continue; + } + + var mapperText = DayOfWeekTextMapper[item]; + + tr.Element("th", "t-calendar__table-head-cell") + .Content(cell => cell.Span().Content(mapperText).Close()) + .Close(); + } + }) + .Close(); + }) + .Close(); + } + + /// + /// 构建日历的主体。 + /// + /// + void BuildCalendarBody(RenderTreeBuilder builder) + { + builder.Element("tbody", "t-calendar__table-body") + .Content(body => + { + if(Mode== CalendarMode.Mounth ) + { + + } + else + { + BuildYearBody(body); + } + }) + .Close(); + } + + /// + /// 构建年的单元格和行 + /// + /// + void BuildYearBody(RenderTreeBuilder builder) + { + //1年12个月,4列3行 + + builder.OpenElement(0, "tr"); + builder.AddAttribute(1, "class", "t-calendar__table-body-row"); + var sequence = 0; + for ( int i = 1; i <= 12; i++ ) + { + builder.AddContent(sequence + 2, cell => + { + BuildBodyCell(cell, $"{i} 月", now: CurrentYear == Year && CurrentMonth == i); + }); + if ( i % 4 == 0 ) + { + sequence += 4 * i; + ClosePrevRowAndBuildNewRow(builder, sequence); + } + } + builder.CloseElement(); + } + + /// + /// 关闭上一个 tr 并开始新的 tr。用于动态换行。 + /// + /// + /// + private static void ClosePrevRowAndBuildNewRow(RenderTreeBuilder builder, int sequence) + { + builder.CloseElement(); + builder.OpenElement(sequence, "tr"); + builder.AddAttribute(sequence + 1, "class", "t-calendar__table-body-row"); + } + + /// + /// 构建单元格。 + /// + /// + /// + /// + /// + static void BuildBodyCell(RenderTreeBuilder builder,string content,bool disabled = false,bool now=default) + { + builder.Div("t-calendar__table-body-cell") + .Class("t-is-disabled", disabled) + .Class("t-calendar__table-body-cell--now", now) + .Class("t-is-checked",now) + .Content(cell => + { + cell.Div() + .Style("display: flex; flex-direction: column; align-items: flex-end;") + .Content(inner => + { + inner.Div("t-calendar__table-body-cell-display").Content(content).Close(); + inner.Div("t-calendar__table-body-cell-content").Close(); + }) + .Close(); + }) + .Close(); + } +} + +/// +/// 日历模式。 +/// +public enum CalendarMode +{ + /// + /// 按月显示。 + /// + Mounth, + /// + /// 按年显示。 + /// + Year, +} \ No newline at end of file From d2928c212de7bc6172fe8629729b20391f7efcfa Mon Sep 17 00:00:00 2001 From: Zhou Zihao Date: Thu, 29 Jun 2023 15:31:45 +0800 Subject: [PATCH 2/3] =?UTF-8?q?=E5=AE=8C=E6=88=90=E6=97=A5=E5=8E=86?= =?UTF-8?q?=E7=9A=84=E5=A4=A9=E6=95=B0=E7=AE=97=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- doc/TDesign.Docs.ServerSide/TDesign.xml | 32 +++++ .../Pages/Components/Data/CalendarPage.razor | 3 +- .../wwwroot/TDesign.xml | 32 +++++ src/TDesign/Components/TCalendar.cs | 135 +++++++++++++++--- 4 files changed, 178 insertions(+), 24 deletions(-) diff --git a/doc/TDesign.Docs.ServerSide/TDesign.xml b/doc/TDesign.Docs.ServerSide/TDesign.xml index 4d66ac21..bf1d127d 100644 --- a/doc/TDesign.Docs.ServerSide/TDesign.xml +++ b/doc/TDesign.Docs.ServerSide/TDesign.xml @@ -3804,6 +3804,13 @@ + + + 是否跳过周末 + + + + 构建日历的主体。 @@ -3816,6 +3823,31 @@ + + + 构建月视图。 + + + 要计算日期。 + + + + + + 关闭上一个 tr 并开始新的 tr。用于动态换行。 + + + + + + + 构建单元格。 + + + + + + 日历模式。 diff --git a/doc/TDesign.Docs.Shared/Pages/Components/Data/CalendarPage.razor b/doc/TDesign.Docs.Shared/Pages/Components/Data/CalendarPage.razor index 92a04fc3..12de16cf 100644 --- a/doc/TDesign.Docs.Shared/Pages/Components/Data/CalendarPage.razor +++ b/doc/TDesign.Docs.Shared/Pages/Components/Data/CalendarPage.razor @@ -8,7 +8,8 @@ 在日期中可显示事项的日期显示容器。常用于有足够空间,且需要承载或显示事项信息时使用。 - + + @Code.Create(@" diff --git a/doc/TDesign.Docs.WebAssembly/wwwroot/TDesign.xml b/doc/TDesign.Docs.WebAssembly/wwwroot/TDesign.xml index 4d66ac21..bf1d127d 100644 --- a/doc/TDesign.Docs.WebAssembly/wwwroot/TDesign.xml +++ b/doc/TDesign.Docs.WebAssembly/wwwroot/TDesign.xml @@ -3804,6 +3804,13 @@ + + + 是否跳过周末 + + + + 构建日历的主体。 @@ -3816,6 +3823,31 @@ + + + 构建月视图。 + + + 要计算日期。 + + + + + + 关闭上一个 tr 并开始新的 tr。用于动态换行。 + + + + + + + 构建单元格。 + + + + + + 日历模式。 diff --git a/src/TDesign/Components/TCalendar.cs b/src/TDesign/Components/TCalendar.cs index a9441ab0..25c4de87 100644 --- a/src/TDesign/Components/TCalendar.cs +++ b/src/TDesign/Components/TCalendar.cs @@ -14,7 +14,7 @@ public class TCalendar : TDesignComponentBase /// /// 日历的模式。 /// - [Parameter] public CalendarMode Mode { get; set; } = CalendarMode.Year; + [Parameter] public CalendarMode Mode { get; set; } = CalendarMode.Mounth; /// /// 是否显示周末。 有效。 @@ -128,7 +128,7 @@ void BuildCalendarHeader(RenderTreeBuilder builder) foreach ( var item in DayOfWeekList ) { //不显示周末 - if ( !ShowWeekend && new[] { DayOfWeek.Saturday, DayOfWeek.Sunday }.Contains(item) ) + if ( IsSkipWeekend(item) ) { continue; } @@ -145,6 +145,13 @@ void BuildCalendarHeader(RenderTreeBuilder builder) .Close(); } + /// + /// 是否跳过周末 + /// + /// + /// + bool IsSkipWeekend(DayOfWeek day) => !ShowWeekend && new[] { DayOfWeek.Saturday, DayOfWeek.Sunday }.Contains(day); + /// /// 构建日历的主体。 /// @@ -156,7 +163,7 @@ void BuildCalendarBody(RenderTreeBuilder builder) { if(Mode== CalendarMode.Mounth ) { - + BuildMonthBody(body); } else { @@ -179,10 +186,7 @@ void BuildYearBody(RenderTreeBuilder builder) var sequence = 0; for ( int i = 1; i <= 12; i++ ) { - builder.AddContent(sequence + 2, cell => - { - BuildBodyCell(cell, $"{i} 月", now: CurrentYear == Year && CurrentMonth == i); - }); + BuildBodyCell(builder, $"{i} 月", now: CurrentYear == Year && CurrentMonth == i); if ( i % 4 == 0 ) { sequence += 4 * i; @@ -192,6 +196,88 @@ void BuildYearBody(RenderTreeBuilder builder) builder.CloseElement(); } + /// + /// 构建月视图。 + /// + /// + /// 要计算日期。 + /// + /// + void BuildMonthBody(RenderTreeBuilder builder) + { + builder.OpenElement(0, "tr"); + builder.AddAttribute(1, "class", "t-calendar__table-body-row"); + + #region 构建上个月剩余的几天在当前这个月的日历 + //当前年月的第一天 + var firstDay = new DateTime(CurrentYear, CurrentMonth, 1); + + var firstDayOfWeek = firstDay.DayOfWeek; //第一天所在周几 + var firstDayOfWeekIndex = (int)firstDayOfWeek; + + var findLastMonthDayIndex = DayOfWeekList.FindIndex(m => m == firstDayOfWeek); + + var lastDays = firstDay.AddDays(-findLastMonthDayIndex); + for ( int i = 0; i < findLastMonthDayIndex; i++ ) + { + var day = i + lastDays.Day; + + var lastMonthDateTime = new DateTime(lastDays.Year, lastDays.Month, day); + + if( IsSkipWeekend(lastMonthDateTime.DayOfWeek) ) + { + continue; + } + + BuildBodyCell(builder, day.ToString(), true); + } + #endregion + + #region 构建这个月的日历 + var daysOfThisMonth = DateTime.DaysInMonth(CurrentYear, CurrentMonth);//这个月有多少天 + for ( int i = 0; i < daysOfThisMonth; i++ ) + { + var day = i + 1; + var currentDateTime = new DateTime(CurrentYear, CurrentMonth, day); + if(IsSkipWeekend(currentDateTime.DayOfWeek) ) + { + ClosePrevRowAndBuildNewRow(builder, i + 10);//跳过周末要换行 + continue; + } + + var today = DateTime.Today; + BuildBodyCell(builder, day.ToString(), false, today == currentDateTime); + + if ( (i + firstDayOfWeekIndex) % 7 == 0 ) + { + ClosePrevRowAndBuildNewRow(builder, i + 10); + } + } + #endregion + + #region 构建下个月的头几天到当前日历 + var lastDateTime = new DateTime(CurrentYear, CurrentMonth, daysOfThisMonth);//本月最后一天 + var lastDayOfWeek = lastDateTime.DayOfWeek;//最后一天是周几 + + var lastDayOfWeekIndex = DayOfWeekList.FindIndex(m => m == lastDayOfWeek); + + var nextMonth = lastDateTime.AddDays(1); + for ( int i = 0; i <= (5 - lastDayOfWeekIndex); i++ ) + { + var day=i + 1; + var nextMonthDateTime = new DateTime(nextMonth.Year, nextMonth.Month, day); + if ( IsSkipWeekend(nextMonthDateTime.DayOfWeek) ) + { + continue; + } + + BuildBodyCell(builder, day.ToString(), true); + } + #endregion + + builder.CloseElement(); + } + /// /// 关闭上一个 tr 并开始新的 tr。用于动态换行。 /// @@ -213,22 +299,25 @@ private static void ClosePrevRowAndBuildNewRow(RenderTreeBuilder builder, int se /// static void BuildBodyCell(RenderTreeBuilder builder,string content,bool disabled = false,bool now=default) { - builder.Div("t-calendar__table-body-cell") - .Class("t-is-disabled", disabled) - .Class("t-calendar__table-body-cell--now", now) - .Class("t-is-checked",now) - .Content(cell => - { - cell.Div() - .Style("display: flex; flex-direction: column; align-items: flex-end;") - .Content(inner => - { - inner.Div("t-calendar__table-body-cell-display").Content(content).Close(); - inner.Div("t-calendar__table-body-cell-content").Close(); - }) - .Close(); - }) - .Close(); + builder.AddContent(0, tr => + { + tr.Div("t-calendar__table-body-cell") + .Class("t-is-disabled", disabled) + .Class("t-calendar__table-body-cell--now", now) + .Class("t-is-checked", now) + .Content(cell => + { + cell.Div() + .Style("display: flex; flex-direction: column; align-items: flex-end;") + .Content(inner => + { + inner.Div("t-calendar__table-body-cell-display").Content(content).Close(); + inner.Div("t-calendar__table-body-cell-content").Close(); + }) + .Close(); + }) + .Close(); + }); } } From 13942c741af9e6020e8044feddf1bf98bb577588 Mon Sep 17 00:00:00 2001 From: Zhou Zihao Date: Thu, 29 Jun 2023 17:49:18 +0800 Subject: [PATCH 3/3] =?UTF-8?q?=E5=AE=8C=E6=88=90=E6=8E=A7=E5=88=B6?= =?UTF-8?q?=E5=99=A8=E7=9A=84=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 但是 TSelect 有问题,导致年月下拉选中不可用 --- doc/TDesign.Docs.ServerSide/TDesign.xml | 67 ++++-- .../Pages/Components/Data/CalendarPage.razor | 1 - .../wwwroot/TDesign.xml | 67 ++++-- src/TDesign/Components/TCalendar.cs | 223 ++++++++++++++++-- 4 files changed, 302 insertions(+), 56 deletions(-) diff --git a/doc/TDesign.Docs.ServerSide/TDesign.xml b/doc/TDesign.Docs.ServerSide/TDesign.xml index bf1d127d..4f9f91f7 100644 --- a/doc/TDesign.Docs.ServerSide/TDesign.xml +++ b/doc/TDesign.Docs.ServerSide/TDesign.xml @@ -3748,6 +3748,11 @@ 按照日历形式展示数据或日期的容器。 + + + 是否隐藏控制面板。 + + 使用卡片模式。 @@ -3760,7 +3765,7 @@ - 是否显示周末。 有效。 + 是否显示周末。 有效。 @@ -3783,6 +3788,26 @@ 周几算第一天。 + + + 年月的开始范围。 + + + + + 年月的结束范围。 + + + + + 设置标题。 + + + + + 设置标题的任意代码片段。 + + 星期对应的文本。 @@ -3798,18 +3823,21 @@ 当前月。 - + - 构建日历的头部。 + 切换月年模式。 - - + - 是否跳过周末 + 切换周末是否显示。 - - + + + + 构建日历的头部。 + + @@ -3832,13 +3860,6 @@ - - - 关闭上一个 tr 并开始新的 tr。用于动态换行。 - - - - 构建单元格。 @@ -3848,12 +3869,26 @@ + + + 是否跳过周末 + + + + + + + 关闭上一个 tr 并开始新的 tr。用于动态换行。 + + + + 日历模式。 - + 按月显示。 diff --git a/doc/TDesign.Docs.Shared/Pages/Components/Data/CalendarPage.razor b/doc/TDesign.Docs.Shared/Pages/Components/Data/CalendarPage.razor index 12de16cf..3e7b0d3e 100644 --- a/doc/TDesign.Docs.Shared/Pages/Components/Data/CalendarPage.razor +++ b/doc/TDesign.Docs.Shared/Pages/Components/Data/CalendarPage.razor @@ -9,7 +9,6 @@ 在日期中可显示事项的日期显示容器。常用于有足够空间,且需要承载或显示事项信息时使用。 - @Code.Create(@" diff --git a/doc/TDesign.Docs.WebAssembly/wwwroot/TDesign.xml b/doc/TDesign.Docs.WebAssembly/wwwroot/TDesign.xml index bf1d127d..4f9f91f7 100644 --- a/doc/TDesign.Docs.WebAssembly/wwwroot/TDesign.xml +++ b/doc/TDesign.Docs.WebAssembly/wwwroot/TDesign.xml @@ -3748,6 +3748,11 @@ 按照日历形式展示数据或日期的容器。 + + + 是否隐藏控制面板。 + + 使用卡片模式。 @@ -3760,7 +3765,7 @@ - 是否显示周末。 有效。 + 是否显示周末。 有效。 @@ -3783,6 +3788,26 @@ 周几算第一天。 + + + 年月的开始范围。 + + + + + 年月的结束范围。 + + + + + 设置标题。 + + + + + 设置标题的任意代码片段。 + + 星期对应的文本。 @@ -3798,18 +3823,21 @@ 当前月。 - + - 构建日历的头部。 + 切换月年模式。 - - + - 是否跳过周末 + 切换周末是否显示。 - - + + + + 构建日历的头部。 + + @@ -3832,13 +3860,6 @@ - - - 关闭上一个 tr 并开始新的 tr。用于动态换行。 - - - - 构建单元格。 @@ -3848,12 +3869,26 @@ + + + 是否跳过周末 + + + + + + + 关闭上一个 tr 并开始新的 tr。用于动态换行。 + + + + 日历模式。 - + 按月显示。 diff --git a/src/TDesign/Components/TCalendar.cs b/src/TDesign/Components/TCalendar.cs index 25c4de87..14514343 100644 --- a/src/TDesign/Components/TCalendar.cs +++ b/src/TDesign/Components/TCalendar.cs @@ -1,20 +1,26 @@ -namespace TDesign; +using TDesign.Specifications; + +namespace TDesign; /// /// 按照日历形式展示数据或日期的容器。 /// [CssClass("t-calendar")] -public class TCalendar : TDesignComponentBase +public class TCalendar : TDesignComponentBase,IHasTitleText,IHasTitleFragment { #region Parameters /// + /// 是否隐藏控制面板。 + /// + [Parameter] public bool HideController { get; set; } + /// /// 使用卡片模式。 /// [Parameter][BooleanCssClass("t-calendar--card","t-calendar--full")]public bool Card { get; set; } /// /// 日历的模式。 /// - [Parameter] public CalendarMode Mode { get; set; } = CalendarMode.Mounth; + [Parameter] public CalendarMode Mode { get; set; } = CalendarMode.Month; /// /// 是否显示周末。 有效。 @@ -36,6 +42,23 @@ public class TCalendar : TDesignComponentBase /// 周几算第一天。 /// [Parameter] public DayOfWeek FirstDayOfWeek { get; set; } = DayOfWeek.Monday; + /// + /// 年月的开始范围。 + /// + [Parameter]public DateTime? Start { get; set; } + /// + /// 年月的结束范围。 + /// + [Parameter] public DateTime? End { get; set; } + /// + /// 设置标题。 + /// + [Parameter] public string? TitleText { get; set; } + /// + /// 设置标题的任意代码片段。 + /// + [Parameter] public RenderFragment? TitleContent { get; set; } + #endregion #region Internal @@ -73,6 +96,37 @@ public class TCalendar : TDesignComponentBase /// 当前月。 /// int CurrentMonth { get; set; } + + #endregion + + #region Callback Function + /// + /// 切换月年模式。 + /// + Task SwitchCalendarMode(CalendarMode mode) + { + Mode = mode; + StateHasChanged(); + return Task.CompletedTask; + } + + /// + /// 切换周末是否显示。 + /// + Task SwitchWeekend() + { + ShowWeekend = !ShowWeekend; + StateHasChanged(); + return Task.CompletedTask; + } + + Task ChangeDate(int year, int month) + { + CurrentYear = Year = year; + CurrentMonth = Month = month; + StateHasChanged(); + return Task.CompletedTask; + } #endregion protected override void OnParametersSet() @@ -82,6 +136,14 @@ protected override void OnParametersSet() CurrentYear = Year; CurrentMonth = Month; + Start ??= new DateTime(CurrentYear - 10, 1, 1); + End ??= new DateTime(CurrentYear + 10, 12, 1); + + if ( Start >= End ) + { + throw new InvalidOperationException($"{nameof(Start)} 不能大于 {nameof(End)}"); + } + #region 调整第一天是周几的顺序 var theFirstDayIndex = DayOfWeekList.FindIndex(m => m == FirstDayOfWeek); var orderRange = DayOfWeekList.GetRange(theFirstDayIndex, DayOfWeekList.Count - theFirstDayIndex); @@ -92,6 +154,8 @@ protected override void OnParametersSet() protected override void AddContent(RenderTreeBuilder builder, int sequence) { + BuildControl(builder); + builder.Div("t-calendar__panel") .Class(Mode.GetCssClass("t-calendar__panel--")) .Content(BuildCalendarTable) @@ -115,7 +179,7 @@ void BuildCalendarTable(RenderTreeBuilder builder) /// void BuildCalendarHeader(RenderTreeBuilder builder) { - if(Mode!= CalendarMode.Mounth ) + if(Mode!= CalendarMode.Month ) { return; } @@ -145,13 +209,6 @@ void BuildCalendarHeader(RenderTreeBuilder builder) .Close(); } - /// - /// 是否跳过周末 - /// - /// - /// - bool IsSkipWeekend(DayOfWeek day) => !ShowWeekend && new[] { DayOfWeek.Saturday, DayOfWeek.Sunday }.Contains(day); - /// /// 构建日历的主体。 /// @@ -161,7 +218,7 @@ void BuildCalendarBody(RenderTreeBuilder builder) builder.Element("tbody", "t-calendar__table-body") .Content(body => { - if(Mode== CalendarMode.Mounth ) + if(Mode== CalendarMode.Month ) { BuildMonthBody(body); } @@ -246,7 +303,10 @@ void BuildMonthBody(RenderTreeBuilder builder) } var today = DateTime.Today; - BuildBodyCell(builder, day.ToString(), false, today == currentDateTime); + + var completeDayString = FiilWithZero && day < 10 ? day.ToString().PadLeft(2, '0') : day.ToString(); + + BuildBodyCell(builder, completeDayString, false, today == currentDateTime); if ( (i + firstDayOfWeekIndex) % 7 == 0 ) { @@ -278,16 +338,115 @@ void BuildMonthBody(RenderTreeBuilder builder) builder.CloseElement(); } - /// - /// 关闭上一个 tr 并开始新的 tr。用于动态换行。 - /// - /// - /// - private static void ClosePrevRowAndBuildNewRow(RenderTreeBuilder builder, int sequence) + void BuildControl(RenderTreeBuilder builder) { - builder.CloseElement(); - builder.OpenElement(sequence, "tr"); - builder.AddAttribute(sequence + 1, "class", "t-calendar__table-body-row"); + builder.Div("t-calendar__control", !HideController) + .Content(control => + { + control.Div("t-calendar__title").Content(TitleContent).Close(); + control.Div("t-calendar__control-section") + .Content(selection => + { + //年 下拉菜单 + BuildSelectionCell(selection, year => + { + year.Component>() + .Attribute(m => m.Value, Year) + .Attribute(m => m.ValueExpression, () => Year) + .Attribute(m => m.ValueChanged, HtmlHelper.Instance.Callback().Create(this, value => ChangeDate(value, Month))) + .Content(options => + { + for ( int i = Start!.Value.Year; i <= End!.Value.Year; i++ ) + { + options.Component>() + .Attribute(m => m.Value, i) + .Attribute(m => m.Label, $"{i}年") + .Close(); + } + }) + .Close(); + }); + //月 下拉菜单 + BuildSelectionCell(selection, month => + { + month.Component>() + .Attribute(m => m.Value, Month) + .Attribute(m => m.ValueExpression, () => Month) + .Attribute(m => m.ValueChanged, HtmlHelper.Instance.Callback().Create(this, value => ChangeDate(Year, value))) + .Content(options => + { + for ( int i = 1; i <= 12; i++ ) + { + options.Component>() + .Attribute(m => m.Value, i) + .Attribute(m => m.Label, $"{i}月") + .Attribute(m => m.Disabled, i <= 6 && i < Start!.Value.Month) + .Attribute(m => m.Disabled, i > 6 && i > End!.Value.Month) + .Close(); + } + }) + .Close(); + }, Mode == CalendarMode.Month); + //年月切换 + BuildSelectionCell(selection, mode => + { + mode.Component>() + .Attribute(m=>m.Value,Mode) + .Attribute(m=>m.ValueExpression,()=>Mode) + .Attribute(m=>m.ValueChanged,HtmlHelper.Instance.Callback().Create(this,SwitchCalendarMode)) + .Attribute(m=>m.ButtonStyle, RadioButtonStyle.Filled) + .Content(radio => + { + radio.Component>() + .Attribute(m=>m.Value,CalendarMode.Month) + .Content("月") + .Close(); + + radio.Component>() + .Attribute(m => m.Value, CalendarMode.Year) + .Content("年") + .Close(); + }) + .Close(); + }); + //切换周末显示 + BuildSelectionCell(selection, weekend => + { + var theme = Theme.Primary; + var text = "显示周末"; + + if ( !ShowWeekend ) + { + theme = Theme.Default; + text = "隐藏周末"; + } + weekend.Component() + .Attribute(m => m.Theme, theme) + .Attribute(m => m.OnClick, HtmlHelper.Instance.Callback().Create(this, SwitchWeekend)) + .Content(text) + .Close(); + }, Mode == CalendarMode.Month); + + //回到今天/本月 + BuildSelectionCell(selection, now => + { + var today = DateTime.Now; + + now.Component() + .Attribute(m => m.Theme, Theme.Primary) + .Attribute(m => m.OnClick, HtmlHelper.Instance.Callback().Create(this, () => ChangeDate(today.Year, today.Month))) + .Content(Mode == CalendarMode.Year ? "本月" : "今天") + .Close(); + }); + }) + .Close(); + }) + .Close(); + + void BuildSelectionCell(RenderTreeBuilder selection,RenderFragment content,Condition? condition=default) + { + selection.Div("t-calendar__control-section-cell", condition).Content(content).Close(); + } } /// @@ -319,6 +478,24 @@ static void BuildBodyCell(RenderTreeBuilder builder,string content,bool disabled .Close(); }); } + + /// + /// 是否跳过周末 + /// + /// + /// + bool IsSkipWeekend(DayOfWeek day) => !ShowWeekend && new[] { DayOfWeek.Saturday, DayOfWeek.Sunday }.Contains(day); + /// + /// 关闭上一个 tr 并开始新的 tr。用于动态换行。 + /// + /// + /// + private static void ClosePrevRowAndBuildNewRow(RenderTreeBuilder builder, int sequence) + { + builder.CloseElement(); + builder.OpenElement(sequence, "tr"); + builder.AddAttribute(sequence + 1, "class", "t-calendar__table-body-row"); + } } /// @@ -329,7 +506,7 @@ public enum CalendarMode /// /// 按月显示。 /// - Mounth, + Month, /// /// 按年显示。 ///