You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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);
        }
    }
}

核心问题分析

  1. 切换牙医时未清空旧数据:HandleDentistApp方法每次添加新牙医的预约数据时,未清空之前的列表,导致切换牙医后旧的预约时段依然被禁用。
  2. 时间判断逻辑不准确:onRenderDayCellHandler仅处理日期级别的禁用,Syncfusion DateTimePicker的时间部分需要单独通过OnRenderTimeCell事件处理,且原代码的时间比较逻辑存在精度匹配问题。
  3. 日期判断冗余:直接比较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);
        }
    }
}

关键修改说明

  1. 清空旧数据:在HandleDentistApp方法开头调用bookedSlots.Clear(),确保切换牙医时只保留当前牙医的预约数据。
  2. 新增时间面板事件:添加OnRenderTimeCell事件处理程序,专门负责禁用已预约的时段。
  3. 优化日期与时间判断:
    • 日期判断直接比较Date属性,避免跨年时DayOfYear的冲突。
    • 时间面板中拼接当前选中日期与时间单元格的时间,精确匹配已预约时段。
  4. 触发组件重渲染:在获取新预约数据后调用StateHasChanged(),确保DateTimePicker立即更新禁用状态。
  5. 预约成功后刷新数据:创建预约成功后重新加载当前牙医的预约数据,避免重复预约。

内容的提问来源于stack exchange,提问作者Pikp

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.26 12:55:03