Files
Atomx/Atomx.Admin/Atomx.Admin.Client/Pages/Login.razor
2025-12-09 04:09:33 +08:00

259 lines
9.8 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
@page "/account/login"
@page "/{locale}/account/login"
@using System.Text.Json
@layout EmptyLayout
@inject ILogger<Login> Logger
@inject IJSRuntime JS
@using Microsoft.Extensions.Localization
@inject IStringLocalizer<Login> L
@inject Atomx.Admin.Client.Services.ILocalizationProvider LocalizationProvider
<PageTitle>@L["login.title"]</PageTitle>
@if (!dataLoaded)
{
<Spin Spinning="_isLoading" />
}
else
{
<Flex Style="height:100vh" Justify="FlexJustify.Center" Align="FlexAlign.Center" Direction="FlexDirection.Vertical">
<GridRow Justify="RowJustify.Center" Class="">
<GridCol>
<Card>
<Form @ref="form" Model="@login" OnFinish="LoginAsync">
<FluentValidationValidator />
<FormItem>
<AntDesign.Input Placeholder="@L["login.account.placeholder"]" Size="InputSize.Large" @bind-Value="@login.Account">
<Prefix><Icon Type="user" /></Prefix>
</AntDesign.Input>
</FormItem>
<FormItem>
<AntDesign.Input Placeholder="@L["login.password.placeholder"]" Size="InputSize.Large" @bind-Value="@login.Password" Type="InputType.Password">
<Prefix><Icon Type="lock" /></Prefix>
</AntDesign.Input>
</FormItem>
<FormItem>
<a style="float: left;">
@L["login.forgot"]
</a>
<a style="float: right;">
<NavLink href="/register">@L["login.register"]</NavLink>
</a>
</FormItem>
<FormItem>
<Button Type="ButtonType.Primary" HtmlType="submit" Class="submit" Size="ButtonSize.Large" Block>@L["login.submit"]</Button>
</FormItem>
<FormItem>
<a @onclick="setAccount">
@L["login.setdev"]
</a>
</FormItem>
</Form>
</Card>
</GridCol>
</GridRow>
<GridRow Style="padding-top:40px">
<span style="font-size:12px;padding-right:3px;">@L["copyright"]</span>
<span style="font-size:12px">runing as @handler</span>
</GridRow>
<GridRow>
<Atomx.Admin.Client.Components.LangSelector />
</GridRow>
<GridRow Style="padding-top:10px">
<div>
<strong>Quick links:</strong>
<span style="padding-left:10px;"><NavLink href="/counter">Counter</NavLink></span>
<span style="padding-left:10px;"><NavLink href="/weather">Weather</NavLink></span>
</div>
<div>
<strong>zh Quick links:</strong>
<span style="padding-left:10px;"><NavLink href="/zh/counter">Counter</NavLink></span>
<span style="padding-left:10px;"><NavLink href="/zh/weather">Weather</NavLink></span>
</div>
<div>
<strong>en Quick links:</strong>
<span style="padding-left:10px;"><NavLink href="/en/counter">Counter</NavLink></span>
<span style="padding-left:10px;"><NavLink href="/en/weather">Weather</NavLink></span>
</div>
</GridRow>
</Flex>
}
@code {
string handler = "Server";
[Parameter]
public string Locale { get; set; } = string.Empty;
[Parameter]
[SupplyParameterFromQuery(Name = "ReturnUrl")]
public string? ReturnUrl { get; set; }
[SupplyParameterFromForm]
public LoginModel login { get; set; } = new();
private Form<LoginModel> form = null!;
bool dataLoaded = false;
string message { get; set; } = string.Empty;
private bool _isLoading = false;
protected override void OnInitialized()
{
if (OperatingSystem.IsBrowser())
{
handler = "Wasm";
}
else
{
handler = "Server";
}
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
var authState = await AuthStateProvider.GetAuthenticationStateAsync();
if (authState.User.Identity != null && authState.User.Identity.IsAuthenticated)
{
Navigation.NavigateTo(ReturnUrl ?? "/");
}
}
if (!dataLoaded)
{
dataLoaded = true;
StateHasChanged();
}
}
private async Task LoginAsync()
{
if (!form.Validate()) return;
_isLoading = true;
StateHasChanged();
try
{
var api = "/api/sign/in";
if (!OperatingSystem.IsBrowser())
{
// Server 模式:使用浏览器发起的 fetch通过 JS并携带 credentials: 'include'
Logger.LogInformation("Server 模式,使用浏览器 fetch 登录");
var jsResult = await JS.InvokeAsync<JsonElement>("__atomx_post_json", api, login);
var success = jsResult.TryGetProperty("success", out var sprop) && sprop.GetBoolean();
if (success && jsResult.TryGetProperty("data", out var dprop) && dprop.ValueKind == JsonValueKind.Object)
{
var token = dprop.TryGetProperty("token", out var t) ? t.GetString() ?? string.Empty : string.Empty;
var refresh = dprop.TryGetProperty("refreshToken", out var r) ? r.GetString() ?? string.Empty : string.Empty;
// WASM 的 localStorage 在 Server Circuit 中无意义兼容auto模式写入 localStorage。
try
{
await localStorage.SetItemAsync(StorageKeys.AccessToken, token);
await localStorage.SetItemAsync(StorageKeys.RefreshToken, refresh);
if (AuthStateProvider is PersistentAuthenticationStateProvider provider)
{
provider.UpdateAuthenticationState(token);
}
}
catch { }
// 浏览器已通过 fetch 收到 Set-Cookie强制重载使 Circuit 使用新 Cookie。
Logger.LogInformation($"登录成功server 跳转: {ReturnUrl}");
Navigation.NavigateTo(ReturnUrl ?? "/", forceLoad: true);
}
else
{
var msg = jsResult.TryGetProperty("message", out var m) ? m.GetString() ?? "登录失败" : "登录失败";
ModalService.Error(new ConfirmOptions() { Title = "提示", Content = msg });
}
}
else
{
// Wasm 模式:继续使用 HttpService之前逻辑保存 localStorage 并更新 AuthStateProvider
var result = await HttpService.Post<ApiResult<AuthResponse>>(api, login);
if (result.Success && result.Data != null)
{
var auth = result.Data;
await localStorage.SetItemAsync(StorageKeys.AccessToken, auth.Token);
await localStorage.SetItemAsync(StorageKeys.RefreshToken, auth.RefreshToken);
if (AuthStateProvider is PersistentAuthenticationStateProvider provider)
{
provider.UpdateAuthenticationState(auth.Token);
}
Logger.LogInformation("登录成功wasm 跳转: {ReturnUrl}", ReturnUrl);
Navigation.NavigateTo(ReturnUrl ?? "/");
}
else
{
ModalService.Error(new ConfirmOptions() { Title = "提示", Content = result.Message });
}
}
}
catch (Exception ex)
{
Logger.LogError(ex, "登录失败");
ModalService.Error(new ConfirmOptions() { Title = "错误", Content = "登录异常,请稍后重试" });
}
finally
{
_isLoading = false;
StateHasChanged();
}
}
private async Task OnPasswordKeyDown(KeyboardEventArgs value)
{
if (value.Key == "Enter")
{
await LoginAsync();
}
}
void setAccount()
{
login.Account = "admin";
login.Password = "admin888";
}
private string GetShortCulture(string current)
{
if (string.IsNullOrEmpty(current)) return current;
if (current.StartsWith("zh", StringComparison.OrdinalIgnoreCase)) return "zh";
if (current.StartsWith("en", StringComparison.OrdinalIgnoreCase)) return "en";
var prefix = current.Split('-', StringSplitOptions.RemoveEmptyEntries).FirstOrDefault();
return prefix ?? current;
}
}
@* 页面内 JS 辅助:用于在 Server 模式下从浏览器发起 POST 并携带凭证,使浏览器接收 Set-Cookie *@
<script>
window.__atomx_post_json = async function (url, data) {
try {
const res = await fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
credentials: 'include',
body: JSON.stringify(data)
});
const text = await res.text();
try {
return JSON.parse(text);
} catch {
return { success: res.ok, message: text };
}
} catch (err) {
return { success: false, message: err?.toString() ?? 'network error' };
}
};
</script>