使用 C# 方法在 Blazor 中创建弹出窗口

Creating a popup in Blazor using C# method

我有一个带有按钮的简单页面 index.razor:

<a class="btn btn-login" @onclick="RedirectPage" >Log in</a>

<div
    @bind-Visible="@InvalidLogin"
    BodyText="Error">
</div>

@code{
    InvalidLogin {get; set;} = false;
}

其中函数 RedirectPage 检查值是否有效。如果不是,我想要一个提供信息的弹出窗口:

private void RedirectPage
{
    this.InvalidLogin = true;
}

此函数在 index.razor.cs 中,并已在正确的命名空间中添加 @using

如何创建它以便在单击按钮时显示弹出窗口?

您可以创建一个简单的弹出(或模态对话框)组件。下面,我使用 Bootstrap 5 toast 组件编写了一个示例弹出式剃须刀组件。

Popup.razor 文件

@{
    var showClass = IsVisible ? "d-block" : "d-none";
}

<div class="toast-container p-3 @showClass" data-bs-autohide="true" data-bs-delay="5000">
    <div class="toast show" role="alert" aria-live="assertive" aria-atomic="true">
        <div class="toast-header">
            <strong class="me-auto">@HeaderText</strong>
            <button type="button" class="btn-close" aria-label="Close" @onclick="Close"></button>
        </div>

        <div class="toast-body">
            @BodyText
        </div>
    </div>
</div>

@code {
    [Parameter]
    public bool IsVisible { get; set; }

    [Parameter]
    public EventCallback<bool> IsVisibleChanged { get; set; }

    [Parameter]
    public string? HeaderText { get; set; }

    [Parameter]
    public string? BodyText { get; set; }

    public void Show(string bodyText, string headerText = "")
    {
        HeaderText = headerText;
        BodyText = bodyText;
        IsVisible = true;
        StateHasChanged();
    }

    private void Close()
    {
        HeaderText = string.Empty;
        BodyText = string.Empty;
        IsVisible = false;
        StateHasChanged();
    }
}

在您的代码中使用 Popup razor 组件:

<a class="btn btn-login" @onclick="RedirectPage" >Log in</a>

<Popup @ref="popupRef" />

@code{
    private Popup popupRef;
    
    private void RedirectPage()
    {
        // Shows the popup at the center of the screen
        popupRef.Show("Popup body text");
    }
}

如何在不依赖第三方库的情况下创建对话框。

我不得不使用最少量的 js,因为新的 HTML5 <dialog... 元素只能用它在对话框模式下打开 .showModal() 而不是通过操作属性来打开。

wwwroot/scripts/dialogJsInteropt.js

export function showDialog(element, parm) {
    return element.showModal();
}

export function closeDialog(element, parm) {
    return element.close();
}

Dialog.razor

<CascadingValue Value=@this IsFixed=true >
    <dialog @ref="@dialogElement" @attributes=@CapturedAttributes>
    @if(visible)
    {
        @ChildContent
    }
    </dialog>
</CascadingValue>

Dialog.razor.cs

public partial class Dialog : ComponentBase, IAsyncDisposable
{
    private readonly Lazy<Task<IJSObjectReference>> moduleTask;
    private ElementReference dialogElement;
    private bool visible = false;

    public Dialog()
    {
        moduleTask = new(() => jsRuntime.InvokeAsync<IJSObjectReference>(
            identifier: "import",
            args: "./scripts/dialogJsInterop.js")
        .AsTask());
    }

    [Inject]
    private IJSRuntime jsRuntime { get; set; }

    [Parameter]
    public RenderFragment ChildContent { get; set; }

    [Parameter(CaptureUnmatchedValues = true)]
    public Dictionary<string, object> CapturedAttributes { get; set; }

    public async ValueTask ShowDialogAsync()
    {
        var module = await moduleTask.Value;
        await module.InvokeVoidAsync("showDialog", dialogElement);
        visible = true;
    }

    public async ValueTask CloseDialogAsync()
    {
        var module = await moduleTask.Value;
        await module.InvokeVoidAsync("closeDialog", dialogElement);
        visible = false;
    }

    public async ValueTask DisposeAsync()
    {
        if (moduleTask.IsValueCreated)
        {
            var module = await moduleTask.Value;
            await module.DisposeAsync();
        }
    }
}

在这个阶段你有一个有效的对话。

为了更方便,我添加了以下组件。 注意:我从这里开始使用 bootstrap 进行样式设置,例如可以轻松将其更改为 tailwind。

DialogCloseButton.razor

<button @attributes=CapturedAttributes @onclick=@CloseDialog />

DialogCloseButton.razor.cs

public partial class DialogCloseButton : ComponentBase
{
    [CascadingParameter]
    public Dialog Dialog { get; set; }

    [Parameter(CaptureUnmatchedValues = true)]
    public Dictionary<string, object> CapturedAttributes { get; set; } = new Dictionary<string, object>
    {
        { "class", "btn btn-close" }
    };

    private async Task CloseDialog() => await Dialog.CloseDialogAsync();
}

DialogCloseButton.razor.css

.btn:focus {
    box-shadow: none;
}

DialogLayout.razor

<div class="d-flex flex-row justify-content-between border-bottom border-1">
    <div class="flex-fill p-1 ps-3 fw-bolder user-select-none app-gradient text-white">
        @Header
    </div>
    <div class="p-1">
        <DialogCloseButton />
    </div>
</div>
<div class="p-3">
    @Content
</div>

DialogLayout.razor.cs

public partial class DialogLayout
{
    [Parameter]
    public RenderFragment Header { get; set; }

    [Parameter]
    public RenderFragment Content { get; set; }
}

Usage :

<Dialog @ref=@dialog class="p-0 border rounded shadow">
    <DialogLayout>
        <Header>
           <MessagesIcon Size=16 /> Add Message
        </Header>
        <Content>
            <MessageFormView />
        </Content>
    </DialogLayout>
</Dialog>
<button class="btn btn-outline-success" @onclick=@OpenDialog>Add Message</button>
@code {
    private Dialog dialog;

    ...

    private async Task OpenDialog() => await dialog.ShowDialogAsync();
}

这是您所问内容的一个非常简单的示例(我将所有内容都放在 index.razor 文件中,但是您可以使用 CSS 隔离和专用的 .cs 文件来处理所有@code{} 部分的内容。

@page "/index"
<style>
    .active {
        display: block;
    }
    .inactive {
        display: none;
    }
</style>

<a class="btn btn-login" @onclick="RedirectPage" >Log in</a>

<div class="@PopupClass">
    Error: @ErrorText
</div>

@code{
    bool InvalidLogin {get; set;} = false;

    string PopupClass => InvalidLogin ? "active" : "inactive";
    public string ErrorText { get; set; } = "Example of exception";

    private void RedirectPage()
    {
        this.InvalidLogin = !this.InvalidLogin;
    }
}

当然,您需要自己适应这个示例以实现更具体的业务逻辑。