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

Blazor点击Book Dates按钮后自动渲染MainLayout内倒计时组件问题

Blazor 全站计时器自动显示问题解决方案

问题根源

你的CountdownTimer组件作为MainLayout的一部分,仅在组件初始化(OnInitializedAsync)时获取一次预订数据。当用户点击「Book Dates」按钮创建新预订后,组件无法感知到数据变化,只有全页刷新时才会重新初始化并加载最新数据,导致无刷新导航时计时器不显示。

解决方案:使用共享状态服务实现跨组件通信

通过创建一个全局共享的状态服务,在预订创建完成后触发更新通知,让CountdownTimer组件实时获取最新数据并刷新UI。

步骤1:创建预订状态服务

这个服务负责维护预订状态变更的事件,其他组件可以订阅该事件以响应数据更新。

public class BookingStateService
{
    // 定义预订更新事件
    public event Action OnBookingUpdated;

    // 触发事件的方法
    public void NotifyBookingUpdated()
    {
        OnBookingUpdated?.Invoke();
    }
}

步骤2:注册状态服务

在Program.cs中将服务注册为单例,确保全站共享同一个实例:

builder.Services.AddSingleton<BookingStateService>();

步骤3:修改CountdownTimer组件订阅事件

注入状态服务,订阅预订更新事件,在事件触发时重新加载数据并刷新UI:

@inject IBookingRepository bookingRepository
@inject ProtectedLocalStorage localStorage
@inject NavigationManager navigationManager
@inject BookingStateService bookingStateService
@implements IDisposable

@if (booking is not null)
{
    if (booking.CheckInDate != DateOnly.MinValue)
    {
        <div class="timer">
            <p>Current booking time left: @timeSpan.ToString(@"mm\:ss")</p>
        </div>
    }
}

<br />
<br />

@code {
    private TimeSpan timeSpan;
    private bool isRunning = false;
    private DateTime startTime;
    private Timer timer;

    private ProtectedBrowserStorageResult<string> result { get; set; }

    public Booking? booking { get; set; }

    protected override async Task OnInitializedAsync()
    {
        await GetLocalStorage();

        if (!isRunning && booking.CheckInDate != DateOnly.MinValue)
        {
            StartTimer();
        }

        // 订阅预订更新事件
        bookingStateService.OnBookingUpdated += HandleBookingUpdated;
    }

    // 处理预订更新事件
    private async void HandleBookingUpdated()
    {
        await GetLocalStorage();
        
        // 停止现有计时器(防止重复启动)
        if (isRunning)
        {
            StopTimer();
        }

        // 如果存在有效预订,启动计时器
        if (booking.CheckInDate != DateOnly.MinValue)
        {
            StartTimer();
        }

        // 强制刷新UI
        await InvokeAsync(StateHasChanged);
    }

    private async Task GetLocalStorage()
    {
        result = await localStorage.GetAsync<string>(SettingsUtility.sessionId);

        if (!result.Success)
        {
            await localStorage.SetAsync(SettingsUtility.sessionId, Guid.NewGuid());
            result = await localStorage.GetAsync<string>(SettingsUtility.sessionId);
        }

        bookingRepository.DeleteStaleBookings();
        booking = bookingRepository.GetBySessionId(Guid.Parse(result.Value));

        if (booking is null)
        {
            booking = new Booking();
            booking.SessionId = Guid.Parse(result.Value);
        }
    }

    private void StartTimer()
    {
        startTime = booking.CreatedDate.AddSeconds(SettingsUtility.bookingTime);
        timer = new Timer(UpdateTime, null, 0, 1000);
        isRunning = true;
    }

    private void StopTimer()
    {
        timer?.Dispose();
        isRunning = false;
    }

    private void UpdateTime(object state)
    {
        timeSpan = startTime - DateTime.UtcNow;
        InvokeAsync(StateHasChanged);

        if (timeSpan.TotalMilliseconds < 0)
        {
            StopTimer();
            bookingRepository.DeleteStaleBookings();
        }
    }

    // 组件销毁时取消订阅,避免内存泄漏
    public void Dispose()
    {
        bookingStateService.OnBookingUpdated -= HandleBookingUpdated;
        StopTimer();
    }
}

