Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
<span class="sidebar-text">Bootstrap Blazor</span>
</div>
<TutorialsNavMenu></TutorialsNavMenu>
<Wwads IsVertical="true"></Wwads>
<LayoutSplitBar Min="220" Max="330" ContainerSelector=".section"></LayoutSplitBar>
</aside>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,8 @@ .. _layoutFileList
private readonly string[] _template5 =
[
"Tutorials/LoginAndRegister/Template5.razor",
"Tutorials/LoginAndRegister/Template5.razor.cs",
"Tutorials/LoginAndRegister/Template5.razor.js",
"Tutorials/LoginAndRegister/Template5.razor.css",
"../Layout/TutorialsLayout.razor",
"../Layout/TutorialsLayout.razor.css"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
@page "/tutorials/template5"
@layout TutorialsLayout
@inherits WebSiteModuleComponentBase
@attribute [JSModuleAutoLoader("Samples/Tutorials/LoginAndRegister/Template5.razor.js", AutoInvokeDispose = false)]

<HeadContent>
<style>
Expand All @@ -21,8 +23,8 @@

<div class="background-image">
<div class="login-container">
<div class="login-box animate-fade-in">
<div class="header-row">
<div class="login-box animate-fade-in" id="@Id">
<div class="login-header">
@if (!isAuth)
{
if (isEmailEntered)
Expand All @@ -45,45 +47,12 @@
</div>
}
</div>
@if (!isEmailEntered)
{
<h3>登录</h3>
<p class="subtitle">使用你的 BootstrapBlazor 帐户。</p>
<ValidateForm Model="_loginModel" OnValidSubmit="OnEmailSubmit">
<BootstrapInput type="email" class="input" SkipValidate="true" IsAutoFocus="true"
placeholder="电子邮件或电话号码" @bind-Value="@_loginModel.Email"></BootstrapInput>
<div class="error" hidden="@(!showEmailError)">请输入有效的电子邮件地址或电话号码</div>
<Button class="button" Color="Color.Primary" ButtonType="ButtonType.Submit" Text="下一步"></Button>
</ValidateForm>
<div class="links">
<a href="#" @onclick:preventDefault>忘记用户名?</a>
</div>
<div class="small">
不熟悉 BootstrapBlazor?<a href="/">去看文档</a>
</div>
}
else if (!isAuth)
{
<h3>输入你的密码</h3>
<p class="email-display">@_loginModel.Email</p>
<ValidateForm Model="_loginModel" OnValidSubmit="OnPasswordSubmit">
<BootstrapInput type="password" class="input" SkipValidate="true" IsAutoFocus="true"
placeholder="密码" @bind-Value="@_loginModel.Password"></BootstrapInput>
<div class="links">
<a href="#" @onclick:preventDefault>忘记了密码?</a>
</div>
<Button class="button" Color="Color.Primary" ButtonType="ButtonType.Submit" Text="下一步"></Button>
</ValidateForm>
<div class="links">
<a href="#" @onclick:preventDefault>其他登录方法</a>
</div>
}
else
@if (isAuth)
{
<div class="email-display2"><span>@_loginModel.Email</span></div>
<h5 class="text-center mt-3 mb-0">欢迎,您已成功登录</h5>
<div class="login-video-wrap">
<video class="login-video" autoplay="autoplay" playsinline="playsinline" disablepictureinpicture="">
<video class="login-video" autoplay playsinline disablepictureinpicture>
<source src="samples/login5/loading1.mp4" type="video/mp4; codecs=av01.0.05M.08">
<source src="samples/login5/loading.mov" type="video/quicktime; codecs=hvc1.1.6.H120.b0">
<source src="samples/login5/loading.webm" type="video/webm; codecs=vp9">
Expand All @@ -92,55 +61,45 @@
</video>
</div>
<p class="text-center text-muted" style="font-size: 0.75rem;">此登录高仿微软登录 UI</p>
<Button class="button" Color="Color.Primary">进入</Button>
<Button class="button" Color="Color.Primary" OnClick="OnReset">进入</Button>
}
else
{
<div class="login-body">
<div class="login-item login-item-email show">
<h3>登录</h3>
<p class="subtitle">使用你的 BootstrapBlazor 帐户。</p>
<ValidateForm Model="_loginModel" OnValidSubmit="OnEmailSubmit">
<BootstrapInput type="email" class="input" SkipValidate="true" IsAutoFocus="true"
placeholder="电子邮件或电话号码" @bind-Value="@_loginModel.Email"></BootstrapInput>
<div class="error" hidden="@(!showEmailError)">请输入有效的电子邮件地址或电话号码</div>
<Button class="button" Color="Color.Primary" ButtonType="ButtonType.Submit" Text="下一步"></Button>
</ValidateForm>
<div class="links">
<a href="#" @onclick:preventDefault>忘记用户名?</a>
</div>
<div class="small">
不熟悉 BootstrapBlazor?<a href="/">去看文档</a>
</div>
</div>

<div class="login-item login-item-password">
<h3>输入你的密码</h3>
<p class="email-display">@_loginModel.Email</p>
<ValidateForm Model="_loginModel" OnValidSubmit="OnPasswordSubmit">
<BootstrapInput type="password" class="input" SkipValidate="true" IsAutoFocus="true"
placeholder="密码" @bind-Value="@_loginModel.Password"></BootstrapInput>
<div class="links">
<a href="#" @onclick:preventDefault>忘记了密码?</a>
</div>
<Button class="button" Color="Color.Primary" ButtonType="ButtonType.Submit" Text="登录"></Button>
</ValidateForm>
<div class="links">
<a href="#" @onclick:preventDefault>其他登录方法</a>
</div>
</div>
</div>
}
</div>
</div>
</div>

@code {
private bool isEmailEntered = false;
private bool isAuth = false;
private bool showEmailError = false;
private LoginModel _loginModel = new LoginModel()
{
Email = "[email protected]",
Password = "123456"
};

private Task OnEmailSubmit(EditContext context)
{
if (string.IsNullOrWhiteSpace(_loginModel.Email))
{
showEmailError = true;
}
else
{
showEmailError = false;
isEmailEntered = true;
}
StateHasChanged();
return Task.CompletedTask;
}

private Task OnPasswordSubmit(EditContext context)
{
// 数据库检查密码逻辑可以在这里实现
// 演示代码一律通过
isAuth = true;
StateHasChanged();
return Task.CompletedTask;
}

private void GoBack()
{
isEmailEntered = false;
}

class LoginModel
{
public string? Email { get; set; }

public string? Password { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License
// See the LICENSE file in the project root for more information.
// Maintainer: Argo Zhang([email protected]) Website: https://www.blazor.zone

using Microsoft.AspNetCore.Components.Forms;

namespace BootstrapBlazor.Server.Components.Samples.Tutorials.LoginAndRegister;

/// <summary>
/// 高仿 Microsoft 登录界面
/// </summary>
public partial class Template5
{
private bool isAuth = false;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (complexity): Consider replacing multiple boolean flags with a single state enum, removing the unused lifecycle method, and extracting the model class to its own file for clarity.

Here are a few focused tweaks that preserve all functionality while collapsing the three bool flags into a single state enum, removing the no-op lifecycle hook, and pulling your model out of the component:

  1. Introduce an enum and single Stage field:
private enum Stage { EnterEmail, EnterPassword, Authenticated }
private Stage _stage = Stage.EnterEmail;
  1. Update your handlers to drive that one state and drop all manual StateHasChanged() calls (Blazor auto-re-renders on events):
private async Task OnEmailSubmit(EditContext ctx)
{
    if (string.IsNullOrWhiteSpace(_loginModel.Email))
    {
        // e.g. set validation message via EditContext
        return;
    }

    _stage = Stage.EnterPassword;
    await InvokeVoidAsync("go", Id);
}

private Task OnPasswordSubmit(EditContext ctx)
{
    _stage = Stage.Authenticated;
    return Task.CompletedTask;
}

private async Task GoBack()
{
    _stage = Stage.EnterEmail;
    await InvokeVoidAsync("back", Id);
}

private void OnReset() => _stage = Stage.EnterEmail;
  1. Remove the empty OnAfterRenderAsync override completely.

  2. Extract LoginModel into its own file (e.g. LoginModel.cs) under the same namespace:

namespace BootstrapBlazor.Server.Components.Samples.Tutorials.LoginAndRegister
{
    public class LoginModel
    {
        public string? Email { get; set; }
        public string? Password { get; set; }
    }
}
  1. In your Razor markup, branch on _stage instead of multiple booleans:
@if (_stage == Stage.EnterEmail)
{
  <!-- email form -->
}
else if (_stage == Stage.EnterPassword)
{
  <!-- password form -->
}
else
{
  <!-- authenticated view -->
}

This collapses your three flags (isAuth, showEmailError, isEmailEntered) into one, removes redundant lifecycle overrides, and pulls the model to its own file.

private bool showEmailError = false;
private bool isEmailEntered = false;

private readonly LoginModel _loginModel = new()
{
Email = "[email protected]",
Password = "123456"
};

private async Task OnEmailSubmit(EditContext context)
{
if (string.IsNullOrEmpty(_loginModel.Email))
{
showEmailError = true;
isEmailEntered = false;
}
else
{
showEmailError = false;
isEmailEntered = true;
await InvokeVoidAsync("go", Id);
}
StateHasChanged();
}

private Task OnPasswordSubmit(EditContext context)
{
// 数据库检查密码逻辑可以在这里实现
// 演示代码一律通过
isAuth = true;
StateHasChanged();
return Task.CompletedTask;
}

private async Task GoBack()
{
isEmailEntered = false;
showEmailError = false;
StateHasChanged();
await InvokeVoidAsync("back", Id);
}

private void OnReset()
{
isAuth = false;
isEmailEntered = false;
}

/// <summary>
/// <inheritdoc/>
/// </summary>
/// <param name="firstRender"></param>
/// <returns></returns>
protected override async Task OnAfterRenderAsync(bool firstRender)
{
await base.OnAfterRenderAsync(firstRender);
}

private class LoginModel
{
public string? Email { get; set; }

public string? Password { get; set; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
border-radius: 4px;
}

.header-row {
.login-header {
display: flex;
align-items: center;
position: relative;
Expand Down Expand Up @@ -177,7 +177,7 @@
}

.animate-fade-out {
animation: fadeOut 0.5s ease-in-out forwards;
animation: fadeOut 0.2s ease-in-out forwards;
}

@keyframes fadeOut {
Expand All @@ -191,3 +191,18 @@
transform: translateY(-20px);
}
}

.login-body {
position: relative;
height: 290px;
}

.login-item {
position: absolute;
inset: 0;
}

.login-item:not(.show) {
opacity: 0;
pointer-events: none;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
export function init(id) {
const el = document.getElementById(id);
if (el) {
const email = el.querySelector('.login-item-email');
if (email) {
email.classList.add('show');
}
Comment on lines +1 to +7
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): The 'init' function is defined but not invoked from C#.

Call 'init' from the C# side (e.g., in OnAfterRenderAsync) to ensure the login form displays the email step correctly on first render.

}
}

export function go(id) {
const el = document.getElementById(id);
if (el) {
const email = el.querySelector('.login-item-email');
if (email) {
email.classList.remove('show');
email.classList.remove('animate-fade-in')
email.classList.add('animate-fade-out');
}

const password = el.querySelector('.login-item-password');
if (password) {
password.classList.add('show');
password.classList.add('animate-fade-in')
password.classList.remove('animate-fade-out')
}
}
}

export function back(id) {
const el = document.getElementById(id);
if (el) {
const email = el.querySelector('.login-item-email');
if (email) {
email.classList.add('show');
email.classList.remove('animate-fade-out');
email.classList.add('animate-fade-in');
}
Comment on lines +33 to +38
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: Consider removing all animation classes before adding new ones to prevent animation conflicts.

Residual animation classes can cause unintended effects. Remove both 'animate-fade-in' and 'animate-fade-out' before adding the new class.

Suggested change
const email = el.querySelector('.login-item-email');
if (email) {
email.classList.add('show');
email.classList.remove('animate-fade-out');
email.classList.add('animate-fade-in');
}
const email = el.querySelector('.login-item-email');
if (email) {
// Remove all animation classes before adding new ones to prevent animation conflicts
email.classList.remove('animate-fade-in', 'animate-fade-out');
email.classList.add('show');
email.classList.add('animate-fade-in');
}


const password = el.querySelector('.login-item-password');
if (password) {
password.classList.remove('show');
password.classList.remove('animate-fade-in')
password.classList.add('animate-fade-out')
}
}
}
Loading