chore fix
This commit is contained in:
@@ -1,29 +0,0 @@
|
||||
@inject IJSRuntime JSRuntime
|
||||
@inject ILocalizationService LocalizationService
|
||||
@inject NavigationManager Navigation
|
||||
|
||||
@* <select @bind="_selectedCulture" @onchange="OnCultureChanged">
|
||||
<option value="en-US">English</option>
|
||||
<option value="zh-CN">中文</option>
|
||||
<option value="ja-JP">日本語</option>
|
||||
</select> *@
|
||||
|
||||
@code {
|
||||
private string _selectedCulture = "en-US";
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
_selectedCulture = await JSRuntime.InvokeAsync<string>("blazorCulture.get") ?? "en-US";
|
||||
}
|
||||
|
||||
private async Task OnCultureChanged(ChangeEventArgs e)
|
||||
{
|
||||
var culture = e.Value?.ToString();
|
||||
if (!string.IsNullOrEmpty(culture))
|
||||
{
|
||||
await JSRuntime.InvokeVoidAsync("blazorCulture.set", culture);
|
||||
await LocalizationService.LoadResourcesAsync(culture);
|
||||
Navigation.NavigateTo(Navigation.Uri, forceLoad: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
@inject LanguageProvider LanguageProvider
|
||||
@inject IStringLocalizer<LanguageSelector> Localizer
|
||||
|
||||
<div class="dropdown">
|
||||
<button class="btn btn-outline-secondary dropdown-toggle"
|
||||
type="button"
|
||||
data-bs-toggle="dropdown"
|
||||
aria-expanded="false">
|
||||
@GetLanguageDisplayName(LanguageProvider.CurrentLanguage)
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
@foreach (var language in LanguageProvider.SupportedLanguages)
|
||||
{
|
||||
<li>
|
||||
<button class="dropdown-item @(language == LanguageProvider.CurrentLanguage ? "active" : "")"
|
||||
@onclick="() => ChangeLanguage(language)"
|
||||
type="button">
|
||||
@GetLanguageDisplayName(language)
|
||||
@if (language == LanguageProvider.CurrentLanguage)
|
||||
{
|
||||
<span class="badge bg-primary">✓</span>
|
||||
}
|
||||
</button>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
// 订阅变更以便在语言切换时即时更新该组件
|
||||
LanguageProvider.OnLanguageChanged += OnLanguageChanged;
|
||||
}
|
||||
|
||||
private void OnLanguageChanged()
|
||||
{
|
||||
// 在 UI 线程上下文中触发 StateHasChanged
|
||||
_ = InvokeAsync(StateHasChanged);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
LanguageProvider.OnLanguageChanged -= OnLanguageChanged;
|
||||
}
|
||||
|
||||
private string GetLanguageDisplayName(string languageCode)
|
||||
{
|
||||
return languageCode switch
|
||||
{
|
||||
"zh-Hans" => "简体中文",
|
||||
"en-US" => "English (US)",
|
||||
_ => languageCode
|
||||
};
|
||||
}
|
||||
|
||||
private async Task ChangeLanguage(string languageCode)
|
||||
{
|
||||
await LanguageProvider.ChangeLanguageAsync(languageCode);
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
@inject ILocalizationService LocalizationService
|
||||
|
||||
@Text
|
||||
|
||||
@code {
|
||||
private string? _text;
|
||||
|
||||
[Parameter]
|
||||
public string Key { get; set; } = string.Empty;
|
||||
|
||||
[Parameter]
|
||||
public string? Culture { get; set; }
|
||||
|
||||
private string Text => _text ?? Key;
|
||||
|
||||
protected override async Task OnParametersSetAsync()
|
||||
{
|
||||
await LoadText();
|
||||
}
|
||||
|
||||
private async Task LoadText()
|
||||
{
|
||||
_text = await LocalizationService.GetStringAsync(Key, Culture) ?? Key;
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
17
Atomx.Admin/Atomx.Admin.Client/Models/LocaleResourceItem.cs
Normal file
17
Atomx.Admin/Atomx.Admin.Client/Models/LocaleResourceItem.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using Atomx.Common.Entities;
|
||||
|
||||
namespace Atomx.Admin.Client.Models
|
||||
{
|
||||
public class LocaleResourceItem:LocaleResource
|
||||
{
|
||||
/// <summary>
|
||||
/// 语言标题
|
||||
/// </summary>
|
||||
public string Title { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 语言名称
|
||||
/// </summary>
|
||||
public string Culture { get; set; } = string.Empty;
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,11 @@
|
||||
/// </summary>
|
||||
public int LanguageId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 语言标题
|
||||
/// </summary>
|
||||
public string Title { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 语言名称
|
||||
/// </summary>
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
@layout EmptyLayout
|
||||
@inject ILogger<Login> Logger
|
||||
@inject IJSRuntime JS
|
||||
@inject IStringLocalizer<Login> Localizer
|
||||
|
||||
<PageTitle>登录</PageTitle>
|
||||
|
||||
@@ -12,6 +13,10 @@
|
||||
}
|
||||
else
|
||||
{
|
||||
<LanguageSelector></LanguageSelector>
|
||||
<p>
|
||||
@LanguageProvider.CurrentLanguage 网站名称 @Localizer["site.name"]
|
||||
</p>
|
||||
<Flex Style="height:100vh" Justify="FlexJustify.Center" Align="FlexAlign.Center" Direction="FlexDirection.Vertical">
|
||||
<GridRow Justify="RowJustify.Center" Class="">
|
||||
<GridCol>
|
||||
|
||||
@@ -0,0 +1,207 @@
|
||||
@page "/system/locale/resource/detail/{Name}"
|
||||
@inject ILogger<LocaleResourceList> Logger
|
||||
@attribute [Authorize]
|
||||
|
||||
|
||||
<PageTitle>本地化语言资源</PageTitle>
|
||||
<Title Level="4">多语言本地资源管理</Title>
|
||||
<Spin Spinning="loading">
|
||||
<Card Class="mt-3">
|
||||
<Table DataSource="ResourceItems" PageSize="100" HidePagination="true" Resizable>
|
||||
<TitleTemplate>
|
||||
<Flex Justify="FlexJustify.SpaceBetween">
|
||||
@(@Name)多语言资源列表,可用语言@(@languages.Count)种
|
||||
<div>
|
||||
<Button Class="me-3" OnClick="OnCreateClick" Type="ButtonType.Primary">新增</Button>
|
||||
</div>
|
||||
</Flex>
|
||||
</TitleTemplate>
|
||||
<ColumnDefinitions>
|
||||
<PropertyColumn Property="c => c.Name" Title="资源Key">
|
||||
</PropertyColumn>
|
||||
<PropertyColumn Property="c => c.Title" Title="语言">
|
||||
</PropertyColumn>
|
||||
<PropertyColumn Property="c => c.Value" Title="内容">
|
||||
|
||||
</PropertyColumn>
|
||||
<PropertyColumn Property="c => c.UpdateTime" Title="最后更新" Width="120px" />
|
||||
<ActionColumn Title="操作" Align="ColumnAlign.Right" Width="100px">
|
||||
<Space>
|
||||
<SpaceItem>
|
||||
<Dropdown Trigger="@(new Trigger[] { Trigger.Click })">
|
||||
<Overlay>
|
||||
<Menu>
|
||||
|
||||
<MenuItem>
|
||||
<a @onclick="(e) => OnEditClick(context)"> <Icon Type="@IconType.Outline.Edit" /> 编辑</a>
|
||||
</MenuItem>
|
||||
<MenuDivider />
|
||||
<MenuItem>
|
||||
<Popconfirm Placement="@Placement.Left" Title="@("删除这条数据无法恢复,您确定要删除吗?")"
|
||||
OnConfirm="@(e=>HandleDeleteConfirmAsync(e,context.Id))"
|
||||
OkText="确定"
|
||||
CancelText="取消">
|
||||
<a> <Icon Type="@IconType.Outline.Delete" /> 删除</a>
|
||||
</Popconfirm>
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
</Overlay>
|
||||
<ChildContent>
|
||||
<a class="ant-dropdown-link" @onclick:preventDefault>
|
||||
<Icon Type="@IconType.Outline.Menu" />
|
||||
</a>
|
||||
</ChildContent>
|
||||
</Dropdown>
|
||||
</SpaceItem>
|
||||
</Space>
|
||||
</ActionColumn>
|
||||
</ColumnDefinitions>
|
||||
</Table>
|
||||
</Card>
|
||||
</Spin>
|
||||
|
||||
<Modal Title="@("语言资源设置")" Visible="@modalVisible" Width="700" MaskClosable="true" OkText="@("保存")" CancelText="@("取消")" OnOk="@HandleModalOk" OnCancel="@HandleCancel">
|
||||
<Form Model="@model" @ref="@editform" LabelCol="new ColLayoutParam { Span = 5 }" WrapperCol="new ColLayoutParam { Span = 15 }" Name="modalForm" OnFinish="OnFormFinish">
|
||||
<FluentValidationValidator />
|
||||
<FormItem Label="语言文字">
|
||||
@if (context.Id == 0)
|
||||
{
|
||||
<Select DataSource="@languages" @bind-Value="@context.LanguageId" ItemValue="p => p.Id" ItemLabel="p => p.Title">
|
||||
</Select>
|
||||
}
|
||||
else
|
||||
{
|
||||
<Input Placeholder="资源名称" @bind-Value="@context.Title" Disabled />
|
||||
}
|
||||
|
||||
</FormItem>
|
||||
<FormItem Label="资源名称">
|
||||
<Input Placeholder="资源名称" @bind-Value="@context.Name" Disabled />
|
||||
</FormItem>
|
||||
<FormItem Label="资源内容">
|
||||
<TextArea Placeholder="资源内容" @bind-Value="@context.Value" />
|
||||
</FormItem>
|
||||
</Form>
|
||||
</Modal>
|
||||
|
||||
@code {
|
||||
|
||||
bool loading = false;
|
||||
|
||||
[Parameter]
|
||||
public string Name { get; set; }
|
||||
|
||||
[SupplyParameterFromForm]
|
||||
LocaleResourceModel model { get; set; } = default!;
|
||||
Form<LocaleResourceModel> editform = null!;
|
||||
|
||||
List<LocaleResourceItem> ResourceItems = new();
|
||||
List<Language> languages = new();
|
||||
|
||||
bool modalVisible = false;
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
model ??= new LocaleResourceModel() { };
|
||||
base.OnInitialized();
|
||||
}
|
||||
|
||||
protected override async Task OnParametersSetAsync()
|
||||
{
|
||||
_ = LoadLanguages();
|
||||
_ = LoadList();
|
||||
}
|
||||
|
||||
private async Task LoadLanguages()
|
||||
{
|
||||
var url = $"/api/language/enabled";
|
||||
var apiResult = await HttpService.Get<ApiResult<List<Language>>>(url);
|
||||
if (apiResult.Success)
|
||||
{
|
||||
if (apiResult.Data != null)
|
||||
{
|
||||
languages = apiResult.Data;
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task LoadList()
|
||||
{
|
||||
loading = true;
|
||||
|
||||
var url = $"/api/localeresource/{Name}";
|
||||
|
||||
var apiResult = await HttpService.Get<ApiResult<List<LocaleResourceItem>>>(url);
|
||||
if (apiResult.Success)
|
||||
{
|
||||
if (apiResult.Data != null)
|
||||
{
|
||||
ResourceItems = apiResult.Data;
|
||||
}
|
||||
}
|
||||
|
||||
loading = false;
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
void OnCreateClick()
|
||||
{
|
||||
Console.WriteLine("OnCreateClick");
|
||||
model = new() { Name = Name };
|
||||
modalVisible = true;
|
||||
}
|
||||
|
||||
void OnEditClick(LocaleResourceItem data)
|
||||
{
|
||||
this.model = data.Adapt<LocaleResourceModel>();
|
||||
modalVisible = true;
|
||||
}
|
||||
|
||||
async Task HandleDeleteConfirmAsync(MouseEventArgs e, long id)
|
||||
{
|
||||
var url = $"/api/localeresource/delete/{id}";
|
||||
var apiResult = await HttpService.Post<ApiResult<string>>(url, new());
|
||||
if (apiResult.Success)
|
||||
{
|
||||
_ = LoadList();
|
||||
await ModalService.InfoAsync(new ConfirmOptions() { Title = "操作提示", Content = "删除数据成功" });
|
||||
}
|
||||
else
|
||||
{
|
||||
await ModalService.ErrorAsync(new ConfirmOptions() { Title = "操作提示", Content = $"数据删除失败.{apiResult.Message}" });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void HandleModalOk()
|
||||
{
|
||||
editform.Submit();
|
||||
}
|
||||
|
||||
async Task OnFormFinish()
|
||||
{
|
||||
if (editform.Validate())
|
||||
{
|
||||
var result = new ApiResult<bool>();
|
||||
|
||||
var url = $"api/localeresource/save";
|
||||
result = await HttpService.Post<ApiResult<bool>>(url, model);
|
||||
|
||||
if (result.Success)
|
||||
{
|
||||
modalVisible = false;
|
||||
_ = LoadList();
|
||||
}
|
||||
else
|
||||
{
|
||||
await ModalService.ErrorAsync(new ConfirmOptions() { Title = "服务异常", Content = result.Message });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void HandleCancel()
|
||||
{
|
||||
modalVisible = false;
|
||||
}
|
||||
}
|
||||
@@ -33,7 +33,7 @@
|
||||
<Table DataSource="PagingList.Items" PageSize="100" HidePagination="true" Resizable>
|
||||
<TitleTemplate>
|
||||
<Flex Justify="FlexJustify.SpaceBetween">
|
||||
多语言列表
|
||||
@(@language.Name)语言资源列表
|
||||
<div>
|
||||
<Button Class="me-3" OnClick="OnCreateClick" Type="ButtonType.Primary">新增</Button>
|
||||
</div>
|
||||
@@ -45,11 +45,11 @@
|
||||
<PropertyColumn Property="c => c.Value" Title="内容">
|
||||
|
||||
</PropertyColumn>
|
||||
<PropertyColumn Property="c => c.UpdateTime" Title="最后更新" />
|
||||
<ActionColumn Title="操作" Align="ColumnAlign.Right">
|
||||
<PropertyColumn Property="c => c.UpdateTime" Title="最后更新" Width="120px" />
|
||||
<ActionColumn Title="操作" Align="ColumnAlign.Right" Width="120px">
|
||||
<Space>
|
||||
<SpaceItem>
|
||||
<a href="@($"/system/locale/resource/list/{context.Id}")"> <Icon Type="@IconType.Outline.Edit" /> 语言资源</a>
|
||||
<a @onclick="(e) => OnEditClick(context)"> <Icon Type="@IconType.Outline.Edit" /> 编辑</a>
|
||||
</SpaceItem>
|
||||
<SpaceItem>
|
||||
<Dropdown Trigger="@(new Trigger[] { Trigger.Click })">
|
||||
@@ -57,7 +57,7 @@
|
||||
<Menu>
|
||||
|
||||
<MenuItem>
|
||||
<a @onclick="(e) => OnEditClick(context)"> <Icon Type="@IconType.Outline.Edit" /> 编辑</a>
|
||||
<a href="@($"/system/locale/resource/detail/{context.Name}")"> <Icon Type="@IconType.Outline.Edit" /> 其他语言比对</a>
|
||||
</MenuItem>
|
||||
<MenuDivider />
|
||||
<MenuItem>
|
||||
@@ -88,14 +88,17 @@
|
||||
</Card>
|
||||
</Spin>
|
||||
|
||||
<Modal Title="@("消息模版设置")" Visible="@modalVisible" Width="700" MaskClosable="true" OkText="@("保存")" CancelText="@("取消")" OnOk="@HandleModalOk" OnCancel="@HandleCancel">
|
||||
<Modal Title="@("语言资源设置")" Visible="@modalVisible" Width="700" MaskClosable="true" OkText="@("保存")" CancelText="@("取消")" OnOk="@HandleModalOk" OnCancel="@HandleCancel">
|
||||
<Form Model="@model" @ref="@editform" LabelCol="new ColLayoutParam { Span = 5 }" WrapperCol="new ColLayoutParam { Span = 15 }" Name="modalForm" OnFinish="OnFormFinish">
|
||||
<FluentValidationValidator />
|
||||
<FormItem Label="消息模版名称">
|
||||
<Input Placeholder="消息模版名称" @bind-Value="@context.Name" />
|
||||
<FormItem Label="语言文字">
|
||||
@language.Name
|
||||
</FormItem>
|
||||
<FormItem Label="消息内容">
|
||||
<TextArea Placeholder="消息内容" @bind-Value="@context.Value" />
|
||||
<FormItem Label="资源名称">
|
||||
<Input Placeholder="资源名称" @bind-Value="@context.Name" />
|
||||
</FormItem>
|
||||
<FormItem Label="资源内容">
|
||||
<TextArea Placeholder="资源内容" @bind-Value="@context.Value" />
|
||||
</FormItem>
|
||||
</Form>
|
||||
</Modal>
|
||||
@@ -121,6 +124,7 @@
|
||||
LocaleResourceModel model { get; set; } = default!;
|
||||
Form<LocaleResourceModel> editform = null!;
|
||||
|
||||
Language language = new();
|
||||
PagingList<LocaleResource> PagingList = new();
|
||||
|
||||
bool modalVisible = false;
|
||||
@@ -134,9 +138,23 @@
|
||||
|
||||
protected override async Task OnParametersSetAsync()
|
||||
{
|
||||
await LoadLanguage();
|
||||
await LoadList();
|
||||
}
|
||||
|
||||
private async Task LoadLanguage()
|
||||
{
|
||||
var url = $"/api/language/{Id}";
|
||||
var apiResult = await HttpService.Get<ApiResult<Language>>(url);
|
||||
if (apiResult.Success)
|
||||
{
|
||||
if (apiResult.Data != null)
|
||||
{
|
||||
language = apiResult.Data;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task LoadList()
|
||||
{
|
||||
loading = true;
|
||||
@@ -172,18 +190,20 @@
|
||||
|
||||
void OnCreateClick()
|
||||
{
|
||||
model = new() { Culture = LanguageCulture.zhHans };
|
||||
Console.WriteLine("OnCreateClick");
|
||||
model = new() { Culture = LanguageCulture.zhHans, LanguageId = Id };
|
||||
modalVisible = true;
|
||||
}
|
||||
|
||||
void OnEditClick(LocaleResource data)
|
||||
{
|
||||
this.model = data.Adapt<LocaleResourceModel>();
|
||||
// drawerVisible = true;
|
||||
modalVisible = true;
|
||||
}
|
||||
|
||||
async Task HandleDeleteConfirmAsync(MouseEventArgs e, long id)
|
||||
{
|
||||
var url = $"/api/language/delete/{id}";
|
||||
var url = $"/api/localeresource/delete/{id}";
|
||||
var apiResult = await HttpService.Post<ApiResult<string>>(url, new());
|
||||
if (apiResult.Success)
|
||||
{
|
||||
@@ -196,35 +216,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
async Task OnFormFinish()
|
||||
{
|
||||
if (editform.Validate())
|
||||
{
|
||||
|
||||
var url = $"api/language/save";
|
||||
var result = await HttpService.Post<ApiResult<bool>>(url, model);
|
||||
if (result.Success)
|
||||
{
|
||||
if (result.Data)
|
||||
{
|
||||
|
||||
// CloseDrawer();
|
||||
_ = LoadList();
|
||||
await ModalService.InfoAsync(new ConfirmOptions() { Title = "提示", Content = "数据提交成功!" });
|
||||
}
|
||||
else
|
||||
{
|
||||
await ModalService.ErrorAsync(new ConfirmOptions() { Title = "服务异常", Content = result.Message });
|
||||
}
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
await ModalService.ErrorAsync(new ConfirmOptions() { Title = "服务异常", Content = result.Message });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnSearch(int page)
|
||||
{
|
||||
var queryString = search.BuildQueryString();
|
||||
@@ -257,9 +248,34 @@
|
||||
OnSearch(args.Page);
|
||||
}
|
||||
|
||||
void CloseDrawer()
|
||||
void HandleModalOk()
|
||||
{
|
||||
// drawerVisible = false;
|
||||
editform.Reset();
|
||||
editform.Submit();
|
||||
}
|
||||
|
||||
async Task OnFormFinish()
|
||||
{
|
||||
if (editform.Validate())
|
||||
{
|
||||
var result = new ApiResult<bool>();
|
||||
|
||||
var url = $"api/localeresource/save";
|
||||
result = await HttpService.Post<ApiResult<bool>>(url, model);
|
||||
|
||||
if (result.Success)
|
||||
{
|
||||
modalVisible = false;
|
||||
_ = LoadList();
|
||||
}
|
||||
else
|
||||
{
|
||||
await ModalService.ErrorAsync(new ConfirmOptions() { Title = "服务异常", Content = result.Message });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void HandleCancel()
|
||||
{
|
||||
modalVisible = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@ using Blazored.LocalStorage;
|
||||
using Microsoft.AspNetCore.Components.Authorization;
|
||||
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Localization;
|
||||
using System.Net.Http;
|
||||
|
||||
var builder = WebAssemblyHostBuilder.CreateDefault(args);
|
||||
|
||||
@@ -18,7 +20,22 @@ builder.Services.AddSingleton<AuthenticationStateProvider, PersistentAuthenticat
|
||||
// Ȩ<><C8A8> & <20><><EFBFBD>ػ<EFBFBD><D8BB><EFBFBD><EFBFBD>ͻ<EFBFBD><CDBB><EFBFBD>ʵ<EFBFBD>֣<EFBFBD>
|
||||
builder.Services.AddScoped<IPermissionService, ClientPermissionService>();
|
||||
builder.Services.AddScoped<IconsExtension>();
|
||||
builder.Services.AddScoped<ILocalizationService, LocalizationClientService>();
|
||||
|
||||
// Ϊ<><CEAA>̬<EFBFBD><CCAC>Դ<EFBFBD><D4B4>wwwroot<6F><74>ע<EFBFBD><D7A2>һ<EFBFBD><D2BB>ר<EFBFBD><D7A8> HttpClient<6E><74>BaseAddress ָ<><D6B8>Ӧ<EFBFBD>ø<EFBFBD><C3B8><EFBFBD>
|
||||
// <20><> HttpClient <20><><EFBFBD>ڼ<EFBFBD><DABC><EFBFBD> wwwroot/Localization/{culture}.json <20>Ⱦ<EFBFBD>̬<EFBFBD><CCAC><EFBFBD><EFBFBD><EFBFBD>ļ<EFBFBD>
|
||||
builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
|
||||
|
||||
// ע<>᱾<EFBFBD>ػ<EFBFBD><D8BB><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʹ<EFBFBD><CAB9><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>վ<EFBFBD><D5BE><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ŀ¼<C4BF><C2BC> "Localization" <20><>ע<EFBFBD><D7A2><EFBFBD><EFBFBD><EFBFBD>ڼ<EFBFBD><DABC>ؾ<EFBFBD>̬<EFBFBD>ļ<EFBFBD><C4BC><EFBFBD> HttpClient<6E><74>
|
||||
builder.Services.AddScoped<IStringLocalizerFactory>(sp =>
|
||||
new JsonStringLocalizerFactory("Localization", sp.GetRequiredService<HttpClient>()));
|
||||
|
||||
// ע<><D7A2>IStringLocalizer
|
||||
builder.Services.AddTransient(typeof(IStringLocalizer<>), typeof(StringLocalizer<>));
|
||||
// <20><><EFBFBD>ӱ<EFBFBD><D3B1>ػ<EFBFBD><D8BB><EFBFBD><EFBFBD><EFBFBD>
|
||||
builder.Services.AddLocalization();
|
||||
|
||||
// ע<><D7A2><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ṩ<EFBFBD><E1B9A9>
|
||||
builder.Services.AddScoped<LanguageProvider>();
|
||||
|
||||
// ע<><D7A2><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Զ<EFBFBD><D4B6><EFBFBD><EFBFBD><EFBFBD> token & ˢ<>µ<EFBFBD> DelegatingHandler
|
||||
builder.Services.AddScoped<AuthHeaderHandler>();
|
||||
@@ -38,7 +55,7 @@ builder.Services.AddHttpClient("RefreshClient", client =>
|
||||
client.BaseAddress = new Uri(apiBase);
|
||||
});
|
||||
|
||||
// Ĭ<><C4AC> HttpClient<6E><74><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ע<EFBFBD><D7A2> HttpClient<6E><74>
|
||||
// Ĭ<><C4AC> HttpClient<6E><74><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ע<EFBFBD><D7A2> HttpClient<6E><74><EFBFBD>˴<EFBFBD><EFBFBD><EFBFBD>ʹ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> ApiClient<6E><74>
|
||||
builder.Services.AddScoped(sp => sp.GetRequiredService<IHttpClientFactory>().CreateClient("ApiClient"));
|
||||
|
||||
// <20><> WASM <20><> Program.cs<63><73><EFBFBD>ͻ<EFBFBD><CDBB>ˣ<EFBFBD><CBA3><EFBFBD>ע<EFBFBD><D7A2> HttpService ʱ<><CAB1><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ܴ<EFBFBD><DCB4>ڵ<EFBFBD> IHttpContextAccessor<6F><72>Server <20>ṩ<EFBFBD><E1B9A9>WASM Ϊ null<6C><6C>
|
||||
|
||||
141
Atomx.Admin/Atomx.Admin.Client/Services/JsonStringLocalizer.cs
Normal file
141
Atomx.Admin/Atomx.Admin.Client/Services/JsonStringLocalizer.cs
Normal file
@@ -0,0 +1,141 @@
|
||||
using Microsoft.Extensions.Localization;
|
||||
using System.Globalization;
|
||||
using System.Net.Http;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace Atomx.Admin.Client.Services
|
||||
{
|
||||
public class JsonStringLocalizer : IStringLocalizer
|
||||
{
|
||||
private readonly string _resourcesPath;
|
||||
private readonly Dictionary<string, Dictionary<string, string>> _resourcesCache = new();
|
||||
private readonly object _lock = new();
|
||||
private readonly HttpClient? _httpClient;
|
||||
|
||||
// resourcesPath 应为相对于站点根的“目录”名称,例如 "Localization"
|
||||
// 在 Blazor WebAssembly 场景下,会使用注入的 HttpClient 从 wwwroot/Localization/{culture}.json 获取资源
|
||||
public JsonStringLocalizer(string resourcesPath, HttpClient? httpClient = null)
|
||||
{
|
||||
_resourcesPath = (resourcesPath ?? "Localization").Trim('/'); // 规范化
|
||||
_httpClient = httpClient;
|
||||
}
|
||||
|
||||
public LocalizedString this[string name]
|
||||
{
|
||||
get
|
||||
{
|
||||
var value = GetString(name);
|
||||
return new LocalizedString(name, value ?? name, resourceNotFound: value == null);
|
||||
}
|
||||
}
|
||||
|
||||
public LocalizedString this[string name, params object[] arguments]
|
||||
{
|
||||
get
|
||||
{
|
||||
var format = GetString(name);
|
||||
var value = string.Format(format ?? name, arguments);
|
||||
return new LocalizedString(name, value, resourceNotFound: format == null);
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<LocalizedString> GetAllStrings(bool includeParentCultures)
|
||||
{
|
||||
var culture = CultureInfo.CurrentUICulture.Name;
|
||||
var resources = LoadResources(culture);
|
||||
return resources.Select(r => new LocalizedString(r.Key, r.Value, false));
|
||||
}
|
||||
|
||||
private string? GetString(string name)
|
||||
{
|
||||
var culture = CultureInfo.CurrentUICulture.Name;
|
||||
var resources = LoadResources(culture);
|
||||
|
||||
// 尝试当前文化
|
||||
if (resources.TryGetValue(name, out var value))
|
||||
return value;
|
||||
|
||||
// 如果还找不到,尝试英文作为后备
|
||||
if (culture != "en-US")
|
||||
{
|
||||
var enResources = LoadResources("en-US");
|
||||
if (enResources.TryGetValue(name, out var enValue))
|
||||
return enValue;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private Dictionary<string, string> LoadResources(string culture)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
if (_resourcesCache.TryGetValue(culture, out var cachedResources))
|
||||
return cachedResources;
|
||||
|
||||
var resources = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||
var fileName = $"{culture}.json";
|
||||
|
||||
// 在浏览器(WASM)环境下,通过 HttpClient 从静态资源目录获取
|
||||
if (OperatingSystem.IsBrowser() && _httpClient != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 构造相对 URL,例如 "Localization/zh-Hans.json"
|
||||
var relativeUrl = $"{_resourcesPath}/{fileName}";
|
||||
var json = _httpClient.GetStringAsync(relativeUrl).ConfigureAwait(false).GetAwaiter().GetResult();
|
||||
var jsonResources = JsonSerializer.Deserialize<Dictionary<string, string>>(json);
|
||||
if (jsonResources != null)
|
||||
{
|
||||
foreach (var item in jsonResources)
|
||||
{
|
||||
resources[item.Key] = item.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Error loading localization file {fileName} via HttpClient: {ex.Message}");
|
||||
}
|
||||
|
||||
_resourcesCache[culture] = resources;
|
||||
return resources;
|
||||
}
|
||||
|
||||
// 非浏览器(例如 Server 或在 prerender 阶段)尝试从文件系统读取。
|
||||
// 尝试几种可能的路径:基路径为 AppContext.BaseDirectory 或当前工作目录,或直接使用传入的路径。
|
||||
try
|
||||
{
|
||||
var candidates = new[]
|
||||
{
|
||||
Path.Combine(AppContext.BaseDirectory, _resourcesPath, fileName),
|
||||
Path.Combine(Directory.GetCurrentDirectory(), _resourcesPath, fileName),
|
||||
Path.Combine(_resourcesPath, fileName),
|
||||
};
|
||||
|
||||
var filePath = candidates.FirstOrDefault(File.Exists);
|
||||
|
||||
if (!string.IsNullOrEmpty(filePath))
|
||||
{
|
||||
var json = File.ReadAllText(filePath);
|
||||
var jsonResources = JsonSerializer.Deserialize<Dictionary<string, string>>(json);
|
||||
if (jsonResources != null)
|
||||
{
|
||||
foreach (var item in jsonResources)
|
||||
{
|
||||
resources[item.Key] = item.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Error loading localization file {fileName} from disk: {ex.Message}");
|
||||
}
|
||||
|
||||
_resourcesCache[culture] = resources;
|
||||
return resources;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
using Microsoft.Extensions.Localization;
|
||||
using System.Net.Http;
|
||||
|
||||
namespace Atomx.Admin.Client.Services
|
||||
{
|
||||
public class JsonStringLocalizerFactory : IStringLocalizerFactory
|
||||
{
|
||||
private readonly string _resourcesPath;
|
||||
private readonly HttpClient _httpClient;
|
||||
|
||||
public JsonStringLocalizerFactory(string resourcesPath, HttpClient httpClient)
|
||||
{
|
||||
_resourcesPath = (resourcesPath ?? "Localization").Trim('/');
|
||||
_httpClient = httpClient;
|
||||
}
|
||||
|
||||
public IStringLocalizer Create(Type resourceSource)
|
||||
{
|
||||
return new JsonStringLocalizer(_resourcesPath, _httpClient);
|
||||
}
|
||||
|
||||
public IStringLocalizer Create(string baseName, string location)
|
||||
{
|
||||
return new JsonStringLocalizer(_resourcesPath, _httpClient);
|
||||
}
|
||||
}
|
||||
}
|
||||
129
Atomx.Admin/Atomx.Admin.Client/Services/LanguageProvider.cs
Normal file
129
Atomx.Admin/Atomx.Admin.Client/Services/LanguageProvider.cs
Normal file
@@ -0,0 +1,129 @@
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.JSInterop;
|
||||
using System.Globalization;
|
||||
|
||||
namespace Atomx.Admin.Client.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// 语言提供者服务
|
||||
/// </summary>
|
||||
public class LanguageProvider
|
||||
{
|
||||
private readonly IJSRuntime _jsRuntime;
|
||||
private readonly NavigationManager _navigationManager;
|
||||
private string _currentLanguage = "zh-Hans";
|
||||
|
||||
public event Action? OnLanguageChanged;
|
||||
|
||||
public LanguageProvider(IJSRuntime jsRuntime, NavigationManager navigationManager)
|
||||
{
|
||||
_jsRuntime = jsRuntime;
|
||||
_navigationManager = navigationManager;
|
||||
}
|
||||
|
||||
public string CurrentLanguage
|
||||
{
|
||||
get => _currentLanguage;
|
||||
private set
|
||||
{
|
||||
if (_currentLanguage != value)
|
||||
{
|
||||
_currentLanguage = value;
|
||||
|
||||
// 设置全局线程文化,确保 IStringLocalizer 等在随后的渲染中读取到新文化
|
||||
try
|
||||
{
|
||||
var ci = new CultureInfo(value);
|
||||
CultureInfo.DefaultThreadCurrentCulture = ci;
|
||||
CultureInfo.DefaultThreadCurrentUICulture = ci;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// 忽略无效 culture 字符串
|
||||
}
|
||||
|
||||
OnLanguageChanged?.Invoke();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public List<string> SupportedLanguages { get; } = new()
|
||||
{
|
||||
"zh-Hans", // 简体中文
|
||||
"en-US" // 英文(美国)
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// 初始化语言
|
||||
/// </summary>
|
||||
public async Task InitializeAsync()
|
||||
{
|
||||
// 尝试从本地存储获取保存的语言
|
||||
try
|
||||
{
|
||||
var savedLanguage = await _jsRuntime.InvokeAsync<string>("localStorage.getItem", "preferred-language");
|
||||
if (!string.IsNullOrEmpty(savedLanguage) && SupportedLanguages.Contains(savedLanguage))
|
||||
{
|
||||
CurrentLanguage = savedLanguage;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 从浏览器获取语言
|
||||
var browserLanguage = await _jsRuntime.InvokeAsync<string>("getBrowserLanguage");
|
||||
CurrentLanguage = GetSupportedLanguage(browserLanguage);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// JS互操作可能不可用(在预渲染时)
|
||||
CurrentLanguage = "zh-Hans";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 切换语言
|
||||
/// </summary>
|
||||
public async Task ChangeLanguageAsync(string languageCode)
|
||||
{
|
||||
if (SupportedLanguages.Contains(languageCode) && CurrentLanguage != languageCode)
|
||||
{
|
||||
CurrentLanguage = languageCode;
|
||||
|
||||
// 保存到本地存储
|
||||
try
|
||||
{
|
||||
await _jsRuntime.InvokeVoidAsync("localStorage.setItem", "preferred-language", languageCode);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// 忽略错误
|
||||
}
|
||||
|
||||
// setter 已触发 OnLanguageChanged
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取支持的语言
|
||||
/// </summary>
|
||||
private string GetSupportedLanguage(string browserLanguage)
|
||||
{
|
||||
if (string.IsNullOrEmpty(browserLanguage))
|
||||
return "zh-Hans";
|
||||
|
||||
// 检查完全匹配
|
||||
if (SupportedLanguages.Contains(browserLanguage))
|
||||
return browserLanguage;
|
||||
|
||||
// 检查中性语言匹配
|
||||
var neutralLanguage = browserLanguage.Split('-')[0];
|
||||
foreach (var supported in SupportedLanguages)
|
||||
{
|
||||
if (supported.StartsWith(neutralLanguage))
|
||||
return supported;
|
||||
}
|
||||
|
||||
return "zh-Hans"; // 默认语言
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,322 +0,0 @@
|
||||
using Atomx.Common.Models;
|
||||
using Microsoft.JSInterop;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace Atomx.Admin.Client.Services
|
||||
{
|
||||
public interface ILocalizationService
|
||||
{
|
||||
/// <summary>
|
||||
/// 根据name获取制定文化语言的译文
|
||||
/// </summary>
|
||||
/// <param name="name"></param>
|
||||
/// <param name="culture"></param>
|
||||
/// <returns></returns>
|
||||
Task<string?> GetStringAsync(string name, string? culture = null);
|
||||
|
||||
/// <summary>
|
||||
/// 把本地化文化语言加载到内存中
|
||||
/// </summary>
|
||||
/// <param name="culture"></param>
|
||||
/// <returns></returns>
|
||||
Task<bool> LoadResourcesAsync(string culture);
|
||||
|
||||
|
||||
event EventHandler<string>? ResourcesUpdated;
|
||||
}
|
||||
|
||||
public class LocalizationClientService : ILocalizationService, IAsyncDisposable
|
||||
{
|
||||
private readonly HttpService _httpService;
|
||||
private readonly IJSRuntime _jsRuntime;
|
||||
private readonly ILogger<LocalizationClientService> _logger;
|
||||
|
||||
private readonly Dictionary<string, Dictionary<string, string>> _resources = new();
|
||||
private readonly Dictionary<string, string> _versions = new();
|
||||
private readonly SemaphoreSlim _semaphore = new(1, 1);
|
||||
|
||||
public event EventHandler<string>? ResourcesUpdated;
|
||||
|
||||
public LocalizationClientService(
|
||||
HttpService httpService,
|
||||
IJSRuntime jsRuntime,
|
||||
ILogger<LocalizationClientService> logger)
|
||||
{
|
||||
_httpService = httpService;
|
||||
_jsRuntime = jsRuntime;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据name获取制定文化语言的译文
|
||||
/// </summary>
|
||||
/// <param name="name"></param>
|
||||
/// <param name="culture"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<string?> GetStringAsync(string name, string? culture = null)
|
||||
{
|
||||
culture ??= await GetCurrentCultureAsync();
|
||||
|
||||
if (_resources.TryGetValue(culture, out var cultureResources))
|
||||
{
|
||||
if (cultureResources.TryGetValue(name, out var value))
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
// 键不存在,触发资源更新检查
|
||||
_ = Task.Run(async () => await CheckAndUpdateResourcesAsync(culture));
|
||||
}
|
||||
else
|
||||
{
|
||||
// 资源未加载,立即加载
|
||||
await LoadResourcesAsync(culture);
|
||||
|
||||
// 重试获取
|
||||
if (_resources.TryGetValue(culture, out cultureResources) &&
|
||||
cultureResources.TryGetValue(name, out var value))
|
||||
{
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
_logger.LogWarning("Localization key not found: {Key} for culture: {Culture}", name, culture);
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 把本地化文化语言加载到内存中
|
||||
/// </summary>
|
||||
/// <param name="culture"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<bool> LoadResourcesAsync(string culture)
|
||||
{
|
||||
await _semaphore.WaitAsync();
|
||||
try
|
||||
{
|
||||
// 先尝试从localStorage加载
|
||||
if (await TryLoadFromLocalStorage(culture))
|
||||
{
|
||||
// 检查服务器版本,如果需要更新则从服务器加载
|
||||
if (await CheckAndUpdateFromServer(culture))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return true; // 本地版本仍然有效
|
||||
}
|
||||
|
||||
// 从服务器加载
|
||||
return await LoadFromServer(culture);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_semaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从单例对象中获取版本记录
|
||||
/// </summary>
|
||||
/// <param name="culture"></param>
|
||||
/// <returns></returns>
|
||||
private async Task<string?> GetResourceVersionAsync(string culture)
|
||||
{
|
||||
if (_versions.TryGetValue(culture, out var version))
|
||||
{
|
||||
return version;
|
||||
}
|
||||
|
||||
var storedVersion = await GetStoredVersion(culture);
|
||||
return storedVersion;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查更新资源
|
||||
/// </summary>
|
||||
/// <param name="culture"></param>
|
||||
/// <returns></returns>
|
||||
private async Task<bool> CheckAndUpdateResourcesAsync(string culture)
|
||||
{
|
||||
return await CheckAndUpdateFromServer(culture);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从本地加载文化语言数据
|
||||
/// </summary>
|
||||
/// <param name="culture"></param>
|
||||
/// <returns></returns>
|
||||
private async Task<bool> TryLoadFromLocalStorage(string culture)
|
||||
{
|
||||
try
|
||||
{
|
||||
var resourcesJson = await _jsRuntime.InvokeAsync<string?>("localStorage.getItem", $"locales_{culture}");
|
||||
var version = await _jsRuntime.InvokeAsync<string?>("localStorage.getItem", $"locales_version_{culture}");
|
||||
|
||||
if (string.IsNullOrEmpty(resourcesJson) || string.IsNullOrEmpty(version))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var resources = JsonSerializer.Deserialize<Dictionary<string, string>>(resourcesJson);
|
||||
|
||||
if (resources != null && version != null)
|
||||
{
|
||||
_resources[culture] = resources;
|
||||
_versions[culture] = version;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Failed to load resources from localStorage for culture: {Culture}", culture);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从服务器更新获取文化语言数据
|
||||
/// </summary>
|
||||
/// <param name="culture"></param>
|
||||
/// <returns></returns>
|
||||
private async Task<bool> LoadFromServer(string culture)
|
||||
{
|
||||
try
|
||||
{
|
||||
var response = await _httpService.Get<ApiResult<LocalizationFile>>($"/api/localeresource/resources/{culture}");
|
||||
if (response.Success)
|
||||
{
|
||||
_resources[culture] = response.Data.Translations;
|
||||
_versions[culture] = response.Data.ResourceVersion;
|
||||
// 保存到localStorage
|
||||
await SaveToLocalStorage(culture, response.Data.Translations, response.Data.ResourceVersion);
|
||||
ResourcesUpdated?.Invoke(this, culture);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Failed to load resources from server for culture: {Culture}", culture);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 根据版本信息上从服务器数据对比版本更新数据
|
||||
/// </summary>
|
||||
/// <param name="culture"></param>
|
||||
/// <returns></returns>
|
||||
private async Task<bool> CheckAndUpdateFromServer(string culture)
|
||||
{
|
||||
try
|
||||
{
|
||||
var currentVersion = await GetResourceVersionAsync(culture);
|
||||
var serverVersion = await GetServerVersion(culture);
|
||||
|
||||
if (serverVersion != null && (currentVersion == null || serverVersion != currentVersion))
|
||||
{
|
||||
_logger.LogInformation("Updating resources for culture: {Culture}", culture);
|
||||
return await LoadFromServer(culture);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Failed to check and update resources for culture: {Culture}", culture);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从服务器获取当前多语言文化数据版本信息
|
||||
/// </summary>
|
||||
/// <param name="culture"></param>
|
||||
/// <returns></returns>
|
||||
private async Task<string?> GetServerVersion(string culture)
|
||||
{
|
||||
try
|
||||
{
|
||||
var api = $"/api/localeresource/version/{culture}";
|
||||
var result = await _httpService.Get<ApiResult<string>>(api);
|
||||
if (result.Success)
|
||||
{
|
||||
return result.Data;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Failed to get server version for culture: {Culture}", culture);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取存储在本地的文化语言版本信息
|
||||
/// </summary>
|
||||
/// <param name="culture"></param>
|
||||
/// <returns></returns>
|
||||
private async Task<string?> GetStoredVersion(string culture)
|
||||
{
|
||||
try
|
||||
{
|
||||
var version = await _jsRuntime.InvokeAsync<string?>("localStorage.getItem", $"locales_version_{culture}");
|
||||
if (!string.IsNullOrEmpty(version))
|
||||
{
|
||||
return version;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Failed to get stored version for culture: {Culture}", culture);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 存储版本和文化语言信息
|
||||
/// </summary>
|
||||
/// <param name="culture"></param>
|
||||
/// <param name="resources"></param>
|
||||
/// <param name="version"></param>
|
||||
/// <returns></returns>
|
||||
private async Task SaveToLocalStorage(string culture, Dictionary<string, string> resources, string version)
|
||||
{
|
||||
try
|
||||
{
|
||||
var resourcesJson = JsonSerializer.Serialize(resources);
|
||||
|
||||
await _jsRuntime.InvokeVoidAsync("localStorage.setItem", $"locales_{culture}", resourcesJson);
|
||||
await _jsRuntime.InvokeVoidAsync("localStorage.setItem", $"locales_version_{culture}", version);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Failed to save resources to localStorage for culture: {Culture}", culture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取浏览器上的文化语言信息
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private async Task<string> GetCurrentCultureAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
return await _jsRuntime.InvokeAsync<string>("blazorCulture.get");
|
||||
}
|
||||
catch
|
||||
{
|
||||
return "en-US"; // 默认文化
|
||||
}
|
||||
}
|
||||
|
||||
public async ValueTask DisposeAsync()
|
||||
{
|
||||
_semaphore?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
using Atomx.Admin.Client.Services;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace Atomx.Admin.Client.Utils
|
||||
{
|
||||
/// <summary>
|
||||
/// 继承此基类的组件会自动订阅 LanguageProvider 的语言变更事件并在变更时重新渲染。
|
||||
/// </summary>
|
||||
public abstract class LocalizedComponentBase : ComponentBase, IDisposable
|
||||
{
|
||||
[Inject]
|
||||
protected LanguageProvider LanguageProvider { get; set; } = null!;
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
base.OnInitialized();
|
||||
LanguageProvider.OnLanguageChanged += LanguageChangedHandler;
|
||||
}
|
||||
|
||||
private void LanguageChangedHandler()
|
||||
{
|
||||
// 在组件上下文中安全调用 StateHasChanged
|
||||
_ = InvokeAsync(StateHasChanged);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
LanguageProvider.OnLanguageChanged -= LanguageChangedHandler;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@
|
||||
@using static Microsoft.AspNetCore.Components.Web.RenderMode
|
||||
@using Microsoft.AspNetCore.Components.Web.Virtualization
|
||||
@using Microsoft.JSInterop
|
||||
@using Microsoft.Extensions.Localization
|
||||
@using Atomx.Admin.Client.Components
|
||||
@using Atomx.Admin.Client
|
||||
@using Atomx.Admin.Client.Services
|
||||
@@ -38,3 +39,4 @@
|
||||
@inject HttpService HttpService
|
||||
@inject MessageService MessageService
|
||||
@inject ModalService ModalService
|
||||
@inject LanguageProvider LanguageProvider
|
||||
9
Atomx.Admin/Atomx.Admin.Client/wwwroot/js/language.js
Normal file
9
Atomx.Admin/Atomx.Admin.Client/wwwroot/js/language.js
Normal file
@@ -0,0 +1,9 @@
|
||||
// 获取浏览器语言
|
||||
function getBrowserLanguage() {
|
||||
return navigator.language || navigator.userLanguage || 'zh-Hans';
|
||||
}
|
||||
|
||||
// 设置HTML lang属性
|
||||
function setHtmlLang(lang) {
|
||||
document.documentElement.lang = lang;
|
||||
}
|
||||
@@ -6,6 +6,13 @@
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Remove="Resources\**" />
|
||||
<Content Remove="Resources\**" />
|
||||
<EmbeddedResource Remove="Resources\**" />
|
||||
<None Remove="Resources\**" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Atomx.Data\Atomx.Data.csproj" />
|
||||
<ProjectReference Include="..\..\Atomx.Utils\Atomx.Utils.csproj" />
|
||||
@@ -28,7 +35,12 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Resources\" />
|
||||
<Content Update="Localization\en-US.json">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Update="Localization\zh-Hans.json">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
<!DOCTYPE html>
|
||||
@using Atomx.Admin.Client.Services
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
@@ -11,11 +13,12 @@
|
||||
<link rel="stylesheet" href="@Assets["Atomx.Admin.styles.css"]" />
|
||||
<ImportMap />
|
||||
<link rel="icon" type="image/png" href="favicon.png" />
|
||||
<HeadOutlet @rendermode="InteractiveAuto" />
|
||||
<HeadOutlet @rendermode="InteractiveServer" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<Routes @rendermode="InteractiveAuto" />
|
||||
<Routes @rendermode="InteractiveServer" />
|
||||
|
||||
<script src="_framework/blazor.web.js"></script>
|
||||
</body>
|
||||
|
||||
|
||||
@@ -132,7 +132,7 @@ namespace Atomx.Admin.Controllers
|
||||
else
|
||||
{
|
||||
var data = _mapper.Map<Address>(model);
|
||||
data.Id = _idCreator.NewId();
|
||||
data.Id = _idCreator.CreateId();
|
||||
data.CreateTime = DateTime.UtcNow;
|
||||
_dbContext.Addresses.Add(data);
|
||||
_dbContext.SaveChanges();
|
||||
|
||||
@@ -184,7 +184,7 @@ namespace Atomx.Admin.Controllers
|
||||
|
||||
try
|
||||
{
|
||||
model.Id = _idCreator.NewId();
|
||||
model.Id = _idCreator.CreateId();
|
||||
model.Password = model.Password.ToMd5Password();
|
||||
|
||||
var admin = _mapper.Map<Common.Entities.Admin>(model);
|
||||
|
||||
@@ -149,7 +149,7 @@ namespace Atomx.Admin.Controllers
|
||||
|
||||
try
|
||||
{
|
||||
model.Id = _idCreator.NewId();
|
||||
model.Id = _idCreator.CreateId();
|
||||
var data = _mapper.Map<AppVersion>(model);
|
||||
data.CreateTime = DateTime.UtcNow;
|
||||
|
||||
|
||||
@@ -158,7 +158,7 @@ namespace Atomx.Admin.Controllers
|
||||
}
|
||||
try
|
||||
{
|
||||
model.Id = _idCreator.NewId();
|
||||
model.Id = _idCreator.CreateId();
|
||||
|
||||
var data = _mapper.Map<Category>(model);
|
||||
data.CreateTime = DateTime.UtcNow;
|
||||
|
||||
@@ -6,7 +6,6 @@ using Atomx.Common.Models;
|
||||
using Atomx.Data;
|
||||
using Atomx.Data.CacheServices;
|
||||
using Atomx.Data.Services;
|
||||
using Atomx.Utils.Models;
|
||||
using MapsterMapper;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
@@ -58,11 +57,11 @@ namespace Atomx.Admin.Controllers
|
||||
[HttpGet("enabled")]
|
||||
public async Task<IActionResult> CacheLanguage()
|
||||
{
|
||||
var result = new Result<List<Language>>();
|
||||
var result = new ApiResult<List<Language>>();
|
||||
|
||||
var list = await _cacheService.GetLanguages(true);
|
||||
|
||||
result.Data = list;
|
||||
result = result.IsSuccess(list);
|
||||
|
||||
return new JsonResult(result);
|
||||
}
|
||||
@@ -101,8 +100,8 @@ namespace Atomx.Admin.Controllers
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <returns></returns>
|
||||
[HttpGet("detail")]
|
||||
public IActionResult GetManufacturer(long id)
|
||||
[HttpGet("{id:int}")]
|
||||
public IActionResult GetManufacturer(int id)
|
||||
{
|
||||
var result = new ApiResult<Language>();
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Atomx.Admin.Client.Models;
|
||||
using AntDesign;
|
||||
using Atomx.Admin.Client.Models;
|
||||
using Atomx.Admin.Client.Validators;
|
||||
using Atomx.Admin.Services;
|
||||
using Atomx.Common.Entities;
|
||||
@@ -7,8 +8,10 @@ using Atomx.Data;
|
||||
using Atomx.Data.CacheServices;
|
||||
using Atomx.Data.Services;
|
||||
using Atomx.Utils.Models;
|
||||
using IdGen;
|
||||
using MapsterMapper;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
|
||||
namespace Atomx.Admin.Controllers
|
||||
{
|
||||
@@ -87,12 +90,48 @@ namespace Atomx.Admin.Controllers
|
||||
return new JsonResult(result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 通过名称获取多语言资源列表
|
||||
/// </summary>
|
||||
/// <param name="name"></param>
|
||||
/// <returns></returns>
|
||||
[HttpGet("{name}")]
|
||||
public IActionResult GetDetail(string name)
|
||||
{
|
||||
var result = new ApiResult<List<LocaleResourceItem>>();
|
||||
var list = new List<LocaleResourceItem>();
|
||||
var query = from p in _dbContext.LocaleResources
|
||||
where p.Name == name
|
||||
select p;
|
||||
|
||||
var data = query.ToList();
|
||||
|
||||
var language = _dbContext.Languages.ToList();
|
||||
|
||||
foreach (var item in data)
|
||||
{
|
||||
var resourceItem = _mapper.Map<LocaleResourceItem>(item);
|
||||
|
||||
var lang = language.SingleOrDefault(p => p.Id == item.LanguageId);
|
||||
if (lang != null)
|
||||
{
|
||||
resourceItem.Culture = lang.Culture;
|
||||
resourceItem.Title = lang.Title;
|
||||
}
|
||||
list.Add(resourceItem);
|
||||
}
|
||||
|
||||
result = result.IsSuccess(list);
|
||||
|
||||
return new JsonResult(result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 通过ID获取多语言资源
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <returns></returns>
|
||||
[HttpGet]
|
||||
[HttpGet("{id:long}")]
|
||||
public async Task<IActionResult> Get(long id)
|
||||
{
|
||||
var result = new Result<LocaleResourceModel>();
|
||||
@@ -129,13 +168,20 @@ namespace Atomx.Admin.Controllers
|
||||
result = result.IsFail(ModelState.Values.First().Errors[0].ErrorMessage);
|
||||
return new JsonResult(result);
|
||||
}
|
||||
|
||||
var data = _dbContext.LocaleResources.SingleOrDefault(p => p.Name == model.Name && p.LanguageId == model.LanguageId && p.Id != model.Id);
|
||||
if (data != null)
|
||||
{
|
||||
result = result.IsFail("该多语言资源名称已存在,请更换!");
|
||||
return new JsonResult(result);
|
||||
}
|
||||
|
||||
if (model.Id > 0)
|
||||
{
|
||||
var data = _dbContext.LocaleResources.SingleOrDefault(p => p.Id == model.Id);
|
||||
data = _dbContext.LocaleResources.SingleOrDefault(p => p.Id == model.Id);
|
||||
if (data == null)
|
||||
{
|
||||
result.Message = "数据不存在,请更换!";
|
||||
result.Success = false;
|
||||
result = result.IsFail("数据不存在,请更换!");
|
||||
return new JsonResult(result);
|
||||
}
|
||||
|
||||
@@ -146,7 +192,8 @@ namespace Atomx.Admin.Controllers
|
||||
}
|
||||
else
|
||||
{
|
||||
var data = _mapper.Map<LocaleResource>(model);
|
||||
data = _mapper.Map<LocaleResource>(model);
|
||||
data.Id = _idCreator.CreateId();
|
||||
data.UpdateTime = DateTime.UtcNow;
|
||||
_dbContext.LocaleResources.Add(data);
|
||||
_dbContext.SaveChanges();
|
||||
@@ -155,7 +202,7 @@ namespace Atomx.Admin.Controllers
|
||||
|
||||
//异步更新对应的json文件
|
||||
|
||||
result.Data = true;
|
||||
result = result.IsSuccess(true);
|
||||
|
||||
return new JsonResult(result);
|
||||
}
|
||||
|
||||
@@ -159,7 +159,7 @@ namespace Atomx.Admin.Controllers
|
||||
model.Depth = data.Depth + 1;
|
||||
model.Path = $"{data.Path},{model.Id}";
|
||||
}
|
||||
model.Id = _idCreator.NewId();
|
||||
model.Id = _idCreator.CreateId();
|
||||
|
||||
var menu = _mapper.Map<Menu>(model);
|
||||
menu.CreateTime = DateTime.UtcNow;
|
||||
|
||||
@@ -125,7 +125,7 @@ namespace Atomx.Admin.Controllers
|
||||
}
|
||||
try
|
||||
{
|
||||
model.Id = _idCreator.NewId();
|
||||
model.Id = _idCreator.CreateId();
|
||||
var message = _mapper.Map<MessageTemplate>(model);
|
||||
message.CreateTime = DateTime.UtcNow;
|
||||
|
||||
|
||||
@@ -143,7 +143,7 @@ namespace Atomx.Admin.Controllers
|
||||
|
||||
try
|
||||
{
|
||||
model.Id = _idCreator.NewId();
|
||||
model.Id = _idCreator.CreateId();
|
||||
var data = _mapper.Map<ProductAttribute>(model);
|
||||
data.CreateTime = DateTime.UtcNow;
|
||||
|
||||
|
||||
@@ -124,7 +124,7 @@ namespace Atomx.Admin.Controllers
|
||||
|
||||
try
|
||||
{
|
||||
model.Id = _idCreator.NewId();
|
||||
model.Id = _idCreator.CreateId();
|
||||
var data = _mapper.Map<ProductAttributeOption>(model);
|
||||
data.CreateTime = DateTime.UtcNow;
|
||||
_dbContext.ProductAttributeOptions.Add(data);
|
||||
|
||||
@@ -349,7 +349,7 @@ namespace Atomx.Admin.Controllers
|
||||
|
||||
try
|
||||
{
|
||||
model.Id = _idCreator.NewId();
|
||||
model.Id = _idCreator.CreateId();
|
||||
var product = _mapper.Map<Product>(model);
|
||||
product.CreateTime = DateTime.UtcNow;
|
||||
|
||||
@@ -367,7 +367,7 @@ namespace Atomx.Admin.Controllers
|
||||
DisplayOrder = 0,
|
||||
ProductId = product.Id,
|
||||
Image = item.Image,
|
||||
Id = _idCreator.NewId()
|
||||
Id = _idCreator.CreateId()
|
||||
};
|
||||
|
||||
foreach (var value in item.ProductAttributeValues)
|
||||
@@ -380,7 +380,7 @@ namespace Atomx.Admin.Controllers
|
||||
{
|
||||
ProductAttributeRelationId = attributeRelated.Id,
|
||||
DisplayOrder = value.DisplayOrder,
|
||||
Id = _idCreator.NewId(),
|
||||
Id = _idCreator.CreateId(),
|
||||
Name = value.Name,
|
||||
OptionId = value.OptionId,
|
||||
ProductId = product.Id,
|
||||
@@ -413,7 +413,7 @@ namespace Atomx.Admin.Controllers
|
||||
{
|
||||
var data = _mapper.Map<ProductAttributeCombination>(item);
|
||||
data.AttributesJson = item.SkuAttributes.ToJson();
|
||||
data.Id = _idCreator.NewId();
|
||||
data.Id = _idCreator.CreateId();
|
||||
data.ProductId = product.Id;
|
||||
data.UpdateTime = DateTime.UtcNow;
|
||||
|
||||
@@ -440,7 +440,7 @@ namespace Atomx.Admin.Controllers
|
||||
CorporationId = item.CorporationId,
|
||||
StockQuantity = item.StockQuantity,
|
||||
CreateTime = DateTime.UtcNow,
|
||||
Id = _idCreator.NewId(),
|
||||
Id = _idCreator.CreateId(),
|
||||
ProductAttributeCombinationId = item.Id,
|
||||
ProductId = item.ProductId,
|
||||
WarehouseId = 0
|
||||
@@ -451,7 +451,7 @@ namespace Atomx.Admin.Controllers
|
||||
WarehouseId = 0,
|
||||
ProductId = item.ProductId,
|
||||
ProductAttributeCombinationId = item.Id,
|
||||
Id = _idCreator.NewId(),
|
||||
Id = _idCreator.CreateId(),
|
||||
AfterStock = item.StockQuantity,
|
||||
BeforeStock = 0,
|
||||
ChangeAmount = item.StockQuantity,
|
||||
@@ -573,7 +573,7 @@ namespace Atomx.Admin.Controllers
|
||||
DisplayOrder = 0,
|
||||
ProductId = product.Id,
|
||||
Image = item.Image,
|
||||
Id = _idCreator.NewId()
|
||||
Id = _idCreator.CreateId()
|
||||
};
|
||||
}
|
||||
|
||||
@@ -599,7 +599,7 @@ namespace Atomx.Admin.Controllers
|
||||
};
|
||||
if (attributeValue.Id == 0)
|
||||
{
|
||||
attributeValue.Id = _idCreator.NewId();
|
||||
attributeValue.Id = _idCreator.CreateId();
|
||||
}
|
||||
if (!string.IsNullOrEmpty(value.Image))
|
||||
{
|
||||
@@ -631,7 +631,7 @@ namespace Atomx.Admin.Controllers
|
||||
data.AttributesJson = item.SkuAttributes.ToJson();
|
||||
if (data.Id == 0)
|
||||
{
|
||||
data.Id = _idCreator.NewId();
|
||||
data.Id = _idCreator.CreateId();
|
||||
data.ProductId = product.Id;
|
||||
}
|
||||
productAttributeCombination.Add(data);
|
||||
@@ -948,7 +948,7 @@ namespace Atomx.Admin.Controllers
|
||||
var relation = productAttributeRelations.SingleOrDefault(p => p.AttributeId == item.AttributeId);
|
||||
var attributeValue = new ProductAttributeValue()
|
||||
{
|
||||
Id = _idCreator.NewId(),
|
||||
Id = _idCreator.CreateId(),
|
||||
Name = item.AttributeValue.Name,
|
||||
OptionId = item.AttributeValue.OptionId,
|
||||
ProductAttributeRelationId = relation.Id,
|
||||
@@ -971,7 +971,7 @@ namespace Atomx.Admin.Controllers
|
||||
|
||||
productAttributeCombination = new ProductAttributeCombination()
|
||||
{
|
||||
Id = _idCreator.NewId(),
|
||||
Id = _idCreator.CreateId(),
|
||||
ProductId = model.ProductId,
|
||||
AttributesJson = productSkus.ToJson(),
|
||||
Enabled = true,
|
||||
@@ -994,7 +994,7 @@ namespace Atomx.Admin.Controllers
|
||||
|
||||
var productWarehouseInventory = new ProductInventory()
|
||||
{
|
||||
Id = _idCreator.NewId(),
|
||||
Id = _idCreator.CreateId(),
|
||||
ProductId = model.ProductId,
|
||||
ProductAttributeCombinationId = productAttributeCombination.Id,
|
||||
WarehouseId = model.WarehouseId.ToLong(),
|
||||
@@ -1036,7 +1036,7 @@ namespace Atomx.Admin.Controllers
|
||||
var value = item.ProductAttributeValues.Where(p => p.Id == item.AttributeValue.OptionId).SingleOrDefault();
|
||||
var attributeValue = new ProductAttributeValue()
|
||||
{
|
||||
Id = _idCreator.NewId(),
|
||||
Id = _idCreator.CreateId(),
|
||||
Name = value.Name,
|
||||
OptionId = item.AttributeValue.OptionId,
|
||||
ProductAttributeRelationId = item.AttributeValue.ProductAttributeRelationId,
|
||||
@@ -1069,7 +1069,7 @@ namespace Atomx.Admin.Controllers
|
||||
{
|
||||
productWarehouseInventory = new ProductInventory()
|
||||
{
|
||||
Id = _idCreator.NewId(),
|
||||
Id = _idCreator.CreateId(),
|
||||
ProductId = model.ProductId,
|
||||
ProductAttributeCombinationId = model.Id,
|
||||
WarehouseId = model.WarehouseId.ToLong(),
|
||||
@@ -1160,7 +1160,7 @@ namespace Atomx.Admin.Controllers
|
||||
{
|
||||
productWarehouseInventory = new ProductInventory()
|
||||
{
|
||||
Id = _idCreator.NewId(),
|
||||
Id = _idCreator.CreateId(),
|
||||
ProductId = model.ProductId,
|
||||
ProductAttributeCombinationId = model.Id,
|
||||
WarehouseId = model.WarehouseId,
|
||||
|
||||
@@ -125,7 +125,7 @@ namespace Atomx.Admin.Controllers
|
||||
}
|
||||
try
|
||||
{
|
||||
model.Id = _idCreator.NewId();
|
||||
model.Id = _idCreator.CreateId();
|
||||
var message = _mapper.Map<SiteApp>(model);
|
||||
message.CreateTime = DateTime.UtcNow;
|
||||
|
||||
|
||||
@@ -142,7 +142,7 @@ namespace Atomx.Admin.Controllers
|
||||
|
||||
try
|
||||
{
|
||||
model.Id = _idCreator.NewId();
|
||||
model.Id = _idCreator.CreateId();
|
||||
var data = _mapper.Map<SpecificationAttribute>(model);
|
||||
data.CreateTime = DateTime.UtcNow;
|
||||
|
||||
|
||||
@@ -132,7 +132,7 @@ namespace Atomx.Admin.Controllers
|
||||
|
||||
try
|
||||
{
|
||||
model.Id = _idCreator.NewId();
|
||||
model.Id = _idCreator.CreateId();
|
||||
var data = _mapper.Map<SpecificationAttributeOption>(model);
|
||||
data.CreateTime = DateTime.UtcNow;
|
||||
|
||||
|
||||
@@ -121,7 +121,7 @@ namespace Atomx.Admin.Controllers
|
||||
}
|
||||
try
|
||||
{
|
||||
model.Id = _idCreator.NewId();
|
||||
model.Id = _idCreator.CreateId();
|
||||
var message = _mapper.Map<UploadFile>(model);
|
||||
message.CreateTime = DateTime.UtcNow;
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ namespace Atomx.Admin.Extensions
|
||||
config.Logo = "";
|
||||
config.Name = "Atomx";
|
||||
|
||||
db.Settings.Add(new Setting() { Id = idCreater.NewId(), Name = "普通设置", Key = ConfigKeys.General, Content = config.ToJson() });
|
||||
db.Settings.Add(new Setting() { Id = idCreater.CreateId(), Name = "普通设置", Key = ConfigKeys.General, Content = config.ToJson() });
|
||||
}
|
||||
|
||||
if (db.Settings.Count(p => p.Key == ConfigKeys.Upload) == 0)
|
||||
@@ -51,7 +51,7 @@ namespace Atomx.Admin.Extensions
|
||||
config.ImageMaxSize = 2048;
|
||||
config.StorageServiceEnabled = false;
|
||||
config.ImageTypes = "";
|
||||
db.Settings.Add(new Setting() { Id = idCreater.NewId(), Name = "上传设置", Key = ConfigKeys.Upload, Content = config.ToJson() });
|
||||
db.Settings.Add(new Setting() { Id = idCreater.CreateId(), Name = "上传设置", Key = ConfigKeys.Upload, Content = config.ToJson() });
|
||||
}
|
||||
|
||||
if (!db.Languages.Any())
|
||||
@@ -72,7 +72,7 @@ namespace Atomx.Admin.Extensions
|
||||
{
|
||||
foreach (var permission in Permissions.GetAllPermissions())
|
||||
{
|
||||
db.Permissions.Add(new Permission() { Id = idCreater.NewId(), Name = permission, Description = Permissions.GetPermissionDescription(permission), Category = permission.Split('.')[0] });
|
||||
db.Permissions.Add(new Permission() { Id = idCreater.CreateId(), Name = permission, Description = Permissions.GetPermissionDescription(permission), Category = permission.Split('.')[0] });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,7 +93,7 @@ namespace Atomx.Admin.Extensions
|
||||
|
||||
if (!db.Admins.Any())
|
||||
{
|
||||
Common.Entities.Admin admin = new Common.Entities.Admin() { RoleId = 1, Id = idCreater.NewId(), Email = "admin@admin.com", Password = "admin888".ToMd5Password(), Username = "admin", Status = (int)DataStatus.Enable, CreateTime = DateTime.UtcNow };
|
||||
Common.Entities.Admin admin = new Common.Entities.Admin() { RoleId = 1, Id = idCreater.CreateId(), Email = "admin@admin.com", Password = "admin888".ToMd5Password(), Username = "admin", Status = (int)DataStatus.Enable, CreateTime = DateTime.UtcNow };
|
||||
db.Admins.Add(admin);
|
||||
}
|
||||
|
||||
@@ -102,7 +102,7 @@ namespace Atomx.Admin.Extensions
|
||||
{
|
||||
List<Menu> menus = new List<Menu>();
|
||||
var home = new Menu() { Type = 1, Code = "", Key = "", Url = "/", Icon = "Dashboard", Name = "主页面板", DisplayOrder = 100, Enabled = true, CreateTime = DateTime.UtcNow, UpdateTime = DateTime.UtcNow };
|
||||
home.Id = idCreater.NewId();
|
||||
home.Id = idCreater.CreateId();
|
||||
home.ParentId = 0;
|
||||
home.Path = $"{home.Id}";
|
||||
menus.Add(home);
|
||||
@@ -174,41 +174,41 @@ namespace Atomx.Admin.Extensions
|
||||
|
||||
|
||||
var mall = new Menu() { Type = 1, Code = "", Key = "mall", Url = "", Icon = "Dataset", Name = "商品管理", DisplayOrder = 120, Enabled = true, CreateTime = DateTime.UtcNow, UpdateTime = DateTime.UtcNow };
|
||||
mall.Id = idCreater.NewId();
|
||||
mall.Id = idCreater.CreateId();
|
||||
mall.ParentId = 0;
|
||||
mall.Path = $"{mall.Id}";
|
||||
menus.Add(mall);
|
||||
|
||||
var mall_catalog = new Menu() { Type = 1, Code = "", Key = "mall.category", Url = "/category/list", Icon = "", Name = "分类管理", DisplayOrder = 1, Enabled = true, CreateTime = DateTime.UtcNow, UpdateTime = DateTime.UtcNow };
|
||||
mall_catalog.Id = idCreater.NewId();
|
||||
mall_catalog.Id = idCreater.CreateId();
|
||||
mall_catalog.ParentId = mall.Id;
|
||||
mall_catalog.Depth = 1;
|
||||
mall_catalog.Path = $"{mall.Id},{mall_catalog.Id}";
|
||||
menus.Add(mall_catalog);
|
||||
|
||||
var mall_product = new Menu() { Type = 1, Code = "", Key = "mall.product", Url = "/product/list", Icon = "", Name = "产品管理", DisplayOrder = 2, Enabled = true, CreateTime = DateTime.UtcNow, UpdateTime = DateTime.UtcNow };
|
||||
mall_product.Id = idCreater.NewId();
|
||||
mall_product.Id = idCreater.CreateId();
|
||||
mall_product.ParentId = mall.Id;
|
||||
mall_product.Depth = 1;
|
||||
mall_product.Path = $"{mall.Id},{mall_product.Id}";
|
||||
menus.Add(mall_product);
|
||||
|
||||
var mall_product_stock = new Menu() { Type = 1, Code = "", Key = "mall.product", Url = "/product/stock/list", Icon = "", Name = "产品库存管理", DisplayOrder = 2, Enabled = true, CreateTime = DateTime.UtcNow, UpdateTime = DateTime.UtcNow };
|
||||
mall_product_stock.Id = idCreater.NewId();
|
||||
mall_product_stock.Id = idCreater.CreateId();
|
||||
mall_product_stock.ParentId = mall.Id;
|
||||
mall_product_stock.Depth = 1;
|
||||
mall_product_stock.Path = $"{mall.Id},{mall_product_stock.Id}";
|
||||
menus.Add(mall_product_stock);
|
||||
|
||||
var mall_manufacturer = new Menu() { Type = 1, Code = "", Key = "mall.manufacturer", Url = "/manufacturer/list", Icon = "", Name = "品牌厂商", DisplayOrder = 3, Enabled = true, CreateTime = DateTime.UtcNow, UpdateTime = DateTime.UtcNow };
|
||||
mall_manufacturer.Id = idCreater.NewId();
|
||||
mall_manufacturer.Id = idCreater.CreateId();
|
||||
mall_manufacturer.ParentId = mall.Id;
|
||||
mall_manufacturer.Depth = 1;
|
||||
mall_manufacturer.Path = $"{mall.Id},{mall_manufacturer.Id}";
|
||||
menus.Add(mall_manufacturer);
|
||||
|
||||
var mall_warehouse = new Menu() { Type = 1, Code = "", Key = "mall.warehouse", Url = "/warehouse/list", Icon = "", Name = "货品仓储", DisplayOrder = 3, Enabled = true, CreateTime = DateTime.UtcNow, UpdateTime = DateTime.UtcNow };
|
||||
mall_warehouse.Id = idCreater.NewId();
|
||||
mall_warehouse.Id = idCreater.CreateId();
|
||||
mall_warehouse.ParentId = mall.Id;
|
||||
mall_warehouse.Depth = 1;
|
||||
mall_warehouse.Path = $"{mall.Id},{mall_warehouse.Id}";
|
||||
@@ -217,21 +217,21 @@ namespace Atomx.Admin.Extensions
|
||||
|
||||
|
||||
var mall_attributes = new Menu() { Type = 1, Code = "", Key = "mall.attribute", Url = "", Icon = "", Name = "属性规格", DisplayOrder = 5, Enabled = true, CreateTime = DateTime.UtcNow, UpdateTime = DateTime.UtcNow };
|
||||
mall_attributes.Id = idCreater.NewId();
|
||||
mall_attributes.Id = idCreater.CreateId();
|
||||
mall_attributes.ParentId = mall.Id;
|
||||
mall_attributes.Depth = 1;
|
||||
mall_attributes.Path = $"{mall.Id},{mall_attributes.Id}";
|
||||
menus.Add(mall_attributes);
|
||||
|
||||
var mall_product_attributes = new Menu() { Type = 1, Code = "", Key = "mall.attribute.product", Url = "/attribute/list", Icon = "", Name = "产品属性", DisplayOrder = 6, Enabled = true, CreateTime = DateTime.UtcNow, UpdateTime = DateTime.UtcNow };
|
||||
mall_product_attributes.Id = idCreater.NewId();
|
||||
mall_product_attributes.Id = idCreater.CreateId();
|
||||
mall_product_attributes.ParentId = mall_attributes.Id;
|
||||
mall_product_attributes.Depth = 2;
|
||||
mall_product_attributes.Path = $"{mall.Id},{mall_attributes.Id},{mall_product_attributes.Id}";
|
||||
menus.Add(mall_product_attributes);
|
||||
|
||||
var mall_product_specification = new Menu() { Type = 1, Code = "", Key = "mall.attribute.specification", Url = "/specification/list", Icon = "", Name = "产品规格", DisplayOrder = 7, Enabled = true, CreateTime = DateTime.UtcNow, UpdateTime = DateTime.UtcNow };
|
||||
mall_product_specification.Id = idCreater.NewId();
|
||||
mall_product_specification.Id = idCreater.CreateId();
|
||||
mall_product_specification.ParentId = mall_attributes.Id;
|
||||
mall_product_specification.Depth = 2;
|
||||
mall_product_specification.Path = $"{mall.Id},{mall_attributes.Id},{mall_product_specification.Id}";
|
||||
@@ -354,47 +354,47 @@ namespace Atomx.Admin.Extensions
|
||||
|
||||
|
||||
var user = new Menu() { Type = 1, Code = "", Key = "user", Url = "", Icon = "People", Name = "客户会员", DisplayOrder = 170, Enabled = true, CreateTime = DateTime.UtcNow, UpdateTime = DateTime.UtcNow };
|
||||
user.Id = idCreater.NewId();
|
||||
user.Id = idCreater.CreateId();
|
||||
user.Path = $"{user.Id}";
|
||||
menus.Add(user);
|
||||
|
||||
var user_customer = new Menu() { Type = 1, Code = "", Key = "user.customer", Url = "", Icon = "", Name = "用户会员", DisplayOrder = 1, Enabled = true, CreateTime = DateTime.UtcNow, UpdateTime = DateTime.UtcNow };
|
||||
user_customer.Id = idCreater.NewId();
|
||||
user_customer.Id = idCreater.CreateId();
|
||||
user_customer.ParentId = user.Id;
|
||||
user_customer.Depth = 1;
|
||||
user_customer.Path = $"{user.Id},{user_customer.Id}";
|
||||
menus.Add(user_customer);
|
||||
|
||||
var user_list = new Menu() { Type = 1, Code = "", Key = "user.list", Url = "user/list", Icon = "", Name = "用户列表", DisplayOrder = 1, Enabled = true, CreateTime = DateTime.UtcNow, UpdateTime = DateTime.UtcNow };
|
||||
user_list.Id = idCreater.NewId();
|
||||
user_list.Id = idCreater.CreateId();
|
||||
user_list.ParentId = user_customer.Id;
|
||||
user_list.Depth = 2;
|
||||
user_list.Path = $"{user.Id},{user_customer.Id},{user_list.Id}";
|
||||
menus.Add(user_list);
|
||||
|
||||
var user_online = new Menu() { Type = 1, Code = "", Key = "user.online", Url = "user/online/list", Icon = "", Name = "在线用户", DisplayOrder = 2, Enabled = true, CreateTime = DateTime.UtcNow, UpdateTime = DateTime.UtcNow };
|
||||
user_online.Id = idCreater.NewId();
|
||||
user_online.Id = idCreater.CreateId();
|
||||
user_online.ParentId = user_customer.Id;
|
||||
user_online.Depth = 2;
|
||||
user_online.Path = $"{user.Id},{user_customer.Id},{user_online.Id}";
|
||||
menus.Add(user_online);
|
||||
|
||||
var user_level = new Menu() { Type = 1, Code = "", Key = "user.level", Url = "user/level/list", Icon = "", Name = "会员等级", DisplayOrder = 3, Enabled = true, CreateTime = DateTime.UtcNow, UpdateTime = DateTime.UtcNow };
|
||||
user_level.Id = idCreater.NewId();
|
||||
user_level.Id = idCreater.CreateId();
|
||||
user_level.ParentId = user_customer.Id;
|
||||
user_level.Depth = 2;
|
||||
user_level.Path = $"{user.Id},{user_customer.Id},{user_level.Id}";
|
||||
menus.Add(user_level);
|
||||
|
||||
var user_corporation = new Menu() { Type = 1, Code = "", Key = "user.corporation", Url = "", Icon = "", Name = "企业客户", DisplayOrder = 3, Enabled = true, CreateTime = DateTime.UtcNow, UpdateTime = DateTime.UtcNow };
|
||||
user_corporation.Id = idCreater.NewId();
|
||||
user_corporation.Id = idCreater.CreateId();
|
||||
user_corporation.ParentId = user.Id;
|
||||
user_corporation.Depth = 1;
|
||||
user_corporation.Path = $"{user.Id},{user_corporation.Id}";
|
||||
menus.Add(user_corporation);
|
||||
|
||||
var user_corporation_list = new Menu() { Type = 1, Code = "", Key = "user.corporation.list", Url = "corporation/list", Icon = "", Name = "企业列表", DisplayOrder = 3, Enabled = true, CreateTime = DateTime.UtcNow, UpdateTime = DateTime.UtcNow };
|
||||
user_corporation_list.Id = idCreater.NewId();
|
||||
user_corporation_list.Id = idCreater.CreateId();
|
||||
user_corporation_list.ParentId = user_corporation.Id;
|
||||
user_corporation_list.Depth = 2;
|
||||
user_corporation_list.Path = $"{user.Id},{user_corporation.Id},{user_corporation_list.Id}";
|
||||
@@ -408,7 +408,7 @@ namespace Atomx.Admin.Extensions
|
||||
//menus.Add(user_corporation_staff_list);
|
||||
|
||||
var user_corporation_shop_list = new Menu() { Type = 1, Code = "", Key = "user.corporation.shop", Url = "/corpotation/shop/list", Icon = "", Name = "企业店铺", DisplayOrder = 3, Enabled = true, CreateTime = DateTime.UtcNow, UpdateTime = DateTime.UtcNow };
|
||||
user_corporation_shop_list.Id = idCreater.NewId();
|
||||
user_corporation_shop_list.Id = idCreater.CreateId();
|
||||
user_corporation_shop_list.ParentId = user_corporation.Id;
|
||||
user_corporation_shop_list.Depth = 2;
|
||||
user_corporation_shop_list.Path = $"{user.Id},{user_corporation.Id},{user_corporation_shop_list.Id}";
|
||||
@@ -443,31 +443,31 @@ namespace Atomx.Admin.Extensions
|
||||
|
||||
|
||||
var stats = new Menu() { Type = 1, Code = "", Key = "stats", Url = "", Icon = "Analytics", Name = "统计报告", DisplayOrder = 190, Enabled = true, CreateTime = DateTime.UtcNow, UpdateTime = DateTime.UtcNow };
|
||||
stats.Id = idCreater.NewId();
|
||||
stats.Id = idCreater.CreateId();
|
||||
stats.Path = $"{stats.Id}";
|
||||
menus.Add(stats);
|
||||
|
||||
var stats_payment = new Menu() { Type = 1, Code = "", Key = "stats", Url = "", Icon = "", Name = "支付统计报告", DisplayOrder = 1, Enabled = true, CreateTime = DateTime.UtcNow, UpdateTime = DateTime.UtcNow };
|
||||
stats_payment.Id = idCreater.NewId();
|
||||
stats_payment.Id = idCreater.CreateId();
|
||||
stats_payment.ParentId = stats.Id;
|
||||
stats_payment.Depth = 1;
|
||||
stats_payment.Path = $"{stats.Id},{stats_payment.Id}";
|
||||
menus.Add(stats_payment);
|
||||
|
||||
var setting = new Menu() { Type = 1, Code = "", Key = "config", Url = "", Icon = "Api", Name = "系统配置", DisplayOrder = 200, Enabled = true, CreateTime = DateTime.UtcNow, UpdateTime = DateTime.UtcNow };
|
||||
setting.Id = idCreater.NewId();
|
||||
setting.Id = idCreater.CreateId();
|
||||
setting.Path = $"{setting.Id}";
|
||||
menus.Add(setting);
|
||||
|
||||
var setting_base = new Menu() { Type = 1, Code = "", Key = "config.settings", Url = "/settings", Icon = "", Name = "系统设置", DisplayOrder = 1, Enabled = true, CreateTime = DateTime.UtcNow, UpdateTime = DateTime.UtcNow };
|
||||
setting_base.Id = idCreater.NewId();
|
||||
setting_base.Id = idCreater.CreateId();
|
||||
setting_base.ParentId = setting.Id;
|
||||
setting_base.Depth = 1;
|
||||
setting_base.Path = $"{setting.Id},{setting_base.Id}";
|
||||
menus.Add(setting_base);
|
||||
|
||||
var setting_messagetemplate = new Menu() { Type = 1, Code = "", Key = "config.messagetemplate", Url = "/setting/messagetemplate/list", Icon = "", Name = "消息模板", DisplayOrder = 1, Enabled = true, CreateTime = DateTime.UtcNow, UpdateTime = DateTime.UtcNow };
|
||||
setting_messagetemplate.Id = idCreater.NewId();
|
||||
setting_messagetemplate.Id = idCreater.CreateId();
|
||||
setting_messagetemplate.ParentId = setting.Id;
|
||||
setting_messagetemplate.Depth = 1;
|
||||
setting_messagetemplate.Path = $"{setting.Id},{setting_messagetemplate.Id}";
|
||||
@@ -495,14 +495,14 @@ namespace Atomx.Admin.Extensions
|
||||
//menus.Add(setting_currency);
|
||||
|
||||
var setting_provider = new Menu() { Type = 1, Code = "", Key = "config.provider", Url = "/setting/provider/list", Icon = "", Name = "服务供应商", DisplayOrder = 1, Enabled = true, CreateTime = DateTime.UtcNow, UpdateTime = DateTime.UtcNow };
|
||||
setting_provider.Id = idCreater.NewId();
|
||||
setting_provider.Id = idCreater.CreateId();
|
||||
setting_provider.ParentId = setting.Id;
|
||||
setting_provider.Depth = 1;
|
||||
setting_provider.Path = $"{setting.Id},{setting_provider.Id}";
|
||||
menus.Add(setting_provider);
|
||||
|
||||
var setting_methods = new Menu() { Type = 1, Code = "", Key = "config.payment", Url = "/payment/methods", Icon = "", Name = "支付方式", DisplayOrder = 1, Enabled = true, CreateTime = DateTime.UtcNow, UpdateTime = DateTime.UtcNow };
|
||||
setting_methods.Id = idCreater.NewId();
|
||||
setting_methods.Id = idCreater.CreateId();
|
||||
setting_methods.ParentId = setting.Id;
|
||||
setting_methods.Depth = 1;
|
||||
setting_methods.Path = $"{setting.Id},{setting_methods.Id}";
|
||||
@@ -517,33 +517,33 @@ namespace Atomx.Admin.Extensions
|
||||
|
||||
|
||||
var system = new Menu() { Type = 1, Code = "", Key = "system", Url = "", Icon = "Settings", Name = "系统功能", DisplayOrder = 210, Enabled = true, CreateTime = DateTime.UtcNow, UpdateTime = DateTime.UtcNow };
|
||||
system.Id = idCreater.NewId();
|
||||
system.Id = idCreater.CreateId();
|
||||
system.Path = $"{system.Id}";
|
||||
menus.Add(system);
|
||||
|
||||
var system_admin = new Menu() { Type = 1, Code = "", Key = "system.admin", Url = "/admin/list", Icon = "", Name = "管理员管理", DisplayOrder = 1, Enabled = true, CreateTime = DateTime.UtcNow, UpdateTime = DateTime.UtcNow };
|
||||
system_admin.Id = idCreater.NewId();
|
||||
system_admin.Id = idCreater.CreateId();
|
||||
system_admin.ParentId = system.Id;
|
||||
system_admin.Depth = 1;
|
||||
system_admin.Path = $"{system.Id},{system_admin.Id}";
|
||||
menus.Add(system_admin);
|
||||
|
||||
var system_role = new Menu() { Type = 1, Code = "", Key = "system.role", Url = "/system/role/list", Icon = "", Name = "角色管理", DisplayOrder = 2, Enabled = true, CreateTime = DateTime.UtcNow, UpdateTime = DateTime.UtcNow };
|
||||
system_role.Id = idCreater.NewId();
|
||||
system_role.Id = idCreater.CreateId();
|
||||
system_role.ParentId = system.Id;
|
||||
system_role.Depth = 1;
|
||||
system_role.Path = $"{system.Id},{system_role.Id}";
|
||||
menus.Add(system_role);
|
||||
|
||||
var system_version = new Menu() { Type = 1, Code = "", Key = "system.version", Url = "/system/app/version/list", Icon = "", Name = "版本管理", DisplayOrder = 1, Enabled = true, CreateTime = DateTime.UtcNow, UpdateTime = DateTime.UtcNow };
|
||||
system_version.Id = idCreater.NewId();
|
||||
system_version.Id = idCreater.CreateId();
|
||||
system_version.ParentId = system.Id;
|
||||
system_version.Depth = 1;
|
||||
system_version.Path = $"{system.Id},{system_version.Id}";
|
||||
menus.Add(system_version);
|
||||
|
||||
var system_menu = new Menu() { Type = 1, Code = "", Key = "system.menu", Url = "/system/menu/list", Icon = "", Name = "菜单设置", DisplayOrder = 3, Enabled = true, CreateTime = DateTime.UtcNow, UpdateTime = DateTime.UtcNow };
|
||||
system_menu.Id = idCreater.NewId();
|
||||
system_menu.Id = idCreater.CreateId();
|
||||
system_menu.ParentId = system.Id;
|
||||
system_menu.Depth = 1;
|
||||
system_menu.Path = $"{system.Id},{system_menu.Id}";
|
||||
@@ -552,14 +552,14 @@ namespace Atomx.Admin.Extensions
|
||||
|
||||
|
||||
var system_manual = new Menu() { Type = 1, Code = "", Key = "system.tools", Url = "/system/tools", Icon = "", Name = "系统工具", DisplayOrder = 4, Enabled = true, CreateTime = DateTime.UtcNow, UpdateTime = DateTime.UtcNow };
|
||||
system_manual.Id = idCreater.NewId();
|
||||
system_manual.Id = idCreater.CreateId();
|
||||
system_manual.ParentId = system.Id;
|
||||
system_manual.Depth = 1;
|
||||
system_manual.Path = $"{system.Id},{system_manual.Id}";
|
||||
menus.Add(system_manual);
|
||||
|
||||
var system_info = new Menu() { Type = 1, Code = "", Key = "system.info", Url = "/system/info", Icon = "", Name = "系统信息", DisplayOrder = 5, Enabled = true, CreateTime = DateTime.UtcNow, UpdateTime = DateTime.UtcNow };
|
||||
system_info.Id = idCreater.NewId();
|
||||
system_info.Id = idCreater.CreateId();
|
||||
system_info.ParentId = system.Id;
|
||||
system_info.Depth = 1;
|
||||
system_info.Path = $"{system.Id},{system_info.Id}";
|
||||
|
||||
3
Atomx.Admin/Atomx.Admin/Localization/en-US.json
Normal file
3
Atomx.Admin/Atomx.Admin/Localization/en-US.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"site.name": "Atomx Studio"
|
||||
}
|
||||
3
Atomx.Admin/Atomx.Admin/Localization/zh-Hans.json
Normal file
3
Atomx.Admin/Atomx.Admin/Localization/zh-Hans.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"site.name": "Atomx 管理员"
|
||||
}
|
||||
48
Atomx.Admin/Atomx.Admin/Middlewares/CultureMiddleware.cs
Normal file
48
Atomx.Admin/Atomx.Admin/Middlewares/CultureMiddleware.cs
Normal file
@@ -0,0 +1,48 @@
|
||||
using Atomx.Admin.Client.Services;
|
||||
|
||||
namespace Atomx.Admin.Middlewares
|
||||
{
|
||||
/// <summary>
|
||||
/// 文化设置中间件
|
||||
/// </summary>
|
||||
public class CultureMiddleware
|
||||
{
|
||||
private readonly RequestDelegate _next;
|
||||
|
||||
public CultureMiddleware(RequestDelegate next)
|
||||
{
|
||||
_next = next;
|
||||
}
|
||||
|
||||
public async Task InvokeAsync(HttpContext context, LanguageProvider languageProvider)
|
||||
{
|
||||
// 从查询字符串、Cookie或Header中获取语言设置
|
||||
var cultureQuery = context.Request.Query["culture"];
|
||||
var cultureCookie = context.Request.Cookies["preferred-language"];
|
||||
|
||||
string? culture = null;
|
||||
|
||||
if (!string.IsNullOrEmpty(cultureQuery))
|
||||
{
|
||||
culture = cultureQuery;
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(cultureCookie))
|
||||
{
|
||||
culture = cultureCookie;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(culture))
|
||||
{
|
||||
var supportedCultures = new[] { "zh-Hans", "en-US" };
|
||||
if (supportedCultures.Contains(culture))
|
||||
{
|
||||
var cultureInfo = new System.Globalization.CultureInfo(culture);
|
||||
System.Threading.Thread.CurrentThread.CurrentCulture = cultureInfo;
|
||||
System.Threading.Thread.CurrentThread.CurrentUICulture = cultureInfo;
|
||||
}
|
||||
}
|
||||
|
||||
await _next(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,14 +14,14 @@ using Blazored.LocalStorage;
|
||||
using Mapster;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Components.Authorization;
|
||||
using Microsoft.AspNetCore.Diagnostics.HealthChecks;
|
||||
using Microsoft.AspNetCore.HttpOverrides;
|
||||
using Microsoft.AspNetCore.Localization;
|
||||
using Microsoft.AspNetCore.ResponseCompression;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Localization;
|
||||
using Scalar.AspNetCore;
|
||||
using Serilog;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Globalization;
|
||||
using System.Text.Encodings.Web;
|
||||
using System.Text.Json;
|
||||
using System.Text.Unicode;
|
||||
@@ -51,10 +51,41 @@ builder.Services.AddControllers().AddJsonOptions(options =>
|
||||
|
||||
builder.Services.AddRouting(p => p.LowercaseUrls = true);
|
||||
|
||||
|
||||
|
||||
builder.Services.AddMapster();
|
||||
builder.Services.AddBlazoredLocalStorage();
|
||||
builder.Services.AddCascadingAuthenticationState();
|
||||
builder.Services.AddHttpContextAccessor();
|
||||
// HttpClient <20><><EFBFBD><EFBFBD><EFBFBD>ݷ<EFBFBD><DDB7><EFBFBD>
|
||||
builder.Services.AddHttpClientApiService(builder.Configuration["WebApi:ServerUrl"] ?? "http://localhost");
|
||||
|
||||
// ע<><D7A2>JSON<4F><4E><EFBFBD>ػ<EFBFBD><D8BB><EFBFBD><EFBFBD><EFBFBD>
|
||||
builder.Services.AddSingleton<IStringLocalizerFactory>(sp =>
|
||||
{
|
||||
var env = sp.GetRequiredService<IWebHostEnvironment>();
|
||||
var resourcesPath = Path.Combine(env.ContentRootPath, "Localization");
|
||||
// <20><><EFBFBD>Դ<EFBFBD> DI <20><>ȡ IHttpClientFactory<72><79><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʹ<EFBFBD>ã<EFBFBD><C3A3><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>˵<EFBFBD> new HttpClient()
|
||||
var httpFactory = sp.GetService<IHttpClientFactory>();
|
||||
HttpClient httpClient;
|
||||
if (httpFactory != null)
|
||||
{
|
||||
httpClient = httpFactory.CreateClient();
|
||||
}
|
||||
else
|
||||
{
|
||||
// <20>ڷ<EFBFBD><DAB7><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ҫʹ<D2AA><CAB9><EFBFBD>ļ<EFBFBD>ϵͳ<CFB5><CDB3>ȡ<EFBFBD><C8A1><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ļ<EFBFBD><C4BC><EFBFBD><EFBFBD>˴<EFBFBD><CBB4><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> HttpClient ֻ<><D6BB>Ϊ<EFBFBD><CEAA><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ҫ httpClient <20><><EFBFBD>캯<EFBFBD><ECBAAF><EFBFBD>Ĺ<EFBFBD><C4B9><EFBFBD><EFBFBD><EFBFBD>
|
||||
httpClient = new HttpClient();
|
||||
}
|
||||
|
||||
return new JsonStringLocalizerFactory(resourcesPath, httpClient);
|
||||
});
|
||||
|
||||
// ע<><D7A2>IStringLocalizer
|
||||
builder.Services.AddTransient(typeof(IStringLocalizer<>), typeof(StringLocalizer<>));
|
||||
|
||||
// <20><><EFBFBD>ӱ<EFBFBD><D3B1>ػ<EFBFBD><D8BB><EFBFBD><EFBFBD><EFBFBD>
|
||||
builder.Services.AddLocalization();
|
||||
|
||||
// Ȩ<><EFBFBD><DEB7><EFBFBD> & <20><>Ȩ<EFBFBD><C8A8><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||
builder.Services.AddScoped<IPermissionService, PermissionService>();
|
||||
@@ -67,16 +98,15 @@ builder.Services.AddScoped<PersistentAuthenticationStateProvider>();
|
||||
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>߷<EFBFBD><DFB7><EFBFBD>
|
||||
builder.Services.AddScoped<IIdCreatorService, IdCreatorService>();
|
||||
builder.Services.AddScoped<IIdentityService, IdentityService>();
|
||||
builder.Services.AddScoped<ILocalizationService, LocalizationService>();
|
||||
builder.Services.AddScoped<LocalizationFile, LocalizationFile>();
|
||||
|
||||
// ע<><D7A2><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ṩ<EFBFBD>ߣ<EFBFBD><DFA3><EFBFBD>ΪScoped<65><64><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||
builder.Services.AddScoped<LanguageProvider>();
|
||||
builder.Services.AddScoped<AuthHeaderHandler>();
|
||||
|
||||
// SignalR<6C><52><EFBFBD><EFBFBD><EFBFBD>÷<EFBFBD><C3B7><EFBFBD><EFBFBD><EFBFBD> Hub ֧<>֣<EFBFBD>ע<EFBFBD>⣺JWT <20><> OnMessageReceived <20><><EFBFBD><EFBFBD> AuthorizationExtension <20>д<EFBFBD><D0B4><EFBFBD><EFBFBD><EFBFBD>
|
||||
builder.Services.AddSignalR();
|
||||
|
||||
// HttpClient <20><><EFBFBD><EFBFBD><EFBFBD>ݷ<EFBFBD><DDB7><EFBFBD>
|
||||
builder.Services.AddHttpClientApiService(builder.Configuration["WebApi:ServerUrl"] ?? "http://localhost");
|
||||
|
||||
builder.Services.AddDataService();
|
||||
builder.Services.AddAuthorize(builder.Configuration, builder.Environment); // <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>úõ<C3BA><C3B5><EFBFBD>֤/<2F><>Ȩ
|
||||
|
||||
@@ -182,6 +212,22 @@ app.MapStaticAssets();
|
||||
app.UseMiddleware<MonitoringMiddleware>();
|
||||
app.UseMiddleware<ExceptionHandlingMiddleware>();
|
||||
|
||||
// <20><><EFBFBD>ñ<EFBFBD><C3B1>ػ<EFBFBD><D8BB>м<EFBFBD><D0BC><EFBFBD>
|
||||
app.UseRequestLocalization(new RequestLocalizationOptions
|
||||
{
|
||||
DefaultRequestCulture = new RequestCulture("zh-Hans"),
|
||||
SupportedCultures = new[]
|
||||
{
|
||||
new CultureInfo("zh-Hans"),
|
||||
new CultureInfo("en-US")
|
||||
},
|
||||
SupportedUICultures = new[]
|
||||
{
|
||||
new CultureInfo("zh-Hans"),
|
||||
new CultureInfo("en-US")
|
||||
}
|
||||
});
|
||||
|
||||
// SignalR endpoints<74><73><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ŀ<EFBFBD><C4BF><EFBFBD><EFBFBD> Hub<75><62><EFBFBD><EFBFBD><EFBFBD>ڴ˴<DAB4>ӳ<EFBFBD>䣩
|
||||
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> Hub <20><><EFBFBD><EFBFBD> ChatHub<75><62>NotificationHub<75><62><EFBFBD><EFBFBD><EFBFBD>ڴ<EFBFBD>ȡ<EFBFBD><C8A1>ע<EFBFBD>Ͳ<EFBFBD>ӳ<EFBFBD><D3B3>
|
||||
// app.MapHub<ChatHub>("/hubs/chat");
|
||||
|
||||
@@ -1,70 +0,0 @@
|
||||
using Atomx.Admin.Client.Services;
|
||||
using Atomx.Data.CacheServices;
|
||||
using Atomx.Data.Services;
|
||||
|
||||
namespace Atomx.Admin.Services
|
||||
{
|
||||
public class LocalizationService : ILocalizationService
|
||||
{
|
||||
private readonly ICacheService _cacheService;
|
||||
private readonly IWebHostEnvironment _environment;
|
||||
private readonly ILogger<LocalizationService> _logger;
|
||||
|
||||
public event EventHandler<string>? ResourcesUpdated;
|
||||
|
||||
public LocalizationService(
|
||||
ICacheService cacheService,
|
||||
IWebHostEnvironment environment,
|
||||
ILogger<LocalizationService> logger)
|
||||
{
|
||||
_cacheService = cacheService;
|
||||
_environment = environment;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据name获取制定文化语言的译文
|
||||
/// </summary>
|
||||
/// <param name="name"></param>
|
||||
/// <param name="culture"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<string?> GetStringAsync(string name, string? culture = null)
|
||||
{
|
||||
culture ??= Thread.CurrentThread.CurrentCulture.Name;
|
||||
|
||||
return await _cacheService.GetLocaleResourcesString(culture, name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 加载资源到内存
|
||||
/// </summary>
|
||||
/// <param name="culture"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<bool> LoadResourcesAsync(string culture)
|
||||
{
|
||||
await _cacheService.GetLocaleResourcesAsync(culture);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
private string GetJsonFilePath(string culture)
|
||||
{
|
||||
return Path.Combine(_environment.WebRootPath, "locales", $"{culture}.json");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计算文件哈希值
|
||||
/// </summary>
|
||||
/// <param name="filePath"></param>
|
||||
/// <returns></returns>
|
||||
private static string CalculateFileHash(string filePath)
|
||||
{
|
||||
using var stream = File.OpenRead(filePath);
|
||||
using var sha256 = System.Security.Cryptography.SHA256.Create();
|
||||
var hash = sha256.ComputeHash(stream);
|
||||
return Convert.ToBase64String(hash);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,6 @@
|
||||
using Atomx.Admin.Client.Services;
|
||||
using Atomx.Common.Entities;
|
||||
using Atomx.Data;
|
||||
using Atomx.Data.CacheServices;
|
||||
using Atomx.Data.Services;
|
||||
using System.Security.Claims;
|
||||
|
||||
namespace Atomx.Admin.Services
|
||||
{
|
||||
|
||||
@@ -105,6 +105,9 @@ namespace Atomx.Admin.Utils
|
||||
|
||||
if (!string.IsNullOrEmpty(id) && !string.IsNullOrEmpty(name))
|
||||
{
|
||||
try
|
||||
{
|
||||
// Persist 用户信息到 PersistentComponentState(用于 prerender 时传递给客户端)
|
||||
_state.PersistAsJson(nameof(UserInfo), new UserInfo
|
||||
{
|
||||
Id = id.ToLong(),
|
||||
@@ -115,6 +118,14 @@ namespace Atomx.Admin.Utils
|
||||
Permissions = permission
|
||||
});
|
||||
}
|
||||
catch (ArgumentException)
|
||||
{
|
||||
// 当同一轮持久化过程中注册了多次回调(例如同时为 InteractiveServer 和 InteractiveWebAssembly 注册)
|
||||
// 可能会导致针对同一 key 重复调用 PersistAsJson,从而抛出 ArgumentException。
|
||||
// 在这种情况下安全地忽略重复持久化,避免抛出未捕获异常打断流程。
|
||||
Debug.WriteLine("PersistentComponentState: 忽略重复持久化键 'UserInfo'。");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ namespace Atomx.Data.CacheServices
|
||||
public partial interface ICacheService
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取语言信息
|
||||
/// 获取语言信息,Enabled = true, 可选择强制更新缓存
|
||||
/// </summary>
|
||||
/// <param name="reload"></param>
|
||||
/// <returns></returns>
|
||||
|
||||
@@ -12,7 +12,7 @@ namespace Atomx.Data.Services
|
||||
///
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
long NewId();
|
||||
long CreateId();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -39,7 +39,7 @@ namespace Atomx.Data.Services
|
||||
///
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public long NewId()
|
||||
public long CreateId()
|
||||
{
|
||||
return IdCreator.CreateId();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user