步骤4:修改日历组件触发更新事件

在「Book Dates」按钮的提交方法中,创建预订完成后调用状态服务的通知方法:

@inject IBookingRepository _bookingRepository
@inject BookingStateService bookingStateService

<div class="calendar-days grid-7">
    <CalendarDisabledDays calendarMonth="calendarMonth" offsetDays="offsetDays" />

    @for (int i = startDay; i <= daysInMonth; i++)
    {
        DateTime date = new DateTime(calendarMonth.Year, calendarMonth.Month, i);

        <div class="active">
            <button @onclick="() => SetDate(DateOnly.FromDateTime(date))">@i</button>
        </div>
    }
</div>

<div class="calendar-submit grid-1">
    <EditForm OnSubmit="CreateDates" EditContext="editContext">
        <div>
            <button type="reset" class="btn btn-main" @onclick="ClearDates">Clear Dates<i class="fa-solid fa-eraser btn-icon"></i></button>
            <button type="submit" class="btn btn-main">Book Dates<i class="fa-regular fa-calendar-check btn-icon"></i></button>
        </div>

        <div>
            <DataAnnotationsValidator />
            <ValidationSummary />
        </div>
    </EditForm>
</div>

@code {
    [Parameter]
    public int daysInMonth { get; set; }
    [Parameter]
    public bool status { get; set; }
    [Parameter]
    public DateOnly calendarMonth { get; set; }
    [Parameter]
    public int offsetDays { get; set; }
    [Parameter]
    public BookingDatesVM booking { get; set; }

    private int startDay { get; set; }
    EditContext? editContext;
    ValidationMessageStore? validationMessages;
    bool valid;

    protected override async Task OnInitializedAsync()
    {
        editContext = new(booking);
        validationMessages = new(editContext);

        await base.OnInitializedAsync();
    }

    protected override Task OnParametersSetAsync()
    {
        SetStartDate();
        return base.OnParametersSetAsync();
    }

    private void SetStartDate()
    {
        startDay = 1;
        if ((calendarMonth.Month == DateTime.Now.Month) && (calendarMonth.Year == DateTime.Now.Year))
        {
            startDay = DateTime.Now.AddDays(1).Day;
        }
    }

    private void SetDate(DateOnly date)
    {
        if (status)
        {
            booking.CheckInDate = date;
        }
        else
        {
            booking.CheckOutDate = date;
        }
    }

    private void ClearDates()
    {
        booking.CheckInDate = DateOnly.MinValue;
        booking.CheckOutDate = DateOnly.MinValue;
    }

    private async Task CreateDates()
    {
        valid = ValidateForm();

        if (valid)
        {
            await _bookingRepository.CreateAsync(BookingMapper.FromDatesVM(booking));
            // 触发预订更新通知
            bookingStateService.NotifyBookingUpdated();
        }
    }

    private bool ValidateForm()
    {
        valid = editContext.Validate();
        validationMessages?.Clear();

        var checkOutDate = editContext.Field(nameof(booking.CheckOutDate));
        var checkInDate = editContext.Field(nameof(booking.CheckInDate));

        if (booking.CheckInDate == DateOnly.MinValue)
        {
            validationMessages.Add(checkInDate, "Check in date is required.");
            valid = false;
        }
        if (booking.CheckOutDate == DateOnly.MinValue)
        {
            validationMessages.Add(checkOutDate, "Check out date is required.");
            valid = false;
        }

        return editContext.Validate();
    }
}

关键说明

  • 状态服务通过事件机制实现跨组件通信,无需依赖组件层级关系,适合全站范围的状态更新通知。
  • CountdownTimer组件实现IDisposable接口,在销毁时取消事件订阅,避免内存泄漏。
  • 每次预订创建完成后触发通知,确保CountdownTimer实时获取最新预订数据并自动启动计时器。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.02 02:35:19