diff --git a/Atomx.Admin/Atomx.Admin.Client/Atomx.Admin.Client.csproj b/Atomx.Admin/Atomx.Admin.Client/Atomx.Admin.Client.csproj index d442b1b..9a1ffb5 100644 --- a/Atomx.Admin/Atomx.Admin.Client/Atomx.Admin.Client.csproj +++ b/Atomx.Admin/Atomx.Admin.Client/Atomx.Admin.Client.csproj @@ -8,6 +8,13 @@ Default + + + + + + + @@ -26,8 +33,4 @@ - - - - diff --git a/Atomx.Admin/Atomx.Admin.Client/Pages/Login.razor b/Atomx.Admin/Atomx.Admin.Client/Pages/Login.razor index f45f7dc..f7ce2dc 100644 --- a/Atomx.Admin/Atomx.Admin.Client/Pages/Login.razor +++ b/Atomx.Admin/Atomx.Admin.Client/Pages/Login.razor @@ -1,12 +1,9 @@ @page "/account/login" @page "/{locale}/account/login" -@using System.Text.Json @layout EmptyLayout @inject ILogger Logger -@inject IJSRuntime JS -@using Microsoft.Extensions.Localization @inject IStringLocalizer L -@inject Atomx.Admin.Client.Services.ILocalizationProvider LocalizationProvider + @L["login.title"] @@ -144,41 +141,30 @@ else if (!OperatingSystem.IsBrowser()) { // Server 模式:使用浏览器发起的 fetch(通过 JS)并携带 credentials: 'include' - Logger.LogInformation("Server 模式,使用浏览器 fetch 登录"); - var jsResult = await JS.InvokeAsync("__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 jsResult = await JS.InvokeAsync("ajax.Post", api, login); + var result = jsResult.ToJson().FromJson>(); + if (result != null && result.Success) { - 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; + var auth = result.Data; + await localStorage.SetItemAsync(StorageKeys.AccessToken, auth.Token); + await localStorage.SetItemAsync(StorageKeys.RefreshToken, auth.RefreshToken); - // WASM 的 localStorage 在 Server Circuit 中无意义,兼容auto模式写入 localStorage。 - try + if (AuthStateProvider is PersistentAuthenticationStateProvider provider) { - await localStorage.SetItemAsync(StorageKeys.AccessToken, token); - await localStorage.SetItemAsync(StorageKeys.RefreshToken, refresh); - - if (AuthStateProvider is PersistentAuthenticationStateProvider provider) - { - provider.UpdateAuthenticationState(token); - } + provider.UpdateAuthenticationState(auth.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 }); + ModalService.Error(new ConfirmOptions() { Title = "提示", Content = result.Message }); } } else { - // Wasm 模式:继续使用 HttpService(之前逻辑),保存 localStorage 并更新 AuthStateProvider + // Wasm 模式:保存 localStorage 并更新 AuthStateProvider var result = await HttpService.Post>(api, login); if (result.Success && result.Data != null) { @@ -225,35 +211,4 @@ else 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 *@ - \ No newline at end of file +} \ No newline at end of file diff --git a/Atomx.Admin/Atomx.Admin.Client/Program.cs b/Atomx.Admin/Atomx.Admin.Client/Program.cs index f83dfb8..bd2d48d 100644 --- a/Atomx.Admin/Atomx.Admin.Client/Program.cs +++ b/Atomx.Admin/Atomx.Admin.Client/Program.cs @@ -1,14 +1,12 @@ using Atomx.Admin.Client.Services; using Atomx.Admin.Client.Utils; +using Atomx.Admin.Client.Validators; using Blazored.LocalStorage; +using FluentValidation; using Microsoft.AspNetCore.Components.Authorization; using Microsoft.AspNetCore.Components.WebAssembly.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Localization; -using System.Net.Http; -using FluentValidation; -using System.Linq; -using System.Reflection; var builder = WebAssemblyHostBuilder.CreateDefault(args); @@ -70,7 +68,7 @@ builder.Services.AddScoped(sp => return new HttpService(httpClient, httpContextAccessor); }); -builder.Services.AddValidatorsFromAssembly(typeof(Atomx.Admin.Client.Validators.LoginModelValidator).Assembly); +builder.Services.AddValidatorsFromAssembly(typeof(LoginModelValidator).Assembly); builder.Services.AddAntDesign(); diff --git a/Atomx.Admin/Atomx.Admin.Client/Services/LocalizationProvider.cs b/Atomx.Admin/Atomx.Admin.Client/Services/LocalizationProvider.cs index f3938d8..8a8b9f3 100644 --- a/Atomx.Admin/Atomx.Admin.Client/Services/LocalizationProvider.cs +++ b/Atomx.Admin/Atomx.Admin.Client/Services/LocalizationProvider.cs @@ -300,7 +300,7 @@ namespace Atomx.Admin.Client.Services private async Task SetCultureInternalAsync(string cultureFull, bool persistCookie) { - _logger?.LogDebug("SetCultureInternalAsync start: {Culture}, persist={Persist}", cultureFull, persistCookie); + //_logger?.LogDebug("ڲĻ첽ʼ: {Culture}, ־û={Persist}", cultureFull, persistCookie); await EnsureCultureLoadedAsync(cultureFull); try diff --git a/Atomx.Admin/Atomx.Admin.Client/_Imports.razor b/Atomx.Admin/Atomx.Admin.Client/_Imports.razor index 91cc495..98a7ad9 100644 --- a/Atomx.Admin/Atomx.Admin.Client/_Imports.razor +++ b/Atomx.Admin/Atomx.Admin.Client/_Imports.razor @@ -1,5 +1,6 @@ @using System.Net.Http @using System.Net.Http.Json +@using System.Text.Json @using System.Security.Claims @using Microsoft.AspNetCore.Authorization @using Microsoft.AspNetCore.Components.Authorization @@ -32,10 +33,11 @@ @using Blazored.FluentValidation - +@inject IJSRuntime JS @inject ILocalStorageService localStorage +@inject ILocalizationProvider LocalizationProvider @inject AuthenticationStateProvider AuthStateProvider @inject NavigationManager Navigation @inject HttpService HttpService @inject MessageService MessageService -@inject ModalService ModalService \ No newline at end of file +@inject ModalService ModalService diff --git a/Atomx.Admin/Atomx.Admin.Client/wwwroot/js/common.js b/Atomx.Admin/Atomx.Admin.Client/wwwroot/js/common.js index 42e4be5..bf2ede7 100644 --- a/Atomx.Admin/Atomx.Admin.Client/wwwroot/js/common.js +++ b/Atomx.Admin/Atomx.Admin.Client/wwwroot/js/common.js @@ -1,34 +1,58 @@ window.cookies = { - Read: function (name) { - try { - const match = document.cookie.match(new RegExp('(^| )' + name + '=([^;]+)')); - if (match) return decodeURIComponent(match[2]); - return ''; - } catch (e) { - return ''; + Read: function (name) { + try { + const match = document.cookie.match(new RegExp('(^| )' + name + '=([^;]+)')); + if (match) return decodeURIComponent(match[2]); + return ''; + } catch (e) { + return ''; + } + }, + Write: function (name, value, expiresIso) { + try { + var expires = ''; + if (expiresIso) { + expires = '; expires=' + new Date(expiresIso).toUTCString(); + } + document.cookie = name + '=' + encodeURIComponent(value) + expires + '; path=/'; + } catch (e) { } + }, + Delete: function (cookie_name) { + document.cookie = cookie_name + "=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;"; + } +}; + +window.ajax = { + Post: 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' }; + } } - }, - Write: function (name, value, expiresIso) { - try { - var expires = ''; - if (expiresIso) { - expires = '; expires=' + new Date(expiresIso).toUTCString(); - } - document.cookie = name + '=' + encodeURIComponent(value) + expires + '; path=/'; - } catch (e) { } - } }; window.getBrowserLanguage = function () { - try { - return (navigator.languages && navigator.languages[0]) || navigator.language || navigator.userLanguage || ''; - } catch (e) { - return ''; - } + try { + return (navigator.languages && navigator.languages[0]) || navigator.language || navigator.userLanguage || ''; + } catch (e) { + return ''; + } }; window.setHtmlLang = function (lang) { - try { - if (document && document.documentElement) document.documentElement.lang = lang || ''; - } catch (e) { } + try { + if (document && document.documentElement) document.documentElement.lang = lang || ''; + } catch (e) { } }; diff --git a/Atomx.Admin/Atomx.Admin/Atomx.Admin.csproj b/Atomx.Admin/Atomx.Admin/Atomx.Admin.csproj index b6ede15..367211d 100644 --- a/Atomx.Admin/Atomx.Admin/Atomx.Admin.csproj +++ b/Atomx.Admin/Atomx.Admin/Atomx.Admin.csproj @@ -8,9 +8,13 @@ + + + + @@ -43,8 +47,4 @@ - - - - diff --git a/Atomx.Admin/Atomx.Admin/Program.cs b/Atomx.Admin/Atomx.Admin/Program.cs index 9b77014..d021fee 100644 --- a/Atomx.Admin/Atomx.Admin/Program.cs +++ b/Atomx.Admin/Atomx.Admin/Program.cs @@ -155,38 +155,6 @@ builder.Services.AddValidatorsFromAssembly(typeof(Atomx.Admin.Client.Validators. var app = builder.Build(); -// Preload localization files on startup to ensure server-side rendering finds translations -try -{ - using var scope = app.Services.CreateScope(); - var logger = scope.ServiceProvider.GetService()?.CreateLogger("LocalizationStartup"); - var provider = scope.ServiceProvider.GetService(); - if (provider != null) - { - logger?.LogInformation("Preloading localization files for server startup"); - // preload supported cultures - var supported = new[] { "zh-Hans", "en-US" }; - foreach (var c in supported) - { - try - { - provider.LoadCultureAsync(c).GetAwaiter().GetResult(); - logger?.LogInformation("Preloaded localization for {Culture}", c); - } - catch (Exception ex) - { - logger?.LogWarning(ex, "Failed to preload localization for {Culture}", c); - } - } - } -} -catch (Exception ex) -{ - // don't block startup - var l = app.Services.GetService()?.CreateLogger("LocalizationStartup"); - l?.LogWarning(ex, "Failed to run localization preload"); -} - app.AddDataMigrate(); // Forwarded headers diff --git a/Atomx.Admin/Atomx.Admin/appsettings.json b/Atomx.Admin/Atomx.Admin/appsettings.json index 7c1b3d1..96bb03d 100644 --- a/Atomx.Admin/Atomx.Admin/appsettings.json +++ b/Atomx.Admin/Atomx.Admin/appsettings.json @@ -68,7 +68,7 @@ }, "Seq": { - "ServerUrl": "http://log.firestones.cn/", + "ServerUrl": "http://log.atomlust.com/", "ApiKey": "bBWmvSE2LJh4KsMeidvF", "MinimumLevel": "Warning", "LevelOverride": {