Blazor WebAssembly中Syncfusion DateTimePicker禁用已预约时间求助
Blazor WebAssembly Syncfusion DateTimePicker 禁用已预约时段解决方案
问题场景
使用C#开发Blazor WebAssembly项目,需实现Syncfusion DateTimePicker组件仅允许选择后端返回的可预约日期与时间,已被预约的时段需禁用,但现有代码无法正确实现该逻辑。
原尝试代码
<EditForm class="d-flex flex-column align-items-left" Model="@newAppointment" OnValidSubmit="@HandleValidSubmit"> <FluentValidationValidator /> <div class="w-25 p-3"> <label for="id" class="col-sm-2 col-form-label">Dentist: </label> <div class="col-sm-10"> <InputSelect class="form-select" id="id" ValueExpression="@( () => newAppointment.DentistId)" Value="@newAppointment.DentistId" ValueChanged="@( (Guid value) => {newAppointment.DentistId=value;HandleDentistApp(value);})"> @foreach (var dentist in dentists) { <option value="@dentist.Id">@dentist.FirstName @dentist.LastName</option> } </InputSelect> <ValidationMessage For="@(() => newAppointment.DentistId)" /> </div> </div> <Syncfusion.Blazor.Calendars.SfDateTimePicker TValue="DateTime?" Min='@MinDate' Max='@MaxDate' @bind-Value='@newAppointment.dateTime' StrictMode=true Placeholder="Select a date and time" ShowTodayButton="true" ShowClearButton="true"> <DateTimePickerEvents TValue="DateTime?" OnRenderDayCell="@onRenderDayCellHandler"></DateTimePickerEvents> <ValidationMessage For="@(() => newAppointment.dateTime)"/> </Syncfusion.Blazor.Calendars.SfDateTimePicker> <br /> <button type="submit" class="btn btn-primary">Create appointment</button> </EditForm> @code { private List<DentistResponseModel> dentists = new List<DentistResponseModel>(); private List<DateTime> appointmentsSelectedDentist = new List<DateTime>(); protected override async Task OnInitializedAsync() { var tempDentistsList = await Http.GetFromJsonAsync<ApiResult<IEnumerable<DentistResponseModel>>>("api/Dentists/all"); dentists = tempDentistsList.Result.ToList(); } protected async Task HandleDentistApp(Guid dentistIdSelected) { //checked var stringDentistIdSelected = dentistIdSelected.ToString(); var tempAppointmentsSelectedDentist = await Http.GetFromJsonAsync<ApiResult<IEnumerable<AppointmentResponse>>>($"api/Appointment/allapp/dentist{stringDentistIdSelected}"); foreach (var item in tempAppointmentsSelectedDentist.Result.ToList()) { appointmentsSelectedDentist.Add(item.dateTime); } } private RequestAppointment newAppointment = new RequestAppointment { CustomerId = Guid.Parse("c210cc12-7484-41b9-96da-834d2faf3aa2") // by default an ID customer. Need to be current Id customer }; private string response = string.Empty; public DateTime MinDate { get; set; } = new DateTime(DateTime.Now.Year, DateTime.Now.Month, DateTime.Today.AddDays(1).Day, 02, 00, 00); public DateTime MaxDate { get; set; } = new DateTime(DateTime.Today.AddYears(2).Year, DateTime.Now.Month, 25, 12, 00, 00); public DateTime? DateValue { get; set; } = new DateTime(DateTime.Now.Year, DateTime.Now.Month, 10); public void onRenderDayCellHandler(RenderDayCellEventArgs args) { if (args.Date.DayOfWeek.ToString() == "Saturday" || args.Date.DayOfWeek.ToString() == "Sunday") { args.IsDisabled = true; } //implement only dentist available dates foreach (var appRegister in appointmentsSelectedDentist) { if (appRegister.Date.DayOfYear == args.Date.DayOfYear) { if (appRegister.Date.TimeOfDay == args.Date.TimeOfDay) { Console.WriteLine(appRegister.Date + " first IF: arg.date is " + args.Date); args.IsDisabled = true; } } } } public async Task HandleValidSubmit() { bool confirmed = await JsRuntime.InvokeAsync<bool>("confirm", "Are you sure?"); // Confirm if (confirmed) { var request = await Http.PostAsJsonAsync("/api/Appointment", newAppointment); this.response = await request.Content.ReadAsStringAsync(); } else { NavManager.NavigateTo(Routes.Index); } } }
核心问题分析
- 切换牙医时未清空旧数据:
HandleDentistApp方法每次添加新牙医的预约数据时,未清空之前的列表,导致切换牙医后旧的预约时段依然被禁用。 - 时间判断逻辑不准确:
onRenderDayCellHandler仅处理日期级别的禁用,Syncfusion DateTimePicker的时间部分需要单独通过OnRenderTimeCell事件处理,且原代码的时间比较逻辑存在精度匹配问题。 - 日期判断冗余:直接比较
DateTime的日期部分即可,无需通过DayOfYear(存在跨年问题)。
修正后的完整代码
<EditForm class="d-flex flex-column align-items-left" Model="@newAppointment" OnValidSubmit="@HandleValidSubmit"> <FluentValidationValidator /> <div class="w-25 p-3"> <label for="id" class="col-sm-2 col-form-label">牙医: </label> <div class="col-sm-10"> <InputSelect class="form-select" id="id" ValueExpression="@(() => newAppointment.DentistId)" Value="@newAppointment.DentistId" ValueChanged="@(async (Guid value) => { newAppointment.DentistId = value; await HandleDentistApp(value); })"> @foreach (var dentist in dentists) { <option value="@dentist.Id">@dentist.FirstName @dentist.LastName</option> } </InputSelect> <ValidationMessage For="@(() => newAppointment.DentistId)" /> </div> </div> <Syncfusion.Blazor.Calendars.SfDateTimePicker TValue="DateTime?" Min="@MinDate" Max="@MaxDate" @bind-Value="@newAppointment.dateTime" StrictMode="true" Placeholder="选择预约时间" ShowTodayButton="true" ShowClearButton="true"> <DateTimePickerEvents TValue="DateTime?" OnRenderDayCell="@OnRenderDayCellHandler" OnRenderTimeCell="@OnRenderTimeCellHandler"></DateTimePickerEvents> <ValidationMessage For="@(() => newAppointment.dateTime)"/> </Syncfusion.Blazor.Calendars.SfDateTimePicker> <br /> <button type="submit" class="btn btn-primary">创建预约</button> </EditForm> @code { private List<DentistResponseModel> dentists = new List<DentistResponseModel>(); // 存储已预约的完整时间(含时分秒) private List<DateTime> bookedSlots = new List<DateTime>(); // 当前选中的日期,用于时间面板的禁用判断 private DateTime? selectedDate; protected override async Task OnInitializedAsync() { var tempDentistsList = await Http.GetFromJsonAsync<ApiResult<IEnumerable<DentistResponseModel>>>("api/Dentists/all"); dentists = tempDentistsList.Result.ToList(); } protected async Task HandleDentistApp(Guid dentistIdSelected) { // 每次切换牙医时清空旧的预约数据 bookedSlots.Clear(); var tempAppointments = await Http.GetFromJsonAsync<ApiResult<IEnumerable<AppointmentResponse>>>($"api/Appointment/allapp/dentist{dentistIdSelected}"); foreach (var item in tempAppointments.Result) { bookedSlots.Add(item.dateTime); } // 触发组件重新渲染,更新禁用状态 StateHasChanged(); } private RequestAppointment newAppointment = new RequestAppointment { CustomerId = Guid.Parse("c210cc12-7484-41b9-96da-834d2faf3aa2") }; private string response = string.Empty; public DateTime MinDate { get; set; } = DateTime.Today.AddDays(1).Date.AddHours(2); public DateTime MaxDate { get; set; } = DateTime.Today.AddYears(2).Date.AddMonths(DateTime.Now.Month - DateTime.Today.Month).AddDays(25-1).AddHours(12); public void OnRenderDayCellHandler(RenderDayCellEventArgs args) { // 禁用周末 if (args.Date.DayOfWeek is DayOfWeek.Saturday or DayOfWeek.Sunday) { args.IsDisabled = true; } // 禁用当天已被完全预约的日期(如果某天所有时段都被占满) var hasAvailableSlots = bookedSlots.Any(slot => slot.Date == args.Date.Date) == false || bookedSlots.Where(slot => slot.Date == args.Date.Date).Count() < (12-2); // 假设每天2-12点共10个时段 if (!hasAvailableSlots) { args.IsDisabled = true; } // 记录当前鼠标 hover 或选中的日期,用于时间面板判断 if (args.IsSelected) { selectedDate = args.Date.Date; } } public void OnRenderTimeCellHandler(RenderTimeCellEventArgs args) { if (selectedDate == null) return; // 拼接当前日期和时间单元格的时间 var currentSlot = new DateTime(selectedDate.Value.Year, selectedDate.Value.Month, selectedDate.Value.Day, args.Time.Hours, args.Time.Minutes, args.Time.Seconds); // 如果该时段已被预约,则禁用 if (bookedSlots.Any(slot => slot.Equals(currentSlot))) { args.IsDisabled = true; } } public async Task HandleValidSubmit() { bool confirmed = await JsRuntime.InvokeAsync<bool>("confirm", "确定要创建该预约吗?"); if (confirmed) { var request = await Http.PostAsJsonAsync("/api/Appointment", newAppointment); response = await request.Content.ReadAsStringAsync(); // 创建成功后清空当前预约数据并刷新牙医预约列表 if (request.IsSuccessStatusCode) { newAppointment.dateTime = null; await HandleDentistApp(newAppointment.DentistId); } } else { NavManager.NavigateTo(Routes.Index); } } }
关键修改说明
- 清空旧数据:在
HandleDentistApp方法开头调用bookedSlots.Clear(),确保切换牙医时只保留当前牙医的预约数据。 - 新增时间面板事件:添加
OnRenderTimeCell事件处理程序,专门负责禁用已预约的时段。 - 优化日期与时间判断:
- 日期判断直接比较
Date属性,避免跨年时DayOfYear的冲突。 - 时间面板中拼接当前选中日期与时间单元格的时间,精确匹配已预约时段。
- 日期判断直接比较
- 触发组件重渲染:在获取新预约数据后调用
StateHasChanged(),确保DateTimePicker立即更新禁用状态。 - 预约成功后刷新数据:创建预约成功后重新加载当前牙医的预约数据,避免重复预约。
内容的提问来源于stack exchange,提问作者Pikp
相关产品推荐
相关产品推荐

