Blazor WebAssembly中Bootstrap下拉菜单点击无响应,需刷新页面才生效的问题求助
Blazor WebAssembly中Bootstrap下拉菜单点击无响应,需刷新页面才生效的问题求助
我最近在做Blazor WebAssembly项目时遇到了一个头疼的问题:当我导航到创建/编辑Item的页面时,页面上的Bootstrap下拉菜单点击完全没反应,但只要刷新一下页面,下拉菜单就正常工作了。我已经在Index.html里导入了Bootstrap的相关样式和脚本,实在搞不懂哪里出问题了,希望大家能帮我看看!
我的组件代码(创建/编辑Item的Razor组件)
@page "/items/creer" @page "/items/modifier/{Id:int}" @inject IItemService _itemService @inject IDepartementService _departementService @inject NavigationManager _navigationManager @if (IsProcessing) { <div style="position:fixed;top:50%;left:50%;margin-left:-100px;"> <img src="images/ajax-loader.gif" /> </div> } else { <div class="container mt-5"> <h3 class="card-title text-primary mb-3 ml-3">@Titre un item</h3> <EditForm Model="Item" OnValidSubmit="GererItem"> <DataAnnotationsValidator /> <ValidationSummary /> <div class="form-group mt-3"> <label>Nom</label> <InputText @bind-Value="Item.Nom" class="form-control"></InputText> <ValidationMessage For="()=>Item.Nom"></ValidationMessage> </div> <div class="form-group mt-3"> <label>Image</label> <InputText @bind-Value="Item.Image" class="form-control"></InputText> <ValidationMessage For="()=>Item.Image"></ValidationMessage> <img src="@Item.Image" class="img-fluid w-25" /> </div> <div class="form-group mt-3"> <label>Départements</label> <div class="dropdown"> <button class="btn btn-secondary dropdown-toggle" type="button" id="dropdownMenu2" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> @(Item.Departement != null ? Item.Departement.Nom : "Choisir un département...") </button> <div class="dropdown-menu" aria-labelledby="dropdownMenu2"> @foreach (var departement in Departements) { <button class="dropdown-item" type="button" @onclick="() => SelectDepartement(departement)">@departement.Nom</button> } </div> </div> </div> <div class="mt-3 mb-3"> <button class="btn btn-primary py-2">@Titre</button> <NavLink href="items" class="btn btn-secondary">Retour</NavLink> </div> </EditForm> </div> } @code { public bool IsProcessing { get; set; } = false; public ItemDto Item { get; set; } = new ItemDto { Id = 0, Nom = "", Departement = null }; [Parameter] public int Id { get; set; } private string Titre { get; set; } = "Créer"; private IEnumerable<DepartementDto> Departements { get; set; } = new List<DepartementDto>(); private void SelectDepartement(DepartementDto departement) { Item.Departement = departement; } protected override async Task OnInitializedAsync() { IsProcessing = true; if (Id != 0) { Titre = "Modifier"; Item = await _itemService.Obtenir(Id); } Departements = await _departementService.ObtenirTous(); IsProcessing = false; } private async Task GererItem() { if (Id == 0) { await _itemService.Creer(Item); } else { await _itemService.Modifier(Item); } _navigationManager.NavigateTo("items"); } }
Index.html中导入的资源
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" /> <title>Epicerie_Client</title> <base href="/" /> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.3.1/dist/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous"> <link href="css/bootstrap/bootstrap.min.css" rel="stylesheet" /> <link href="css/app.css" rel="stylesheet" /> <link href="Epicerie_Client.styles.css" rel="stylesheet" /> <link href="//cdnjs.cloudflare.com/ajax/libs/toastr.js/latest/toastr.min.css" rel="stylesheet" /> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.7.1/font/bootstrap-icons.css"> </head> <body> <div id="app"> <div id="app" style="position:fixed;top:50%;left:50%;margin-top:-50px;"> <img src="images/loader.gif" /> </div> </div> <div id="blazor-error-ui"> An unhandled error has occurred. <a href="" class="reload">Reload</a> <a class="dismiss"></a> </div> <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script> <script src="https://cdn.jsdelivr.net/npm/popper.js@1.14.7/dist/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap@4.3.1/dist/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js" integrity="sha512-894YE6QWD5I59HgZOGReFYm4dnWc1Qt5NtvYSaNcOP+u1T9qYdvdihz0PPSiiqn/+/3e7Jo4EaG7TubfWGUrMQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script> <script src="//cdn.jsdelivr.net/npm/sweetalert2@11"></script> <script src="//cdnjs.cloudflare.com/ajax/libs/toastr.js/latest/toastr.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script> <script src="js/common.js"></script> <script src="_framework/blazor.webassembly.js"></script> </body> </html>
问题原因分析
这个问题其实是Blazor客户端路由和Bootstrap JS初始化机制的冲突导致的:
- Bootstrap JS的初始化逻辑:Bootstrap的JavaScript组件(比如下拉菜单)是在页面首次加载时,通过扫描DOM元素来完成初始化的。只有被初始化过的组件才能正常响应用户交互。
- Blazor的路由特性:Blazor WebAssembly使用客户端路由,当你通过
NavLink或者NavigationManager导航到新页面时,页面不会重新加载,只是Blazor会替换DOM中的内容。这时候新渲染出来的下拉菜单元素并没有被Bootstrap的JS初始化,所以点击没反应。而刷新页面时,整个页面重新加载,Bootstrap JS会重新扫描所有DOM元素,初始化下拉菜单,所以就能正常工作了。 - 额外的冲突点:你的Index.html里同时导入了Bootstrap 4.x和Bootstrap 5.x的JavaScript文件,这两个版本的Bootstrap JS是不兼容的,会互相干扰,也可能加剧这个问题。
解决方案
第一步:清理Index.html中的重复Bootstrap脚本
首先把Index.html里重复的Bootstrap JS删掉,统一使用一个版本。比如选择Bootstrap 5.x(因为它不需要jQuery,更适合Blazor),修改后的脚本部分大概是这样:
<!-- 只保留Bootstrap 5的脚本,去掉Bootstrap 4和jQuery相关的脚本 --> <script src="//cdn.jsdelivr.net/npm/sweetalert2@11"></script> <script src="//cdnjs.cloudflare.com/ajax/libs/toastr.js/latest/toastr.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script> <script src="js/common.js"></script> <script src="_framework/blazor.webassembly.js"></script>
同时注意把Bootstrap 4的CSS也换成Bootstrap 5的,保持样式和脚本版本一致。
第二步:手动初始化Bootstrap下拉菜单
因为Blazor导航不会触发页面重新加载,所以我们需要在组件渲染完成后,手动调用Bootstrap的初始化方法。可以通过Blazor的JS互操作来实现:
- 在wwwroot目录下创建一个
bootstrap-helpers.js文件,内容如下:
window.initBootstrapDropdowns = function() { // 初始化所有未被初始化的下拉菜单 document.querySelectorAll('.dropdown-toggle').forEach(toggle => { new bootstrap.Dropdown(toggle); }); }
- 在Index.html中引入这个脚本(放在Bootstrap脚本之后):
<script src="bootstrap-helpers.js"></script>
- 在你的Razor组件中,注入
IJSRuntime,然后在组件渲染完成后调用这个JS方法:
@inject IJSRuntime _jsRuntime <!-- 组件其他内容 --> @code { // 其他代码 protected override async Task OnAfterRenderAsync(bool firstRender) { if (firstRender) { // 首次渲染完成后初始化下拉菜单 await _jsRuntime.InvokeVoidAsync("initBootstrapDropdowns"); } await base.OnAfterRenderAsync(firstRender); } }
替代方案:使用Blazor专用的Bootstrap组件库
如果你不想手动处理JS初始化的问题,可以使用专门为Blazor设计的Bootstrap组件库,这类库完全用C#实现,不需要依赖Bootstrap的JS,能完美适配Blazor的生命周期,避免这类DOM初始化问题。
备注:内容来源于stack exchange,提问作者mathdx
相关产品推荐
相关产品推荐

