Compare commits
29 Commits
9631e00a12
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 43820c6ec2 | |||
| 13cc258cad | |||
| 030299fa53 | |||
| 46794708ff | |||
| f7bb6bb2dc | |||
| 7b6ec9c1d8 | |||
| 53b6ceaa69 | |||
| e396e66959 | |||
| a1516490d2 | |||
| 3c4144335f | |||
| 1f0c84f75e | |||
| 903d6d9304 | |||
| da8ac8a22b | |||
| 9d396fa96e | |||
| ed32b98867 | |||
| 98e3f7ab73 | |||
| 9b8bf43eb6 | |||
| 9edff983d8 | |||
| 54e9c7962d | |||
| 0741368b44 | |||
| 8a1ff0edf9 | |||
| cd43abc7eb | |||
| 46e209081d | |||
| ed2e3ecd24 | |||
| 2318dff192 | |||
| 7334a9576f | |||
| e4550a9533 | |||
| 24512412e5 | |||
| 429fb39140 |
@@ -7,4 +7,16 @@
|
|||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Update="Microsoft.Testing.Extensions.TrxReport" Version="2.0.2" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Update="MSTest.TestAdapter" Version="4.0.2" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Update="MSTest.TestFramework" Version="4.0.2" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -9,16 +9,24 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="AntDesign" Version="1.5.0" />
|
<Compile Remove="wwwroot\localization\**" />
|
||||||
|
<Content Remove="wwwroot\localization\**" />
|
||||||
|
<EmbeddedResource Remove="wwwroot\localization\**" />
|
||||||
|
<None Remove="wwwroot\localization\**" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="AntDesign" Version="1.5.1" />
|
||||||
<PackageReference Include="AntDesign.ProLayout" Version="1.4.0" />
|
<PackageReference Include="AntDesign.ProLayout" Version="1.4.0" />
|
||||||
<PackageReference Include="Blazilla" Version="2.0.1" />
|
|
||||||
<PackageReference Include="Blazored.LocalStorage" Version="4.5.0" />
|
<PackageReference Include="Blazored.LocalStorage" Version="4.5.0" />
|
||||||
<PackageReference Include="Blazored.FluentValidation" Version="2.2.0" />
|
<PackageReference Include="Blazored.FluentValidation" Version="2.2.0" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Components.Authorization" Version="10.0.0" />
|
<PackageReference Include="FluentValidation.DependencyInjectionExtensions" Version="12.1.1" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="10.0.0" />
|
<PackageReference Include="Microsoft.AspNetCore.Components.Authorization" Version="10.0.1" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Http" Version="10.0.0" />
|
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="10.0.1" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Localization" Version="10.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Http" Version="10.0.1" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Localization" Version="10.0.1" />
|
||||||
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.15.0" />
|
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.15.0" />
|
||||||
|
<PackageReference Include="TinyMCE.Blazor" Version="2.2.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
82
Atomx.Admin/Atomx.Admin.Client/Components/LangSelector.razor
Normal file
82
Atomx.Admin/Atomx.Admin.Client/Components/LangSelector.razor
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
@inject ILocalizationProvider LocalizationProvider
|
||||||
|
@inject NavigationManager Navigation
|
||||||
|
@inject IJSRuntime JS
|
||||||
|
|
||||||
|
<select @onchange="OnChange" value="@selected">
|
||||||
|
@foreach (var item in options)
|
||||||
|
{
|
||||||
|
<option value="@item.Key">@item.Value</option>
|
||||||
|
}
|
||||||
|
</select>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
private string selected = "zh";
|
||||||
|
|
||||||
|
private readonly Dictionary<string, string> options = new(StringComparer.OrdinalIgnoreCase)
|
||||||
|
{
|
||||||
|
{ "zh", "<22><><EFBFBD><EFBFBD>" },
|
||||||
|
{ "en", "English" }
|
||||||
|
};
|
||||||
|
|
||||||
|
protected override async Task OnInitializedAsync()
|
||||||
|
{
|
||||||
|
var current = LocalizationProvider.CurrentCulture;
|
||||||
|
var mapping = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
|
||||||
|
{
|
||||||
|
{ "zh", "zh-Hans" },
|
||||||
|
{ "en", "en-US" }
|
||||||
|
};
|
||||||
|
var found = mapping.FirstOrDefault(kv => string.Equals(kv.Value, current, StringComparison.OrdinalIgnoreCase)).Key;
|
||||||
|
if (!string.IsNullOrEmpty(found)) selected = found;
|
||||||
|
|
||||||
|
LocalizationProvider.LanguageChanged += (s, c) =>
|
||||||
|
{
|
||||||
|
var found2 = mapping.FirstOrDefault(kv => string.Equals(kv.Value, c, StringComparison.OrdinalIgnoreCase)).Key;
|
||||||
|
if (!string.IsNullOrEmpty(found2)) selected = found2;
|
||||||
|
StateHasChanged();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task OnChange(ChangeEventArgs e)
|
||||||
|
{
|
||||||
|
if (e?.Value is string val)
|
||||||
|
{
|
||||||
|
selected = val;
|
||||||
|
await LocalizationProvider.SetCultureAsync(selected);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Use NavigationManager to inspect and modify the current client-side path
|
||||||
|
var relative = Navigation.ToBaseRelativePath(Navigation.Uri).Trim('/');
|
||||||
|
var segments = string.IsNullOrEmpty(relative) ? Array.Empty<string>() : relative.Split('/', StringSplitOptions.RemoveEmptyEntries);
|
||||||
|
var mapping = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
|
||||||
|
{
|
||||||
|
{ "zh", "zh-Hans" },
|
||||||
|
{ "en", "en-US" }
|
||||||
|
};
|
||||||
|
|
||||||
|
string newUrl;
|
||||||
|
if (segments.Length > 0 && mapping.ContainsKey(segments[0]))
|
||||||
|
{
|
||||||
|
// replace existing locale prefix
|
||||||
|
var remaining = segments.Length > 1 ? "/" + string.Join('/', segments.Skip(1)) : "/";
|
||||||
|
newUrl = "/" + selected + remaining;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// keep current path, but trigger remount by navigating to same URI
|
||||||
|
newUrl = Navigation.Uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
// trigger client-side navigation (no hard reload) so components remount and re-evaluate localizer
|
||||||
|
Navigation.NavigateTo(newUrl, forceLoad: false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// fallback: just request UI refresh
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,9 +1,41 @@
|
|||||||
@inherits LayoutComponentBase
|
@inherits LayoutComponentBase
|
||||||
|
@inject Atomx.Admin.Client.Services.ILocalizationProvider LocalizationProvider
|
||||||
|
|
||||||
<div style="min-height:100vh">
|
<div style="min-height:100vh">
|
||||||
@Body
|
@Body
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
|
protected override void OnInitialized()
|
||||||
|
{
|
||||||
|
if (LocalizationProvider != null)
|
||||||
|
{
|
||||||
|
LocalizationProvider.LanguageChanged += OnLanguageChanged;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnLanguageChanged(object? sender, string culture)
|
||||||
|
{
|
||||||
|
_ = InvokeAsync(StateHasChanged);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||||
|
{
|
||||||
|
if (firstRender && LocalizationProvider != null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await LocalizationProvider.InitializeAsync();
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (LocalizationProvider != null)
|
||||||
|
{
|
||||||
|
LocalizationProvider.LanguageChanged -= OnLanguageChanged;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
@inherits LayoutComponentBase
|
@inherits LayoutComponentBase
|
||||||
@inject ILogger<MainLayout> _logger
|
@inject ILogger<MainLayout> _logger
|
||||||
|
@inject Atomx.Admin.Client.Services.ILocalizationProvider LocalizationProvider
|
||||||
|
@inject NavigationManager Navigation
|
||||||
|
|
||||||
<ErrorBoundary>
|
<ErrorBoundary>
|
||||||
<ChildContent>
|
<ChildContent>
|
||||||
@@ -45,6 +47,9 @@
|
|||||||
<ChildContent>
|
<ChildContent>
|
||||||
@Body
|
@Body
|
||||||
</ChildContent>
|
</ChildContent>
|
||||||
|
<FooterRender>
|
||||||
|
<FooterView Copyright="2025 Atomlust.com"></FooterView>
|
||||||
|
</FooterRender>
|
||||||
</AntDesign.ProLayout.BasicLayout>
|
</AntDesign.ProLayout.BasicLayout>
|
||||||
</ChildContent>
|
</ChildContent>
|
||||||
<ErrorContent Context="ex">
|
<ErrorContent Context="ex">
|
||||||
@@ -86,10 +91,10 @@
|
|||||||
|
|
||||||
private AvatarMenuItem[] AvatarMenuItems =>
|
private AvatarMenuItem[] AvatarMenuItems =>
|
||||||
[
|
[
|
||||||
new() { Key = "center", IconType = "user", Option = "通知消息"},
|
new() { Key = "center", IconType = "user", Option = "通知消息" },
|
||||||
new() { Key = "setting", IconType = "setting", Option ="修改资料" },
|
new() { Key = "setting", IconType = "setting", Option = "修改资料" },
|
||||||
new() { IsDivider = true },
|
new() { IsDivider = true },
|
||||||
new() { Key = "logout", IconType = "logout", Option = "退出登录"}
|
new() { Key = "logout", IconType = "logout", Option = "退出登录" }
|
||||||
];
|
];
|
||||||
|
|
||||||
public void HandleSelectUser(AntDesign.MenuItem item)
|
public void HandleSelectUser(AntDesign.MenuItem item)
|
||||||
@@ -131,5 +136,46 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void OnInitialized()
|
||||||
|
{
|
||||||
|
if (LocalizationProvider != null)
|
||||||
|
{
|
||||||
|
LocalizationProvider.LanguageChanged += OnLanguageChanged;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnLanguageChanged(object? sender, string culture)
|
||||||
|
{
|
||||||
|
// ensure UI updates on the SyncContext; small delay to let cache update
|
||||||
|
_ = InvokeAsync(async () =>
|
||||||
|
{
|
||||||
|
await Task.Yield();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Force route and page components to remount and re-render translations
|
||||||
|
Navigation.NavigateTo(Navigation.Uri, forceLoad: false);
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||||
|
{
|
||||||
|
if (firstRender && LocalizationProvider != null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await LocalizationProvider.InitializeAsync();
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (LocalizationProvider != null)
|
||||||
|
{
|
||||||
|
LocalizationProvider.LanguageChanged -= OnLanguageChanged;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,15 +1,10 @@
|
|||||||
using System.ComponentModel.DataAnnotations.Schema;
|
namespace Atomx.Admin.Client.Models
|
||||||
using System.ComponentModel.DataAnnotations;
|
|
||||||
|
|
||||||
namespace Atomx.Admin.Client.Models
|
|
||||||
{
|
{
|
||||||
public class AppVersionModel
|
public class AppVersionModel
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 数据ID
|
/// 数据ID
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DatabaseGenerated(DatabaseGeneratedOption.None)]
|
|
||||||
[Key]
|
|
||||||
public long Id { get; set; }
|
public long Id { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -20,43 +15,36 @@ namespace Atomx.Admin.Client.Models
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 应用名称KEY
|
/// 应用名称KEY
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Column(TypeName = "varchar(64)")]
|
|
||||||
public string AppName { get; set; } = string.Empty;
|
public string AppName { get; set; } = string.Empty;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 版本标题
|
/// 版本标题
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Column(TypeName = "varchar(64)")]
|
|
||||||
public string Title { get; set; } = string.Empty;
|
public string Title { get; set; } = string.Empty;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 版本
|
/// 版本
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Column(TypeName = "varchar(64)")]
|
|
||||||
public string Version { get; set; } = string.Empty;
|
public string Version { get; set; } = string.Empty;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 主版本号(major)无法向下兼容时,需要递增
|
/// 主版本号(major)无法向下兼容时,需要递增
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Column(TypeName = "varchar(64)")]
|
|
||||||
public int VersionX { get; set; }
|
public int VersionX { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 次版本号(minor)新增新的特性时,需要递增
|
/// 次版本号(minor)新增新的特性时,需要递增
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Column(TypeName = "varchar(64)")]
|
|
||||||
public int VersionY { get; set; }
|
public int VersionY { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 修订版本号(patch)修复问题时,需要递增
|
/// 修订版本号(patch)修复问题时,需要递增
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Column(TypeName = "varchar(64)")]
|
|
||||||
public int VersionZ { get; set; }
|
public int VersionZ { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 版本日期
|
/// 版本日期
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Column(TypeName = "varchar(64)")]
|
|
||||||
public int VersionDate { get; set; }
|
public int VersionDate { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -67,7 +55,6 @@ namespace Atomx.Admin.Client.Models
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 更新内容说明
|
/// 更新内容说明
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Column(TypeName = "text")]
|
|
||||||
public string Content { get; set; } = string.Empty;
|
public string Content { get; set; } = string.Empty;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 状态
|
/// 状态
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Status { get; set; } = string.Empty;
|
public string? Status { get; set; } = string.Empty;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 开始时间
|
/// 开始时间
|
||||||
|
|||||||
8
Atomx.Admin/Atomx.Admin.Client/Models/AreaModel.cs
Normal file
8
Atomx.Admin/Atomx.Admin.Client/Models/AreaModel.cs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
using Atomx.Common.Entities;
|
||||||
|
|
||||||
|
namespace Atomx.Admin.Client.Models
|
||||||
|
{
|
||||||
|
public class AreaModel:Area
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
9
Atomx.Admin/Atomx.Admin.Client/Models/AreaSearch.cs
Normal file
9
Atomx.Admin/Atomx.Admin.Client/Models/AreaSearch.cs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
namespace Atomx.Admin.Client.Models
|
||||||
|
{
|
||||||
|
public class AreaSearch : BaseSearch
|
||||||
|
{
|
||||||
|
public long CountryId { get; set; }
|
||||||
|
public long StateProvinceId { get; set; }
|
||||||
|
public string Name { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
13
Atomx.Admin/Atomx.Admin.Client/Models/CountryModel.cs
Normal file
13
Atomx.Admin/Atomx.Admin.Client/Models/CountryModel.cs
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
using Atomx.Common.Entities;
|
||||||
|
|
||||||
|
namespace Atomx.Admin.Client.Models
|
||||||
|
{
|
||||||
|
public class CountryModel:Country
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 语言
|
||||||
|
/// </summary>
|
||||||
|
public string LanguageId { get; set; } = string.Empty;
|
||||||
|
public List<LocalizedProperty> Localized { get; set; } = new List<LocalizedProperty>();
|
||||||
|
}
|
||||||
|
}
|
||||||
7
Atomx.Admin/Atomx.Admin.Client/Models/CountrySearch.cs
Normal file
7
Atomx.Admin/Atomx.Admin.Client/Models/CountrySearch.cs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
namespace Atomx.Admin.Client.Models
|
||||||
|
{
|
||||||
|
public class CountrySearch
|
||||||
|
{
|
||||||
|
public string Name { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,7 +5,7 @@
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 数据ID
|
/// 数据ID
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int Id { get; set; }
|
public int? Id { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 语言
|
/// 语言
|
||||||
@@ -52,5 +52,10 @@
|
|||||||
/// 是否编辑
|
/// 是否编辑
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool IsEdit { get; set; }
|
public bool IsEdit { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 是否主货币
|
||||||
|
/// </summary>
|
||||||
|
public bool PrimaryCurrency { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
20
Atomx.Admin/Atomx.Admin.Client/Models/CurrencySearchModel.cs
Normal file
20
Atomx.Admin/Atomx.Admin.Client/Models/CurrencySearchModel.cs
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
namespace Atomx.Admin.Client.Models
|
||||||
|
{
|
||||||
|
public class CurrencySearchModel:BaseSearch
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 用户名
|
||||||
|
/// </summary>
|
||||||
|
public string Name { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 状态
|
||||||
|
/// </summary>
|
||||||
|
public string Status { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 开始时间
|
||||||
|
/// </summary>
|
||||||
|
public DateTime?[] RangeTime { get; set; } = new DateTime?[] { null, null };
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
namespace Atomx.Admin.Client.Models
|
|
||||||
{
|
|
||||||
public class MaterialSearch : BaseSearch
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 类型
|
|
||||||
/// </summary>
|
|
||||||
public int? Type { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 原料归属公司ID
|
|
||||||
/// </summary>
|
|
||||||
public long CorporationId { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 原料归属店铺网点ID
|
|
||||||
/// </summary>
|
|
||||||
public long StoreId { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -7,6 +7,16 @@
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public long Id { get; set; }
|
public long Id { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 站点ID
|
||||||
|
/// </summary>
|
||||||
|
public long SiteId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 语言编码
|
||||||
|
/// </summary>
|
||||||
|
public int LanguageId { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 消息模板类型
|
/// 消息模板类型
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -32,6 +42,11 @@
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public string Body { get; set; } = string.Empty;
|
public string Body { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 附件文件地址列表,多个附件以逗号分隔
|
||||||
|
/// </summary>
|
||||||
|
public string Attachments { get; set; } = string.Empty;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 是否可用
|
/// 是否可用
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -4,6 +4,8 @@
|
|||||||
{
|
{
|
||||||
public int? Type { get; set; }
|
public int? Type { get; set; }
|
||||||
|
|
||||||
|
public int? Language { get; set; }
|
||||||
|
|
||||||
public string Key { get; set; } = string.Empty;
|
public string Key { get; set; } = string.Empty;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
using Atomx.Common.Entities;
|
||||||
|
|
||||||
|
namespace Atomx.Admin.Client.Models
|
||||||
|
{
|
||||||
|
public class StateProvinceModel: StateProvince
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
11
Atomx.Admin/Atomx.Admin.Client/Models/StateProvinceSearch.cs
Normal file
11
Atomx.Admin/Atomx.Admin.Client/Models/StateProvinceSearch.cs
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
using System.Runtime.Serialization;
|
||||||
|
|
||||||
|
namespace Atomx.Admin.Client.Models
|
||||||
|
{
|
||||||
|
public class StateProvinceSearch
|
||||||
|
{
|
||||||
|
[IgnoreDataMember]
|
||||||
|
public long? CountryId { get; set; }
|
||||||
|
public string Name { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
24
Atomx.Admin/Atomx.Admin.Client/Pages/Contents/BlogEdit.razor
Normal file
24
Atomx.Admin/Atomx.Admin.Client/Pages/Contents/BlogEdit.razor
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
@page "/content/blog/create"
|
||||||
|
@page "/content/blog/edit/{id:long}"
|
||||||
|
@page "/{locale}/content/blog/create"
|
||||||
|
@page "/{locale}/content/blog/edit/{id:long}"
|
||||||
|
|
||||||
|
<PageContainer Title="@(Id > 0 ? "编辑博客文章" : "新增博客文章")">
|
||||||
|
<Breadcrumb>
|
||||||
|
<Breadcrumb>
|
||||||
|
<BreadcrumbItem Href="/">管理后台</BreadcrumbItem>
|
||||||
|
<BreadcrumbItem Href="/content/page/list">内容管理</BreadcrumbItem>
|
||||||
|
<BreadcrumbItem>博客文章</BreadcrumbItem>
|
||||||
|
</Breadcrumb>
|
||||||
|
</Breadcrumb>
|
||||||
|
<ChildContent>
|
||||||
|
<h3>Tools</h3>
|
||||||
|
</ChildContent>
|
||||||
|
</PageContainer>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
[Parameter]
|
||||||
|
public string Locale { get; set; } = string.Empty;
|
||||||
|
[Parameter]
|
||||||
|
public long? Id { get; set; }
|
||||||
|
}
|
||||||
20
Atomx.Admin/Atomx.Admin.Client/Pages/Contents/BlogList.razor
Normal file
20
Atomx.Admin/Atomx.Admin.Client/Pages/Contents/BlogList.razor
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
@page "/content/blog/list"
|
||||||
|
@page "/{locale}/content/blog/list"
|
||||||
|
|
||||||
|
<PageContainer Title="博客文章">
|
||||||
|
<Breadcrumb>
|
||||||
|
<Breadcrumb>
|
||||||
|
<BreadcrumbItem Href="/">管理后台</BreadcrumbItem>
|
||||||
|
<BreadcrumbItem Href="/content/page/list">内容管理</BreadcrumbItem>
|
||||||
|
<BreadcrumbItem>博客文章</BreadcrumbItem>
|
||||||
|
</Breadcrumb>
|
||||||
|
</Breadcrumb>
|
||||||
|
<ChildContent>
|
||||||
|
<h3>Tools</h3>
|
||||||
|
</ChildContent>
|
||||||
|
</PageContainer>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
[Parameter]
|
||||||
|
public string Locale { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
@@ -1,5 +1,24 @@
|
|||||||
<h3>PageEdit</h3>
|
@page "/content/page/create"
|
||||||
|
@page "/content/page/edit/{id:long}"
|
||||||
|
@page "/{locale}/content/page/create"
|
||||||
|
@page "/{locale}/content/page/edit/{id:long}"
|
||||||
|
|
||||||
|
<PageContainer Title="@(Id > 0 ? "编辑主题页面" : "新增主题页面")">
|
||||||
|
<Breadcrumb>
|
||||||
|
<Breadcrumb>
|
||||||
|
<BreadcrumbItem Href="/">管理后台</BreadcrumbItem>
|
||||||
|
<BreadcrumbItem Href="/content/page/list">内容管理</BreadcrumbItem>
|
||||||
|
<BreadcrumbItem>主题页面</BreadcrumbItem>
|
||||||
|
</Breadcrumb>
|
||||||
|
</Breadcrumb>
|
||||||
|
<ChildContent>
|
||||||
|
<h3>Tools</h3>
|
||||||
|
</ChildContent>
|
||||||
|
</PageContainer>
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
|
[Parameter]
|
||||||
|
public string Locale { get; set; } = string.Empty;
|
||||||
|
[Parameter]
|
||||||
|
public long? Id { get; set; }
|
||||||
}
|
}
|
||||||
@@ -1,8 +1,20 @@
|
|||||||
@page "/content/page/list"
|
@page "/content/page/list"
|
||||||
|
@page "/{locale}/content/page/list"
|
||||||
|
|
||||||
|
<PageContainer Title="主题页面">
|
||||||
<h3>PageList</h3>
|
<Breadcrumb>
|
||||||
|
<Breadcrumb>
|
||||||
|
<BreadcrumbItem Href="/">管理后台</BreadcrumbItem>
|
||||||
|
<BreadcrumbItem Href="/content/page/list">内容管理</BreadcrumbItem>
|
||||||
|
<BreadcrumbItem>主题页面</BreadcrumbItem>
|
||||||
|
</Breadcrumb>
|
||||||
|
</Breadcrumb>
|
||||||
|
<ChildContent>
|
||||||
|
<h3>Tools</h3>
|
||||||
|
</ChildContent>
|
||||||
|
</PageContainer>
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
|
[Parameter]
|
||||||
|
public string Locale { get; set; } = string.Empty;
|
||||||
}
|
}
|
||||||
@@ -1,18 +1,69 @@
|
|||||||
@page "/counter"
|
@page "/counter"
|
||||||
|
@page "/{locale}/counter"
|
||||||
|
|
||||||
<PageTitle>Counter</PageTitle>
|
@using Microsoft.Extensions.Localization
|
||||||
|
@inject IStringLocalizer<Counter> L
|
||||||
|
@inject Atomx.Admin.Client.Services.ILocalizationProvider LocalizationProvider
|
||||||
|
|
||||||
<h1>Counter</h1>
|
<PageTitle>@L["site.name"]</PageTitle>
|
||||||
|
|
||||||
<p role="status">Current count: @currentCount</p>
|
<h1>@L["site.name"]</h1>
|
||||||
|
|
||||||
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
|
<p role="status">@(L["current.count"] ?? "Current count"): @currentCount</p>
|
||||||
|
|
||||||
|
<button class="btn btn-primary" @onclick="IncrementCount">@(L["click.me"] ?? "Click me")</button>
|
||||||
|
|
||||||
|
<Atomx.Admin.Client.Components.LangSelector />
|
||||||
|
|
||||||
|
<div style="margin-top:16px;">
|
||||||
|
<strong>Quick links:</strong>
|
||||||
|
<span style="padding-left:10px;"><NavLink href="/account/login">Login</NavLink></span>
|
||||||
|
<span style="padding-left:10px;"><NavLink href="/weather">Weather</NavLink></span>
|
||||||
|
</div>
|
||||||
|
<div style="margin-top:16px;">
|
||||||
|
<strong>zh Quick links:</strong>
|
||||||
|
<span style="padding-left:10px;"><NavLink href="/zh/account/login">Login</NavLink></span>
|
||||||
|
<span style="padding-left:10px;"><NavLink href="/zh/weather">Weather</NavLink></span>
|
||||||
|
</div>
|
||||||
|
<div style="margin-top:16px;">
|
||||||
|
<strong>en Quick links:</strong>
|
||||||
|
<span style="padding-left:10px;"><NavLink href="/en/account/login">Login</NavLink></span>
|
||||||
|
<span style="padding-left:10px;"><NavLink href="/en/weather">Weather</NavLink></span>
|
||||||
|
</div>
|
||||||
@code {
|
@code {
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public string Locale { get; set; } = string.Empty;
|
||||||
|
|
||||||
private int currentCount = 0;
|
private int currentCount = 0;
|
||||||
|
|
||||||
|
protected override Task OnInitializedAsync()
|
||||||
|
{
|
||||||
|
// localization handled globally in Routes. No per-page initialization needed.
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnLanguageChanged(object? sender, string culture)
|
||||||
|
{
|
||||||
|
// no-op; global router remount will update page translations
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
// no per-page unsubscribe required
|
||||||
|
}
|
||||||
|
|
||||||
private void IncrementCount()
|
private void IncrementCount()
|
||||||
{
|
{
|
||||||
currentCount++;
|
currentCount++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
123
Atomx.Admin/Atomx.Admin.Client/Pages/DebugLocalization.razor
Normal file
123
Atomx.Admin/Atomx.Admin.Client/Pages/DebugLocalization.razor
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
@page "/debug/localization"
|
||||||
|
@inject Atomx.Admin.Client.Services.ILocalizationProvider LocalizationProvider
|
||||||
|
@inject IHttpClientFactory HttpClientFactory
|
||||||
|
@inject IJSRuntime JS
|
||||||
|
@inject ILogger<DebugLocalization> Logger
|
||||||
|
@inject HttpClient Http
|
||||||
|
@inject NavigationManager Nav
|
||||||
|
|
||||||
|
<h3>Localization Debug</h3>
|
||||||
|
|
||||||
|
<p>CurrentCulture: <b>@LocalizationProvider.CurrentCulture</b></p>
|
||||||
|
|
||||||
|
<button @onclick="SetProviderZh">Set culture zh</button>
|
||||||
|
<button @onclick="SetProviderEn">Set culture en</button>
|
||||||
|
<button @onclick="FetchFiles">Fetch files via HttpClient</button>
|
||||||
|
<button @onclick="ReadCookie">Read cookie</button>
|
||||||
|
|
||||||
|
<h4>Provider Results</h4>
|
||||||
|
<ul>
|
||||||
|
@foreach (var kv in providerResults)
|
||||||
|
{
|
||||||
|
<li>@kv</li>
|
||||||
|
}
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h4>HTTP Fetch Results</h4>
|
||||||
|
<ul>
|
||||||
|
@foreach (var kv in httpResults)
|
||||||
|
{
|
||||||
|
<li>@kv</li>
|
||||||
|
}
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h4>JS Read</h4>
|
||||||
|
<p>@jsResult</p>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
private List<string> providerResults = new();
|
||||||
|
private List<string> httpResults = new();
|
||||||
|
private string jsResult = string.Empty;
|
||||||
|
|
||||||
|
private async Task SetProviderZh()
|
||||||
|
{
|
||||||
|
providerResults.Clear();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Use short code; provider will map to full culture and set active culture
|
||||||
|
await LocalizationProvider.SetCultureAsync("zh");
|
||||||
|
providerResults.Add($"Set culture to {LocalizationProvider.CurrentCulture}");
|
||||||
|
providerResults.Add($"site.name={LocalizationProvider.GetString("site.name")}");
|
||||||
|
providerResults.Add($"login.title={LocalizationProvider.GetString("login.title")}");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
providerResults.Add("Set provider zh failed: " + ex.Message);
|
||||||
|
Logger.LogError(ex, "Set provider zh failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task SetProviderEn()
|
||||||
|
{
|
||||||
|
providerResults.Clear();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await LocalizationProvider.SetCultureAsync("en");
|
||||||
|
providerResults.Add($"Set culture to {LocalizationProvider.CurrentCulture}");
|
||||||
|
providerResults.Add($"site.name={LocalizationProvider.GetString("site.name")}");
|
||||||
|
providerResults.Add($"login.title={LocalizationProvider.GetString("login.title")}");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
providerResults.Add("Set provider en failed: " + ex.Message);
|
||||||
|
Logger.LogError(ex, "Set provider en failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task FetchFiles()
|
||||||
|
{
|
||||||
|
httpResults.Clear();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Prefer injected HttpClient that has BaseAddress set in Program.cs for WASM
|
||||||
|
var client = Http ?? HttpClientFactory.CreateClient();
|
||||||
|
|
||||||
|
var urlZ = new Uri(new Uri(Nav.BaseUri), $"localization/zh-Hans.json");
|
||||||
|
httpResults.Add($"GET {urlZ}");
|
||||||
|
var resZ = await client.GetAsync(urlZ);
|
||||||
|
httpResults.Add($"=> {resZ.StatusCode}");
|
||||||
|
if (resZ.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
var txt = await resZ.Content.ReadAsStringAsync();
|
||||||
|
httpResults.Add("zh content len=" + txt.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
var urlE = new Uri(new Uri(Nav.BaseUri), $"localization/en-US.json");
|
||||||
|
httpResults.Add($"GET {urlE}");
|
||||||
|
var resE = await client.GetAsync(urlE);
|
||||||
|
httpResults.Add($"=> {resE.StatusCode}");
|
||||||
|
if (resE.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
var txt = await resE.Content.ReadAsStringAsync();
|
||||||
|
httpResults.Add("en content len=" + txt.Length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
httpResults.Add("Fetch error: " + ex.Message);
|
||||||
|
Logger.LogError(ex, "FetchFiles error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ReadCookie()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
jsResult = await JS.InvokeAsync<string>("cookies.Read", "atomx.culture");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
jsResult = "JS error: " + ex.Message;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,24 +1,38 @@
|
|||||||
@page "/"
|
@page "/"
|
||||||
|
@page "/{locale}/"
|
||||||
@attribute [Authorize]
|
@attribute [Authorize]
|
||||||
|
<PageContainer Title="控制台首页">
|
||||||
|
|
||||||
<PageTitle>Home</PageTitle>
|
|
||||||
|
|
||||||
<h1>Hello, world!</h1>
|
|
||||||
|
|
||||||
Welcome to your new app.
|
<h1>Hello, world!</h1>
|
||||||
|
|
||||||
<li>
|
Welcome to your new app.
|
||||||
<a href="/category/list">产品分类</a>
|
|
||||||
</li>
|
<li>
|
||||||
<li>
|
<a href="/category/list">产品分类</a>
|
||||||
<a href="/product/category/edit">产品分类编辑</a>
|
</li>
|
||||||
</li>
|
<li>
|
||||||
<li>
|
<a href="/product/category/edit">产品分类编辑</a>
|
||||||
<a href="/system/language/list">多语言设置</a>
|
</li>
|
||||||
</li>
|
<li>
|
||||||
<li>
|
<a href="/system/language/list">多语言设置</a>
|
||||||
<a href="/system/locale/resource/list">多语言资源设置</a>
|
</li>
|
||||||
</li>
|
<li>
|
||||||
<li>
|
<a href="/system/locale/resource/list">多语言资源设置</a>
|
||||||
<a href="/system/role/list">角色管理</a>
|
</li>
|
||||||
</li>
|
<li>
|
||||||
|
<a href="/system/role/list">角色管理</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="/currency/list">货币设置</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="/country/list">国家管理</a>
|
||||||
|
</li>
|
||||||
|
</PageContainer>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
[Parameter]
|
||||||
|
public string Locale { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
@page "/account/login"
|
@page "/account/login"
|
||||||
@page "/{locale}/account/login"
|
@page "/{locale}/account/login"
|
||||||
@using System.Text.Json
|
|
||||||
@layout EmptyLayout
|
@layout EmptyLayout
|
||||||
@inject ILogger<Login> Logger
|
@inject ILogger<Login> Logger
|
||||||
@inject IJSRuntime JS
|
@inject IStringLocalizer<Login> L
|
||||||
|
|
||||||
<PageTitle>登录</PageTitle>
|
|
||||||
|
<PageTitle>@L["login.title"]</PageTitle>
|
||||||
|
|
||||||
@if (!dataLoaded)
|
@if (!dataLoaded)
|
||||||
{
|
{
|
||||||
@@ -20,29 +20,29 @@ else
|
|||||||
<Form @ref="form" Model="@login" OnFinish="LoginAsync">
|
<Form @ref="form" Model="@login" OnFinish="LoginAsync">
|
||||||
<FluentValidationValidator />
|
<FluentValidationValidator />
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<AntDesign.Input Placeholder="登录账号" Size="InputSize.Large" @bind-Value="@login.Account">
|
<AntDesign.Input Placeholder="@L["login.account.placeholder"]" Size="InputSize.Large" @bind-Value="@login.Account">
|
||||||
<Prefix><Icon Type="user" /></Prefix>
|
<Prefix><Icon Type="user" /></Prefix>
|
||||||
</AntDesign.Input>
|
</AntDesign.Input>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<AntDesign.Input Placeholder="登录密码" Size="InputSize.Large" @bind-Value="@login.Password" Type="InputType.Password">
|
<AntDesign.Input Placeholder="@L["login.password.placeholder"]" Size="InputSize.Large" @bind-Value="@login.Password" Type="InputType.Password">
|
||||||
<Prefix><Icon Type="lock" /></Prefix>
|
<Prefix><Icon Type="lock" /></Prefix>
|
||||||
</AntDesign.Input>
|
</AntDesign.Input>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<a style="float: left;">
|
<a style="float: left;">
|
||||||
忘记密码
|
@L["login.forgot"]
|
||||||
</a>
|
</a>
|
||||||
<a style="float: right;">
|
<a style="float: right;">
|
||||||
<NavLink href="/register">马上注册</NavLink>
|
<NavLink href="/register">@L["login.register"]</NavLink>
|
||||||
</a>
|
</a>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<Button Type="ButtonType.Primary" HtmlType="submit" Class="submit" Size="ButtonSize.Large" Block>登录</Button>
|
<Button Type="ButtonType.Primary" HtmlType="submit" Class="submit" Size="ButtonSize.Large" Block>@L["login.submit"]</Button>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<a @onclick="setAccount">
|
<a @onclick="setAccount">
|
||||||
设置开发帐号
|
@L["login.setdev"]
|
||||||
</a>
|
</a>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
</Form>
|
</Form>
|
||||||
@@ -50,9 +50,31 @@ else
|
|||||||
</GridCol>
|
</GridCol>
|
||||||
</GridRow>
|
</GridRow>
|
||||||
<GridRow Style="padding-top:40px">
|
<GridRow Style="padding-top:40px">
|
||||||
<span style="font-size:12px;padding-right:3px;">Copyright © 2025-@DateTime.Now.Year Atomlust.com All rights reserved.</span>
|
<span style="font-size:12px;padding-right:3px;">@L["copyright"]</span>
|
||||||
<span style="font-size:12px">runing as @handler</span>
|
<span style="font-size:12px">runing as @handler</span>
|
||||||
</GridRow>
|
</GridRow>
|
||||||
|
|
||||||
|
<GridRow>
|
||||||
|
<Atomx.Admin.Client.Components.LangSelector />
|
||||||
|
</GridRow>
|
||||||
|
|
||||||
|
<GridRow Style="padding-top:10px">
|
||||||
|
<div>
|
||||||
|
<strong>Quick links:</strong>
|
||||||
|
<span style="padding-left:10px;"><NavLink href="/counter">Counter</NavLink></span>
|
||||||
|
<span style="padding-left:10px;"><NavLink href="/weather">Weather</NavLink></span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<strong>zh Quick links:</strong>
|
||||||
|
<span style="padding-left:10px;"><NavLink href="/zh/counter">Counter</NavLink></span>
|
||||||
|
<span style="padding-left:10px;"><NavLink href="/zh/weather">Weather</NavLink></span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<strong>en Quick links:</strong>
|
||||||
|
<span style="padding-left:10px;"><NavLink href="/en/counter">Counter</NavLink></span>
|
||||||
|
<span style="padding-left:10px;"><NavLink href="/en/weather">Weather</NavLink></span>
|
||||||
|
</div>
|
||||||
|
</GridRow>
|
||||||
</Flex>
|
</Flex>
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -119,41 +141,30 @@ else
|
|||||||
if (!OperatingSystem.IsBrowser())
|
if (!OperatingSystem.IsBrowser())
|
||||||
{
|
{
|
||||||
// Server 模式:使用浏览器发起的 fetch(通过 JS)并携带 credentials: 'include'
|
// Server 模式:使用浏览器发起的 fetch(通过 JS)并携带 credentials: 'include'
|
||||||
Logger.LogInformation("Server 模式,使用浏览器 fetch 登录");
|
var jsResult = await JS.InvokeAsync<JsonElement>("ajax.Post", api, login);
|
||||||
var jsResult = await JS.InvokeAsync<JsonElement>("__atomx_post_json", api, login);
|
var result = jsResult.ToJson().FromJson<ApiResult<AuthResponse>>();
|
||||||
|
if (result != null && result.Success)
|
||||||
var success = jsResult.TryGetProperty("success", out var sprop) && sprop.GetBoolean();
|
|
||||||
if (success && jsResult.TryGetProperty("data", out var dprop) && dprop.ValueKind == JsonValueKind.Object)
|
|
||||||
{
|
{
|
||||||
var token = dprop.TryGetProperty("token", out var t) ? t.GetString() ?? string.Empty : string.Empty;
|
var auth = result.Data;
|
||||||
var refresh = dprop.TryGetProperty("refreshToken", out var r) ? r.GetString() ?? string.Empty : string.Empty;
|
await localStorage.SetItemAsync(StorageKeys.AccessToken, auth.Token);
|
||||||
|
await localStorage.SetItemAsync(StorageKeys.RefreshToken, auth.RefreshToken);
|
||||||
|
|
||||||
// WASM 的 localStorage 在 Server Circuit 中无意义,兼容auto模式写入 localStorage。
|
if (AuthStateProvider is PersistentAuthenticationStateProvider provider)
|
||||||
try
|
|
||||||
{
|
{
|
||||||
await localStorage.SetItemAsync(StorageKeys.AccessToken, token);
|
provider.UpdateAuthenticationState(auth.Token);
|
||||||
await localStorage.SetItemAsync(StorageKeys.RefreshToken, refresh);
|
|
||||||
|
|
||||||
if (AuthStateProvider is PersistentAuthenticationStateProvider provider)
|
|
||||||
{
|
|
||||||
provider.UpdateAuthenticationState(token);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch { }
|
|
||||||
|
|
||||||
// 浏览器已通过 fetch 收到 Set-Cookie;强制重载使 Circuit 使用新 Cookie。
|
|
||||||
Logger.LogInformation($"登录成功,server 跳转: {ReturnUrl}");
|
Logger.LogInformation($"登录成功,server 跳转: {ReturnUrl}");
|
||||||
Navigation.NavigateTo(ReturnUrl ?? "/", forceLoad: true);
|
Navigation.NavigateTo(ReturnUrl ?? "/", forceLoad: true);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var msg = jsResult.TryGetProperty("message", out var m) ? m.GetString() ?? "登录失败" : "登录失败";
|
ModalService.Error(new ConfirmOptions() { Title = "提示", Content = result.Message });
|
||||||
ModalService.Error(new ConfirmOptions() { Title = "提示", Content = msg });
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Wasm 模式:继续使用 HttpService(之前逻辑),保存 localStorage 并更新 AuthStateProvider
|
// Wasm 模式:保存 localStorage 并更新 AuthStateProvider
|
||||||
var result = await HttpService.Post<ApiResult<AuthResponse>>(api, login);
|
var result = await HttpService.Post<ApiResult<AuthResponse>>(api, login);
|
||||||
if (result.Success && result.Data != null)
|
if (result.Success && result.Data != null)
|
||||||
{
|
{
|
||||||
@@ -201,25 +212,3 @@ else
|
|||||||
login.Password = "admin888";
|
login.Password = "admin888";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@* 页面内 JS 辅助:用于在 Server 模式下从浏览器发起 POST 并携带凭证,使浏览器接收 Set-Cookie *@
|
|
||||||
<script>
|
|
||||||
window.__atomx_post_json = async function (url, data) {
|
|
||||||
try {
|
|
||||||
const res = await fetch(url, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
credentials: 'include',
|
|
||||||
body: JSON.stringify(data)
|
|
||||||
});
|
|
||||||
const text = await res.text();
|
|
||||||
try {
|
|
||||||
return JSON.parse(text);
|
|
||||||
} catch {
|
|
||||||
return { success: res.ok, message: text };
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
return { success: false, message: err?.toString() ?? 'network error' };
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
@page "/deposit/list"
|
||||||
|
@page "/{locale}/deposit/list"
|
||||||
|
|
||||||
|
<PageContainer Title="储值订单">
|
||||||
|
<Breadcrumb>
|
||||||
|
<Breadcrumb>
|
||||||
|
<BreadcrumbItem Href="/">管理后台</BreadcrumbItem>
|
||||||
|
<BreadcrumbItem Href="/content/page/list">内容管理</BreadcrumbItem>
|
||||||
|
<BreadcrumbItem>储值订单</BreadcrumbItem>
|
||||||
|
</Breadcrumb>
|
||||||
|
</Breadcrumb>
|
||||||
|
<ChildContent>
|
||||||
|
<h3>Tools</h3>
|
||||||
|
</ChildContent>
|
||||||
|
</PageContainer>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
[Parameter]
|
||||||
|
public string Locale { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
@page "/deposit/detail/{id:long}"
|
||||||
|
@page "/{locale}/deposit/detail/{id:long}"
|
||||||
|
|
||||||
|
<PageContainer Title="@($"订单{Id}详情")">
|
||||||
|
<Breadcrumb>
|
||||||
|
<Breadcrumb>
|
||||||
|
<BreadcrumbItem Href="/">管理后台</BreadcrumbItem>
|
||||||
|
<BreadcrumbItem Href="/content/page/list">内容管理</BreadcrumbItem>
|
||||||
|
<BreadcrumbItem>主题页面</BreadcrumbItem>
|
||||||
|
</Breadcrumb>
|
||||||
|
</Breadcrumb>
|
||||||
|
<ChildContent>
|
||||||
|
<h3>Tools</h3>
|
||||||
|
</ChildContent>
|
||||||
|
</PageContainer>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
[Parameter]
|
||||||
|
public string Locale { get; set; } = string.Empty;
|
||||||
|
[Parameter]
|
||||||
|
public long? Id { get; set; }
|
||||||
|
}
|
||||||
@@ -1,5 +1,20 @@
|
|||||||
<h3>OrderList</h3>
|
@page "/order/list"
|
||||||
|
@page "/{locale}/order/list"
|
||||||
|
|
||||||
|
<PageContainer Title="购物订单">
|
||||||
|
<Breadcrumb>
|
||||||
|
<Breadcrumb>
|
||||||
|
<BreadcrumbItem Href="/">管理后台</BreadcrumbItem>
|
||||||
|
<BreadcrumbItem Href="/content/page/list">内容管理</BreadcrumbItem>
|
||||||
|
<BreadcrumbItem>购物订单</BreadcrumbItem>
|
||||||
|
</Breadcrumb>
|
||||||
|
</Breadcrumb>
|
||||||
|
<ChildContent>
|
||||||
|
<h3>Tools</h3>
|
||||||
|
</ChildContent>
|
||||||
|
</PageContainer>
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
|
[Parameter]
|
||||||
|
public string Locale { get; set; } = string.Empty;
|
||||||
}
|
}
|
||||||
@@ -1,18 +1,25 @@
|
|||||||
@page "/product/category/edit"
|
@page "/product/category/edit"
|
||||||
@page "/product/category/edit/{Id:long?}"
|
@page "/product/category/edit/{Id:long?}"
|
||||||
|
|
||||||
|
@page "/{locale}/product/category/edit"
|
||||||
|
@page "/{locale}/product/category/edit/{Id:long?}"
|
||||||
|
|
||||||
@inject ILogger<CategoryEdit> Logger
|
@inject ILogger<CategoryEdit> Logger
|
||||||
@attribute [Authorize]
|
@attribute [Authorize]
|
||||||
|
|
||||||
<PageTitle>分类编辑</PageTitle>
|
<PageContainer Title="编辑产品分类">
|
||||||
<Title Level="4">分类信息</Title>
|
<Breadcrumb>
|
||||||
|
<Breadcrumb>
|
||||||
<Spin Spinning="pageLoading">
|
<BreadcrumbItem Href="/">管理后台</BreadcrumbItem>
|
||||||
<Card Title="" Class="hideborder"
|
<BreadcrumbItem Href="/product/list">系统功能</BreadcrumbItem>
|
||||||
Style="margin-top: 24px;"
|
<BreadcrumbItem>分类管理</BreadcrumbItem>
|
||||||
BodyStyle="padding: 0 32px 40px 32px">
|
</Breadcrumb>
|
||||||
<Form @ref="editform" Model="@model" LabelColSpan="5" WrapperColSpan="19" OnFinish="OnFormFinishAsync">
|
</Breadcrumb>
|
||||||
@* @if (languages.Count > 1 && Id > 0)
|
<ChildContent>
|
||||||
|
<Spin Spinning="pageLoading">
|
||||||
|
<Card Title="分类信息">
|
||||||
|
<Form @ref="editform" Model="@model" LabelColSpan="5" WrapperColSpan="19" OnFinish="OnFormFinishAsync">
|
||||||
|
@* @if (languages.Count > 1 && Id > 0)
|
||||||
{
|
{
|
||||||
<Tabs ActiveKey="@context.Language" OnTabClick="OnLanguageTabChange">
|
<Tabs ActiveKey="@context.Language" OnTabClick="OnLanguageTabChange">
|
||||||
<TabPane Key="0">
|
<TabPane Key="0">
|
||||||
@@ -59,28 +66,33 @@
|
|||||||
}
|
}
|
||||||
</Button> <Button Size="@ButtonSize.Large" Type="@ButtonType.Text" Icon="@formModel.Image"></Button>
|
</Button> <Button Size="@ButtonSize.Large" Type="@ButtonType.Text" Icon="@formModel.Image"></Button>
|
||||||
</FormItem> *@
|
</FormItem> *@
|
||||||
<FormItem Label="Meta描述">
|
<FormItem Label="Meta描述">
|
||||||
<Input @bind-Value="@context.MetaDescription" Placeholder="Meta描述" />
|
<Input @bind-Value="@context.MetaDescription" Placeholder="Meta描述" />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
<FormItem Label="Meta关键词">
|
<FormItem Label="Meta关键词">
|
||||||
<Input @bind-Value="@context.MetaKeywords" Placeholder="Meta关键词" />
|
<Input @bind-Value="@context.MetaKeywords" Placeholder="Meta关键词" />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
<FormItem Label="显示排序">
|
<FormItem Label="显示排序">
|
||||||
<Input @bind-Value="@context.DisplayOrder" Placeholder="显示排序" />
|
<Input @bind-Value="@context.DisplayOrder" Placeholder="显示排序" />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
<FormItem Label="状态">
|
<FormItem Label="状态">
|
||||||
<Checkbox @bind-Checked="@context.Enabled">启用</Checkbox>
|
<Checkbox @bind-Checked="@context.Enabled">启用</Checkbox>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
<FormItem WrapperCol="new ColLayoutParam { Span = 24, Offset = 5 }">
|
<FormItem WrapperCol="new ColLayoutParam { Span = 24, Offset = 5 }">
|
||||||
<Button Type="@ButtonType.Primary" HtmlType="submit">
|
<Button Type="@ButtonType.Primary" HtmlType="submit">
|
||||||
提交保存
|
提交保存
|
||||||
</Button>
|
</Button>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
</Form>
|
</Form>
|
||||||
</Card>
|
</Card>
|
||||||
</Spin>
|
</Spin>
|
||||||
|
</ChildContent>
|
||||||
|
</PageContainer>
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
|
[Parameter]
|
||||||
|
public string Locale { get; set; } = string.Empty;
|
||||||
|
|
||||||
[SupplyParameterFromQuery]
|
[SupplyParameterFromQuery]
|
||||||
int? Lang { get; set; }
|
int? Lang { get; set; }
|
||||||
|
|
||||||
|
|||||||
@@ -1,141 +1,153 @@
|
|||||||
@page "/category/list"
|
@page "/category/list"
|
||||||
|
@page "/{locale}/category/list"
|
||||||
|
|
||||||
@inject ILogger<CategoryList> Logger
|
@inject ILogger<CategoryList> Logger
|
||||||
@attribute [Authorize]
|
@attribute [Authorize]
|
||||||
|
|
||||||
<PageTitle>分类管理</PageTitle>
|
<PageContainer Title="产品分类管理">
|
||||||
<Title Level="4">菜单管理</Title>
|
<Breadcrumb>
|
||||||
<Card>
|
<Breadcrumb>
|
||||||
<Form @ref="searchForm" Model="search" Layout="FormLayout.Inline" Class="search-form" OnFinish="OnSearchFinish">
|
<BreadcrumbItem Href="/">管理后台</BreadcrumbItem>
|
||||||
<Row Justify="RowJustify.Start" Gutter="16">
|
<BreadcrumbItem Href="/product/list">系统功能</BreadcrumbItem>
|
||||||
<Col>
|
<BreadcrumbItem>分类管理</BreadcrumbItem>
|
||||||
<FormItem Label="名称">
|
</Breadcrumb>
|
||||||
<Input @bind-Value="search.Name" Placeholder="名称" AllowClear />
|
</Breadcrumb>
|
||||||
</FormItem>
|
<ChildContent>
|
||||||
</Col>
|
<Card>
|
||||||
|
<Form @ref="searchForm" Model="search" Layout="FormLayout.Inline" Class="search-form" OnFinish="OnSearchFinish">
|
||||||
|
<Row Justify="RowJustify.Start" Gutter="16">
|
||||||
|
<Col>
|
||||||
|
<FormItem Label="名称">
|
||||||
|
<Input @bind-Value="search.Name" Placeholder="名称" AllowClear />
|
||||||
|
</FormItem>
|
||||||
|
</Col>
|
||||||
|
|
||||||
<Col>
|
<Col>
|
||||||
<div class="ant-form-item" style="width:200px;display:flex;">
|
<div class="ant-form-item" style="width:200px;display:flex;">
|
||||||
<Button Type="ButtonType.Primary" HtmlType="submit">查询</Button>
|
<Button Type="ButtonType.Primary" HtmlType="submit">查询</Button>
|
||||||
<Button Style="margin: 0 8px;" OnClick="OnSearchReset">重置</Button>
|
<Button Style="margin: 0 8px;" OnClick="OnSearchReset">重置</Button>
|
||||||
</div>
|
</div>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
</Form>
|
</Form>
|
||||||
</Card>
|
</Card>
|
||||||
<Card Class="mt-3">
|
<br/>
|
||||||
<Table DataSource="categories" PageSize="100" HidePagination="true" Resizable>
|
<Card Class="mt-3">
|
||||||
<TitleTemplate>
|
<Table DataSource="categories" PageSize="100" HidePagination="true" Resizable>
|
||||||
<Flex Justify="FlexJustify.SpaceBetween">
|
<TitleTemplate>
|
||||||
菜单列表
|
<Flex Justify="FlexJustify.SpaceBetween">
|
||||||
<div>
|
菜单列表
|
||||||
<Button Class="me-3" OnClick="OnCreateClick" Type="ButtonType.Primary">新增</Button>
|
<div>
|
||||||
</div>
|
<Button Class="me-3" OnClick="OnCreateClick" Type="ButtonType.Primary">新增</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
</Flex>
|
</Flex>
|
||||||
</TitleTemplate>
|
</TitleTemplate>
|
||||||
<ColumnDefinitions>
|
<ColumnDefinitions>
|
||||||
<PropertyColumn Property="c=>c.Name" Title="名称">
|
<PropertyColumn Property="c => c.Name" Title="名称">
|
||||||
<AntDesign.Text>@GetName(context)</AntDesign.Text>
|
<AntDesign.Text>@GetName(context)</AntDesign.Text>
|
||||||
</PropertyColumn>
|
</PropertyColumn>
|
||||||
<PropertyColumn Property="c=>c.Slug" Title="缩略名" Width="80px" Align="ColumnAlign.Center">
|
<PropertyColumn Property="c => c.Slug" Title="缩略名" Width="80px" Align="ColumnAlign.Center">
|
||||||
|
|
||||||
</PropertyColumn>
|
</PropertyColumn>
|
||||||
<PropertyColumn Property="c=>c.DisplayOrder" Title="排序" Width="100px" />
|
<PropertyColumn Property="c => c.DisplayOrder" Title="排序" Width="100px" />
|
||||||
<PropertyColumn Property="c=>c.Enabled" Title="状态" Width="80px" Align="ColumnAlign.Center">
|
<PropertyColumn Property="c => c.Enabled" Title="状态" Width="80px" Align="ColumnAlign.Center">
|
||||||
@if (context.Enabled)
|
@if (context.Enabled)
|
||||||
{
|
{
|
||||||
<AntDesign.Text Type="TextElementType.Success"><Icon Type="check" Theme=" IconThemeType.Outline" Width="1.3em" Height="1.3em" /></AntDesign.Text>
|
<AntDesign.Text Type="TextElementType.Success"><Icon Type="check" Theme=" IconThemeType.Outline" Width="1.3em" Height="1.3em" /></AntDesign.Text>
|
||||||
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<Icon Type="close" Theme="IconThemeType.Outline" Width="1.3em" Height="1.3em" />
|
<Icon Type="close" Theme="IconThemeType.Outline" Width="1.3em" Height="1.3em" />
|
||||||
}
|
}
|
||||||
</PropertyColumn>
|
</PropertyColumn>
|
||||||
<PropertyColumn Property="c=>c.CreateTime" Title="时间" Width="190px" />
|
<PropertyColumn Property="c => c.CreateTime" Title="时间" Width="190px" />
|
||||||
<ActionColumn Title="操作" Align="ColumnAlign.Right" Width="160px">
|
<ActionColumn Title="操作" Align="ColumnAlign.Right" Width="160px">
|
||||||
<Space>
|
<Space>
|
||||||
@if (context.IsLast)
|
@if (context.IsLast)
|
||||||
{
|
{
|
||||||
<SpaceItem>
|
<SpaceItem>
|
||||||
<a Href="@($"/attribute/list?categoryid={context.Id}")">属性</a>
|
<a Href="@($"/attribute/list?categoryid={context.Id}")">属性</a>
|
||||||
|
|
||||||
</SpaceItem>
|
</SpaceItem>
|
||||||
<SpaceItem>
|
<SpaceItem>
|
||||||
<a Href="@($"/specification/list?categoryid={context.Id}")">规格</a>
|
<a Href="@($"/specification/list?categoryid={context.Id}")">规格</a>
|
||||||
|
|
||||||
</SpaceItem>
|
</SpaceItem>
|
||||||
}
|
}
|
||||||
|
|
||||||
<SpaceItem>
|
<SpaceItem>
|
||||||
<Dropdown Trigger="@(new Trigger[] { Trigger.Click })">
|
<Dropdown Trigger="@(new Trigger[] { Trigger.Click })">
|
||||||
<Overlay>
|
<Overlay>
|
||||||
<Menu>
|
<Menu>
|
||||||
|
|
||||||
<MenuItem>
|
<MenuItem>
|
||||||
<a @onclick="(e)=>OnEditClick(context)"> <Icon Type="@IconType.Outline.Edit" /> 编辑</a>
|
<a @onclick="(e) => OnEditClick(context)"> <Icon Type="@IconType.Outline.Edit" /> 编辑</a>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuDivider />
|
<MenuDivider />
|
||||||
<MenuItem>
|
<MenuItem>
|
||||||
<Popconfirm Placement="@Placement.Left" Title="@("删除这条数据无法恢复,您确定要删除吗?")"
|
<Popconfirm Placement="@Placement.Left" Title="@("删除这条数据无法恢复,您确定要删除吗?")"
|
||||||
OnConfirm="@(e=>HandleDeleteConfirmAsync(e,context.Id))"
|
OnConfirm="@(e=>HandleDeleteConfirmAsync(e,context.Id))"
|
||||||
OkText="确定"
|
OkText="确定"
|
||||||
CancelText="取消">
|
CancelText="取消">
|
||||||
<a> <Icon Type="@IconType.Outline.Delete" /> 删除</a>
|
<a> <Icon Type="@IconType.Outline.Delete" /> 删除</a>
|
||||||
</Popconfirm>
|
</Popconfirm>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
</Menu>
|
</Menu>
|
||||||
</Overlay>
|
</Overlay>
|
||||||
<ChildContent>
|
<ChildContent>
|
||||||
<a class="ant-dropdown-link" @onclick:preventDefault>
|
<a class="ant-dropdown-link" @onclick:preventDefault>
|
||||||
<Icon Type="@IconType.Outline.Menu" />
|
<Icon Type="@IconType.Outline.Menu" />
|
||||||
</a>
|
</a>
|
||||||
</ChildContent>
|
</ChildContent>
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
</SpaceItem>
|
</SpaceItem>
|
||||||
</Space>
|
</Space>
|
||||||
</ActionColumn>
|
</ActionColumn>
|
||||||
</ColumnDefinitions>
|
</ColumnDefinitions>
|
||||||
</Table>
|
</Table>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<Drawer Closable="true" Width="520" Visible="drawerVisible" Title='("设置菜单")' OnClose="_=>CloseDrawer()">
|
<Drawer Closable="true" Width="520" Visible="drawerVisible" Title='("设置菜单")' OnClose="_ => CloseDrawer()">
|
||||||
<Form LabelColSpan="5" @ref="@editform" Model="@model" OnFinish="OnFormFinish">
|
<Form LabelColSpan="5" @ref="@editform" Model="@model" OnFinish="OnFormFinish">
|
||||||
<FluentValidationValidator />
|
<FluentValidationValidator />
|
||||||
<FormItem Label="名称">
|
<FormItem Label="名称">
|
||||||
<Input @bind-Value="model.Name" For="(()=>model.Name)" />
|
<Input @bind-Value="model.Name" For="(()=>model.Name)" />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
<FormItem Label="缩略名">
|
<FormItem Label="缩略名">
|
||||||
<Input @bind-Value="model.Slug" For="(()=>model.Slug)" />
|
<Input @bind-Value="model.Slug" For="(()=>model.Slug)" />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
<FormItem Label="上级分类">
|
<FormItem Label="上级分类">
|
||||||
<Select @bind-Value="@model.ParentId" TItemValue="long" TItem="string" Placeholder="请选择上级分类">
|
<Select @bind-Value="@model.ParentId" TItemValue="long" TItem="string" Placeholder="请选择上级分类">
|
||||||
<SelectOptions>
|
<SelectOptions>
|
||||||
<SelectOption Value="0L" Label="无" />
|
<SelectOption Value="0L" Label="无" />
|
||||||
@foreach (var item in categories)
|
@foreach (var item in categories)
|
||||||
{
|
{
|
||||||
<SelectOption Value="@item.Id" Label="@GetName(item)" />
|
<SelectOption Value="@item.Id" Label="@GetName(item)" />
|
||||||
}
|
}
|
||||||
|
|
||||||
</SelectOptions>
|
</SelectOptions>
|
||||||
</Select>
|
</Select>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
<FormItem Label="排序">
|
<FormItem Label="排序">
|
||||||
<Input @bind-Value="model.DisplayOrder" For="(()=>model.DisplayOrder)" />
|
<Input @bind-Value="model.DisplayOrder" For="(()=>model.DisplayOrder)" />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
<FormItem Label="状态">
|
<FormItem Label="状态">
|
||||||
<Checkbox @bind-Value="model.Enabled" For="(()=>model.Enabled)">启用</Checkbox>
|
<Checkbox @bind-Value="model.Enabled" For="(()=>model.Enabled)">启用</Checkbox>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
<FormItem WrapperColOffset="4">
|
<FormItem WrapperColOffset="4">
|
||||||
<Button Type="ButtonType.Primary" HtmlType="submit" Style="width: 100%;">保存</Button>
|
<Button Type="ButtonType.Primary" HtmlType="submit" Style="width: 100%;">保存</Button>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
</Form>
|
</Form>
|
||||||
</Drawer>
|
</Drawer>
|
||||||
|
</ChildContent>
|
||||||
|
</PageContainer>
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public string Locale { get; set; } = string.Empty;
|
||||||
|
|
||||||
[SupplyParameterFromQuery]
|
[SupplyParameterFromQuery]
|
||||||
int? Page { get; set; }
|
int? Page { get; set; }
|
||||||
|
|||||||
@@ -120,8 +120,6 @@
|
|||||||
bool searchExpand { get; set; } = false;
|
bool searchExpand { get; set; } = false;
|
||||||
private bool drawerVisible;
|
private bool drawerVisible;
|
||||||
|
|
||||||
SpecificationAttributeOptionModelValidator validator = new();
|
|
||||||
|
|
||||||
protected override void OnInitialized()
|
protected override void OnInitialized()
|
||||||
{
|
{
|
||||||
base.OnInitialized();
|
base.OnInitialized();
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<h3>AreaEdit</h3>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
|
||||||
|
}
|
||||||
207
Atomx.Admin/Atomx.Admin.Client/Pages/Settings/AreaList.razor
Normal file
207
Atomx.Admin/Atomx.Admin.Client/Pages/Settings/AreaList.razor
Normal file
@@ -0,0 +1,207 @@
|
|||||||
|
@page "/area/list/{countryId:long}"
|
||||||
|
@page "/{locale}/area/list/{countryId:long}"
|
||||||
|
@inject ILogger<CountryList> Logger
|
||||||
|
@attribute [Authorize]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<PageContainer Title="国家管理">
|
||||||
|
<Breadcrumb>
|
||||||
|
<Breadcrumb>
|
||||||
|
<BreadcrumbItem>Home</BreadcrumbItem>
|
||||||
|
<BreadcrumbItem>系统配置</BreadcrumbItem>
|
||||||
|
<BreadcrumbItem>国家管理</BreadcrumbItem>
|
||||||
|
</Breadcrumb>
|
||||||
|
</Breadcrumb>
|
||||||
|
<ChildContent>
|
||||||
|
<Spin Spinning="pageLoading">
|
||||||
|
<Card>
|
||||||
|
<Form @ref="searchForm" Model="search" Layout="FormLayout.Inline" Class="search-form" OnFinish="OnSearchFinish">
|
||||||
|
<Row Justify="RowJustify.Start" Gutter="16">
|
||||||
|
<Col>
|
||||||
|
<FormItem Label="名称">
|
||||||
|
<Input @bind-Value="search.Name" Placeholder="名称" AllowClear />
|
||||||
|
</FormItem>
|
||||||
|
<div class="ant-form-item">
|
||||||
|
<Button Type="ButtonType.Primary" HtmlType="submit">查询</Button>
|
||||||
|
<Button Style="margin: 0 8px;" OnClick="OnSearchReset">重置</Button>
|
||||||
|
</div>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</Form>
|
||||||
|
</Card>
|
||||||
|
<Card Title="" Class="hideborder">
|
||||||
|
<Extra>
|
||||||
|
<div class="extraContent">
|
||||||
|
<Button Type="ButtonType.Primary" HtmlType="submit" OnClick="HandleAddNew">新增国家</Button>
|
||||||
|
</div>
|
||||||
|
</Extra>
|
||||||
|
<ChildContent>
|
||||||
|
<Table DataSource="PagingList.Items" PageSize="100" HidePagination="true">
|
||||||
|
<Selection CheckStrictly />
|
||||||
|
<PropertyColumn Property="c => c.Name" Title="名称" />
|
||||||
|
<PropertyColumn Property="c => c.Initial" Title="首字母" />
|
||||||
|
<PropertyColumn Property="c => c.NumericISOCode" Title="ISO代码" />
|
||||||
|
<PropertyColumn Property="c => c.Enabled" Title="状态">
|
||||||
|
@if (context.Enabled)
|
||||||
|
{
|
||||||
|
<Text>已激活</text>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<Text>未激活</text>
|
||||||
|
}
|
||||||
|
</PropertyColumn>
|
||||||
|
<PropertyColumn Property="c => c.DisplayOrder" Title="排序" />
|
||||||
|
<ActionColumn Title="操作" Align="ColumnAlign.Right">
|
||||||
|
<Space>
|
||||||
|
<SpaceItem>
|
||||||
|
<a @onclick="(e)=>HandleEdit(context)">编辑</a>
|
||||||
|
</SpaceItem>
|
||||||
|
@*<SpaceItem>
|
||||||
|
<Popconfirm Placement="@Placement.Left" Title="@("删除这条数据无法恢复,您确定要删除吗?")"
|
||||||
|
OnConfirm="@(e=>HandleDeleteConfirmAsync(e,context.Id))"
|
||||||
|
OkText="确定"
|
||||||
|
CancelText="取消">
|
||||||
|
<a>删除</a>
|
||||||
|
</Popconfirm>
|
||||||
|
</SpaceItem>*@
|
||||||
|
</Space>
|
||||||
|
</ActionColumn>
|
||||||
|
</Table>
|
||||||
|
<br />
|
||||||
|
<Row Justify="RowJustify.End">
|
||||||
|
@if (PagingList.Count > 0)
|
||||||
|
{
|
||||||
|
<Pagination Current="PagingList.Index" Total="PagingList.Count" PageSize="PagingList.Size" ShowSizeChanger="false" OnChange="OnPageChanged"></Pagination>
|
||||||
|
}
|
||||||
|
</Row>
|
||||||
|
</ChildContent>
|
||||||
|
</Card>
|
||||||
|
</Spin>
|
||||||
|
</ChildContent>
|
||||||
|
</PageContainer>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
[Parameter]
|
||||||
|
public string Locale { get; set; } = string.Empty;
|
||||||
|
[Parameter]
|
||||||
|
public long CountryId { get; set; }
|
||||||
|
|
||||||
|
[SupplyParameterFromQuery]
|
||||||
|
int? Page { get; set; }
|
||||||
|
|
||||||
|
[SupplyParameterFromQuery(Name = "size")]
|
||||||
|
int? PageSize { get; set; }
|
||||||
|
|
||||||
|
bool pageLoading = false;
|
||||||
|
|
||||||
|
Form<CountrySearch> searchForm = new();
|
||||||
|
CountrySearch search = new();
|
||||||
|
|
||||||
|
PagingList<Country> PagingList = new() { Size = 20 };
|
||||||
|
|
||||||
|
|
||||||
|
protected override async Task OnInitializedAsync()
|
||||||
|
{
|
||||||
|
await base.OnInitializedAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async Task OnParametersSetAsync()
|
||||||
|
{
|
||||||
|
loadQueryString();
|
||||||
|
await LoadListAsync();
|
||||||
|
await base.OnParametersSetAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
void loadQueryString()
|
||||||
|
{
|
||||||
|
var uri = new Uri(Navigation.Uri);
|
||||||
|
var query = uri.Query;
|
||||||
|
search.Name = query.GetQueryString("Name");
|
||||||
|
}
|
||||||
|
|
||||||
|
async Task LoadListAsync()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
pageLoading = true;
|
||||||
|
var url = "/api/country/search";
|
||||||
|
var apiResult = await HttpService.GetPagingList<Country>(url, search, Page.GetValueOrDefault(1), PageSize.GetValueOrDefault(20));
|
||||||
|
if (apiResult.Success)
|
||||||
|
{
|
||||||
|
if (apiResult.Data != null)
|
||||||
|
{
|
||||||
|
PagingList = apiResult.Data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (apiResult.Code == 403)
|
||||||
|
{
|
||||||
|
ModalService.Error(new ConfirmOptions() { Title = "权限不足", Content = apiResult.Message });
|
||||||
|
}
|
||||||
|
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
pageLoading = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnSearchReset()
|
||||||
|
{
|
||||||
|
search = new CountrySearch();
|
||||||
|
searchForm?.Reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnSearch(int page)
|
||||||
|
{
|
||||||
|
var queryString = search.BuildQueryString();
|
||||||
|
if (string.IsNullOrEmpty(queryString))
|
||||||
|
{
|
||||||
|
if (page > 1)
|
||||||
|
{
|
||||||
|
Navigation.NavigateTo($"/country/list?page={page}");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Navigation.NavigateTo($"/country/list");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (page > 1)
|
||||||
|
{
|
||||||
|
Navigation.NavigateTo($"/country/list?page={page}&{queryString}");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Navigation.NavigateTo($"/country/list?{queryString}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnSearchFinish()
|
||||||
|
{
|
||||||
|
Page = Page.GetValueOrDefault(1) - 1;
|
||||||
|
|
||||||
|
OnSearch(Page.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnPageChanged(PaginationEventArgs args)
|
||||||
|
{
|
||||||
|
OnSearch(args.Page);
|
||||||
|
}
|
||||||
|
|
||||||
|
void HandleAddNew()
|
||||||
|
{
|
||||||
|
Navigation.NavigateTo($"/country/create");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void HandleEdit(Country model)
|
||||||
|
{
|
||||||
|
Navigation.NavigateTo($"/country/edit/{model.Id}");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
202
Atomx.Admin/Atomx.Admin.Client/Pages/Settings/CountryEdit.razor
Normal file
202
Atomx.Admin/Atomx.Admin.Client/Pages/Settings/CountryEdit.razor
Normal file
@@ -0,0 +1,202 @@
|
|||||||
|
@page "/country/create"
|
||||||
|
@page "/country/edit/{id:long}"
|
||||||
|
@page "/{locale}/country/create"
|
||||||
|
@page "/{locale}/country/edit/{id:long}"
|
||||||
|
|
||||||
|
@inject ILogger<CountryEdit> Logger
|
||||||
|
@attribute [Authorize]
|
||||||
|
|
||||||
|
<PageContainer Title="@(Id > 0 ? "编辑国家信息" : "新增国家信息")">
|
||||||
|
<Breadcrumb>
|
||||||
|
<Breadcrumb>
|
||||||
|
<BreadcrumbItem Href="/">管理后台</BreadcrumbItem>
|
||||||
|
<BreadcrumbItem Href="/settings">系统配置</BreadcrumbItem>
|
||||||
|
<BreadcrumbItem Href="/country/list">国家管理</BreadcrumbItem>
|
||||||
|
</Breadcrumb>
|
||||||
|
</Breadcrumb>
|
||||||
|
<ChildContent>
|
||||||
|
|
||||||
|
<Spin Spinning="pageLoading">
|
||||||
|
<Card Title="国家信息">
|
||||||
|
<Form @ref="editform" Model="@model" LabelColSpan="5" WrapperColSpan="14" OnFinish="OnFormFinishAsync">
|
||||||
|
@if (Id > 0 && languageList.Count() > 0)
|
||||||
|
{
|
||||||
|
<Tabs ActiveKey="@model.LanguageId" OnTabClick="OnLanguageTabChange">
|
||||||
|
@* <TabPane Key="0">
|
||||||
|
<TabTemplate>
|
||||||
|
<span>标准</span>
|
||||||
|
</TabTemplate>
|
||||||
|
</TabPane> *@
|
||||||
|
@foreach (var item in languageList)
|
||||||
|
{
|
||||||
|
<TabPane Key="@item.Key.ToString()">
|
||||||
|
<TabTemplate>
|
||||||
|
<span>@item.Value</span>
|
||||||
|
</TabTemplate>
|
||||||
|
</TabPane>
|
||||||
|
}
|
||||||
|
</Tabs>
|
||||||
|
}
|
||||||
|
<FormItem Label="名称" Required>
|
||||||
|
<Input @bind-Value="@model.Name" Placeholder="国家" />
|
||||||
|
</FormItem>
|
||||||
|
<FormItem Label="首字母">
|
||||||
|
<Input @bind-Value="@model.Initial" Placeholder="首字母" />
|
||||||
|
</FormItem>
|
||||||
|
<FormItem Label="两个字母ISO代码">
|
||||||
|
<Input @bind-Value="@model.TwoLetterISOCode" Placeholder="两个字母ISO代码" />
|
||||||
|
</FormItem>
|
||||||
|
<FormItem Label="三字母ISO代码">
|
||||||
|
<Input @bind-Value="@model.ThreeLetterISOCode" Placeholder="三字母ISO代码" />
|
||||||
|
</FormItem>
|
||||||
|
<FormItem Label="数字ISO代码">
|
||||||
|
<Input @bind-Value="@model.NumericISOCode" Placeholder="数字ISO代码" />
|
||||||
|
</FormItem>
|
||||||
|
<FormItem Label="允许发货">
|
||||||
|
<Checkbox Checked="@model.AllowShipping">允许发货</Checkbox>
|
||||||
|
</FormItem>
|
||||||
|
<FormItem Label="显示排序">
|
||||||
|
<Input @bind-Value="@model.DisplayOrder" Placeholder="显示排序" />
|
||||||
|
</FormItem>
|
||||||
|
<FormItem Label="状态">
|
||||||
|
<Checkbox T="bool" Label="启用" @bind-value="model.Enabled" Size="InputSize.Small" Class="ps-0" />
|
||||||
|
</FormItem>
|
||||||
|
<FormItem WrapperCol="new ColLayoutParam { Span = 24, Offset = 5 }">
|
||||||
|
<Button Type="@ButtonType.Primary" HtmlType="submit" Loading="saving">
|
||||||
|
提交保存
|
||||||
|
</Button>
|
||||||
|
</FormItem>
|
||||||
|
</Form>
|
||||||
|
</Card>
|
||||||
|
</Spin>
|
||||||
|
</ChildContent>
|
||||||
|
</PageContainer>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
[Parameter]
|
||||||
|
public string Locale { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public long Id { get; set; }
|
||||||
|
|
||||||
|
[SupplyParameterFromForm]
|
||||||
|
CountryModel model { get; set; } = new();
|
||||||
|
Form<CountryModel> editform = null!;
|
||||||
|
|
||||||
|
CountryLocalizedModel country = new();
|
||||||
|
|
||||||
|
List<KeyValue> languageList = new();
|
||||||
|
|
||||||
|
bool pageLoading = false;
|
||||||
|
bool saving = false;
|
||||||
|
|
||||||
|
protected override void OnInitialized()
|
||||||
|
{
|
||||||
|
base.OnInitialized();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnParametersSet()
|
||||||
|
{
|
||||||
|
_ = LoadLanguage();
|
||||||
|
if (Id > 0)
|
||||||
|
{
|
||||||
|
LoadData();
|
||||||
|
}
|
||||||
|
base.OnParametersSet();
|
||||||
|
}
|
||||||
|
|
||||||
|
async Task LoadLanguage()
|
||||||
|
{
|
||||||
|
var url = $"/api/language/enabled";
|
||||||
|
var apiResult = await HttpService.Get<ApiResult<List<KeyValue>>>(url);
|
||||||
|
if (apiResult.Success)
|
||||||
|
{
|
||||||
|
if(apiResult.Data == null)
|
||||||
|
{
|
||||||
|
languageList = new List<KeyValue>();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
languageList = apiResult.Data;
|
||||||
|
languageList.Insert(0, new KeyValue() { Key = "0", Value = "标准" });
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async void LoadData()
|
||||||
|
{
|
||||||
|
pageLoading = true;
|
||||||
|
var url = $"/api/country/detail?id={Id}";
|
||||||
|
var apiResult = await HttpService.Get<ApiResult<CountryLocalizedModel>>(url);
|
||||||
|
if (apiResult.Success)
|
||||||
|
{
|
||||||
|
if (apiResult.Data == null)
|
||||||
|
{
|
||||||
|
Navigation.NavigateTo($"/country/create");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
country = apiResult.Data;
|
||||||
|
model = apiResult.Data.Adapt<CountryModel>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Navigation.NavigateTo($"/country/create");
|
||||||
|
}
|
||||||
|
|
||||||
|
pageLoading = false;
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
async void OnFormFinishAsync()
|
||||||
|
{
|
||||||
|
if (editform.Validate())
|
||||||
|
{
|
||||||
|
Console.WriteLine(model.ToJson());
|
||||||
|
saving = true;
|
||||||
|
var url = $"api/country/save";
|
||||||
|
var result = new ApiResult<string>();
|
||||||
|
result = await HttpService.Post<ApiResult<string>>(url, model);
|
||||||
|
if (result.Success)
|
||||||
|
{
|
||||||
|
saving = false;
|
||||||
|
await ModalService.InfoAsync(new ConfirmOptions() { Title = "提示", Content = "数据提交成功!" });
|
||||||
|
Navigation.NavigateTo($"/country/list");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
saving = false;
|
||||||
|
await ModalService.ErrorAsync(new ConfirmOptions() { Title = "服务异常", Content = result.Message });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnLanguageTabChange(string key)
|
||||||
|
{
|
||||||
|
if (key != "0")
|
||||||
|
{
|
||||||
|
model.LanguageId = key;
|
||||||
|
var data = country.Locales.Where(p => p.LanguageId == key.ToInt()).ToList();
|
||||||
|
if (data.Any())
|
||||||
|
{
|
||||||
|
var name = nameof(model.Name);
|
||||||
|
|
||||||
|
model.Name = data.SingleOrDefault(p => p.Key == name)?.Value ?? "";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
model.Name = string.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
model = country.Adapt<CountryModel>();
|
||||||
|
model.LanguageId = key;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
224
Atomx.Admin/Atomx.Admin.Client/Pages/Settings/CountryList.razor
Normal file
224
Atomx.Admin/Atomx.Admin.Client/Pages/Settings/CountryList.razor
Normal file
@@ -0,0 +1,224 @@
|
|||||||
|
@page "/country/list"
|
||||||
|
@page "/{locale}/country/list"
|
||||||
|
@inject ILogger<CountryList> Logger
|
||||||
|
@attribute [Authorize]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<PageContainer Title="国家管理">
|
||||||
|
<Breadcrumb>
|
||||||
|
<Breadcrumb>
|
||||||
|
<BreadcrumbItem Href="/">管理后台</BreadcrumbItem>
|
||||||
|
<BreadcrumbItem Href="/settings">系统配置</BreadcrumbItem>
|
||||||
|
<BreadcrumbItem>国家管理</BreadcrumbItem>
|
||||||
|
</Breadcrumb>
|
||||||
|
</Breadcrumb>
|
||||||
|
<ChildContent>
|
||||||
|
<Spin Spinning="pageLoading">
|
||||||
|
<Card>
|
||||||
|
<Form @ref="searchForm" Model="search" Layout="FormLayout.Inline" Class="search-form" OnFinish="OnSearchFinish">
|
||||||
|
<Row Justify="RowJustify.Start" Gutter="16">
|
||||||
|
<Col>
|
||||||
|
<FormItem Label="名称">
|
||||||
|
<Input @bind-Value="search.Name" Placeholder="名称" AllowClear />
|
||||||
|
</FormItem>
|
||||||
|
</Col>
|
||||||
|
<Col>
|
||||||
|
<div class="ant-form-item">
|
||||||
|
<Button Type="ButtonType.Primary" HtmlType="submit">查询</Button>
|
||||||
|
<Button Style="margin: 0 8px;" OnClick="OnSearchReset">重置</Button>
|
||||||
|
</div>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</Form>
|
||||||
|
</Card>
|
||||||
|
<br />
|
||||||
|
<Card Title="国家列表" Class="hideborder">
|
||||||
|
<Extra>
|
||||||
|
<div class="extraContent">
|
||||||
|
<Button Type="ButtonType.Primary" HtmlType="submit" OnClick="HandleAddNew">新增国家</Button>
|
||||||
|
</div>
|
||||||
|
</Extra>
|
||||||
|
<ChildContent>
|
||||||
|
<Table DataSource="PagingList.Items" PageSize="100" HidePagination="true">
|
||||||
|
<Selection CheckStrictly />
|
||||||
|
<PropertyColumn Property="c => c.Name" Title="名称" />
|
||||||
|
<PropertyColumn Property="c => c.Initial" Title="首字母" />
|
||||||
|
<PropertyColumn Property="c => c.NumericISOCode" Title="ISO代码" />
|
||||||
|
<PropertyColumn Property="c => c.Enabled" Title="状态">
|
||||||
|
@if (context.Enabled)
|
||||||
|
{
|
||||||
|
<AntDesign.Text Type="TextElementType.Success"><Icon Type="check" Theme=" IconThemeType.Outline" Width="1.3em" Height="1.3em" /></AntDesign.Text>
|
||||||
|
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<Icon Type="close" Theme="IconThemeType.Outline" Width="1.3em" Height="1.3em" />
|
||||||
|
}
|
||||||
|
</PropertyColumn>
|
||||||
|
<PropertyColumn Property="c => c.DisplayOrder" Title="排序" />
|
||||||
|
<ActionColumn Title="操作" Align="ColumnAlign.Right">
|
||||||
|
<Space>
|
||||||
|
<SpaceItem>
|
||||||
|
<a @onclick="(e) => GotoStateProvince(context)">州省管理</a>
|
||||||
|
</SpaceItem>
|
||||||
|
<SpaceItem>
|
||||||
|
<a @onclick="(e) => GotoArea(context)">城市管理</a>
|
||||||
|
</SpaceItem>
|
||||||
|
<SpaceItem>
|
||||||
|
<a @onclick="(e)=>HandleEdit(context)">编辑</a>
|
||||||
|
</SpaceItem>
|
||||||
|
@*<SpaceItem>
|
||||||
|
<Popconfirm Placement="@Placement.Left" Title="@("删除这条数据无法恢复,您确定要删除吗?")"
|
||||||
|
OnConfirm="@(e=>HandleDeleteConfirmAsync(e,context.Id))"
|
||||||
|
OkText="确定"
|
||||||
|
CancelText="取消">
|
||||||
|
<a>删除</a>
|
||||||
|
</Popconfirm>
|
||||||
|
</SpaceItem>*@
|
||||||
|
</Space>
|
||||||
|
</ActionColumn>
|
||||||
|
</Table>
|
||||||
|
<br />
|
||||||
|
<Row Justify="RowJustify.End">
|
||||||
|
@if (PagingList.Count > 0)
|
||||||
|
{
|
||||||
|
<Pagination Current="PagingList.Index" Total="PagingList.Count" PageSize="PagingList.Size" ShowSizeChanger="false" OnChange="OnPageChanged"></Pagination>
|
||||||
|
}
|
||||||
|
</Row>
|
||||||
|
</ChildContent>
|
||||||
|
</Card>
|
||||||
|
</Spin>
|
||||||
|
</ChildContent>
|
||||||
|
</PageContainer>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
[Parameter]
|
||||||
|
public string Locale { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
[SupplyParameterFromQuery]
|
||||||
|
int? Page { get; set; }
|
||||||
|
|
||||||
|
[SupplyParameterFromQuery(Name = "size")]
|
||||||
|
int? PageSize { get; set; }
|
||||||
|
|
||||||
|
bool pageLoading = false;
|
||||||
|
|
||||||
|
Form<CountrySearch> searchForm = new();
|
||||||
|
CountrySearch search = new();
|
||||||
|
|
||||||
|
PagingList<Country> PagingList = new() { Size = 20 };
|
||||||
|
|
||||||
|
|
||||||
|
protected override async Task OnInitializedAsync()
|
||||||
|
{
|
||||||
|
await base.OnInitializedAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async Task OnParametersSetAsync()
|
||||||
|
{
|
||||||
|
loadQueryString();
|
||||||
|
await LoadListAsync();
|
||||||
|
await base.OnParametersSetAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
void loadQueryString()
|
||||||
|
{
|
||||||
|
var uri = new Uri(Navigation.Uri);
|
||||||
|
var query = uri.Query;
|
||||||
|
search.Name = query.GetQueryString("Name");
|
||||||
|
}
|
||||||
|
|
||||||
|
async Task LoadListAsync()
|
||||||
|
{
|
||||||
|
pageLoading = true;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var url = "/api/country/search";
|
||||||
|
var apiResult = await HttpService.GetPagingList<Country>(url, search, Page.GetValueOrDefault(1), PageSize.GetValueOrDefault(20));
|
||||||
|
if (apiResult.Success)
|
||||||
|
{
|
||||||
|
if (apiResult.Data != null)
|
||||||
|
{
|
||||||
|
PagingList = apiResult.Data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (apiResult.Code == 403)
|
||||||
|
{
|
||||||
|
ModalService.Error(new ConfirmOptions() { Title = "权限不足", Content = apiResult.Message });
|
||||||
|
}
|
||||||
|
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
pageLoading = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnSearchReset()
|
||||||
|
{
|
||||||
|
search = new CountrySearch();
|
||||||
|
searchForm?.Reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnSearch(int page)
|
||||||
|
{
|
||||||
|
var queryString = search.BuildQueryString();
|
||||||
|
if (string.IsNullOrEmpty(queryString))
|
||||||
|
{
|
||||||
|
if (page > 1)
|
||||||
|
{
|
||||||
|
Navigation.NavigateTo($"/country/list?page={page}");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Navigation.NavigateTo($"/country/list");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (page > 1)
|
||||||
|
{
|
||||||
|
Navigation.NavigateTo($"/country/list?page={page}&{queryString}");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Navigation.NavigateTo($"/country/list?{queryString}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnSearchFinish()
|
||||||
|
{
|
||||||
|
Page = Page.GetValueOrDefault(1) - 1;
|
||||||
|
|
||||||
|
OnSearch(Page.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnPageChanged(PaginationEventArgs args)
|
||||||
|
{
|
||||||
|
OnSearch(args.Page);
|
||||||
|
}
|
||||||
|
|
||||||
|
void HandleAddNew()
|
||||||
|
{
|
||||||
|
Navigation.NavigateTo($"/country/create");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void HandleEdit(Country model)
|
||||||
|
{
|
||||||
|
Navigation.NavigateTo($"/country/edit/{model.Id}");
|
||||||
|
}
|
||||||
|
|
||||||
|
void GotoStateProvince(Country model)
|
||||||
|
{
|
||||||
|
Navigation.NavigateTo($"/stateprovince/list/{model.Id}");
|
||||||
|
}
|
||||||
|
|
||||||
|
void GotoArea(Country model)
|
||||||
|
{
|
||||||
|
Navigation.NavigateTo($"/area/list/{model.Id}");
|
||||||
|
}
|
||||||
|
}
|
||||||
137
Atomx.Admin/Atomx.Admin.Client/Pages/Settings/CurrencyEdit.razor
Normal file
137
Atomx.Admin/Atomx.Admin.Client/Pages/Settings/CurrencyEdit.razor
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
@page "/currency/create"
|
||||||
|
@page "/currency/edit/{id:long}"
|
||||||
|
@page "/{locale}/currency/create"
|
||||||
|
@page "/{locale}/currency/edit/{id:long}"
|
||||||
|
|
||||||
|
@inject ILogger<CurrencyEdit> Logger
|
||||||
|
@attribute [Authorize]
|
||||||
|
|
||||||
|
<PageContainer Title="@(Id > 0 ? "编辑货币信息" : "新增货币信息")">
|
||||||
|
<Breadcrumb>
|
||||||
|
<Breadcrumb>
|
||||||
|
<BreadcrumbItem Href="/">管理后台</BreadcrumbItem>
|
||||||
|
<BreadcrumbItem Href="/admin/list">系统配置</BreadcrumbItem>
|
||||||
|
<BreadcrumbItem Href="/currency/list">货币管理</BreadcrumbItem>
|
||||||
|
</Breadcrumb>
|
||||||
|
</Breadcrumb>
|
||||||
|
<ChildContent>
|
||||||
|
|
||||||
|
<Spin Spinning="pageLoading">
|
||||||
|
<Card Title="货币信息">
|
||||||
|
<Form @ref="editform" Model="@model" LabelColSpan="5" WrapperColSpan="14" OnFinish="OnFormFinishAsync">
|
||||||
|
<FormItem Label="名称" Required>
|
||||||
|
<Input @bind-Value="@context.Name" Placeholder="货币名称" />
|
||||||
|
</FormItem>
|
||||||
|
<FormItem Label="货币代码">
|
||||||
|
<Input @bind-Value="@context.CurrencyCode" Placeholder="货币代码" />
|
||||||
|
</FormItem>
|
||||||
|
<FormItem Label="汇率">
|
||||||
|
<Input @bind-Value="@context.Rate" Placeholder="汇率" />
|
||||||
|
</FormItem>
|
||||||
|
<FormItem Label="展示本地">
|
||||||
|
<SimpleSelect @bind-Value="@context.DisplayLocale" Placeholder="语言文化">
|
||||||
|
<SelectOptions>
|
||||||
|
@foreach (var item in LanguageCultures)
|
||||||
|
{
|
||||||
|
<SimpleSelectOption Value="@item.Key" Label="@($"{item.Value} - {item.Key}")"></SimpleSelectOption>
|
||||||
|
}
|
||||||
|
</SelectOptions>
|
||||||
|
</SimpleSelect>
|
||||||
|
</FormItem>
|
||||||
|
<FormItem Label="自定义格式">
|
||||||
|
<Input @bind-Value="@context.CustomFormatting" Placeholder="自定义格式" />
|
||||||
|
</FormItem>
|
||||||
|
<FormItem Label="显示排序">
|
||||||
|
<Input @bind-Value="@context.DisplayOrder" Placeholder="显示排序" />
|
||||||
|
</FormItem>
|
||||||
|
<FormItem Label="状态">
|
||||||
|
<Checkbox Checked="@context.Enabled">启用</Checkbox>
|
||||||
|
</FormItem>
|
||||||
|
<FormItem WrapperCol="new ColLayoutParam { Span = 24, Offset = 5 }">
|
||||||
|
<Button Type="@ButtonType.Primary" HtmlType="submit" Loading="saving">
|
||||||
|
提交保存
|
||||||
|
</Button>
|
||||||
|
</FormItem>
|
||||||
|
</Form>
|
||||||
|
</Card>
|
||||||
|
</Spin>
|
||||||
|
</ChildContent>
|
||||||
|
</PageContainer>
|
||||||
|
@code {
|
||||||
|
[Parameter]
|
||||||
|
public string Locale { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public long Id { get; set; }
|
||||||
|
|
||||||
|
[SupplyParameterFromForm]
|
||||||
|
CurrencyModel model { get; set; } = new();
|
||||||
|
Form<CurrencyModel> editform = null!;
|
||||||
|
Dictionary<string, string> LanguageCultures = LanguageCulture.Descriptions.ToDictionary();
|
||||||
|
bool pageLoading = false;
|
||||||
|
bool saving = false;
|
||||||
|
|
||||||
|
protected override void OnInitialized()
|
||||||
|
{
|
||||||
|
base.OnInitialized();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnParametersSet()
|
||||||
|
{
|
||||||
|
if (Id > 0)
|
||||||
|
{
|
||||||
|
LoadData();
|
||||||
|
}
|
||||||
|
base.OnParametersSet();
|
||||||
|
}
|
||||||
|
|
||||||
|
async void LoadData()
|
||||||
|
{
|
||||||
|
pageLoading = true;
|
||||||
|
var url = $"/api/currency/{Id}";
|
||||||
|
var apiResult = await HttpService.Get<ApiResult<Currency>>(url);
|
||||||
|
if (apiResult.Success)
|
||||||
|
{
|
||||||
|
if (apiResult.Data == null)
|
||||||
|
{
|
||||||
|
Navigation.NavigateTo($"/currency/create");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
model = apiResult.Data.Adapt<CurrencyModel>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Navigation.NavigateTo($"/currency/create");
|
||||||
|
}
|
||||||
|
|
||||||
|
pageLoading = false;
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
async void OnFormFinishAsync()
|
||||||
|
{
|
||||||
|
if (editform.Validate())
|
||||||
|
{
|
||||||
|
saving = true;
|
||||||
|
var url = $"api/currency/save";
|
||||||
|
var result = new ApiResult<string>();
|
||||||
|
result = await HttpService.Post<ApiResult<string>>(url, model);
|
||||||
|
|
||||||
|
|
||||||
|
if (result.Code == (int)ResultCode.Success)
|
||||||
|
{
|
||||||
|
saving = false;
|
||||||
|
await ModalService.InfoAsync(new ConfirmOptions() { Title = "提示", Content = "数据提交成功!" });
|
||||||
|
Navigation.NavigateTo($"/currency/list");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
saving = false;
|
||||||
|
await ModalService.ErrorAsync(new ConfirmOptions() { Title = "服务异常", Content = result.Message });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
263
Atomx.Admin/Atomx.Admin.Client/Pages/Settings/CurrencyList.razor
Normal file
263
Atomx.Admin/Atomx.Admin.Client/Pages/Settings/CurrencyList.razor
Normal file
@@ -0,0 +1,263 @@
|
|||||||
|
@page "/currency/list"
|
||||||
|
@page "/{locale}/currency/list"
|
||||||
|
@inject ILogger<CurrencyList> Logger
|
||||||
|
@attribute [Authorize]
|
||||||
|
|
||||||
|
|
||||||
|
<PageContainer Title="货币管理">
|
||||||
|
<Breadcrumb>
|
||||||
|
<Breadcrumb>
|
||||||
|
<BreadcrumbItem Href="/">管理后台</BreadcrumbItem>
|
||||||
|
<BreadcrumbItem Href="/settings">系统配置</BreadcrumbItem>
|
||||||
|
<BreadcrumbItem>货币管理</BreadcrumbItem>
|
||||||
|
</Breadcrumb>
|
||||||
|
</Breadcrumb>
|
||||||
|
<ChildContent>
|
||||||
|
<Spin Spinning="pageLoading">
|
||||||
|
<Card>
|
||||||
|
<Form @ref="searchForm" Model="search" Layout="FormLayout.Inline" Class="search-form" OnFinish="OnSearchFinish">
|
||||||
|
<Row Justify="RowJustify.Start" Gutter="(16, 16)">
|
||||||
|
<Col>
|
||||||
|
<FormItem Label="名称">
|
||||||
|
<Input @bind-Value="search.Name" Placeholder="名称" AllowClear />
|
||||||
|
</FormItem>
|
||||||
|
</Col>
|
||||||
|
@if (searchExpand)
|
||||||
|
{
|
||||||
|
<AntDesign.Col>
|
||||||
|
<FormItem Label="发布时间">
|
||||||
|
<RangePicker @bind-Value="search.RangeTime"></RangePicker>
|
||||||
|
</FormItem>
|
||||||
|
</AntDesign.Col>
|
||||||
|
<AntDesign.Col>
|
||||||
|
<FormItem Label="状态">
|
||||||
|
<SimpleSelect DefaultValue="" Style="width:120px;" @bind-Value="@search.Status">
|
||||||
|
<SelectOptions>
|
||||||
|
<SimpleSelectOption Value="" Label="全部"></SimpleSelectOption>
|
||||||
|
<SimpleSelectOption Value="1" Label="草稿"></SimpleSelectOption>
|
||||||
|
<SimpleSelectOption Value="2" Label="已发布"></SimpleSelectOption>
|
||||||
|
<SimpleSelectOption Value="3" Label="已删除"></SimpleSelectOption>
|
||||||
|
</SelectOptions>
|
||||||
|
</SimpleSelect>
|
||||||
|
</FormItem>
|
||||||
|
</AntDesign.Col>
|
||||||
|
}
|
||||||
|
<Col>
|
||||||
|
<div class="ant-form-item" style="display:flex">
|
||||||
|
<Button Type="ButtonType.Primary" HtmlType="submit">查询</Button>
|
||||||
|
<Button Style="margin: 0 8px;" OnClick="OnSearchReset">重置</Button>
|
||||||
|
<a style="font-size:12px; display:flex; align-items:center;text-align:center;" @onclick="()=>{searchExpand=!searchExpand;}">
|
||||||
|
<Icon Type="@(searchExpand?"up":"down")"></Icon> @if (searchExpand)
|
||||||
|
{
|
||||||
|
<span>收起</span>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<span>展开</span>
|
||||||
|
}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</Form>
|
||||||
|
</Card>
|
||||||
|
<br />
|
||||||
|
<Card Title="" Class="hideborder">
|
||||||
|
<Extra>
|
||||||
|
<div class="extraContent">
|
||||||
|
<Button Type="ButtonType.Primary" HtmlType="submit" OnClick="HandleAddNew">新增货币</Button>
|
||||||
|
</div>
|
||||||
|
</Extra>
|
||||||
|
<ChildContent>
|
||||||
|
<Table DataSource="PagingList.Items" PageSize="100" HidePagination="true">
|
||||||
|
<Selection CheckStrictly />
|
||||||
|
<PropertyColumn Property="c => c.Name" Title="名称" />
|
||||||
|
<PropertyColumn Property="c => c.CurrencyCode" Title="货币代码" />
|
||||||
|
<PropertyColumn Property="c => c.Rate" Title="汇率" />
|
||||||
|
<PropertyColumn Property="c => c.PrimaryCurrency" Title="默认货币">
|
||||||
|
@if (context.PrimaryCurrency)
|
||||||
|
{
|
||||||
|
<AntDesign.Text Type="TextElementType.Success"><Icon Type="check" Theme="IconThemeType.Outline" /></AntDesign.Text>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<Icon Type="minus" Theme="IconThemeType.Outline" />
|
||||||
|
}
|
||||||
|
</PropertyColumn>
|
||||||
|
<PropertyColumn Property="c => c.Enabled" Title="状态">
|
||||||
|
@if (context.Enabled)
|
||||||
|
{
|
||||||
|
<AntDesign.Text Type="TextElementType.Success"><Icon Type="check" Theme=" IconThemeType.Outline" Width="1.3em" Height="1.3em" /></AntDesign.Text>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<Icon Type="close" Theme="IconThemeType.Outline" Width="1.3em" Height="1.3em" />
|
||||||
|
}
|
||||||
|
</PropertyColumn>
|
||||||
|
<PropertyColumn Property="c => c.DisplayOrder" Title="排序" />
|
||||||
|
<ActionColumn Title="操作" Align="ColumnAlign.Right">
|
||||||
|
<Space>
|
||||||
|
<SpaceItem>
|
||||||
|
<a @onclick="(e)=>HandleEdit(context)">编辑</a>
|
||||||
|
</SpaceItem>
|
||||||
|
@*<SpaceItem>
|
||||||
|
<Popconfirm Placement="@Placement.Left" Title="@("删除这条数据无法恢复,您确定要删除吗?")"
|
||||||
|
OnConfirm="@(e=>HandleDeleteConfirmAsync(e,context.Id))"
|
||||||
|
OkText="确定"
|
||||||
|
CancelText="取消">
|
||||||
|
<a>删除</a>
|
||||||
|
</Popconfirm>
|
||||||
|
</SpaceItem>*@
|
||||||
|
</Space>
|
||||||
|
</ActionColumn>
|
||||||
|
</Table>
|
||||||
|
<br />
|
||||||
|
<Row Justify="RowJustify.End">
|
||||||
|
@if (PagingList.Count > 0)
|
||||||
|
{
|
||||||
|
<Pagination Current="PagingList.Index" Total="PagingList.Count" PageSize="PagingList.Size" ShowSizeChanger="false" OnChange="OnPageChanged"></Pagination>
|
||||||
|
}
|
||||||
|
</Row>
|
||||||
|
</ChildContent>
|
||||||
|
</Card>
|
||||||
|
</Spin>
|
||||||
|
</ChildContent>
|
||||||
|
</PageContainer>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
[Parameter]
|
||||||
|
public string Locale { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
[SupplyParameterFromQuery]
|
||||||
|
int? Page { get; set; }
|
||||||
|
|
||||||
|
[SupplyParameterFromQuery(Name = "size")]
|
||||||
|
int? PageSize { get; set; }
|
||||||
|
|
||||||
|
bool pageLoading = false;
|
||||||
|
bool searchExpand = false;
|
||||||
|
|
||||||
|
Form<CurrencySearchModel> searchForm = new();
|
||||||
|
CurrencySearchModel search = new();
|
||||||
|
|
||||||
|
PagingList<CurrencyModel> PagingList = new() { Size = 20 };
|
||||||
|
|
||||||
|
|
||||||
|
protected override async Task OnInitializedAsync()
|
||||||
|
{
|
||||||
|
await base.OnInitializedAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async Task OnParametersSetAsync()
|
||||||
|
{
|
||||||
|
loadQueryString();
|
||||||
|
await LoadListAsync();
|
||||||
|
await base.OnParametersSetAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
void loadQueryString()
|
||||||
|
{
|
||||||
|
var uri = new Uri(Navigation.Uri);
|
||||||
|
var query = uri.Query;
|
||||||
|
search.Name = query.GetQueryString("Name");
|
||||||
|
search.Status = query.GetQueryString("Status");
|
||||||
|
|
||||||
|
var data = query.GetQueryString("RangeTime");
|
||||||
|
if (!string.IsNullOrEmpty(data))
|
||||||
|
{
|
||||||
|
var rangetime = data.Split("-");
|
||||||
|
if (rangetime != null && rangetime.Length > 0)
|
||||||
|
{
|
||||||
|
searchExpand = true;
|
||||||
|
search.RangeTime[0] = rangetime[0].NumberToDateTime();
|
||||||
|
search.RangeTime[1] = rangetime[1].NumberToDateTime();
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
async Task LoadListAsync()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
pageLoading = true;
|
||||||
|
var url = "/api/currency/search";
|
||||||
|
var apiResult = await HttpService.GetPagingList<CurrencyModel>(url, search, Page.GetValueOrDefault(1), PageSize.GetValueOrDefault(20));
|
||||||
|
if (apiResult.Success)
|
||||||
|
{
|
||||||
|
if (apiResult.Data != null)
|
||||||
|
{
|
||||||
|
PagingList = apiResult.Data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (apiResult.Code == 403)
|
||||||
|
{
|
||||||
|
ModalService.Error(new ConfirmOptions() { Title = "权限不足", Content = apiResult.Message });
|
||||||
|
}
|
||||||
|
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
pageLoading = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnSearchReset()
|
||||||
|
{
|
||||||
|
search = new CurrencySearchModel();
|
||||||
|
searchForm?.Reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnSearch(int page)
|
||||||
|
{
|
||||||
|
var queryString = search.BuildQueryString();
|
||||||
|
if (string.IsNullOrEmpty(queryString))
|
||||||
|
{
|
||||||
|
if (page > 1)
|
||||||
|
{
|
||||||
|
Navigation.NavigateTo($"/currency/list?page={page}");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Navigation.NavigateTo($"/currency/list");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (page > 1)
|
||||||
|
{
|
||||||
|
Navigation.NavigateTo($"/currency/list?page={page}&{queryString}");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Navigation.NavigateTo($"/currency/list?{queryString}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnSearchFinish()
|
||||||
|
{
|
||||||
|
Page = Page.GetValueOrDefault(1) - 1;
|
||||||
|
|
||||||
|
OnSearch(Page.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnPageChanged(PaginationEventArgs args)
|
||||||
|
{
|
||||||
|
OnSearch(args.Page);
|
||||||
|
}
|
||||||
|
|
||||||
|
void HandleAddNew()
|
||||||
|
{
|
||||||
|
Navigation.NavigateTo($"/currency/create");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void HandleEdit(CurrencyModel model)
|
||||||
|
{
|
||||||
|
Navigation.NavigateTo($"/currency/edit/{model.Id}");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,144 +1,165 @@
|
|||||||
@page "/setting/messagetemplate/list"
|
@page "/setting/messagetemplate/list"
|
||||||
|
@page "/{locale}/setting/messagetemplate/list"
|
||||||
@inject ILogger<MessageTemplateList> Logger
|
@inject ILogger<MessageTemplateList> Logger
|
||||||
@attribute [Authorize]
|
@attribute [Authorize]
|
||||||
|
|
||||||
<PageTitle>消息模板</PageTitle>
|
<PageContainer Title="消息模板">
|
||||||
<Title Level="4">消息模版管理</Title>
|
<Breadcrumb>
|
||||||
<Card>
|
<Breadcrumb>
|
||||||
<Form @ref="searchForm" Model="search" Layout="FormLayout.Inline" Class="search-form" OnFinish="OnSearchFinish">
|
<BreadcrumbItem Href="/">管理后台</BreadcrumbItem>
|
||||||
<Row Justify="RowJustify.Start" Gutter="16">
|
<BreadcrumbItem Href="/admin/list">系统功能</BreadcrumbItem>
|
||||||
<Col>
|
<BreadcrumbItem>系统设置</BreadcrumbItem>
|
||||||
<FormItem Label="名称">
|
</Breadcrumb>
|
||||||
<Input @bind-Value="search.Key" Placeholder="名称" AllowClear />
|
</Breadcrumb>
|
||||||
</FormItem>
|
<ChildContent>
|
||||||
</Col>
|
<Card>
|
||||||
<Col>
|
<Form @ref="searchForm" Model="search" Layout="FormLayout.Inline" Class="search-form" OnFinish="OnSearchFinish">
|
||||||
<div class="ant-form-item" style="width:200px;display:flex;">
|
<Row Justify="RowJustify.Start" Gutter="16">
|
||||||
<Button Type="ButtonType.Primary" HtmlType="submit">查询</Button>
|
<Col>
|
||||||
<Button Style="margin: 0 8px;" OnClick="OnSearchReset">重置</Button>
|
<FormItem Label="名称">
|
||||||
</div>
|
<Input @bind-Value="search.Key" Placeholder="名称" AllowClear />
|
||||||
</Col>
|
</FormItem>
|
||||||
</Row>
|
</Col>
|
||||||
</Form>
|
<Col>
|
||||||
</Card>
|
<div class="ant-form-item" style="width:200px;display:flex;">
|
||||||
|
<Button Type="ButtonType.Primary" HtmlType="submit">查询</Button>
|
||||||
|
<Button Style="margin: 0 8px;" OnClick="OnSearchReset">重置</Button>
|
||||||
|
</div>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</Form>
|
||||||
|
</Card>
|
||||||
|
<br />
|
||||||
|
<Card Class="mt-3">
|
||||||
|
<Table DataSource="PagingList.Items" PageSize="100" HidePagination="true" Resizable>
|
||||||
|
<TitleTemplate>
|
||||||
|
<Flex Justify="FlexJustify.SpaceBetween">
|
||||||
|
菜单列表
|
||||||
|
<div>
|
||||||
|
<Button Class="me-3" OnClick="OnCreateClick" Type="ButtonType.Primary">新增</Button>
|
||||||
|
</div>
|
||||||
|
</Flex>
|
||||||
|
</TitleTemplate>
|
||||||
|
<ColumnDefinitions>
|
||||||
|
<PropertyColumn Property="c => c.Type" Title="模版类型" Width="150px">
|
||||||
|
@if (context.Type == (int)MessageTemplateType.Message)
|
||||||
|
{
|
||||||
|
<AntDesign.Text>站内信</AntDesign.Text>
|
||||||
|
}
|
||||||
|
else if (context.Type == (int)MessageTemplateType.Email)
|
||||||
|
{
|
||||||
|
<AntDesign.Text>邮件</AntDesign.Text>
|
||||||
|
}
|
||||||
|
else if (context.Type == (int)MessageTemplateType.Sms)
|
||||||
|
{
|
||||||
|
<AntDesign.Text>短信</AntDesign.Text>
|
||||||
|
}
|
||||||
|
</PropertyColumn>
|
||||||
|
<PropertyColumn Property="c => c.Name" Title="模版名称" />
|
||||||
|
<PropertyColumn Property="c => c.LanguageId" Title="语言">
|
||||||
|
@GetLanuageName(context.LanguageId)
|
||||||
|
</PropertyColumn>
|
||||||
|
<PropertyColumn Property="c => c.Key" Title="模版Code" Width="100px" />
|
||||||
|
<PropertyColumn Property="c => c.Title" Title="模版标题" />
|
||||||
|
<PropertyColumn Property="c => c.Enabled" Title="状态" Width="80px" Align="ColumnAlign.Center">
|
||||||
|
@if (context.Enabled)
|
||||||
|
{
|
||||||
|
<AntDesign.Text Type="TextElementType.Success"><Icon Type="check" Theme=" IconThemeType.Outline" Width="1.3em" Height="1.3em" /></AntDesign.Text>
|
||||||
|
|
||||||
<Card Class="mt-3">
|
}
|
||||||
<Table DataSource="PagingList.Items" PageSize="100" HidePagination="true" Resizable>
|
else
|
||||||
<TitleTemplate>
|
{
|
||||||
<Flex Justify="FlexJustify.SpaceBetween">
|
<Icon Type="close" Theme="IconThemeType.Outline" Width="1.3em" Height="1.3em" />
|
||||||
菜单列表
|
}
|
||||||
<div>
|
</PropertyColumn>
|
||||||
<Button Class="me-3" OnClick="OnCreateClick" Type="ButtonType.Primary">新增</Button>
|
<PropertyColumn Property="c => c.CreateTime" Title="时间" Width="190px" />
|
||||||
</div>
|
<ActionColumn Title="操作" Align="ColumnAlign.Right" Width="160px">
|
||||||
</Flex>
|
<Space>
|
||||||
</TitleTemplate>
|
<SpaceItem>
|
||||||
<ColumnDefinitions>
|
<Dropdown Trigger="@(new Trigger[] { Trigger.Click })">
|
||||||
<PropertyColumn Property="c => c.Type" Title="模版类型" Width="150px">
|
<Overlay>
|
||||||
@if (context.Type == (int)MessageTemplateType.Message)
|
<Menu>
|
||||||
|
|
||||||
|
<MenuItem>
|
||||||
|
<a @onclick="(e) => HandleEdit(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>
|
||||||
|
<br />
|
||||||
|
<Row Justify="RowJustify.End">
|
||||||
|
@if (PagingList.Count > 0)
|
||||||
{
|
{
|
||||||
<AntDesign.Text>站内信</AntDesign.Text>
|
<Pagination Current="PagingList.Index" Total="PagingList.Count" PageSize="PagingList.Size" ShowSizeChanger="false" OnChange="OnPageChanged"></Pagination>
|
||||||
}
|
}
|
||||||
else if (context.Type == (int)MessageTemplateType.Email)
|
</Row>
|
||||||
{
|
</Card>
|
||||||
<AntDesign.Text>邮件</AntDesign.Text>
|
|
||||||
}
|
|
||||||
else if (context.Type == (int)MessageTemplateType.Sms)
|
|
||||||
{
|
|
||||||
<AntDesign.Text>短信</AntDesign.Text>
|
|
||||||
}
|
|
||||||
</PropertyColumn>
|
|
||||||
<PropertyColumn Property="c => c.Name" Title="模版名称" />
|
|
||||||
<PropertyColumn Property="c => c.Key" Title="模版Code" Width="100px" />
|
|
||||||
<PropertyColumn Property="c => c.Title" Title="模版标题" />
|
|
||||||
<PropertyColumn Property="c => c.Enabled" Title="状态" Width="80px" Align="ColumnAlign.Center">
|
|
||||||
@if (context.Enabled)
|
|
||||||
{
|
|
||||||
<AntDesign.Text Type="TextElementType.Success"><Icon Type="check" Theme=" IconThemeType.Outline" Width="1.3em" Height="1.3em" /></AntDesign.Text>
|
|
||||||
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<Icon Type="close" Theme="IconThemeType.Outline" Width="1.3em" Height="1.3em" />
|
|
||||||
}
|
|
||||||
</PropertyColumn>
|
|
||||||
<PropertyColumn Property="c => c.CreateTime" Title="时间" Width="190px" />
|
|
||||||
<ActionColumn Title="操作" Align="ColumnAlign.Right" Width="160px">
|
|
||||||
<Space>
|
|
||||||
<SpaceItem>
|
|
||||||
<Dropdown Trigger="@(new Trigger[] { Trigger.Click })">
|
|
||||||
<Overlay>
|
|
||||||
<Menu>
|
|
||||||
|
|
||||||
<MenuItem>
|
|
||||||
<a @onclick="(e) => HandleEdit(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>
|
|
||||||
<Row Justify="RowJustify.End">
|
|
||||||
<Pagination PageIndex="PagingList.Index" Total="PagingList.Count" PageSize="PagingList.Size" ShowSizeChanger="false" OnChange="OnPageChanged"></Pagination>
|
|
||||||
</Row>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<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="@template" @ref="@editForm" LabelCol="new ColLayoutParam { Span = 5 }" WrapperCol="new ColLayoutParam { Span = 15 }" Name="modalForm" OnFinish="OnFormFinish">
|
<Form Model="@model" @ref="@editForm" LabelCol="new ColLayoutParam { Span = 5 }" WrapperCol="new ColLayoutParam { Span = 15 }" Name="modalForm" OnFinish="OnFormFinish">
|
||||||
<FluentValidationValidator />
|
<FluentValidationValidator />
|
||||||
<FormItem Label="消息模版类型">
|
<FormItem Label="消息模版类型">
|
||||||
<SimpleSelect DefaultValue="" Style="width:120px;" @bind-Value="@context.Type">
|
<SimpleSelect DefaultValue="" Style="width:120px;" @bind-Value="@context.Type">
|
||||||
<SelectOptions>
|
<SelectOptions>
|
||||||
<SimpleSelectOption Value="" Label="请选择消息模版类型"></SimpleSelectOption>
|
<SimpleSelectOption Value="" Label="请选择消息模版类型"></SimpleSelectOption>
|
||||||
<SimpleSelectOption Value="@(((int)MessageTemplateType.Message).ToString())" Label="站内信"></SimpleSelectOption>
|
<SimpleSelectOption Value="@(((int)MessageTemplateType.Message).ToString())" Label="站内信"></SimpleSelectOption>
|
||||||
<SimpleSelectOption Value="@(((int)MessageTemplateType.Email).ToString())" Label="邮件模版"></SimpleSelectOption>
|
<SimpleSelectOption Value="@(((int)MessageTemplateType.Email).ToString())" Label="邮件模版"></SimpleSelectOption>
|
||||||
<SimpleSelectOption Value="@(((int)MessageTemplateType.Sms).ToString())" Label="短信模版"></SimpleSelectOption>
|
<SimpleSelectOption Value="@(((int)MessageTemplateType.Sms).ToString())" Label="短信模版"></SimpleSelectOption>
|
||||||
</SelectOptions>
|
</SelectOptions>
|
||||||
</SimpleSelect>
|
</SimpleSelect>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
<FormItem Label="消息模版名称">
|
<FormItem Label="消息模版名称">
|
||||||
<Input Placeholder="消息模版名称" @bind-Value="@context.Name" />
|
<Input Placeholder="消息模版名称" @bind-Value="@context.Name" />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
|
<FormItem Label="语言">
|
||||||
<FormItem Label="模版Code">
|
<Select DataSource="@languages" @bind-Value="@context.LanguageId" ItemValue="p => p.Id" ItemLabel="p => p.Title" Disabled="@(context.Id > 0)">
|
||||||
<Input Placeholder="模版Code" @bind-Value="@context.Key" Disabled="@(context.Id > 0)" />
|
</Select>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
<FormItem Label="消息标题">
|
<FormItem Label="模版Code">
|
||||||
<Input Placeholder="消息标题" @bind-Value="@context.Title" />
|
<Input Placeholder="模版Code" @bind-Value="@context.Key" Disabled="@(context.Id > 0)" />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
<FormItem Label="消息内容">
|
<FormItem Label="消息标题">
|
||||||
<TextArea Placeholder="消息内容" @bind-Value="@context.Body" />
|
<Input Placeholder="消息标题" @bind-Value="@context.Title" />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
<FormItem Label="状态">
|
<FormItem Label="消息内容">
|
||||||
<Checkbox @bind-Value="@context.Enabled" Disabled=false>
|
<TextArea Placeholder="消息内容" @bind-Value="@context.Body" />
|
||||||
启用
|
</FormItem>
|
||||||
</Checkbox>
|
<FormItem Label="状态">
|
||||||
</FormItem>
|
<Checkbox @bind-Value="@context.Enabled" Disabled=false>
|
||||||
</Form>
|
启用
|
||||||
</Modal>
|
</Checkbox>
|
||||||
|
</FormItem>
|
||||||
|
</Form>
|
||||||
|
</Modal>
|
||||||
|
</ChildContent>
|
||||||
|
</PageContainer>
|
||||||
|
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
|
[Parameter]
|
||||||
|
public string Locale { get; set; } = string.Empty;
|
||||||
|
|
||||||
[SupplyParameterFromQuery]
|
[SupplyParameterFromQuery]
|
||||||
int? Page { get; set; }
|
int? Page { get; set; }
|
||||||
|
|
||||||
@@ -147,10 +168,10 @@
|
|||||||
Form<MessageTemplateSearch> searchForm = null!;
|
Form<MessageTemplateSearch> searchForm = null!;
|
||||||
|
|
||||||
[SupplyParameterFromForm]
|
[SupplyParameterFromForm]
|
||||||
MessageTemplateModel template { get; set; } = new();
|
MessageTemplateModel model { get; set; } = new();
|
||||||
Form<MessageTemplateModel> editForm = null!;
|
Form<MessageTemplateModel> editForm = null!;
|
||||||
|
|
||||||
|
List<Language> languages = new();
|
||||||
PagingList<MessageTemplate> PagingList = new();
|
PagingList<MessageTemplate> PagingList = new();
|
||||||
bool loading { get; set; } = true;
|
bool loading { get; set; } = true;
|
||||||
bool searchExpand { get; set; } = false;
|
bool searchExpand { get; set; } = false;
|
||||||
@@ -166,7 +187,7 @@
|
|||||||
{
|
{
|
||||||
loadQueryString();
|
loadQueryString();
|
||||||
LoadList();
|
LoadList();
|
||||||
|
_ = LoadLanguages();
|
||||||
base.OnParametersSet();
|
base.OnParametersSet();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -177,6 +198,20 @@
|
|||||||
search.Key = query.GetQueryString("Key");
|
search.Key = query.GetQueryString("Key");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 void LoadList()
|
private async void LoadList()
|
||||||
{
|
{
|
||||||
|
|
||||||
@@ -265,13 +300,13 @@
|
|||||||
|
|
||||||
void OnCreateClick()
|
void OnCreateClick()
|
||||||
{
|
{
|
||||||
template = new();
|
model = new();
|
||||||
modalVisible = true;
|
modalVisible = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void HandleEdit(MessageTemplate model)
|
void HandleEdit(MessageTemplate data)
|
||||||
{
|
{
|
||||||
template = model.Adapt<MessageTemplateModel>();
|
model = data.Adapt<MessageTemplateModel>();
|
||||||
modalVisible = true;
|
modalVisible = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -285,18 +320,8 @@
|
|||||||
if (editForm.Validate())
|
if (editForm.Validate())
|
||||||
{
|
{
|
||||||
var result = new ApiResult<string>();
|
var result = new ApiResult<string>();
|
||||||
var data = template.Adapt<MessageTemplate>();
|
var url = $"api/messagetemplate/save";
|
||||||
|
result = await HttpService.Post<ApiResult<string>>(url, model);
|
||||||
if (template.Id > 0)
|
|
||||||
{
|
|
||||||
var url = $"api/messagetemplate/edit";
|
|
||||||
result = await HttpService.Post<ApiResult<string>>(url, data);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var url = $"api/messagetemplate/add";
|
|
||||||
result = await HttpService.Post<ApiResult<string>>(url, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result.Code == (int)ResultCode.Success)
|
if (result.Code == (int)ResultCode.Success)
|
||||||
{
|
{
|
||||||
@@ -314,4 +339,14 @@
|
|||||||
{
|
{
|
||||||
modalVisible = false;
|
modalVisible = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
string GetLanuageName(int languageId)
|
||||||
|
{
|
||||||
|
var language = languages.FirstOrDefault(l => l.Id == languageId);
|
||||||
|
if (language != null)
|
||||||
|
{
|
||||||
|
return language.Title;
|
||||||
|
}
|
||||||
|
return "-";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,22 @@
|
|||||||
@page "/settings"
|
@page "/settings"
|
||||||
|
@page "/{locale}/settings"
|
||||||
@attribute [Authorize]
|
@attribute [Authorize]
|
||||||
@inject ILogger<Settings> Logger
|
@inject ILogger<Settings> Logger
|
||||||
|
|
||||||
@code {
|
<PageContainer Title="系统设置">
|
||||||
|
<Breadcrumb>
|
||||||
|
<Breadcrumb>
|
||||||
|
<BreadcrumbItem Href="/">管理后台</BreadcrumbItem>
|
||||||
|
<BreadcrumbItem Href="/admin/list">系统功能</BreadcrumbItem>
|
||||||
|
<BreadcrumbItem>系统设置</BreadcrumbItem>
|
||||||
|
</Breadcrumb>
|
||||||
|
</Breadcrumb>
|
||||||
|
<ChildContent>
|
||||||
|
<h3>Tools</h3>
|
||||||
|
</ChildContent>
|
||||||
|
</PageContainer>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
[Parameter]
|
||||||
|
public string Locale { get; set; } = string.Empty;
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,175 @@
|
|||||||
|
@page "/stateprovince/{countryId:long}/create"
|
||||||
|
@page "/stateprovince/{countryId:long}/edit/{id:long}"
|
||||||
|
@page "/{locale}/stateprovince/{countryId:long}/create"
|
||||||
|
@page "/{locale}/stateprovince/{countryId:long}/edit/{id:long}"
|
||||||
|
|
||||||
|
@inject ILogger<StateProvinceEdit> Logger
|
||||||
|
@attribute [Authorize]
|
||||||
|
|
||||||
|
<PageContainer Title="@(Id > 0 ? "编辑州省信息" : "新增州省信息")">
|
||||||
|
<Breadcrumb>
|
||||||
|
<Breadcrumb>
|
||||||
|
<BreadcrumbItem Href="/">管理后台</BreadcrumbItem>
|
||||||
|
<BreadcrumbItem Href="/settings">系统配置</BreadcrumbItem>
|
||||||
|
<BreadcrumbItem Href="/country/list">国家管理</BreadcrumbItem>
|
||||||
|
<BreadcrumbItem Href="@($"/stateprovince/list/{CountryId}")">州省管理</BreadcrumbItem>
|
||||||
|
</Breadcrumb>
|
||||||
|
</Breadcrumb>
|
||||||
|
<ChildContent>
|
||||||
|
|
||||||
|
<Spin Spinning="pageLoading">
|
||||||
|
<Card Title="州省信息">
|
||||||
|
<Form @ref="editform" Model="@model" LabelColSpan="5" WrapperColSpan="14" OnFinish="OnFormFinishAsync">
|
||||||
|
<FormItem Label="国家">
|
||||||
|
<Input @bind-Value="@country.Name" Placeholder="国家名称" Disabled />
|
||||||
|
</FormItem>
|
||||||
|
<FormItem Label="名称" Required>
|
||||||
|
<Input @bind-Value="@context.Name" Placeholder="名称" />
|
||||||
|
</FormItem>
|
||||||
|
<FormItem Label="首字母">
|
||||||
|
<Input @bind-Value="@context.Initial" Placeholder="首字母" />
|
||||||
|
</FormItem>
|
||||||
|
<FormItem Label="缩写">
|
||||||
|
<Input @bind-Value="@context.Abbreviation" Placeholder="缩写" />
|
||||||
|
</FormItem>
|
||||||
|
<FormItem Label="显示排序">
|
||||||
|
<Input @bind-Value="@context.DisplayOrder" Placeholder="显示排序" />
|
||||||
|
</FormItem>
|
||||||
|
<FormItem Label="状态">
|
||||||
|
<Checkbox T="bool" Label="启用" @bind-value="model.Enabled" Size="InputSize.Small" Class="ps-0" />
|
||||||
|
</FormItem>
|
||||||
|
<FormItem WrapperCol="new ColLayoutParam { Span = 24, Offset = 5 }">
|
||||||
|
<Button Type="@ButtonType.Primary" HtmlType="submit" Loading="saving">
|
||||||
|
提交保存
|
||||||
|
</Button>
|
||||||
|
</FormItem>
|
||||||
|
</Form>
|
||||||
|
</Card>
|
||||||
|
</Spin>
|
||||||
|
</ChildContent>
|
||||||
|
</PageContainer>
|
||||||
|
@code {
|
||||||
|
[Parameter]
|
||||||
|
public string Locale { get; set; } = string.Empty;
|
||||||
|
[Parameter]
|
||||||
|
public long CountryId { get; set; }
|
||||||
|
[Parameter]
|
||||||
|
public long Id { get; set; }
|
||||||
|
|
||||||
|
[SupplyParameterFromForm]
|
||||||
|
StateProvinceModel model { get; set; } = new();
|
||||||
|
Form<StateProvinceModel> editform = null!;
|
||||||
|
List<KeyValue> languageList = new();
|
||||||
|
Country country = new();
|
||||||
|
StateProvinceLocalizedModel stateProvince = new();
|
||||||
|
bool pageLoading = false;
|
||||||
|
bool saving = false;
|
||||||
|
|
||||||
|
protected override void OnInitialized()
|
||||||
|
{
|
||||||
|
base.OnInitialized();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnParametersSet()
|
||||||
|
{
|
||||||
|
model.CountryId = CountryId;
|
||||||
|
|
||||||
|
_ = LoadLanguage();
|
||||||
|
_ = LoadCountry();
|
||||||
|
if (Id > 0)
|
||||||
|
{
|
||||||
|
LoadData();
|
||||||
|
}
|
||||||
|
base.OnParametersSet();
|
||||||
|
}
|
||||||
|
|
||||||
|
async Task LoadCountry()
|
||||||
|
{
|
||||||
|
var url = $"/api/country?id={CountryId}";
|
||||||
|
var apiResult = await HttpService.Get<ApiResult<Country>>(url);
|
||||||
|
if (apiResult.Success)
|
||||||
|
{
|
||||||
|
if (apiResult.Data != null)
|
||||||
|
{
|
||||||
|
country = apiResult.Data;
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Navigation.NavigateTo($"/country/list");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async Task LoadLanguage()
|
||||||
|
{
|
||||||
|
var url = $"/api/language/enabled";
|
||||||
|
var apiResult = await HttpService.Get<ApiResult<List<KeyValue>>>(url);
|
||||||
|
if (apiResult.Success)
|
||||||
|
{
|
||||||
|
if (apiResult.Data == null)
|
||||||
|
{
|
||||||
|
languageList = new List<KeyValue>();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
languageList = apiResult.Data;
|
||||||
|
languageList.Insert(0, new KeyValue() { Key = "0", Value = "标准" });
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async void LoadData()
|
||||||
|
{
|
||||||
|
pageLoading = true;
|
||||||
|
var url = $"/api/stateprovince/detail?id={Id}";
|
||||||
|
var apiResult = await HttpService.Get<ApiResult<StateProvinceLocalizedModel>>(url);
|
||||||
|
if (apiResult.Success)
|
||||||
|
{
|
||||||
|
if (apiResult.Data == null)
|
||||||
|
{
|
||||||
|
Navigation.NavigateTo($"/country/list");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
stateProvince = apiResult.Data;
|
||||||
|
model = apiResult.Data.Adapt<StateProvinceModel>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Navigation.NavigateTo($"/country/list");
|
||||||
|
}
|
||||||
|
|
||||||
|
pageLoading = false;
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
async void OnFormFinishAsync()
|
||||||
|
{
|
||||||
|
if (editform.Validate())
|
||||||
|
{
|
||||||
|
saving = true;
|
||||||
|
var url = $"api/stateprovince/save";
|
||||||
|
var result = new ApiResult<string>();
|
||||||
|
result = await HttpService.Post<ApiResult<string>>(url, model);
|
||||||
|
|
||||||
|
|
||||||
|
if (result.Code == (int)ResultCode.Success)
|
||||||
|
{
|
||||||
|
saving = false;
|
||||||
|
await ModalService.InfoAsync(new ConfirmOptions() { Title = "提示", Content = "数据提交成功!" });
|
||||||
|
Navigation.NavigateTo($"/stateprovince/list/{CountryId}");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
saving = false;
|
||||||
|
await ModalService.ErrorAsync(new ConfirmOptions() { Title = "服务异常", Content = result.Message });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,215 @@
|
|||||||
|
@page "/stateprovince/list/{countryId:long}"
|
||||||
|
@page "/{locale}/stateprovince/list/{countryId:long}"
|
||||||
|
@inject ILogger<StateProvinceList> Logger
|
||||||
|
@attribute [Authorize]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<PageContainer Title="州省管理">
|
||||||
|
<Breadcrumb>
|
||||||
|
<Breadcrumb>
|
||||||
|
<BreadcrumbItem Href="/">管理后台</BreadcrumbItem>
|
||||||
|
<BreadcrumbItem Href="/settings">系统配置</BreadcrumbItem>
|
||||||
|
<BreadcrumbItem Href="/country/list">国家管理</BreadcrumbItem>
|
||||||
|
<BreadcrumbItem>州省管理</BreadcrumbItem>
|
||||||
|
</Breadcrumb>
|
||||||
|
</Breadcrumb>
|
||||||
|
<ChildContent>
|
||||||
|
<Spin Spinning="pageLoading">
|
||||||
|
<Card>
|
||||||
|
<Form @ref="searchForm" Model="search" Layout="FormLayout.Inline" Class="search-form" OnFinish="OnSearchFinish">
|
||||||
|
<Row Justify="RowJustify.Start" Gutter="(16, 16)">
|
||||||
|
<Col>
|
||||||
|
<FormItem Label="名称">
|
||||||
|
<Input @bind-Value="search.Name" Placeholder="名称" AllowClear />
|
||||||
|
</FormItem>
|
||||||
|
</Col>
|
||||||
|
|
||||||
|
<Col>
|
||||||
|
<div class="ant-form-item" style="display:flex">
|
||||||
|
<Button Type="ButtonType.Primary" HtmlType="submit">查询</Button>
|
||||||
|
<Button Style="margin: 0 8px;" OnClick="OnSearchReset">重置</Button>
|
||||||
|
</div>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</Form>
|
||||||
|
</Card>
|
||||||
|
<br />
|
||||||
|
<Card Title="州、省列表" Class="hideborder">
|
||||||
|
<Extra>
|
||||||
|
<div class="extraContent">
|
||||||
|
<Button Type="ButtonType.Primary" HtmlType="submit" OnClick="HandleAddNew">新增州省</Button>
|
||||||
|
</div>
|
||||||
|
</Extra>
|
||||||
|
<ChildContent>
|
||||||
|
<Table DataSource="PagingList.Items" PageSize="100" HidePagination="true">
|
||||||
|
<Selection CheckStrictly />
|
||||||
|
<PropertyColumn Property="c => c.Name" Title="名称" />
|
||||||
|
<PropertyColumn Property="c => c.Initial" Title="首字母" />
|
||||||
|
<PropertyColumn Property="c => c.Abbreviation" Title="缩写" />
|
||||||
|
<PropertyColumn Property="c => c.Enabled" Title="状态">
|
||||||
|
@if (context.Enabled)
|
||||||
|
{
|
||||||
|
<AntDesign.Text Type="TextElementType.Success"><Icon Type="check" Theme=" IconThemeType.Outline" Width="1.3em" Height="1.3em" /></AntDesign.Text>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<Icon Type="close" Theme="IconThemeType.Outline" Width="1.3em" Height="1.3em" />
|
||||||
|
}
|
||||||
|
</PropertyColumn>
|
||||||
|
<PropertyColumn Property="c => c.DisplayOrder" Title="排序" />
|
||||||
|
<ActionColumn Title="操作" Align="ColumnAlign.Right">
|
||||||
|
<Space>
|
||||||
|
<SpaceItem>
|
||||||
|
<a @onclick="(e) => HandleEdit(context)">编辑</a>
|
||||||
|
</SpaceItem>
|
||||||
|
@*<SpaceItem>
|
||||||
|
<Popconfirm Placement="@Placement.Left" Title="@("删除这条数据无法恢复,您确定要删除吗?")"
|
||||||
|
OnConfirm="@(e=>HandleDeleteConfirmAsync(e,context.Id))"
|
||||||
|
OkText="确定"
|
||||||
|
CancelText="取消">
|
||||||
|
<a>删除</a>
|
||||||
|
</Popconfirm>
|
||||||
|
</SpaceItem>*@
|
||||||
|
</Space>
|
||||||
|
</ActionColumn>
|
||||||
|
</Table>
|
||||||
|
<br />
|
||||||
|
<Row Justify="RowJustify.End">
|
||||||
|
@if (PagingList.Count > 0)
|
||||||
|
{
|
||||||
|
<Pagination Current="PagingList.Index" Total="PagingList.Count" PageSize="PagingList.Size" ShowSizeChanger="false" OnChange="OnPageChanged"></Pagination>
|
||||||
|
}
|
||||||
|
</Row>
|
||||||
|
</ChildContent>
|
||||||
|
</Card>
|
||||||
|
</Spin>
|
||||||
|
</ChildContent>
|
||||||
|
</PageContainer>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
[Parameter]
|
||||||
|
public string Locale { get; set; } = string.Empty;
|
||||||
|
[Parameter]
|
||||||
|
public long CountryId { get; set; }
|
||||||
|
|
||||||
|
[SupplyParameterFromQuery]
|
||||||
|
int? Page { get; set; }
|
||||||
|
|
||||||
|
[SupplyParameterFromQuery(Name = "size")]
|
||||||
|
int? PageSize { get; set; }
|
||||||
|
|
||||||
|
bool pageLoading = false;
|
||||||
|
bool searchExpand = false;
|
||||||
|
|
||||||
|
Form<StateProvinceSearch> searchForm = new();
|
||||||
|
StateProvinceSearch search = new();
|
||||||
|
|
||||||
|
PagingList<StateProvince> PagingList = new() { Size = 20 };
|
||||||
|
|
||||||
|
|
||||||
|
protected override async Task OnInitializedAsync()
|
||||||
|
{
|
||||||
|
await base.OnInitializedAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async Task OnParametersSetAsync()
|
||||||
|
{
|
||||||
|
loadQueryString();
|
||||||
|
await LoadListAsync();
|
||||||
|
await base.OnParametersSetAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
void loadQueryString()
|
||||||
|
{
|
||||||
|
var uri = new Uri(Navigation.Uri);
|
||||||
|
var query = uri.Query;
|
||||||
|
search.Name = query.GetQueryString("Name");
|
||||||
|
search.CountryId = CountryId;
|
||||||
|
}
|
||||||
|
|
||||||
|
async Task LoadListAsync()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Console.WriteLine(search.ToJson());
|
||||||
|
pageLoading = true;
|
||||||
|
var url = "/api/stateprovince/search";
|
||||||
|
var apiResult = await HttpService.GetPagingList<StateProvince>(url, search, Page.GetValueOrDefault(1), PageSize.GetValueOrDefault(20));
|
||||||
|
if (apiResult.Success)
|
||||||
|
{
|
||||||
|
if (apiResult.Data != null)
|
||||||
|
{
|
||||||
|
PagingList = apiResult.Data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (apiResult.Code == 403)
|
||||||
|
{
|
||||||
|
ModalService.Error(new ConfirmOptions() { Title = "权限不足", Content = apiResult.Message });
|
||||||
|
}
|
||||||
|
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
pageLoading = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnSearchReset()
|
||||||
|
{
|
||||||
|
search = new();
|
||||||
|
searchForm?.Reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnSearch(int page)
|
||||||
|
{
|
||||||
|
var queryString = search.BuildQueryString();
|
||||||
|
if (string.IsNullOrEmpty(queryString))
|
||||||
|
{
|
||||||
|
if (page > 1)
|
||||||
|
{
|
||||||
|
Navigation.NavigateTo($"/stateprovince/list/{CountryId}?page={page}");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Navigation.NavigateTo($"/stateprovince/list/{CountryId}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (page > 1)
|
||||||
|
{
|
||||||
|
Navigation.NavigateTo($"/stateprovince/list/{CountryId}?page={page}&{queryString}");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Navigation.NavigateTo($"/stateprovince/list/{CountryId}?{queryString}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnSearchFinish()
|
||||||
|
{
|
||||||
|
Page = Page.GetValueOrDefault(1) - 1;
|
||||||
|
|
||||||
|
OnSearch(Page.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnPageChanged(PaginationEventArgs args)
|
||||||
|
{
|
||||||
|
OnSearch(args.Page);
|
||||||
|
}
|
||||||
|
|
||||||
|
void HandleAddNew()
|
||||||
|
{
|
||||||
|
Navigation.NavigateTo($"/stateprovince/{CountryId}/create");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void HandleEdit(StateProvince model)
|
||||||
|
{
|
||||||
|
Navigation.NavigateTo($"/stateprovince/{CountryId}/edit/{model.Id}");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,152 @@
|
|||||||
|
@page "/system/app/version/create"
|
||||||
|
@page "/system/app/version/edit/{id:long}"
|
||||||
|
@page "/{locale}/system/app/version/create"
|
||||||
|
@page "/{locale}/system/app/version/edit/{id:long}"
|
||||||
|
|
||||||
|
@inject ILogger<AppVersionEdit> Logger
|
||||||
|
@attribute [Authorize]
|
||||||
|
|
||||||
|
<PageContainer Title="@(Id > 0 ? "编辑版本信息" : "新增版本信息")">
|
||||||
|
<Breadcrumb>
|
||||||
|
<Breadcrumb>
|
||||||
|
<BreadcrumbItem Href="/">管理后台</BreadcrumbItem>
|
||||||
|
<BreadcrumbItem Href="/admin/list">系统功能</BreadcrumbItem>
|
||||||
|
<BreadcrumbItem>版本管理</BreadcrumbItem>
|
||||||
|
</Breadcrumb>
|
||||||
|
</Breadcrumb>
|
||||||
|
<ChildContent>
|
||||||
|
|
||||||
|
<Spin Spinning="pageLoading">
|
||||||
|
<Card Title="版本信息">
|
||||||
|
<Form @ref="editform" Model="@model" LabelColSpan="5" WrapperColSpan="14" OnFinish="OnFormFinishAsync">
|
||||||
|
<FormItem Label="应用名称" Required>
|
||||||
|
<Input @bind-Value="@context.AppName" Placeholder="应用名称" />
|
||||||
|
</FormItem>
|
||||||
|
<FormItem Label="版本标题" Required>
|
||||||
|
<Input @bind-Value="@context.Title" Placeholder="版本标题" />
|
||||||
|
</FormItem>
|
||||||
|
<FormItem Label="运行平台">
|
||||||
|
<Select TItemValue="int" TItem="int" Style="width:250px;" @bind-Value="@context.Platform">
|
||||||
|
<SelectOption Value="0" Label="请选择运行平台"></SelectOption>
|
||||||
|
<SelectOption Value="1" Label="Windows桌面"></SelectOption>
|
||||||
|
<SelectOption Value="2" Label="安卓系统"></SelectOption>
|
||||||
|
<SelectOption Value="3" Label="iOS系统"></SelectOption>
|
||||||
|
</Select>
|
||||||
|
</FormItem>
|
||||||
|
<FormItem Label="版本状态">
|
||||||
|
<Select TItemValue="int" TItem="int" Style="width:250px;" @bind-Value="@context.VersionState">
|
||||||
|
<SelectOption Value="0" Label="请选版本状态"></SelectOption>
|
||||||
|
<SelectOption Value="1" Label="开发版"></SelectOption>
|
||||||
|
<SelectOption Value="2" Label="Alphal内测版"></SelectOption>
|
||||||
|
<SelectOption Value="3" Label="Beta公测版"></SelectOption>
|
||||||
|
<SelectOption Value="4" Label="Rc版"></SelectOption>
|
||||||
|
<SelectOption Value="5" Label="正式版"></SelectOption>
|
||||||
|
</Select>
|
||||||
|
</FormItem>
|
||||||
|
<FormItem Label="版本信息" Required>
|
||||||
|
<InputGroup Compact>
|
||||||
|
<Input @bind-Value="@context.VersionX" Placeholder="主版本号" Style="width: 20%;" />
|
||||||
|
<Input @bind-Value="@context.VersionY" Placeholder="次版本号" Style="width: 20%;" />
|
||||||
|
<Input @bind-Value="@context.VersionZ" Placeholder="修订版本" Style="width: 20%;" />
|
||||||
|
</InputGroup>
|
||||||
|
</FormItem>
|
||||||
|
<FormItem Label="更新内容" Required>
|
||||||
|
<TextArea Rows="10" @bind-Value="@context.Content" Placeholder="更新内容" />
|
||||||
|
</FormItem>
|
||||||
|
<FormItem Label="状态">
|
||||||
|
<RadioGroup @bind-Value="@context.Status">
|
||||||
|
<Radio RadioButton Value=1>草稿</Radio>
|
||||||
|
<Radio RadioButton Value=2>发布</Radio>
|
||||||
|
</RadioGroup>
|
||||||
|
</FormItem>
|
||||||
|
<FormItem WrapperCol="new ColLayoutParam { Span = 24, Offset = 5 }">
|
||||||
|
<Button Type="@ButtonType.Primary" HtmlType="submit">
|
||||||
|
提交保存
|
||||||
|
</Button>
|
||||||
|
</FormItem>
|
||||||
|
</Form>
|
||||||
|
</Card>
|
||||||
|
</Spin>
|
||||||
|
</ChildContent>
|
||||||
|
</PageContainer>
|
||||||
|
@code {
|
||||||
|
[Parameter]
|
||||||
|
public string Locale { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public long Id { get; set; }
|
||||||
|
|
||||||
|
[SupplyParameterFromForm]
|
||||||
|
AppVersionModel model { get; set; } = new();
|
||||||
|
Form<AppVersionModel> editform = null!;
|
||||||
|
bool pageLoading = false;
|
||||||
|
|
||||||
|
protected override void OnInitialized()
|
||||||
|
{
|
||||||
|
base.OnInitialized();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnParametersSet()
|
||||||
|
{
|
||||||
|
if (Id > 0)
|
||||||
|
{
|
||||||
|
LoadData();
|
||||||
|
}
|
||||||
|
base.OnParametersSet();
|
||||||
|
}
|
||||||
|
|
||||||
|
async void LoadData()
|
||||||
|
{
|
||||||
|
pageLoading = true;
|
||||||
|
var url = $"/api/appversion/{Id}";
|
||||||
|
var apiResult = await HttpService.Get<ApiResult<AppVersion>>(url);
|
||||||
|
if (apiResult.Success)
|
||||||
|
{
|
||||||
|
if (apiResult.Data == null)
|
||||||
|
{
|
||||||
|
Navigation.NavigateTo($"/system/app/version/create");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
model = apiResult.Data.Adapt<AppVersionModel>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Navigation.NavigateTo($"/system/app/version/create");
|
||||||
|
}
|
||||||
|
|
||||||
|
pageLoading = false;
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
async void OnFormFinishAsync()
|
||||||
|
{
|
||||||
|
if (editform.Validate())
|
||||||
|
{
|
||||||
|
|
||||||
|
var result = new ApiResult<string>();
|
||||||
|
if (model.Id > 0)
|
||||||
|
{
|
||||||
|
var url = $"api/appversion/edit";
|
||||||
|
result = await HttpService.Post<ApiResult<string>>(url, model);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var url = $"api/appversion/add";
|
||||||
|
result = await HttpService.Post<ApiResult<string>>(url, model);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.Code == (int)ResultCode.Success)
|
||||||
|
{
|
||||||
|
await ModalService.InfoAsync(new ConfirmOptions() { Title = "提示", Content = "数据提交成功!" });
|
||||||
|
Navigation.NavigateTo($"/system/app/version/list");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await ModalService.ErrorAsync(new ConfirmOptions() { Title = "服务异常", Content = result.Message });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,9 +1,318 @@
|
|||||||
@page "/system/app/version/list"
|
@page "/system/app/version/list"
|
||||||
|
@page "/{locale}/system/app/version/list"
|
||||||
|
|
||||||
@inject ILogger<AppVersionList> Logger
|
@inject ILogger<AppVersionList> Logger
|
||||||
@attribute [Authorize]
|
@attribute [Authorize]
|
||||||
|
|
||||||
|
<PageContainer Title="App版本管理">
|
||||||
|
<Breadcrumb>
|
||||||
|
<Breadcrumb>
|
||||||
|
<BreadcrumbItem Href="/">管理后台</BreadcrumbItem>
|
||||||
|
<BreadcrumbItem Href="/settings">系统配置</BreadcrumbItem>
|
||||||
|
<BreadcrumbItem>版本管理</BreadcrumbItem>
|
||||||
|
</Breadcrumb>
|
||||||
|
</Breadcrumb>
|
||||||
|
<ChildContent>
|
||||||
|
<Card Class="">
|
||||||
|
<Form @ref="searchForm" Model="search" Layout="FormLayout.Inline" Class="search-form" OnFinish="OnSearchFinish">
|
||||||
|
<Row Justify="RowJustify.Start" Gutter="16">
|
||||||
|
<Col>
|
||||||
|
<FormItem Label="名称">
|
||||||
|
<Input @bind-Value="search.Name" Placeholder="名称" AllowClear />
|
||||||
|
</FormItem>
|
||||||
|
</Col>
|
||||||
|
<Col>
|
||||||
|
<FormItem Label="状态">
|
||||||
|
<SimpleSelect DefaultValue="" Style="width:120px;" @bind-Value="@context.Status">
|
||||||
|
<SelectOptions>
|
||||||
|
<SimpleSelectOption Value="" Label="全部"></SimpleSelectOption>
|
||||||
|
<SimpleSelectOption Value="1" Label="草稿"></SimpleSelectOption>
|
||||||
|
<SimpleSelectOption Value="2" Label="已发布"></SimpleSelectOption>
|
||||||
|
</SelectOptions>
|
||||||
|
</SimpleSelect>
|
||||||
|
</FormItem>
|
||||||
|
</Col>
|
||||||
|
<Col>
|
||||||
|
<div class="ant-form-item" style="width:200px;display:flex;">
|
||||||
|
<Button Type="ButtonType.Primary" HtmlType="submit">查询</Button>
|
||||||
|
<Button Style="margin: 0 8px;" OnClick="OnReset">重置</Button>
|
||||||
|
</div>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</Form>
|
||||||
|
</Card>
|
||||||
|
<br />
|
||||||
|
<Card Class="mt-3">
|
||||||
|
<Table DataSource="PagingList.Items" PageSize="100" HidePagination="true" Resizable>
|
||||||
|
<TitleTemplate>
|
||||||
|
<Flex Justify="FlexJustify.SpaceBetween">
|
||||||
|
版本记录
|
||||||
|
<div>
|
||||||
|
<AuthorizeCheck Permission="@Permissions.Admin.Create">
|
||||||
|
<Button Class="me-3" OnClick="OnCreateClick" Type="ButtonType.Primary">新增</Button>
|
||||||
|
</AuthorizeCheck>
|
||||||
|
</div>
|
||||||
|
</Flex>
|
||||||
|
</TitleTemplate>
|
||||||
|
<ColumnDefinitions>
|
||||||
|
<PropertyColumn Property="c => c.Title" Title="标题">
|
||||||
|
</PropertyColumn>
|
||||||
|
<PropertyColumn Property="c => c.AppName" Title="应用">
|
||||||
|
|
||||||
|
</PropertyColumn>
|
||||||
|
<PropertyColumn Property="c => c.Platform" Title="平台">
|
||||||
|
@if (context.Platform == 1)
|
||||||
|
{
|
||||||
|
<Tag>windows</Tag>
|
||||||
|
}
|
||||||
|
else if (context.Platform == 2)
|
||||||
|
{
|
||||||
|
<Tag>安卓</Tag>
|
||||||
|
}
|
||||||
|
else if (context.Platform == 3)
|
||||||
|
{
|
||||||
|
<Tag>iOS</Tag>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<Tag>未设置</Tag>
|
||||||
|
}
|
||||||
|
</PropertyColumn>
|
||||||
|
<PropertyColumn Property="c => c.VersionX" Title="版本号">
|
||||||
|
@context.VersionX.@context.VersionY.@context.VersionZ
|
||||||
|
</PropertyColumn>
|
||||||
|
<PropertyColumn Property="c => c.VersionState" Title="版本状态">
|
||||||
|
@if (context.VersionState == 1)
|
||||||
|
{
|
||||||
|
<Tag>开发版</Tag>
|
||||||
|
}
|
||||||
|
else if (context.VersionState == 2)
|
||||||
|
{
|
||||||
|
<Tag>Alphal内测版</Tag>
|
||||||
|
}
|
||||||
|
else if (context.VersionState == 3)
|
||||||
|
{
|
||||||
|
<Tag>Beta公测版</Tag>
|
||||||
|
}
|
||||||
|
else if (context.VersionState == 4)
|
||||||
|
{
|
||||||
|
<Tag>Rc版</Tag>
|
||||||
|
}
|
||||||
|
else if (context.VersionState == 5)
|
||||||
|
{
|
||||||
|
<Tag>正式版</Tag>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<Tag>未设置</Tag>
|
||||||
|
}
|
||||||
|
</PropertyColumn>
|
||||||
|
<PropertyColumn Property="c => c.Status" Title="状态" Width="80px" Align="ColumnAlign.Center">
|
||||||
|
@if (context.Status == 1)
|
||||||
|
{
|
||||||
|
<Tag>草稿</Tag>
|
||||||
|
}
|
||||||
|
else if (context.Status == 2)
|
||||||
|
{
|
||||||
|
<Tag>发布</Tag>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<Tag>未设置</Tag>
|
||||||
|
}
|
||||||
|
</PropertyColumn>
|
||||||
|
<PropertyColumn Property="c => c.UpdateTime" Title="最后更新" />
|
||||||
|
<ActionColumn Title="操作" Align="ColumnAlign.Right" Width="160px">
|
||||||
|
<Space>
|
||||||
|
<SpaceItem>
|
||||||
|
<Dropdown Trigger="@(new Trigger[] { Trigger.Click })">
|
||||||
|
<Overlay>
|
||||||
|
<Menu>
|
||||||
|
|
||||||
|
<MenuItem>
|
||||||
|
<a @onclick="(e) => OnEditClick(context.Id)"> <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>
|
||||||
|
<br />
|
||||||
|
<Row Justify="RowJustify.End">
|
||||||
|
@if (PagingList.Count > 0)
|
||||||
|
{
|
||||||
|
<Pagination Current="PagingList.Index" Total="PagingList.Count" PageSize="PagingList.Size" ShowSizeChanger="false" OnChange="OnPageChanged"></Pagination>
|
||||||
|
}
|
||||||
|
</Row>
|
||||||
|
</Card>
|
||||||
|
</ChildContent>
|
||||||
|
</PageContainer>
|
||||||
|
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
|
[Parameter]
|
||||||
|
public string Locale { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
[SupplyParameterFromQuery]
|
||||||
|
int? Page { get; set; }
|
||||||
|
|
||||||
|
[SupplyParameterFromQuery(Name = "size")]
|
||||||
|
int? PageSize { get; set; }
|
||||||
|
|
||||||
|
[SupplyParameterFromForm]
|
||||||
|
AppVersionSearch search { get; set; } = new();
|
||||||
|
Form<AppVersionSearch> searchForm = null!;
|
||||||
|
|
||||||
|
PagingList<AppVersion> PagingList = new();
|
||||||
|
bool loading { get; set; } = true;
|
||||||
|
bool searchExpand { get; set; } = false;
|
||||||
|
|
||||||
|
bool drawerVisible;
|
||||||
|
|
||||||
|
protected override void OnInitialized()
|
||||||
|
{
|
||||||
|
base.OnInitialized();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnParametersSet()
|
||||||
|
{
|
||||||
|
|
||||||
|
loadQueryString();
|
||||||
|
_ = LoadList();
|
||||||
|
|
||||||
|
base.OnParametersSet();
|
||||||
|
}
|
||||||
|
|
||||||
|
void loadQueryString()
|
||||||
|
{
|
||||||
|
var uri = new Uri(Navigation.Uri);
|
||||||
|
var query = uri.Query;
|
||||||
|
search.Name = query.GetQueryString("Name");
|
||||||
|
search.Status = query.GetQueryString("Status");
|
||||||
|
|
||||||
|
var data = query.GetQueryString("RangeTime");
|
||||||
|
if (!string.IsNullOrEmpty(data))
|
||||||
|
{
|
||||||
|
var rangetime = data.Split("-");
|
||||||
|
if (rangetime != null && rangetime.Length > 0)
|
||||||
|
{
|
||||||
|
searchExpand = true;
|
||||||
|
search.RangeTime[0] = rangetime[0].NumberToDateTime();
|
||||||
|
search.RangeTime[1] = rangetime[1].NumberToDateTime();
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private async Task LoadList()
|
||||||
|
{
|
||||||
|
|
||||||
|
loading = true;
|
||||||
|
var url = "/api/appversion/search";
|
||||||
|
var apiResult = await HttpService.GetPagingList<AppVersion>(url, search, Page.GetValueOrDefault(1), PageSize.GetValueOrDefault(20));
|
||||||
|
if (apiResult.Success)
|
||||||
|
{
|
||||||
|
if (apiResult.Data != null)
|
||||||
|
{
|
||||||
|
PagingList = apiResult.Data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
loading = false;
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnSearchFinish()
|
||||||
|
{
|
||||||
|
Page = Page.GetValueOrDefault(1) - 1;
|
||||||
|
|
||||||
|
OnSearch(Page.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void OnReset()
|
||||||
|
{
|
||||||
|
search = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnSearch(int page)
|
||||||
|
{
|
||||||
|
var queryString = search.BuildQueryString();
|
||||||
|
if (string.IsNullOrEmpty(queryString))
|
||||||
|
{
|
||||||
|
if (page > 1)
|
||||||
|
{
|
||||||
|
Navigation.NavigateTo($"/system/app/version/list?page={page}");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Navigation.NavigateTo($"/system/app/version/list");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (page > 1)
|
||||||
|
{
|
||||||
|
Navigation.NavigateTo($"/system/app/version/list?page={page}&{queryString}");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Navigation.NavigateTo($"/system/app/version/list?{queryString}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async Task HandleDeleteConfirmAsync(MouseEventArgs e, long id)
|
||||||
|
{
|
||||||
|
var url = $"/api/appversion/delete/{id}";
|
||||||
|
var apiResult = await HttpService.Post<ApiResult<string>>(url, new());
|
||||||
|
if (apiResult.Success)
|
||||||
|
{
|
||||||
|
await LoadList();
|
||||||
|
await ModalService.InfoAsync(new ConfirmOptions() { Title = "操作提示", Content = "删除数据成功" });
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await ModalService.ErrorAsync(new ConfirmOptions() { Title = "操作提示", Content = $"数据删除失败.{apiResult.Message}" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private void OnPageChanged(PaginationEventArgs args)
|
||||||
|
{
|
||||||
|
OnSearch(args.Page);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void OnCreateClick()
|
||||||
|
{
|
||||||
|
Navigation.NavigateTo($"/system/app/version/create");
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnEditClick(long id)
|
||||||
|
{
|
||||||
|
Navigation.NavigateTo($"/system/app/version/edit/{id}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,22 @@
|
|||||||
@page "/system/app/list"
|
@page "/system/app/list"
|
||||||
|
@page "/{locale}/system/app/list"
|
||||||
@inject ILogger<SiteAppList> Logger
|
@inject ILogger<SiteAppList> Logger
|
||||||
@attribute [Authorize]
|
@attribute [Authorize]
|
||||||
|
|
||||||
@code {
|
<PageContainer Title="系统工具">
|
||||||
|
<Breadcrumb>
|
||||||
|
<Breadcrumb>
|
||||||
|
<BreadcrumbItem Href="/">管理后台</BreadcrumbItem>
|
||||||
|
<BreadcrumbItem Href="/admin/list">系统功能</BreadcrumbItem>
|
||||||
|
<BreadcrumbItem>系统工具</BreadcrumbItem>
|
||||||
|
</Breadcrumb>
|
||||||
|
</Breadcrumb>
|
||||||
|
<ChildContent>
|
||||||
|
<h3>Tools</h3>
|
||||||
|
</ChildContent>
|
||||||
|
</PageContainer>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
[Parameter]
|
||||||
|
public string Locale { get; set; } = string.Empty;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,103 +1,120 @@
|
|||||||
@page "/system/file/list"
|
@page "/system/file/list"
|
||||||
|
@page "/{locale}/system/file/list"
|
||||||
@inject ILogger<UploadList> Logger
|
@inject ILogger<UploadList> Logger
|
||||||
@attribute [Authorize]
|
@attribute [Authorize]
|
||||||
|
|
||||||
<PageTitle>上传文件</PageTitle>
|
<PageContainer Title="上传文件">
|
||||||
<Title Level="4">上传文件</Title>
|
<Breadcrumb>
|
||||||
<Card>
|
<Breadcrumb>
|
||||||
<Form @ref="searchForm" Model="search" Layout="FormLayout.Inline" Class="search-form" OnFinish="OnSearchFinish">
|
<BreadcrumbItem Href="/">管理后台</BreadcrumbItem>
|
||||||
<Row Justify="RowJustify.Start" Gutter="16">
|
<BreadcrumbItem Href="/admin/list">系统功能</BreadcrumbItem>
|
||||||
<Col>
|
<BreadcrumbItem>上传文件</BreadcrumbItem>
|
||||||
<FormItem Label="文件名">
|
</Breadcrumb>
|
||||||
<Input @bind-Value="search.Name" Placeholder="文件名" AllowClear />
|
</Breadcrumb>
|
||||||
</FormItem>
|
<ChildContent>
|
||||||
</Col>
|
<Card>
|
||||||
|
<Form @ref="searchForm" Model="search" Layout="FormLayout.Inline" Class="search-form" OnFinish="OnSearchFinish">
|
||||||
|
<Row Justify="RowJustify.Start" Gutter="16">
|
||||||
|
<Col>
|
||||||
|
<FormItem Label="文件名">
|
||||||
|
<Input @bind-Value="search.Name" Placeholder="文件名" AllowClear />
|
||||||
|
</FormItem>
|
||||||
|
</Col>
|
||||||
|
|
||||||
<Col>
|
<Col>
|
||||||
<div class="ant-form-item" style="width:200px;display:flex;">
|
<div class="ant-form-item" style="width:200px;display:flex;">
|
||||||
<Button Type="ButtonType.Primary" HtmlType="submit">查询</Button>
|
<Button Type="ButtonType.Primary" HtmlType="submit">查询</Button>
|
||||||
<Button Style="margin: 0 8px;" OnClick="OnSearchReset">重置</Button>
|
<Button Style="margin: 0 8px;" OnClick="OnSearchReset">重置</Button>
|
||||||
</div>
|
</div>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
</Form>
|
</Form>
|
||||||
</Card>
|
</Card>
|
||||||
<Card Class="mt-3">
|
<Card Class="mt-3">
|
||||||
<Table DataSource="PagingList.Items" PageSize="100" HidePagination="true" Resizable>
|
<Table DataSource="PagingList.Items" PageSize="100" HidePagination="true" Resizable>
|
||||||
<TitleTemplate>
|
<TitleTemplate>
|
||||||
<Flex Justify="FlexJustify.SpaceBetween">
|
<Flex Justify="FlexJustify.SpaceBetween">
|
||||||
文件列表
|
文件列表
|
||||||
<div>
|
<div>
|
||||||
<Button Class="me-3" OnClick="OnCreateClick" Type="ButtonType.Primary">上传新文件</Button>
|
<Button Class="me-3" OnClick="OnCreateClick" Type="ButtonType.Primary">上传新文件</Button>
|
||||||
</div>
|
</div>
|
||||||
</Flex>
|
</Flex>
|
||||||
</TitleTemplate>
|
</TitleTemplate>
|
||||||
<ColumnDefinitions>
|
<ColumnDefinitions>
|
||||||
<PropertyColumn Property="c => c.Name" Title="文件">
|
<PropertyColumn Property="c => c.Name" Title="文件">
|
||||||
</PropertyColumn>
|
</PropertyColumn>
|
||||||
<PropertyColumn Property="c => c.ContentType" Title="类型" Width="80px" Align="ColumnAlign.Center">
|
<PropertyColumn Property="c => c.ContentType" Title="类型" Width="80px" Align="ColumnAlign.Center">
|
||||||
|
|
||||||
</PropertyColumn>
|
</PropertyColumn>
|
||||||
<PropertyColumn Property="c => c.Size" Title="文件大小" Width="100px" />
|
<PropertyColumn Property="c => c.Size" Title="文件大小" Width="100px" />
|
||||||
<PropertyColumn Property="c => c.Type" Title="状态" Width="80px" Align="ColumnAlign.Center">
|
<PropertyColumn Property="c => c.Type" Title="状态" Width="80px" Align="ColumnAlign.Center">
|
||||||
@if (context.Type == 1)
|
@if (context.Type == 1)
|
||||||
|
{
|
||||||
|
<AntDesign.Text Type="TextElementType.Success"><Icon Type="check" Theme=" IconThemeType.Outline" Width="1.3em" Height="1.3em" /></AntDesign.Text>
|
||||||
|
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<Icon Type="close" Theme="IconThemeType.Outline" Width="1.3em" Height="1.3em" />
|
||||||
|
}
|
||||||
|
</PropertyColumn>
|
||||||
|
<PropertyColumn Property="c => c.CreateTime" Title="最后登录" Width="190px" />
|
||||||
|
<PropertyColumn Property="c => c.UpdateTime" Title="最后更新" Width="190px" />
|
||||||
|
<ActionColumn Title="操作" Align="ColumnAlign.Right" Width="160px">
|
||||||
|
<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>
|
||||||
|
<br />
|
||||||
|
<Row Justify="RowJustify.End">
|
||||||
|
@if (PagingList.Count > 0)
|
||||||
{
|
{
|
||||||
<AntDesign.Text Type="TextElementType.Success"><Icon Type="check" Theme=" IconThemeType.Outline" Width="1.3em" Height="1.3em" /></AntDesign.Text>
|
<Pagination Current="PagingList.Index" Total="PagingList.Count" PageSize="PagingList.Size" ShowSizeChanger="false" OnChange="OnPageChanged"></Pagination>
|
||||||
|
|
||||||
}
|
}
|
||||||
else
|
</Row>
|
||||||
{
|
</Card>
|
||||||
<Icon Type="close" Theme="IconThemeType.Outline" Width="1.3em" Height="1.3em" />
|
|
||||||
}
|
|
||||||
</PropertyColumn>
|
|
||||||
<PropertyColumn Property="c => c.CreateTime" Title="最后登录" Width="190px" />
|
|
||||||
<PropertyColumn Property="c => c.UpdateTime" Title="最后更新" Width="190px" />
|
|
||||||
<ActionColumn Title="操作" Align="ColumnAlign.Right" Width="160px">
|
|
||||||
<Space>
|
|
||||||
<SpaceItem>
|
|
||||||
<Dropdown Trigger="@(new Trigger[] { Trigger.Click })">
|
|
||||||
<Overlay>
|
|
||||||
<Menu>
|
|
||||||
|
|
||||||
<MenuItem>
|
<Drawer Closable="true" Width="520" Visible="drawerVisible" Title='(model.Id == 0 ? "新增帐号" : "编辑帐号")' OnClose="_ => CloseDrawer()">
|
||||||
<a @onclick="(e) => OnEditClick(context)"> <Icon Type="@IconType.Outline.Edit" /> 编辑</a>
|
<Form LabelColSpan="5" @ref="@editform" Model="@model" OnFinish="OnFormFinish">
|
||||||
</MenuItem>
|
<FluentValidationValidator />
|
||||||
<MenuDivider />
|
<FormItem Label="帐号名称">
|
||||||
<MenuItem>
|
<Input @bind-Value="model.Name" For="(()=>model.Name)" Placeholder="帐号名称" />
|
||||||
<Popconfirm Placement="@Placement.Left" Title="@("删除这条数据无法恢复,您确定要删除吗?")"
|
</FormItem>
|
||||||
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>
|
|
||||||
|
|
||||||
<Drawer Closable="true" Width="520" Visible="drawerVisible" Title='(model.Id == 0 ? "新增帐号" : "编辑帐号")' OnClose="_ => CloseDrawer()">
|
<FormItem WrapperColOffset="4">
|
||||||
<Form LabelColSpan="5" @ref="@editform" Model="@model" OnFinish="OnFormFinish">
|
<Button Type="ButtonType.Primary" HtmlType="submit" Style="width: 100%;">保存</Button>
|
||||||
<FluentValidationValidator />
|
</FormItem>
|
||||||
<FormItem Label="帐号名称">
|
</Form>
|
||||||
<Input @bind-Value="model.Name" For="(()=>model.Name)" Placeholder="帐号名称" />
|
</Drawer>
|
||||||
</FormItem>
|
</ChildContent>
|
||||||
|
</PageContainer>
|
||||||
<FormItem WrapperColOffset="4">
|
|
||||||
<Button Type="ButtonType.Primary" HtmlType="submit" Style="width: 100%;">保存</Button>
|
|
||||||
</FormItem>
|
|
||||||
</Form>
|
|
||||||
</Drawer>
|
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
[SupplyParameterFromQuery]
|
[SupplyParameterFromQuery]
|
||||||
@@ -280,9 +297,9 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnPageChanged(int args)
|
private void OnPageChanged(PaginationEventArgs args)
|
||||||
{
|
{
|
||||||
OnSearch(args);
|
OnSearch(args.Page);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CloseDrawer()
|
void CloseDrawer()
|
||||||
|
|||||||
@@ -1,149 +1,168 @@
|
|||||||
@page "/admin/list"
|
@page "/admin/list"
|
||||||
|
@page "/{locale}/admin/list"
|
||||||
@inject ILogger<AdminList> Logger
|
@inject ILogger<AdminList> Logger
|
||||||
@attribute [Authorize]
|
@attribute [Authorize]
|
||||||
|
|
||||||
|
|
||||||
<PageTitle>管理员账号管理</PageTitle>
|
<PageContainer Title="管理员管理">
|
||||||
|
<Breadcrumb>
|
||||||
|
<Breadcrumb>
|
||||||
|
<BreadcrumbItem Href="/">管理后台</BreadcrumbItem>
|
||||||
|
<BreadcrumbItem Href="/admin/list">系统功能</BreadcrumbItem>
|
||||||
|
<BreadcrumbItem>管理员管理</BreadcrumbItem>
|
||||||
|
</Breadcrumb>
|
||||||
|
</Breadcrumb>
|
||||||
|
<ChildContent>
|
||||||
|
<Card Class="">
|
||||||
|
<Form @ref="searchForm" Model="search" Layout="FormLayout.Inline" Class="search-form" OnFinish="OnSearchFinish">
|
||||||
|
<Row Justify="RowJustify.Start" Gutter="16">
|
||||||
|
<Col>
|
||||||
|
<FormItem Label="帐号">
|
||||||
|
<Input @bind-Value="search.Username" Placeholder="帐号" AllowClear />
|
||||||
|
</FormItem>
|
||||||
|
</Col>
|
||||||
|
|
||||||
<Title Level="4">管理员帐号</Title>
|
<Col>
|
||||||
<Card Class="">
|
<div class="ant-form-item" style="width:200px;display:flex;">
|
||||||
<Form @ref="searchForm" Model="search" Layout="FormLayout.Inline" Class="search-form" OnFinish="OnSearchFinish">
|
<Button Type="ButtonType.Primary" HtmlType="submit">查询</Button>
|
||||||
<Row Justify="RowJustify.Start" Gutter="16">
|
<Button Style="margin: 0 8px;" OnClick="OnSearchReset">重置</Button>
|
||||||
<Col>
|
</div>
|
||||||
<FormItem Label="帐号">
|
</Col>
|
||||||
<Input @bind-Value="search.Username" Placeholder="帐号" AllowClear />
|
</Row>
|
||||||
</FormItem>
|
</Form>
|
||||||
</Col>
|
</Card>
|
||||||
|
<br />
|
||||||
|
<Card Class="mt-3">
|
||||||
|
<Table DataSource="PagingList.Items" PageSize="100" HidePagination="true" Resizable>
|
||||||
|
<TitleTemplate>
|
||||||
|
<Flex Justify="FlexJustify.SpaceBetween">
|
||||||
|
帐号列表
|
||||||
|
<div>
|
||||||
|
<AuthorizeCheck Permission="@Permissions.Admin.Create">
|
||||||
|
<Button Class="me-3" OnClick="OnCreateClick" Type="ButtonType.Primary">新增</Button>
|
||||||
|
</AuthorizeCheck>
|
||||||
|
</div>
|
||||||
|
</Flex>
|
||||||
|
</TitleTemplate>
|
||||||
|
<ColumnDefinitions>
|
||||||
|
<PropertyColumn Property="c => c.Username" Title="帐号">
|
||||||
|
</PropertyColumn>
|
||||||
|
<PropertyColumn Property="c => c.Email" Title="邮件">
|
||||||
|
|
||||||
<Col>
|
</PropertyColumn>
|
||||||
<div class="ant-form-item" style="width:200px;display:flex;">
|
<PropertyColumn Property="c => c.Mobile" Title="手机号" />
|
||||||
<Button Type="ButtonType.Primary" HtmlType="submit">查询</Button>
|
<PropertyColumn Property="c => c.Status" Title="状态" Width="80px" Align="ColumnAlign.Center">
|
||||||
<Button Style="margin: 0 8px;" OnClick="OnSearchReset">重置</Button>
|
@if (context.Status == 1)
|
||||||
</div>
|
{
|
||||||
</Col>
|
<AntDesign.Text Type="TextElementType.Success"><Icon Type="check" Theme=" IconThemeType.Outline" Width="1.3em" Height="1.3em" /></AntDesign.Text>
|
||||||
</Row>
|
|
||||||
</Form>
|
|
||||||
</Card>
|
|
||||||
<br />
|
|
||||||
<Card Class="mt-3">
|
|
||||||
<Table DataSource="PagingList.Items" PageSize="100" HidePagination="true" Resizable>
|
|
||||||
<TitleTemplate>
|
|
||||||
<Flex Justify="FlexJustify.SpaceBetween">
|
|
||||||
帐号列表
|
|
||||||
<div>
|
|
||||||
<AuthorizeCheck Permission="@Permissions.Admin.Create">\
|
|
||||||
<Button Class="me-3" OnClick="OnCreateClick" Type="ButtonType.Primary">新增</Button>
|
|
||||||
</AuthorizeCheck>
|
|
||||||
</div>
|
|
||||||
</Flex>
|
|
||||||
</TitleTemplate>
|
|
||||||
<ColumnDefinitions>
|
|
||||||
<PropertyColumn Property="c => c.Username" Title="帐号">
|
|
||||||
</PropertyColumn>
|
|
||||||
<PropertyColumn Property="c => c.Email" Title="邮件">
|
|
||||||
|
|
||||||
</PropertyColumn>
|
}
|
||||||
<PropertyColumn Property="c => c.Mobile" Title="手机号" />
|
else
|
||||||
<PropertyColumn Property="c => c.Status" Title="状态" Width="80px" Align="ColumnAlign.Center">
|
{
|
||||||
@if (context.Status == 1)
|
<Icon Type="close" Theme="IconThemeType.Outline" Width="1.3em" Height="1.3em" />
|
||||||
|
}
|
||||||
|
</PropertyColumn>
|
||||||
|
<PropertyColumn Property="c => c.LastLogin" Title="最后登录" />
|
||||||
|
<PropertyColumn Property="c => c.UpdateTime" Title="最后更新" />
|
||||||
|
<ActionColumn Title="操作" Align="ColumnAlign.Right" Width="160px">
|
||||||
|
<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>
|
||||||
|
<br />
|
||||||
|
<Row Justify="RowJustify.End">
|
||||||
|
@if (PagingList.Count > 0)
|
||||||
{
|
{
|
||||||
<AntDesign.Text Type="TextElementType.Success"><Icon Type="check" Theme=" IconThemeType.Outline" Width="1.3em" Height="1.3em" /></AntDesign.Text>
|
<Pagination Current="PagingList.Index" Total="PagingList.Count" PageSize="PagingList.Size" ShowSizeChanger="false" OnChange="OnPageChanged"></Pagination>
|
||||||
|
|
||||||
}
|
}
|
||||||
else
|
</Row>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Drawer Closable="true" Width="520" Visible="drawerVisible" Title='(model.Id == 0 ? "新增帐号" : "编辑帐号")' OnClose="_ => CloseDrawer()">
|
||||||
|
<Form LabelColSpan="5" @ref="@editform" Model="@model" OnFinish="OnFormFinish">
|
||||||
|
<FluentValidationValidator />
|
||||||
|
<FormItem Label="帐号名称">
|
||||||
|
<Input @bind-Value="model.Username" For="(()=>model.Username)" Placeholder="帐号名称" />
|
||||||
|
</FormItem>
|
||||||
|
<FormItem Label="电子邮件">
|
||||||
|
<Input @bind-Value="model.Email" For="(()=>model.Email)" Placeholder="电子邮件" />
|
||||||
|
</FormItem>
|
||||||
|
<FormItem Label="手机号码">
|
||||||
|
<Input @bind-Value="model.Mobile" For="(()=>model.Mobile)" Placeholder="手机号码" />
|
||||||
|
</FormItem>
|
||||||
|
@if (context.Id > 0)
|
||||||
{
|
{
|
||||||
<Icon Type="close" Theme="IconThemeType.Outline" Width="1.3em" Height="1.3em" />
|
<FormItem Label="密码设置">
|
||||||
|
<Checkbox @bind-Value="@context.SetPassword" Disabled=false>
|
||||||
|
重置密码
|
||||||
|
</Checkbox>
|
||||||
|
</FormItem>
|
||||||
}
|
}
|
||||||
</PropertyColumn>
|
@if (context.Id == 0)
|
||||||
<PropertyColumn Property="c => c.LastLogin" Title="最后登录" Width="120px" />
|
{
|
||||||
<PropertyColumn Property="c => c.UpdateTime" Title="最后更新" />
|
<FormItem Label="登录密码">
|
||||||
<ActionColumn Title="操作" Align="ColumnAlign.Right" Width="160px">
|
<Input @bind-Value="@context.Password" Placeholder="登录密码" />
|
||||||
<Space>
|
</FormItem>
|
||||||
<SpaceItem>
|
}
|
||||||
<Dropdown Trigger="@(new Trigger[] { Trigger.Click })">
|
@if ((context.Id > 0 && context.SetPassword) || context.Id == 0)
|
||||||
<Overlay>
|
{
|
||||||
<Menu>
|
|
||||||
|
|
||||||
<MenuItem>
|
<FormItem Label="新密码">
|
||||||
<a @onclick="(e) => OnEditClick(context)"> <Icon Type="@IconType.Outline.Edit" /> 编辑</a>
|
<Input @bind-Value="@context.NewPassword" Placeholder="新登录密码" />
|
||||||
</MenuItem>
|
</FormItem>
|
||||||
<MenuDivider />
|
}
|
||||||
<MenuItem>
|
@if ((context.Id > 0 && context.SetPassword) || context.Id == 0)
|
||||||
<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>
|
|
||||||
|
|
||||||
<Drawer Closable="true" Width="520" Visible="drawerVisible" Title='(model.Id == 0 ? "新增帐号" : "编辑帐号")' OnClose="_ => CloseDrawer()">
|
<FormItem Label="确认密码">
|
||||||
<Form LabelColSpan="5" @ref="@editform" Model="@model" OnFinish="OnFormFinish">
|
<Input @bind-Value="@context.RePassword" Placeholder="确认密码" />
|
||||||
<FluentValidationValidator />
|
</FormItem>
|
||||||
<FormItem Label="帐号名称">
|
}
|
||||||
<Input @bind-Value="model.Username" For="(()=>model.Username)" Placeholder="帐号名称" />
|
<FormItem Label="可用状态">
|
||||||
</FormItem>
|
<RadioGroup @bind-Value="@context.Status">
|
||||||
<FormItem Label="电子邮件">
|
<Radio RadioButton Value=0>禁用</Radio>
|
||||||
<Input @bind-Value="model.Email" For="(()=>model.Email)" Placeholder="电子邮件" />
|
<Radio RadioButton Value=1>启用</Radio>
|
||||||
</FormItem>
|
</RadioGroup>
|
||||||
<FormItem Label="手机号码">
|
</FormItem>
|
||||||
<Input @bind-Value="model.Mobile" For="(()=>model.Mobile)" Placeholder="手机号码" />
|
<FormItem WrapperColOffset="4">
|
||||||
</FormItem>
|
<Button Type="ButtonType.Primary" HtmlType="submit" Style="width: 100%;">保存</Button>
|
||||||
@if (context.Id > 0)
|
</FormItem>
|
||||||
{
|
</Form>
|
||||||
<FormItem Label="密码设置">
|
</Drawer>
|
||||||
<Checkbox @bind-Value="@context.SetPassword" Disabled=false>
|
</ChildContent>
|
||||||
重置密码
|
</PageContainer>
|
||||||
</Checkbox>
|
|
||||||
</FormItem>
|
|
||||||
}
|
|
||||||
@if (context.Id == 0)
|
|
||||||
{
|
|
||||||
<FormItem Label="登录密码">
|
|
||||||
<Input @bind-Value="@context.Password" Placeholder="登录密码" />
|
|
||||||
</FormItem>
|
|
||||||
}
|
|
||||||
@if ((context.Id > 0 && context.SetPassword) || context.Id == 0)
|
|
||||||
{
|
|
||||||
|
|
||||||
<FormItem Label="新密码">
|
|
||||||
<Input @bind-Value="@context.NewPassword" Placeholder="新登录密码" />
|
|
||||||
</FormItem>
|
|
||||||
}
|
|
||||||
@if ((context.Id > 0 && context.SetPassword) || context.Id == 0)
|
|
||||||
{
|
|
||||||
|
|
||||||
<FormItem Label="确认密码">
|
|
||||||
<Input @bind-Value="@context.RePassword" Placeholder="确认密码" />
|
|
||||||
</FormItem>
|
|
||||||
}
|
|
||||||
<FormItem Label="可用状态">
|
|
||||||
<RadioGroup @bind-Value="@context.Status">
|
|
||||||
<Radio RadioButton Value=0>禁用</Radio>
|
|
||||||
<Radio RadioButton Value=1>启用</Radio>
|
|
||||||
</RadioGroup>
|
|
||||||
</FormItem>
|
|
||||||
<FormItem WrapperColOffset="4">
|
|
||||||
<Button Type="ButtonType.Primary" HtmlType="submit" Style="width: 100%;">保存</Button>
|
|
||||||
</FormItem>
|
|
||||||
</Form>
|
|
||||||
</Drawer>
|
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
|
[Parameter]
|
||||||
|
public string Locale { get; set; } = string.Empty;
|
||||||
|
|
||||||
[SupplyParameterFromQuery]
|
[SupplyParameterFromQuery]
|
||||||
int? Page { get; set; }
|
int? Page { get; set; }
|
||||||
|
|
||||||
@@ -276,6 +295,11 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void OnPageChanged(PaginationEventArgs args)
|
||||||
|
{
|
||||||
|
OnSearch(args.Page);
|
||||||
|
}
|
||||||
|
|
||||||
void OnCreateClick()
|
void OnCreateClick()
|
||||||
{
|
{
|
||||||
model = new();
|
model = new();
|
||||||
@@ -325,7 +349,7 @@
|
|||||||
{
|
{
|
||||||
|
|
||||||
CloseDrawer();
|
CloseDrawer();
|
||||||
_= LoadList();
|
_ = LoadList();
|
||||||
await ModalService.InfoAsync(new ConfirmOptions() { Title = "提示", Content = "数据提交成功!" });
|
await ModalService.InfoAsync(new ConfirmOptions() { Title = "提示", Content = "数据提交成功!" });
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
@page "/system/currency/list"
|
|
||||||
@inject ILogger<CurrencyList> Logger
|
|
||||||
@attribute [Authorize]
|
|
||||||
|
|
||||||
<h3>CurrencyList</h3>
|
|
||||||
|
|
||||||
@code {
|
|
||||||
|
|
||||||
}
|
|
||||||
19
Atomx.Admin/Atomx.Admin.Client/Pages/Systems/Info.razor
Normal file
19
Atomx.Admin/Atomx.Admin.Client/Pages/Systems/Info.razor
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
@page "/system/info"
|
||||||
|
@page "/{locale}/system/info"
|
||||||
|
<PageContainer Title="系统信息">
|
||||||
|
<Breadcrumb>
|
||||||
|
<Breadcrumb>
|
||||||
|
<BreadcrumbItem Href="/">管理后台</BreadcrumbItem>
|
||||||
|
<BreadcrumbItem Href="/admin/list">系统功能</BreadcrumbItem>
|
||||||
|
<BreadcrumbItem>系统信息</BreadcrumbItem>
|
||||||
|
</Breadcrumb>
|
||||||
|
</Breadcrumb>
|
||||||
|
<ChildContent>
|
||||||
|
<h3>Tools</h3>
|
||||||
|
</ChildContent>
|
||||||
|
</PageContainer>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
[Parameter]
|
||||||
|
public string Locale { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
@@ -1,110 +1,129 @@
|
|||||||
@page "/system/language/list"
|
@page "/system/language/list"
|
||||||
|
@page "/{locale}/system/language/list"
|
||||||
@inject ILogger<LanguageList> Logger
|
@inject ILogger<LanguageList> Logger
|
||||||
@attribute [Authorize]
|
@attribute [Authorize]
|
||||||
|
|
||||||
<PageTitle>语言管理</PageTitle>
|
<PageContainer Title="语言管理">
|
||||||
|
<Breadcrumb>
|
||||||
|
<Breadcrumb>
|
||||||
|
<BreadcrumbItem Href="/">管理后台</BreadcrumbItem>
|
||||||
|
<BreadcrumbItem Href="/admin/list">系统功能</BreadcrumbItem>
|
||||||
|
<BreadcrumbItem>语言管理</BreadcrumbItem>
|
||||||
|
</Breadcrumb>
|
||||||
|
</Breadcrumb>
|
||||||
|
<ChildContent>
|
||||||
|
<Card Class="mt-3">
|
||||||
|
<Table DataSource="PagingList.Items" PageSize="100" HidePagination="true" Resizable>
|
||||||
|
<TitleTemplate>
|
||||||
|
<Flex Justify="FlexJustify.SpaceBetween">
|
||||||
|
多语言列表
|
||||||
|
<div>
|
||||||
|
<Button Class="me-3" OnClick="OnCreateClick" Type="ButtonType.Primary">新增</Button>
|
||||||
|
</div>
|
||||||
|
</Flex>
|
||||||
|
</TitleTemplate>
|
||||||
|
<ColumnDefinitions>
|
||||||
|
<PropertyColumn Property="c => c.Title" Title="语言名称">
|
||||||
|
|
||||||
<Title Level="4">多语言</Title>
|
</PropertyColumn>
|
||||||
|
<PropertyColumn Property="c => c.Name" Title="语言本地化">
|
||||||
|
</PropertyColumn>
|
||||||
|
|
||||||
<Card Class="mt-3">
|
<PropertyColumn Property="c => c.Culture" Title="语言文化" Width="100px" />
|
||||||
<Table DataSource="PagingList.Items" PageSize="100" HidePagination="true" Resizable>
|
<PropertyColumn Property="c => c.Enabled" Title="状态" Width="80px" Align="ColumnAlign.Center">
|
||||||
<TitleTemplate>
|
@if (context.Enabled)
|
||||||
<Flex Justify="FlexJustify.SpaceBetween">
|
{
|
||||||
多语言列表
|
<AntDesign.Text Type="TextElementType.Success"><Icon Type="check" Theme=" IconThemeType.Outline" Width="1.3em" Height="1.3em" /></AntDesign.Text>
|
||||||
<div>
|
|
||||||
<Button Class="me-3" OnClick="OnCreateClick" Type="ButtonType.Primary">新增</Button>
|
|
||||||
</div>
|
|
||||||
</Flex>
|
|
||||||
</TitleTemplate>
|
|
||||||
<ColumnDefinitions>
|
|
||||||
<PropertyColumn Property="c => c.Name" Title="语言名称">
|
|
||||||
</PropertyColumn>
|
|
||||||
<PropertyColumn Property="c => c.Title" Title="语言标题">
|
|
||||||
|
|
||||||
</PropertyColumn>
|
}
|
||||||
<PropertyColumn Property="c => c.Culture" Title="语言文化" Width="100px" />
|
else
|
||||||
<PropertyColumn Property="c => c.Enabled" Title="状态" Width="80px" Align="ColumnAlign.Center">
|
{
|
||||||
@if (context.Enabled)
|
<Icon Type="close" Theme="IconThemeType.Outline" Width="1.3em" Height="1.3em" />
|
||||||
|
}
|
||||||
|
</PropertyColumn>
|
||||||
|
<PropertyColumn Property="c => c.ResourceVersion" Title="资源版本" />
|
||||||
|
<PropertyColumn Property="c => c.UpdateTime" Title="最后更新" />
|
||||||
|
<ActionColumn Title="操作" Align="ColumnAlign.Right">
|
||||||
|
<Space>
|
||||||
|
<SpaceItem>
|
||||||
|
<a href="@($"/system/locale/resource/list/{context.Id}")"> <Icon Type="@IconType.Outline.Edit" /> 语言资源</a>
|
||||||
|
</SpaceItem>
|
||||||
|
<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>
|
||||||
|
<br />
|
||||||
|
<Row Justify="RowJustify.End">
|
||||||
|
@if (PagingList.Count > 0)
|
||||||
{
|
{
|
||||||
<AntDesign.Text Type="TextElementType.Success"><Icon Type="check" Theme=" IconThemeType.Outline" Width="1.3em" Height="1.3em" /></AntDesign.Text>
|
<Pagination Current="PagingList.Index" Total="PagingList.Count" PageSize="PagingList.Size" ShowSizeChanger="false" OnChange="OnPageChanged"></Pagination>
|
||||||
|
|
||||||
}
|
}
|
||||||
else
|
</Row>
|
||||||
{
|
</Card>
|
||||||
<Icon Type="close" Theme="IconThemeType.Outline" Width="1.3em" Height="1.3em" />
|
|
||||||
}
|
|
||||||
</PropertyColumn>
|
|
||||||
<PropertyColumn Property="c => c.ResourceVersion" Title="资源版本"/>
|
|
||||||
<PropertyColumn Property="c => c.UpdateTime" Title="最后更新" />
|
|
||||||
<ActionColumn Title="操作" Align="ColumnAlign.Right">
|
|
||||||
<Space>
|
|
||||||
<SpaceItem>
|
|
||||||
<a href="@($"/system/locale/resource/list/{context.Id}")"> <Icon Type="@IconType.Outline.Edit" /> 语言资源</a>
|
|
||||||
</SpaceItem>
|
|
||||||
<SpaceItem>
|
|
||||||
<Dropdown Trigger="@(new Trigger[] { Trigger.Click })">
|
|
||||||
<Overlay>
|
|
||||||
<Menu>
|
|
||||||
|
|
||||||
<MenuItem>
|
<Drawer Closable="true" Width="520" Visible="drawerVisible" Title='(model.Id == 0 ? "新增语言" : "编辑语言")' OnClose="_ => CloseDrawer()">
|
||||||
<a @onclick="(e) => OnEditClick(context)"> <Icon Type="@IconType.Outline.Edit" /> 编辑</a>
|
<Form LabelColSpan="5" @ref="@editform" Model="@model" OnFinish="OnFormFinish">
|
||||||
</MenuItem>
|
<FluentValidationValidator />
|
||||||
<MenuDivider />
|
<FormItem Label="语言标题">
|
||||||
<MenuItem>
|
<Input @bind-Value="model.Title" For="(()=>model.Title)" Placeholder="语言名称" />
|
||||||
<Popconfirm Placement="@Placement.Left" Title="@("删除这条数据无法恢复,您确定要删除吗?")"
|
</FormItem>
|
||||||
OnConfirm="@(e=>HandleDeleteConfirmAsync(e,context.Id))"
|
<FormItem Label="语言名称">
|
||||||
OkText="确定"
|
<Input @bind-Value="model.Name" For="(()=>model.Name)" Placeholder="语言本地化" />
|
||||||
CancelText="取消">
|
</FormItem>
|
||||||
<a> <Icon Type="@IconType.Outline.Delete" /> 删除</a>
|
<FormItem Label="语言文化">
|
||||||
</Popconfirm>
|
<SimpleSelect @bind-Value="model.Culture" For="(()=>model.Culture)" Placeholder="语言文化">
|
||||||
</MenuItem>
|
<SelectOptions>
|
||||||
</Menu>
|
@foreach (var item in LanguageCultures)
|
||||||
</Overlay>
|
{
|
||||||
<ChildContent>
|
<SimpleSelectOption Value="@item.Key" Label="@($"{item.Value} - {item.Key}")"></SimpleSelectOption>
|
||||||
<a class="ant-dropdown-link" @onclick:preventDefault>
|
}
|
||||||
<Icon Type="@IconType.Outline.Menu" />
|
</SelectOptions>
|
||||||
</a>
|
</SimpleSelect>
|
||||||
</ChildContent>
|
</FormItem>
|
||||||
</Dropdown>
|
<FormItem Label="显示排序">
|
||||||
</SpaceItem>
|
<AntDesign.InputNumber @bind-Value="model.DisplayOrder" For="(()=>model.DisplayOrder)" Placeholder="显示排序" />
|
||||||
</Space>
|
</FormItem>
|
||||||
</ActionColumn>
|
<FormItem Label="可用状态">
|
||||||
</ColumnDefinitions>
|
<Checkbox T="bool" Label="启用" @bind-value="model.Enabled" Size="InputSize.Small" Class="ps-0" />
|
||||||
</Table>
|
</FormItem>
|
||||||
</Card>
|
<FormItem WrapperColOffset="4">
|
||||||
|
<Button Type="ButtonType.Primary" HtmlType="submit" Style="width: 100%;">保存</Button>
|
||||||
<Drawer Closable="true" Width="520" Visible="drawerVisible" Title='(model.Id == 0 ? "新增语言" : "编辑语言")' OnClose="_ => CloseDrawer()">
|
</FormItem>
|
||||||
<Form LabelColSpan="5" @ref="@editform" Model="@model" OnFinish="OnFormFinish">
|
</Form>
|
||||||
<FluentValidationValidator />
|
</Drawer>
|
||||||
<FormItem Label="语言标题">
|
</ChildContent>
|
||||||
<Input @bind-Value="model.Title" For="(()=>model.Title)" Placeholder="语言标题" />
|
</PageContainer>
|
||||||
</FormItem>
|
|
||||||
<FormItem Label="语言名称">
|
|
||||||
<Input @bind-Value="model.Name" For="(()=>model.Name)" Placeholder="语言名称" />
|
|
||||||
</FormItem>
|
|
||||||
<FormItem Label="语言文化">
|
|
||||||
<SimpleSelect @bind-Value="model.Culture" For="(()=>model.Culture)" Placeholder="语言文化">
|
|
||||||
<SelectOptions>
|
|
||||||
@foreach (var item in LanguageCultures)
|
|
||||||
{
|
|
||||||
<SimpleSelectOption Value="@item.Key" Label="@($"{item.Value} - {item.Key}")"></SimpleSelectOption>
|
|
||||||
}
|
|
||||||
</SelectOptions>
|
|
||||||
</SimpleSelect>
|
|
||||||
</FormItem>
|
|
||||||
<FormItem Label="显示排序">
|
|
||||||
<AntDesign.InputNumber @bind-Value="model.DisplayOrder" For="(()=>model.DisplayOrder)" Placeholder="显示排序" />
|
|
||||||
</FormItem>
|
|
||||||
<FormItem Label="可用状态">
|
|
||||||
<Checkbox T="bool" Label="启用" @bind-value="model.Enabled" Size="InputSize.Small" Class="ps-0" />
|
|
||||||
</FormItem>
|
|
||||||
<FormItem WrapperColOffset="4">
|
|
||||||
<Button Type="ButtonType.Primary" HtmlType="submit" Style="width: 100%;">保存</Button>
|
|
||||||
</FormItem>
|
|
||||||
</Form>
|
|
||||||
</Drawer>
|
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
|
[Parameter]
|
||||||
|
public string Locale { get; set; } = string.Empty;
|
||||||
|
|
||||||
[SupplyParameterFromQuery]
|
[SupplyParameterFromQuery]
|
||||||
int? Page { get; set; }
|
int? Page { get; set; }
|
||||||
|
|
||||||
@@ -240,9 +259,9 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnPageChanged(int args)
|
private void OnPageChanged(PaginationEventArgs args)
|
||||||
{
|
{
|
||||||
OnSearch(args);
|
OnSearch(args.Page);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CloseDrawer()
|
void CloseDrawer()
|
||||||
|
|||||||
@@ -1,92 +1,103 @@
|
|||||||
@page "/system/locale/resource/detail/{Name}"
|
@page "/system/locale/resource/detail/{Name}"
|
||||||
|
@page "/{locale}/system/locale/resource/detail/{Name}"
|
||||||
@inject ILogger<LocaleResourceList> Logger
|
@inject ILogger<LocaleResourceList> Logger
|
||||||
@attribute [Authorize]
|
@attribute [Authorize]
|
||||||
|
|
||||||
|
<PageContainer Title="本地化语言资源">
|
||||||
|
<Breadcrumb>
|
||||||
|
<Breadcrumb>
|
||||||
|
<BreadcrumbItem Href="/">管理后台</BreadcrumbItem>
|
||||||
|
<BreadcrumbItem Href="/admin/list">系统功能</BreadcrumbItem>
|
||||||
|
<BreadcrumbItem>本地化语言资源</BreadcrumbItem>
|
||||||
|
</Breadcrumb>
|
||||||
|
</Breadcrumb>
|
||||||
|
<ChildContent>
|
||||||
|
<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="内容">
|
||||||
|
|
||||||
<PageTitle>本地化语言资源</PageTitle>
|
</PropertyColumn>
|
||||||
<Title Level="4">多语言本地资源管理</Title>
|
<PropertyColumn Property="c => c.UpdateTime" Title="最后更新" Width="120px" />
|
||||||
<Spin Spinning="loading">
|
<ActionColumn Title="操作" Align="ColumnAlign.Right" Width="100px">
|
||||||
<Card Class="mt-3">
|
<Space>
|
||||||
<Table DataSource="ResourceItems" PageSize="100" HidePagination="true" Resizable>
|
<SpaceItem>
|
||||||
<TitleTemplate>
|
<Dropdown Trigger="@(new Trigger[] { Trigger.Click })">
|
||||||
<Flex Justify="FlexJustify.SpaceBetween">
|
<Overlay>
|
||||||
@(@Name)多语言资源列表,可用语言@(@languages.Count)种
|
<Menu>
|
||||||
<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>
|
<MenuItem>
|
||||||
<PropertyColumn Property="c => c.UpdateTime" Title="最后更新" Width="120px" />
|
<a @onclick="(e) => OnEditClick(context)"> <Icon Type="@IconType.Outline.Edit" /> 编辑</a>
|
||||||
<ActionColumn Title="操作" Align="ColumnAlign.Right" Width="100px">
|
</MenuItem>
|
||||||
<Space>
|
<MenuDivider />
|
||||||
<SpaceItem>
|
<MenuItem>
|
||||||
<Dropdown Trigger="@(new Trigger[] { Trigger.Click })">
|
<Popconfirm Placement="@Placement.Left" Title="@("删除这条数据无法恢复,您确定要删除吗?")"
|
||||||
<Overlay>
|
OnConfirm="@(e=>HandleDeleteConfirmAsync(e,context.Id))"
|
||||||
<Menu>
|
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>
|
||||||
|
|
||||||
<MenuItem>
|
<Modal Title="@("语言资源设置")" Visible="@modalVisible" Width="700" MaskClosable="true" OkText="@("保存")" CancelText="@("取消")" OnOk="@HandleModalOk" OnCancel="@HandleCancel">
|
||||||
<a @onclick="(e) => OnEditClick(context)"> <Icon Type="@IconType.Outline.Edit" /> 编辑</a>
|
<Form Model="@model" @ref="@editform" LabelCol="new ColLayoutParam { Span = 5 }" WrapperCol="new ColLayoutParam { Span = 15 }" Name="modalForm" OnFinish="OnFormFinish">
|
||||||
</MenuItem>
|
<FluentValidationValidator />
|
||||||
<MenuDivider />
|
<FormItem Label="语言文字">
|
||||||
<MenuItem>
|
@if (context.Id == 0)
|
||||||
<Popconfirm Placement="@Placement.Left" Title="@("删除这条数据无法恢复,您确定要删除吗?")"
|
{
|
||||||
OnConfirm="@(e=>HandleDeleteConfirmAsync(e,context.Id))"
|
<Select DataSource="@languages" @bind-Value="@context.LanguageId" ItemValue="p => p.Id" ItemLabel="p => p.Title">
|
||||||
OkText="确定"
|
</Select>
|
||||||
CancelText="取消">
|
}
|
||||||
<a> <Icon Type="@IconType.Outline.Delete" /> 删除</a>
|
else
|
||||||
</Popconfirm>
|
{
|
||||||
</MenuItem>
|
<Input Placeholder="资源名称" @bind-Value="@context.Title" Disabled />
|
||||||
</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">
|
</FormItem>
|
||||||
<Form Model="@model" @ref="@editform" LabelCol="new ColLayoutParam { Span = 5 }" WrapperCol="new ColLayoutParam { Span = 15 }" Name="modalForm" OnFinish="OnFormFinish">
|
<FormItem Label="资源名称">
|
||||||
<FluentValidationValidator />
|
<Input Placeholder="资源名称" @bind-Value="@context.Name" Disabled />
|
||||||
<FormItem Label="语言文字">
|
</FormItem>
|
||||||
@if (context.Id == 0)
|
<FormItem Label="资源内容">
|
||||||
{
|
<TextArea Placeholder="资源内容" @bind-Value="@context.Value" />
|
||||||
<Select DataSource="@languages" @bind-Value="@context.LanguageId" ItemValue="p => p.Id" ItemLabel="p => p.Title">
|
</FormItem>
|
||||||
</Select>
|
</Form>
|
||||||
}
|
</Modal>
|
||||||
else
|
</ChildContent>
|
||||||
{
|
</PageContainer>
|
||||||
<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 {
|
@code {
|
||||||
|
|
||||||
bool loading = false;
|
bool loading = false;
|
||||||
|
[Parameter]
|
||||||
|
public string Locale { get; set; } = string.Empty;
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
|
|||||||
@@ -1,111 +1,124 @@
|
|||||||
@page "/system/locale/resource/list/{Id:int}"
|
@page "/system/locale/resource/list/{Id:int}"
|
||||||
|
@page "/{locale}/system/locale/resource/list/{Id:int}"
|
||||||
@inject ILogger<LocaleResourceList> Logger
|
@inject ILogger<LocaleResourceList> Logger
|
||||||
@attribute [Authorize]
|
@attribute [Authorize]
|
||||||
|
|
||||||
|
<PageContainer Title="多语言本地资源管理">
|
||||||
|
<Breadcrumb>
|
||||||
|
<Breadcrumb>
|
||||||
|
<BreadcrumbItem Href="/">管理后台</BreadcrumbItem>
|
||||||
|
<BreadcrumbItem Href="/admin/list">系统功能</BreadcrumbItem>
|
||||||
|
<BreadcrumbItem>多语言本地资源管理</BreadcrumbItem>
|
||||||
|
</Breadcrumb>
|
||||||
|
</Breadcrumb>
|
||||||
|
<ChildContent>
|
||||||
|
<Spin Spinning="loading">
|
||||||
|
<Card>
|
||||||
|
<Form @ref="searchForm" Model="search" Layout="FormLayout.Inline" Class="search-form" OnFinish="OnSearchFinish">
|
||||||
|
<Row Justify="RowJustify.Start" Gutter="16">
|
||||||
|
<Col>
|
||||||
|
<FormItem Label="名称">
|
||||||
|
<Input @bind-Value="search.Name" Placeholder="名称" AllowClear />
|
||||||
|
</FormItem>
|
||||||
|
</Col>
|
||||||
|
<Col>
|
||||||
|
<FormItem Label="内容">
|
||||||
|
<Input @bind-Value="search.Value" Placeholder="名称" AllowClear />
|
||||||
|
</FormItem>
|
||||||
|
</Col>
|
||||||
|
<Col>
|
||||||
|
<div class="ant-form-item" style="width:200px;display:flex;">
|
||||||
|
<Button Type="ButtonType.Primary" HtmlType="submit">查询</Button>
|
||||||
|
<Button Style="margin: 0 8px;" OnClick="OnSearchReset">重置</Button>
|
||||||
|
</div>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</Form>
|
||||||
|
</Card>
|
||||||
|
<br />
|
||||||
|
<Card Class="mt-3">
|
||||||
|
<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>
|
||||||
|
</Flex>
|
||||||
|
</TitleTemplate>
|
||||||
|
<ColumnDefinitions>
|
||||||
|
<PropertyColumn Property="c => c.Name" Title="资源Key">
|
||||||
|
</PropertyColumn>
|
||||||
|
<PropertyColumn Property="c => c.Value" Title="内容">
|
||||||
|
|
||||||
<PageTitle>本地化语言资源</PageTitle>
|
</PropertyColumn>
|
||||||
<Title Level="4">多语言本地资源管理</Title>
|
<PropertyColumn Property="c => c.UpdateTime" Title="最后更新" Width="120px" />
|
||||||
<Spin Spinning="loading">
|
<ActionColumn Title="操作" Align="ColumnAlign.Right" Width="120px">
|
||||||
<Card>
|
<Space>
|
||||||
<Form @ref="searchForm" Model="search" Layout="FormLayout.Inline" Class="search-form" OnFinish="OnSearchFinish">
|
<SpaceItem>
|
||||||
<Row Justify="RowJustify.Start" Gutter="16">
|
<a @onclick="(e) => OnEditClick(context)"> <Icon Type="@IconType.Outline.Edit" /> 编辑</a>
|
||||||
<Col>
|
</SpaceItem>
|
||||||
<FormItem Label="名称">
|
<SpaceItem>
|
||||||
<Input @bind-Value="search.Name" Placeholder="名称" AllowClear />
|
<Dropdown Trigger="@(new Trigger[] { Trigger.Click })">
|
||||||
|
<Overlay>
|
||||||
|
<Menu>
|
||||||
|
|
||||||
|
<MenuItem>
|
||||||
|
<a href="@($"/system/locale/resource/detail/{context.Name}")"> <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>
|
||||||
|
<br />
|
||||||
|
<Row Justify="RowJustify.End">
|
||||||
|
@if (PagingList.Count > 0)
|
||||||
|
{
|
||||||
|
<Pagination Current="PagingList.Index" Total="PagingList.Count" PageSize="PagingList.Size" ShowSizeChanger="false" OnChange="OnPageChanged"></Pagination>
|
||||||
|
}
|
||||||
|
</Row>
|
||||||
|
</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="语言文字">
|
||||||
|
@language.Name
|
||||||
</FormItem>
|
</FormItem>
|
||||||
</Col>
|
<FormItem Label="资源名称">
|
||||||
<Col>
|
<Input Placeholder="资源名称" @bind-Value="@context.Name" />
|
||||||
<FormItem Label="内容">
|
|
||||||
<Input @bind-Value="search.Value" Placeholder="名称" AllowClear />
|
|
||||||
</FormItem>
|
</FormItem>
|
||||||
</Col>
|
<FormItem Label="资源内容">
|
||||||
<Col>
|
<TextArea Placeholder="资源内容" @bind-Value="@context.Value" />
|
||||||
<div class="ant-form-item" style="width:200px;display:flex;">
|
</FormItem>
|
||||||
<Button Type="ButtonType.Primary" HtmlType="submit">查询</Button>
|
</Form>
|
||||||
<Button Style="margin: 0 8px;" OnClick="OnSearchReset">重置</Button>
|
</Modal>
|
||||||
</div>
|
</ChildContent>
|
||||||
</Col>
|
</PageContainer>
|
||||||
</Row>
|
|
||||||
</Form>
|
|
||||||
</Card>
|
|
||||||
<br />
|
|
||||||
<Card Class="mt-3">
|
|
||||||
<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>
|
|
||||||
</Flex>
|
|
||||||
</TitleTemplate>
|
|
||||||
<ColumnDefinitions>
|
|
||||||
<PropertyColumn Property="c => c.Name" Title="资源Key">
|
|
||||||
</PropertyColumn>
|
|
||||||
<PropertyColumn Property="c => c.Value" Title="内容">
|
|
||||||
|
|
||||||
</PropertyColumn>
|
|
||||||
<PropertyColumn Property="c => c.UpdateTime" Title="最后更新" Width="120px" />
|
|
||||||
<ActionColumn Title="操作" Align="ColumnAlign.Right" Width="120px">
|
|
||||||
<Space>
|
|
||||||
<SpaceItem>
|
|
||||||
<a @onclick="(e) => OnEditClick(context)"> <Icon Type="@IconType.Outline.Edit" /> 编辑</a>
|
|
||||||
</SpaceItem>
|
|
||||||
<SpaceItem>
|
|
||||||
<Dropdown Trigger="@(new Trigger[] { Trigger.Click })">
|
|
||||||
<Overlay>
|
|
||||||
<Menu>
|
|
||||||
|
|
||||||
<MenuItem>
|
|
||||||
<a href="@($"/system/locale/resource/detail/{context.Name}")"> <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>
|
|
||||||
<br />
|
|
||||||
<Row Justify="RowJustify.End">
|
|
||||||
<Pagination PageIndex="pager.Index" Total="PagingList.Count" PageSize="PagingList.Size" ShowSizeChanger="false" OnChange="OnPageChanged"></Pagination>
|
|
||||||
</Row>
|
|
||||||
</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="语言文字">
|
|
||||||
@language.Name
|
|
||||||
</FormItem>
|
|
||||||
<FormItem Label="资源名称">
|
|
||||||
<Input Placeholder="资源名称" @bind-Value="@context.Name" />
|
|
||||||
</FormItem>
|
|
||||||
<FormItem Label="资源内容">
|
|
||||||
<TextArea Placeholder="资源内容" @bind-Value="@context.Value" />
|
|
||||||
</FormItem>
|
|
||||||
</Form>
|
|
||||||
</Modal>
|
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
|
|
||||||
bool loading = false;
|
bool loading = false;
|
||||||
|
[Parameter]
|
||||||
|
public string Locale { get; set; } = string.Empty;
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public int Id { get; set; }
|
public int Id { get; set; }
|
||||||
|
|||||||
@@ -1,142 +1,152 @@
|
|||||||
@page "/system/menu/list"
|
@page "/system/menu/list"
|
||||||
|
@page "/{locale}/system/menu/list"
|
||||||
@inject ILogger<MenuList> Logger
|
@inject ILogger<MenuList> Logger
|
||||||
@using MenuItem = Atomx.Admin.Client.Models.MenuItem
|
@using MenuItem = Atomx.Admin.Client.Models.MenuItem
|
||||||
@using Menu = Atomx.Common.Entities.Menu
|
@using Menu = Atomx.Common.Entities.Menu
|
||||||
@attribute [Authorize]
|
@attribute [Authorize]
|
||||||
|
|
||||||
<PageTitle>菜单管理</PageTitle>
|
<PageContainer Title="菜单管理">
|
||||||
|
<Breadcrumb>
|
||||||
|
<Breadcrumb>
|
||||||
|
<BreadcrumbItem Href="/">管理后台</BreadcrumbItem>
|
||||||
|
<BreadcrumbItem Href="/admin/list">系统功能</BreadcrumbItem>
|
||||||
|
<BreadcrumbItem>菜单管理</BreadcrumbItem>
|
||||||
|
</Breadcrumb>
|
||||||
|
</Breadcrumb>
|
||||||
|
<ChildContent>
|
||||||
|
<Card>
|
||||||
|
<Form @ref="searchForm" Model="search" Layout="FormLayout.Inline" Class="search-form" OnFinish="OnSearchFinish">
|
||||||
|
<Row Justify="RowJustify.Start" Gutter="16">
|
||||||
|
<Col>
|
||||||
|
<FormItem Label="名称">
|
||||||
|
<Input @bind-Value="search.Name" Placeholder="名称" AllowClear />
|
||||||
|
</FormItem>
|
||||||
|
</Col>
|
||||||
|
|
||||||
<Title Level="4">菜单管理</Title>
|
<Col>
|
||||||
<Card>
|
<div class="ant-form-item" style="width:200px;display:flex;">
|
||||||
<Form @ref="searchForm" Model="search" Layout="FormLayout.Inline" Class="search-form" OnFinish="OnSearchFinish">
|
<Button Type="ButtonType.Primary" HtmlType="submit">查询</Button>
|
||||||
<Row Justify="RowJustify.Start" Gutter="16">
|
<Button Style="margin: 0 8px;" OnClick="OnSearchReset">重置</Button>
|
||||||
<Col>
|
</div>
|
||||||
<FormItem Label="名称">
|
</Col>
|
||||||
<Input @bind-Value="search.Name" Placeholder="名称" AllowClear />
|
</Row>
|
||||||
</FormItem>
|
</Form>
|
||||||
</Col>
|
</Card>
|
||||||
|
<Card Class="mt-3">
|
||||||
|
<Table DataSource="Menus" PageSize="100" HidePagination="true" Resizable>
|
||||||
|
<TitleTemplate>
|
||||||
|
<Flex Justify="FlexJustify.SpaceBetween">
|
||||||
|
菜单列表
|
||||||
|
<div>
|
||||||
|
<Button Class="me-3" OnClick="OnCreateClick" Type="ButtonType.Primary">新增</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<Col>
|
</Flex>
|
||||||
<div class="ant-form-item" style="width:200px;display:flex;">
|
</TitleTemplate>
|
||||||
<Button Type="ButtonType.Primary" HtmlType="submit">查询</Button>
|
<ColumnDefinitions>
|
||||||
<Button Style="margin: 0 8px;" OnClick="OnSearchReset">重置</Button>
|
<PropertyColumn Property="c => c.Name" Title="名称">
|
||||||
</div>
|
<AntDesign.Text>@GetName(context)</AntDesign.Text>
|
||||||
</Col>
|
</PropertyColumn>
|
||||||
</Row>
|
<PropertyColumn Property="c => c.Icon" Title="图标" Width="60px" Align="ColumnAlign.Center">
|
||||||
</Form>
|
@if (!string.IsNullOrEmpty(context.Icon))
|
||||||
</Card>
|
{
|
||||||
<Card Class="mt-3">
|
<Icon Type="@context.Icon" Theme="IconThemeType.Outline" Width="1.3em" Height="1.3em" />
|
||||||
<Table DataSource="Menus" PageSize="100" HidePagination="true" Resizable>
|
}
|
||||||
<TitleTemplate>
|
</PropertyColumn>
|
||||||
<Flex Justify="FlexJustify.SpaceBetween">
|
<PropertyColumn Property="c => c.Key" Title="分组标识" Ellipsis />
|
||||||
菜单列表
|
<PropertyColumn Property="c => c.Url" Title="链接" />
|
||||||
<div>
|
<PropertyColumn Property="c => c.DisplayOrder" Title="排序" Width="80px" Align="ColumnAlign.Center" />
|
||||||
<Button Class="me-3" OnClick="OnCreateClick" Type="ButtonType.Primary">新增</Button>
|
<PropertyColumn Property="c => c.Enabled" Title="状态" Width="80px" Align="ColumnAlign.Center">
|
||||||
</div>
|
@if (context.Enabled)
|
||||||
|
{
|
||||||
|
<AntDesign.Text Type="TextElementType.Success"><Icon Type="check" Theme=" IconThemeType.Outline" Width="1.3em" Height="1.3em" /></AntDesign.Text>
|
||||||
|
|
||||||
</Flex>
|
}
|
||||||
</TitleTemplate>
|
else
|
||||||
<ColumnDefinitions>
|
{
|
||||||
<PropertyColumn Property="c=>c.Name" Title="名称">
|
<Icon Type="close" Theme="IconThemeType.Outline" Width="1.3em" Height="1.3em" />
|
||||||
<AntDesign.Text>@GetName(context)</AntDesign.Text>
|
}
|
||||||
</PropertyColumn>
|
</PropertyColumn>
|
||||||
<PropertyColumn Property="c=>c.Icon" Title="图标" Width="60px" Align="ColumnAlign.Center">
|
<PropertyColumn Property="c => c.CreateTime" Title="时间" Width="150px" />
|
||||||
@if (!string.IsNullOrEmpty(context.Icon)) {
|
<ActionColumn Title="操作" Align="ColumnAlign.Right" Width="120px">
|
||||||
<Icon Type="@context.Icon" Theme="IconThemeType.Outline" Width="1.3em" Height="1.3em" />
|
<Space>
|
||||||
}
|
<SpaceItem>
|
||||||
</PropertyColumn>
|
<a @onclick="(e) => OnEditClick(context)">编辑</a>
|
||||||
<PropertyColumn Property="c=>c.Key" Title="分组标识" Ellipsis/>
|
</SpaceItem>
|
||||||
<PropertyColumn Property="c=>c.Url" Title="链接" />
|
<SpaceItem>
|
||||||
<PropertyColumn Property="c=>c.DisplayOrder" Title="排序" Width="80px" Align="ColumnAlign.Center" />
|
<Popconfirm Placement="@Placement.Left" Title="@("删除这条数据无法恢复,您确定要删除吗?")"
|
||||||
<PropertyColumn Property="c=>c.Enabled" Title="状态" Width="80px" Align="ColumnAlign.Center">
|
OnConfirm="@(e=>HandleDeleteConfirmAsync(e,context.Id))"
|
||||||
@if (context.Enabled)
|
OkText="确定"
|
||||||
|
CancelText="取消">
|
||||||
|
<a>删除</a>
|
||||||
|
</Popconfirm>
|
||||||
|
</SpaceItem>
|
||||||
|
</Space>
|
||||||
|
</ActionColumn>
|
||||||
|
</ColumnDefinitions>
|
||||||
|
</Table>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Drawer Closable="true" Width="520" Visible="drawerVisible" Title='("设置菜单")' OnClose="_ => CloseDrawer()">
|
||||||
|
<Form LabelColSpan="4" @ref="@editform" Model="@menu" Class="px-5" OnFinish="OnFormFinish">
|
||||||
|
<FluentValidationValidator />
|
||||||
|
<FormItem Label="名称">
|
||||||
|
<Input @bind-Value="menu.Name" For="(()=>menu.Name)" />
|
||||||
|
</FormItem>
|
||||||
|
<FormItem Label="分组标识">
|
||||||
|
<Input @bind-Value="menu.Key" For="(()=>menu.Key)" />
|
||||||
|
</FormItem>
|
||||||
|
<FormItem Label="链接">
|
||||||
|
<Input @bind-Value="menu.Url" For="(()=>menu.Url)" />
|
||||||
|
</FormItem>
|
||||||
|
<FormItem Label="上级分类">
|
||||||
|
<Select @bind-Value="@menu.ParentId" TItemValue="long" TItem="string" Placeholder="请选择上级分类">
|
||||||
|
<SelectOptions>
|
||||||
|
<SelectOption Value="0L" Label="无" />
|
||||||
|
@foreach (var item in Menus)
|
||||||
|
{
|
||||||
|
<SelectOption Value="@item.Id" Label="@GetName(item)" />
|
||||||
|
}
|
||||||
|
|
||||||
|
</SelectOptions>
|
||||||
|
</Select>
|
||||||
|
</FormItem>
|
||||||
|
@if (menu.ParentId == 0)
|
||||||
{
|
{
|
||||||
<AntDesign.Text Type="TextElementType.Success"><Icon Type="check" Theme=" IconThemeType.Outline" Width="1.3em" Height="1.3em" /></AntDesign.Text>
|
|
||||||
|
|
||||||
|
<FormItem Label="图标">
|
||||||
|
<Select ItemValue="c => c"
|
||||||
|
ItemLabel="c => c"
|
||||||
|
DataSource="IconsExtension.IconList()"
|
||||||
|
@bind-Value="@menu.Icon">
|
||||||
|
<ItemTemplate Context="Icon">
|
||||||
|
<p class="mt-2"><Icon Type="@Icon" /> @Icon</p>
|
||||||
|
</ItemTemplate>
|
||||||
|
<LabelTemplate Context="Icon">
|
||||||
|
<p class="mt-1"><Icon Type="@Icon" /> @Icon</p>
|
||||||
|
</LabelTemplate>
|
||||||
|
</Select>
|
||||||
|
</FormItem>
|
||||||
}
|
}
|
||||||
else
|
<FormItem Label="显示排序">
|
||||||
{
|
<Input @bind-Value="menu.DisplayOrder" For="(()=>menu.DisplayOrder)" />
|
||||||
<Icon Type="close" Theme="IconThemeType.Outline" Width="1.3em" Height="1.3em" />
|
</FormItem>
|
||||||
}
|
<FormItem Label="状态">
|
||||||
</PropertyColumn>
|
<Checkbox T="bool" Label="启用" @bind-value="menu.Enabled" Size="InputSize.Small" Class="ps-0" />
|
||||||
<PropertyColumn Property="c=>c.CreateTime" Title="时间" Width="150px" />
|
</FormItem>
|
||||||
<ActionColumn Title="操作" Align="ColumnAlign.Right" Width="120px">
|
<FormItem WrapperColOffset="4">
|
||||||
<Space>
|
<Button Type="ButtonType.Primary" HtmlType="submit" Class="mt-5" Style="width: 100%;">保存</Button>
|
||||||
<SpaceItem>
|
</FormItem>
|
||||||
<a @onclick="(e)=>OnEditClick(context)">编辑</a>
|
</Form>
|
||||||
</SpaceItem>
|
</Drawer>
|
||||||
<SpaceItem>
|
</ChildContent>
|
||||||
<Popconfirm Placement="@Placement.Left" Title="@("删除这条数据无法恢复,您确定要删除吗?")"
|
</PageContainer>
|
||||||
OnConfirm="@(e=>HandleDeleteConfirmAsync(e,context.Id))"
|
|
||||||
OkText="确定"
|
|
||||||
CancelText="取消">
|
|
||||||
<a>删除</a>
|
|
||||||
</Popconfirm>
|
|
||||||
</SpaceItem>
|
|
||||||
</Space>
|
|
||||||
</ActionColumn>
|
|
||||||
</ColumnDefinitions>
|
|
||||||
</Table>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
<Drawer Closable="true" Width="520" Visible="drawerVisible" Title='("设置菜单")' OnClose="_=>CloseDrawer()">
|
|
||||||
<Form LabelColSpan="4" @ref="@editform" Model="@menu" Class="px-5" OnFinish="OnFormFinish">
|
|
||||||
<FluentValidationValidator />
|
|
||||||
<FormItem Label="名称">
|
|
||||||
<Input @bind-Value="menu.Name" For="(()=>menu.Name)" />
|
|
||||||
</FormItem>
|
|
||||||
<FormItem Label="分组标识">
|
|
||||||
<Input @bind-Value="menu.Key" For="(()=>menu.Key)" />
|
|
||||||
</FormItem>
|
|
||||||
<FormItem Label="链接">
|
|
||||||
<Input @bind-Value="menu.Url" For="(()=>menu.Url)" />
|
|
||||||
</FormItem>
|
|
||||||
<FormItem Label="上级分类">
|
|
||||||
<Select @bind-Value="@menu.ParentId" TItemValue="long" TItem="string" Placeholder="请选择上级分类">
|
|
||||||
<SelectOptions>
|
|
||||||
<SelectOption Value="0L" Label="无" />
|
|
||||||
@foreach(var item in Menus)
|
|
||||||
{
|
|
||||||
<SelectOption Value="@item.Id" Label="@GetName(item)" />
|
|
||||||
}
|
|
||||||
|
|
||||||
</SelectOptions>
|
|
||||||
</Select>
|
|
||||||
</FormItem>
|
|
||||||
@if (menu.ParentId == 0)
|
|
||||||
{
|
|
||||||
|
|
||||||
<FormItem Label="图标">
|
|
||||||
<Select ItemValue="c=>c"
|
|
||||||
ItemLabel="c=>c"
|
|
||||||
DataSource="IconsExtension.IconList()"
|
|
||||||
@bind-Value="@menu.Icon">
|
|
||||||
<ItemTemplate Context="Icon">
|
|
||||||
<p class="mt-2"><Icon Type="@Icon" /> @Icon</p>
|
|
||||||
</ItemTemplate>
|
|
||||||
<LabelTemplate Context="Icon">
|
|
||||||
<p class="mt-1"><Icon Type="@Icon" /> @Icon</p>
|
|
||||||
</LabelTemplate>
|
|
||||||
</Select>
|
|
||||||
</FormItem>
|
|
||||||
}
|
|
||||||
<FormItem Label="显示排序">
|
|
||||||
<Input @bind-Value="menu.DisplayOrder" For="(()=>menu.DisplayOrder)" />
|
|
||||||
</FormItem>
|
|
||||||
<FormItem Label="状态">
|
|
||||||
<Checkbox T="bool" Label="启用" @bind-value="menu.Enabled" Size="InputSize.Small" Class="ps-0" />
|
|
||||||
</FormItem>
|
|
||||||
<FormItem WrapperColOffset="4">
|
|
||||||
<Button Type="ButtonType.Primary" HtmlType="submit" Class="mt-5" Style="width: 100%;">保存</Button>
|
|
||||||
</FormItem>
|
|
||||||
</Form >
|
|
||||||
</Drawer>
|
|
||||||
|
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public string Locale { get; set; } = string.Empty;
|
||||||
|
|
||||||
[SupplyParameterFromQuery]
|
[SupplyParameterFromQuery]
|
||||||
int? Page { get; set; }
|
int? Page { get; set; }
|
||||||
@@ -285,7 +295,7 @@
|
|||||||
|
|
||||||
CloseDrawer();
|
CloseDrawer();
|
||||||
LoadList();
|
LoadList();
|
||||||
await ModalService.InfoAsync(new ConfirmOptions() { Title="提示", Content="数据提交成功!" });
|
await ModalService.InfoAsync(new ConfirmOptions() { Title = "提示", Content = "数据提交成功!" });
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,90 +1,103 @@
|
|||||||
@page "/system/role/permission/{RoleId:long}"
|
@page "/system/role/permission/{RoleId:long}"
|
||||||
|
@page "/{locale}/system/role/permission/{RoleId:long}"
|
||||||
@attribute [Authorize]
|
@attribute [Authorize]
|
||||||
@inject ILogger<RoleList> Logger
|
@inject ILogger<RoleList> Logger
|
||||||
|
|
||||||
<PageTitle>权限设置</PageTitle>
|
<PageContainer Title="权限角色设置">
|
||||||
<Spin Spinning="loading">
|
<Breadcrumb>
|
||||||
<Title Level="4">编辑角色权限</Title>
|
<Breadcrumb>
|
||||||
<Card>
|
<BreadcrumbItem Href="/">管理后台</BreadcrumbItem>
|
||||||
<Form @ref="editForm" Model="model" LabelColSpan="2" WrapperColSpan="22" Class="search-form" OnFinish="OnFormFinishAsync">
|
<BreadcrumbItem Href="/admin/list">系统功能</BreadcrumbItem>
|
||||||
<FormItem Label="角色">
|
<BreadcrumbItem>权限编辑</BreadcrumbItem>
|
||||||
为角色 <Text>@role?.Name</Text> 设置权限
|
</Breadcrumb>
|
||||||
</FormItem>
|
</Breadcrumb>
|
||||||
<FormItem Label="权限">
|
<ChildContent>
|
||||||
<div class="ant-form-item-control-input-content">
|
<Spin Spinning="loading">
|
||||||
@if (!PermissionGroups.Any())
|
<Card>
|
||||||
{
|
<Form @ref="editForm" Model="model" LabelColSpan="2" WrapperColSpan="22" Class="search-form" OnFinish="OnFormFinishAsync">
|
||||||
<div>
|
<FormItem Label="角色">
|
||||||
<p>暂无权限可设置</p>
|
为角色 <Text>@role?.Name</Text> 设置权限
|
||||||
</div>
|
</FormItem>
|
||||||
}
|
<FormItem Label="权限">
|
||||||
else
|
<div class="ant-form-item-control-input-content">
|
||||||
{
|
@if (!PermissionGroups.Any())
|
||||||
@foreach (var group in PermissionGroups)
|
{
|
||||||
{
|
<div>
|
||||||
<GridRow Style="padding-top:10px;">
|
<p>暂无权限可设置</p>
|
||||||
<GridCol Span="24">
|
</div>
|
||||||
<label class="ant-checkbox-wrapper">
|
}
|
||||||
<span class="ant-checkbox @((@group.PermissionItems.Count(p => p.IsSelected) > 0 && @group.PermissionItems.Count(p => p.IsSelected) < @group.PermissionItems.Count) ? "ant-checkbox-indeterminate" : "")">
|
else
|
||||||
<input class="ant-checkbox-input" type="checkbox"
|
{
|
||||||
@onchange="(e) => ToggleAllPermissions(group, (bool)e.Value!)"
|
@foreach (var group in PermissionGroups)
|
||||||
checked="@group.PermissionItems.All(p => p.IsSelected)" />
|
|
||||||
<span class="ant-checkbox-inner"></span>
|
|
||||||
|
|
||||||
</span>
|
|
||||||
<span>
|
|
||||||
<span class="form-check-label fw-bold">
|
|
||||||
@group.CategoryName
|
|
||||||
</span>
|
|
||||||
<small class="text-muted ms-2">
|
|
||||||
(@group.PermissionItems.Count(p => p.IsSelected)/@group.PermissionItems.Count)
|
|
||||||
</small>
|
|
||||||
</span>
|
|
||||||
</label>
|
|
||||||
</GridCol>
|
|
||||||
</GridRow>
|
|
||||||
<GridRow Style="margin-left:20px;margin-right:20px;">
|
|
||||||
@foreach (var permission in group.PermissionItems)
|
|
||||||
{
|
{
|
||||||
<GridCol Span="6">
|
<GridRow Style="padding-top:10px;">
|
||||||
<label class="ant-checkbox-wrapper">
|
<GridCol Span="24">
|
||||||
<span class="ant-checkbox">
|
<label class="ant-checkbox-wrapper">
|
||||||
<input class="ant-checkbox-input" type="checkbox"
|
<span class="ant-checkbox @((@group.PermissionItems.Count(p => p.IsSelected) > 0 && @group.PermissionItems.Count(p => p.IsSelected) < @group.PermissionItems.Count) ? "ant-checkbox-indeterminate" : "")">
|
||||||
@bind="permission.IsSelected"
|
<input class="ant-checkbox-input" type="checkbox"
|
||||||
id="perm_@permission.Name" />
|
@onchange="(e) => ToggleAllPermissions(group, (bool)e.Value!)"
|
||||||
<span class="ant-checkbox-inner"></span>
|
checked="@group.PermissionItems.All(p => p.IsSelected)" />
|
||||||
|
<span class="ant-checkbox-inner"></span>
|
||||||
|
|
||||||
</span>
|
</span>
|
||||||
<span class="form-check-label" for="perm_@permission.Name">
|
<span>
|
||||||
@permission.Description
|
<span class="form-check-label fw-bold">
|
||||||
<small class="text-muted d-block">@permission.Name</small>
|
@group.CategoryName
|
||||||
</span>
|
</span>
|
||||||
</label>
|
<small class="text-muted ms-2">
|
||||||
</GridCol>
|
(@group.PermissionItems.Count(p => p.IsSelected)/@group.PermissionItems.Count)
|
||||||
|
</small>
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
</GridCol>
|
||||||
|
</GridRow>
|
||||||
|
<GridRow Style="margin-left:20px;margin-right:20px;">
|
||||||
|
@foreach (var permission in group.PermissionItems)
|
||||||
|
{
|
||||||
|
<GridCol Span="6">
|
||||||
|
<label class="ant-checkbox-wrapper">
|
||||||
|
<span class="ant-checkbox">
|
||||||
|
<input class="ant-checkbox-input" type="checkbox"
|
||||||
|
@bind="permission.IsSelected"
|
||||||
|
id="perm_@permission.Name" />
|
||||||
|
<span class="ant-checkbox-inner"></span>
|
||||||
|
|
||||||
|
</span>
|
||||||
|
<span class="form-check-label" for="perm_@permission.Name">
|
||||||
|
@permission.Description
|
||||||
|
<small class="text-muted d-block">@permission.Name</small>
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
</GridCol>
|
||||||
|
}
|
||||||
|
</GridRow>
|
||||||
}
|
}
|
||||||
</GridRow>
|
}
|
||||||
}
|
</div>
|
||||||
}
|
</FormItem>
|
||||||
</div>
|
<FormItem WrapperCol="new ColLayoutParam { Span = 24, Offset = 2 }">
|
||||||
</FormItem>
|
<Button Type="@ButtonType.Primary" HtmlType="submit" Loading="@isSaving">
|
||||||
<FormItem WrapperCol="new ColLayoutParam { Span = 24, Offset = 2 }">
|
@if (isSaving)
|
||||||
<Button Type="@ButtonType.Primary" HtmlType="submit" Loading="@isSaving">
|
{
|
||||||
@if (isSaving)
|
<span>保存中...</span>
|
||||||
{
|
}
|
||||||
<span>保存中...</span>
|
else
|
||||||
}
|
{
|
||||||
else
|
<span>保存权限设置</span>
|
||||||
{
|
}
|
||||||
<span>保存权限设置</span>
|
|
||||||
}
|
|
||||||
|
|
||||||
</Button>
|
</Button>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
</Form>
|
</Form>
|
||||||
|
|
||||||
</Card>
|
</Card>
|
||||||
</Spin>
|
</Spin>
|
||||||
|
</ChildContent>
|
||||||
|
</PageContainer>
|
||||||
@code {
|
@code {
|
||||||
|
[Parameter]
|
||||||
|
public string Locale { get; set; } = string.Empty;
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public long RoleId { get; set; }
|
public long RoleId { get; set; }
|
||||||
|
|
||||||
|
|||||||
@@ -1,112 +1,126 @@
|
|||||||
@page "/system/role/list"
|
@page "/system/role/list"
|
||||||
|
@page "/{locale}/system/role/list"
|
||||||
@attribute [Authorize]
|
@attribute [Authorize]
|
||||||
@inject ILogger<RoleList> Logger
|
@inject ILogger<RoleList> Logger
|
||||||
|
|
||||||
<PageTitle>角色管理</PageTitle>
|
<PageContainer Title="角色管理">
|
||||||
|
<Breadcrumb>
|
||||||
|
<Breadcrumb>
|
||||||
|
<BreadcrumbItem Href="/">管理后台</BreadcrumbItem>
|
||||||
|
<BreadcrumbItem Href="/admin/list">系统功能</BreadcrumbItem>
|
||||||
|
<BreadcrumbItem>角色管理</BreadcrumbItem>
|
||||||
|
</Breadcrumb>
|
||||||
|
</Breadcrumb>
|
||||||
|
<ChildContent>
|
||||||
|
<Card Class="mt-3">
|
||||||
|
<Table DataSource="PagingList.Items" PageSize="100" HidePagination="true" Resizable>
|
||||||
|
<TitleTemplate>
|
||||||
|
<Flex Justify="FlexJustify.SpaceBetween">
|
||||||
|
角色列表
|
||||||
|
<div>
|
||||||
|
<Button Class="me-3" OnClick="OnCreateClick" Type="ButtonType.Primary">新增</Button>
|
||||||
|
</div>
|
||||||
|
</Flex>
|
||||||
|
</TitleTemplate>
|
||||||
|
<ColumnDefinitions>
|
||||||
|
<PropertyColumn Property="c => c.Name" Title="角色">
|
||||||
|
</PropertyColumn>
|
||||||
|
<PropertyColumn Property="c => c.Description" Title="说明" />
|
||||||
|
<PropertyColumn Property="c => c.IsSystemRole" Title="系统" Width="80px" Align="ColumnAlign.Center">
|
||||||
|
@if (context.IsSystemRole)
|
||||||
|
{
|
||||||
|
<AntDesign.Text Type="TextElementType.Success"><Icon Type="check" Theme=" IconThemeType.Outline" Width="1.3em" Height="1.3em" /></AntDesign.Text>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<AntDesign.Text Type="TextElementType.Secondary"><Icon Type="minus" Theme="IconThemeType.Outline" Width="1.3em" Height="1.3em" /></AntDesign.Text>
|
||||||
|
}
|
||||||
|
</PropertyColumn>
|
||||||
|
<PropertyColumn Property="c => c.Count" Title="用户数" Width="100px" />
|
||||||
|
<PropertyColumn Property="c => c.Enabled" Title="状态" Width="80px" Align="ColumnAlign.Center">
|
||||||
|
@if (context.Enabled)
|
||||||
|
{
|
||||||
|
<AntDesign.Text Type="TextElementType.Success"><Icon Type="check" Theme=" IconThemeType.Outline" Width="1.3em" Height="1.3em" /></AntDesign.Text>
|
||||||
|
|
||||||
<Title Level="4">角色管理</Title>
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<Icon Type="close" Theme="IconThemeType.Outline" Width="1.3em" Height="1.3em" />
|
||||||
|
}
|
||||||
|
</PropertyColumn>
|
||||||
|
|
||||||
<Card Class="mt-3">
|
<PropertyColumn Property="c => c.UpdateTime" Title="最后更新" Width="190px" />
|
||||||
<Table DataSource="PagingList.Items" PageSize="100" HidePagination="true" Resizable>
|
<ActionColumn Title="操作" Align="ColumnAlign.Right" Width="160px">
|
||||||
<TitleTemplate>
|
<Space>
|
||||||
<Flex Justify="FlexJustify.SpaceBetween">
|
<SpaceItem>
|
||||||
角色列表
|
<Button Type="ButtonType.Link" OnClick="() => OnEditPermissionClick(context)">权限管理</Button>
|
||||||
<div>
|
</SpaceItem>
|
||||||
<Button Class="me-3" OnClick="OnCreateClick" Type="ButtonType.Primary">新增</Button>
|
<SpaceItem>
|
||||||
</div>
|
<Dropdown Trigger="@(new Trigger[] { Trigger.Click })">
|
||||||
</Flex>
|
<Overlay>
|
||||||
</TitleTemplate>
|
<Menu>
|
||||||
<ColumnDefinitions>
|
|
||||||
<PropertyColumn Property="c => c.Name" Title="角色">
|
<MenuItem>
|
||||||
</PropertyColumn>
|
<a @onclick="(e) => OnEditClick(context)"> <Icon Type="@IconType.Outline.Edit" /> 编辑</a>
|
||||||
<PropertyColumn Property="c => c.Description" Title="说明" />
|
</MenuItem>
|
||||||
<PropertyColumn Property="c => c.IsSystemRole" Title="系统" Width="80px" Align="ColumnAlign.Center">
|
@if (!context.IsSystemRole)
|
||||||
@if (context.IsSystemRole)
|
{
|
||||||
|
<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>
|
||||||
|
<br />
|
||||||
|
<Row Justify="RowJustify.End">
|
||||||
|
@if (PagingList.Count > 0)
|
||||||
{
|
{
|
||||||
<AntDesign.Text Type="TextElementType.Success"><Icon Type="check" Theme=" IconThemeType.Outline" Width="1.3em" Height="1.3em" /></AntDesign.Text>
|
<Pagination Current="PagingList.Index" Total="PagingList.Count" PageSize="PagingList.Size" ShowSizeChanger="false" OnChange="OnPageChanged"></Pagination>
|
||||||
}
|
}
|
||||||
else
|
</Row>
|
||||||
{
|
</Card>
|
||||||
<AntDesign.Text Type="TextElementType.Secondary"><Icon Type="minus" Theme="IconThemeType.Outline" Width="1.3em" Height="1.3em" /></AntDesign.Text>
|
|
||||||
}
|
|
||||||
</PropertyColumn>
|
|
||||||
<PropertyColumn Property="c => c.Count" Title="用户数" Width="100px" />
|
|
||||||
<PropertyColumn Property="c => c.Enabled" Title="状态" Width="80px" Align="ColumnAlign.Center">
|
|
||||||
@if (context.Enabled)
|
|
||||||
{
|
|
||||||
<AntDesign.Text Type="TextElementType.Success"><Icon Type="check" Theme=" IconThemeType.Outline" Width="1.3em" Height="1.3em" /></AntDesign.Text>
|
|
||||||
|
|
||||||
}
|
<Drawer Closable="true" Width="520" Visible="drawerVisible" Title='(model.Id == 0 ? "新增角色" : "编辑角色")' OnClose="_ => CloseDrawer()">
|
||||||
else
|
<Form LabelColSpan="5" @ref="@editform" Model="@model" OnFinish="OnFormFinish">
|
||||||
{
|
<FluentValidationValidator />
|
||||||
<Icon Type="close" Theme="IconThemeType.Outline" Width="1.3em" Height="1.3em" />
|
<FormItem Label="角色名称">
|
||||||
}
|
<Input @bind-Value="model.Name" For="(()=>model.Name)" Placeholder="角色名称" />
|
||||||
</PropertyColumn>
|
</FormItem>
|
||||||
|
<FormItem Label="角色说明">
|
||||||
<PropertyColumn Property="c => c.UpdateTime" Title="最后更新" Width="190px" />
|
<Input @bind-Value="model.Description" For="(()=>model.Culture)" Placeholder="角色说明" />
|
||||||
<ActionColumn Title="操作" Align="ColumnAlign.Right" Width="160px">
|
</FormItem>
|
||||||
<Space>
|
<FormItem Label="可用状态">
|
||||||
<SpaceItem>
|
<Checkbox T="bool" Label="启用" @bind-value="model.Enabled" Size="InputSize.Small" Class="ps-0" />
|
||||||
<Button Type="ButtonType.Link" OnClick="() => OnEditPermissionClick(context)">权限管理</Button>
|
</FormItem>
|
||||||
</SpaceItem>
|
<FormItem WrapperColOffset="4">
|
||||||
<SpaceItem>
|
<Button Type="ButtonType.Primary" HtmlType="submit" Style="width: 100%;">保存</Button>
|
||||||
<Dropdown Trigger="@(new Trigger[] { Trigger.Click })">
|
</FormItem>
|
||||||
<Overlay>
|
</Form>
|
||||||
<Menu>
|
</Drawer>
|
||||||
|
</ChildContent>
|
||||||
<MenuItem>
|
</PageContainer>
|
||||||
<a @onclick="(e) => OnEditClick(context)"> <Icon Type="@IconType.Outline.Edit" /> 编辑</a>
|
|
||||||
</MenuItem>
|
|
||||||
@if (!context.IsSystemRole)
|
|
||||||
{
|
|
||||||
<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>
|
|
||||||
<br />
|
|
||||||
<Row Justify="RowJustify.End">
|
|
||||||
<Pagination PageIndex="pager.Index" Total="PagingList.Count" PageSize="PagingList.Size" ShowSizeChanger="false" OnChange="OnPageChanged"></Pagination>
|
|
||||||
</Row>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
<Drawer Closable="true" Width="520" Visible="drawerVisible" Title='(model.Id == 0 ? "新增角色" : "编辑角色")' OnClose="_ => CloseDrawer()">
|
|
||||||
<Form LabelColSpan="5" @ref="@editform" Model="@model" OnFinish="OnFormFinish">
|
|
||||||
<FluentValidationValidator />
|
|
||||||
<FormItem Label="角色名称">
|
|
||||||
<Input @bind-Value="model.Name" For="(()=>model.Name)" Placeholder="角色名称" />
|
|
||||||
</FormItem>
|
|
||||||
<FormItem Label="角色说明">
|
|
||||||
<Input @bind-Value="model.Description" For="(()=>model.Culture)" Placeholder="角色说明" />
|
|
||||||
</FormItem>
|
|
||||||
<FormItem Label="可用状态">
|
|
||||||
<Checkbox T="bool" Label="启用" @bind-value="model.Enabled" Size="InputSize.Small" Class="ps-0" />
|
|
||||||
</FormItem>
|
|
||||||
<FormItem WrapperColOffset="4">
|
|
||||||
<Button Type="ButtonType.Primary" HtmlType="submit" Style="width: 100%;">保存</Button>
|
|
||||||
</FormItem>
|
|
||||||
</Form>
|
|
||||||
</Drawer>
|
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
|
[Parameter]
|
||||||
|
public string Locale { get; set; } = string.Empty;
|
||||||
|
|
||||||
[SupplyParameterFromQuery]
|
[SupplyParameterFromQuery]
|
||||||
int? Page { get; set; }
|
int? Page { get; set; }
|
||||||
|
|
||||||
|
|||||||
20
Atomx.Admin/Atomx.Admin.Client/Pages/Systems/Tools.razor
Normal file
20
Atomx.Admin/Atomx.Admin.Client/Pages/Systems/Tools.razor
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
@page "/system/tools"
|
||||||
|
@page "/{locale}/system/tools"
|
||||||
|
|
||||||
|
<PageContainer Title="系统工具">
|
||||||
|
<Breadcrumb>
|
||||||
|
<Breadcrumb>
|
||||||
|
<BreadcrumbItem Href="/">管理后台</BreadcrumbItem>
|
||||||
|
<BreadcrumbItem Href="/admin/list">系统功能</BreadcrumbItem>
|
||||||
|
<BreadcrumbItem>系统工具</BreadcrumbItem>
|
||||||
|
</Breadcrumb>
|
||||||
|
</Breadcrumb>
|
||||||
|
<ChildContent>
|
||||||
|
<h3>Tools</h3>
|
||||||
|
</ChildContent>
|
||||||
|
</PageContainer>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
[Parameter]
|
||||||
|
public string Locale { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
@@ -1,5 +1,20 @@
|
|||||||
<h3>UserList</h3>
|
@page "/user/list"
|
||||||
|
@page "/{locale}/user/list"
|
||||||
|
|
||||||
|
<PageContainer Title="用户列表">
|
||||||
|
<Breadcrumb>
|
||||||
|
<Breadcrumb>
|
||||||
|
<BreadcrumbItem Href="/">管理后台</BreadcrumbItem>
|
||||||
|
<BreadcrumbItem Href="/content/page/list">内容管理</BreadcrumbItem>
|
||||||
|
<BreadcrumbItem>用户列表</BreadcrumbItem>
|
||||||
|
</Breadcrumb>
|
||||||
|
</Breadcrumb>
|
||||||
|
<ChildContent>
|
||||||
|
<h3>Tools</h3>
|
||||||
|
</ChildContent>
|
||||||
|
</PageContainer>
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
|
[Parameter]
|
||||||
|
public string Locale { get; set; } = string.Empty;
|
||||||
}
|
}
|
||||||
@@ -1,10 +1,33 @@
|
|||||||
@page "/weather"
|
@page "/weather"
|
||||||
|
@page "/{locale}/weather"
|
||||||
|
|
||||||
<PageTitle>Weather</PageTitle>
|
@using Microsoft.Extensions.Localization
|
||||||
|
@inject IStringLocalizer<Weather> L
|
||||||
|
@inject Atomx.Admin.Client.Services.ILocalizationProvider LocalizationProvider
|
||||||
|
|
||||||
<h1>Weather</h1>
|
<PageTitle>@L["weather.title"]</PageTitle>
|
||||||
|
|
||||||
<p>This component demonstrates showing data.</p>
|
<h1>@L["weather.title"]</h1>
|
||||||
|
|
||||||
|
<p>@L["weather.summary"]</p>
|
||||||
|
|
||||||
|
<div style="margin-top:12px; margin-bottom:12px;">
|
||||||
|
<strong>Quick links:</strong>
|
||||||
|
<span style="padding-left:10px;"><NavLink href="/account/login">Login</NavLink></span>
|
||||||
|
<span style="padding-left:10px;"><NavLink href="/counter">Counter</NavLink></span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="margin-top:12px; margin-bottom:12px;">
|
||||||
|
<strong>zh Quick links:</strong>
|
||||||
|
<span style="padding-left:10px;"><NavLink href="/zh/account/login">Login</NavLink></span>
|
||||||
|
<span style="padding-left:10px;"><NavLink href="/zh/counter">Counter</NavLink></span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="margin-top:12px; margin-bottom:12px;">
|
||||||
|
<strong>en Quick links:</strong>
|
||||||
|
<span style="padding-left:10px;"><NavLink href="/en/account/login">Login</NavLink></span>
|
||||||
|
<span style="padding-left:10px;"><NavLink href="/en/counter">Counter</NavLink></span>
|
||||||
|
</div>
|
||||||
|
|
||||||
@if (forecasts == null)
|
@if (forecasts == null)
|
||||||
{
|
{
|
||||||
@@ -16,7 +39,7 @@ else
|
|||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Date</th>
|
<th>Date</th>
|
||||||
<th aria-label="Temperature in Celsius">Temp. (C)</th>
|
<th aria-label="Temperature in Celsius">@L["weather.temperature"]</th>
|
||||||
<th aria-label="Temperature in Farenheit">Temp. (F)</th>
|
<th aria-label="Temperature in Farenheit">Temp. (F)</th>
|
||||||
<th>Summary</th>
|
<th>Summary</th>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -36,23 +59,18 @@ else
|
|||||||
}
|
}
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
|
[Parameter]
|
||||||
|
public string Locale { get; set; } = string.Empty;
|
||||||
private WeatherForecast[]? forecasts;
|
private WeatherForecast[]? forecasts;
|
||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
protected override Task OnInitializedAsync()
|
||||||
{
|
{
|
||||||
// Simulate asynchronous loading to demonstrate a loading indicator
|
// localization handled globally in Routes. No per-page initialization needed.
|
||||||
await Task.Delay(500);
|
return Task.CompletedTask;
|
||||||
|
|
||||||
var startDate = DateOnly.FromDateTime(DateTime.Now);
|
|
||||||
var summaries = new[] { "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" };
|
|
||||||
forecasts = Enumerable.Range(1, 5).Select(index => new WeatherForecast
|
|
||||||
{
|
|
||||||
Date = startDate.AddDays(index),
|
|
||||||
TemperatureC = Random.Shared.Next(-20, 55),
|
|
||||||
Summary = summaries[Random.Shared.Next(summaries.Length)]
|
|
||||||
}).ToArray();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Dispose() { }
|
||||||
|
|
||||||
private class WeatherForecast
|
private class WeatherForecast
|
||||||
{
|
{
|
||||||
public DateOnly Date { get; set; }
|
public DateOnly Date { get; set; }
|
||||||
@@ -60,4 +78,13 @@ else
|
|||||||
public string? Summary { get; set; }
|
public string? Summary { get; set; }
|
||||||
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
|
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
using Atomx.Admin.Client.Services;
|
using Atomx.Admin.Client.Services;
|
||||||
using Atomx.Admin.Client.Utils;
|
using Atomx.Admin.Client.Utils;
|
||||||
|
using Atomx.Admin.Client.Validators;
|
||||||
using Blazored.LocalStorage;
|
using Blazored.LocalStorage;
|
||||||
|
using FluentValidation;
|
||||||
using Microsoft.AspNetCore.Components.Authorization;
|
using Microsoft.AspNetCore.Components.Authorization;
|
||||||
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
|
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.Extensions.Localization;
|
using Microsoft.Extensions.Localization;
|
||||||
using System.Net.Http;
|
|
||||||
|
|
||||||
var builder = WebAssemblyHostBuilder.CreateDefault(args);
|
var builder = WebAssemblyHostBuilder.CreateDefault(args);
|
||||||
|
|
||||||
@@ -26,8 +27,14 @@ builder.Services.AddScoped<IconsExtension>();
|
|||||||
builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
|
builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
|
||||||
|
|
||||||
|
|
||||||
// ע<><D7A2>IStringLocalizer
|
// ע<><D7A2> LocalizationProvider (<28><><EFBFBD><EFBFBD> WASM)
|
||||||
builder.Services.AddTransient(typeof(IStringLocalizer<>), typeof(StringLocalizer<>));
|
// Use Scoped lifetime because LocalizationProvider depends on IJSRuntime (scoped) and IHttpClient etc.
|
||||||
|
builder.Services.AddScoped<ILocalizationProvider, LocalizationProvider>();
|
||||||
|
// ע<><D7A2> ILocalizationService <20><><EFBFBD><EFBFBD>ͬ<EFBFBD><CDAC> Culture <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>䴫<EFBFBD><E4B4AB>
|
||||||
|
builder.Services.AddScoped<ILocalizationService, LocalizationService>();
|
||||||
|
|
||||||
|
// ע<><D7A2> IStringLocalizer<T> ʵ<><CAB5>
|
||||||
|
builder.Services.AddTransient(typeof(IStringLocalizer<>), typeof(JsonStringLocalizer<>));
|
||||||
// <20><><EFBFBD>ӱ<EFBFBD><D3B1>ػ<EFBFBD><D8BB><EFBFBD><EFBFBD><EFBFBD>
|
// <20><><EFBFBD>ӱ<EFBFBD><D3B1>ػ<EFBFBD><D8BB><EFBFBD><EFBFBD><EFBFBD>
|
||||||
builder.Services.AddLocalization();
|
builder.Services.AddLocalization();
|
||||||
|
|
||||||
@@ -61,7 +68,11 @@ builder.Services.AddScoped<HttpService>(sp =>
|
|||||||
return new HttpService(httpClient, httpContextAccessor);
|
return new HttpService(httpClient, httpContextAccessor);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
builder.Services.AddValidatorsFromAssembly(typeof(LoginModelValidator).Assembly);
|
||||||
|
|
||||||
builder.Services.AddAntDesign();
|
builder.Services.AddAntDesign();
|
||||||
|
|
||||||
|
|
||||||
await builder.Build().RunAsync();
|
var host = builder.Build();
|
||||||
|
|
||||||
|
await host.RunAsync();
|
||||||
|
|||||||
@@ -1,13 +1,129 @@
|
|||||||
<Router AppAssembly="typeof(Program).Assembly">
|
@using Microsoft.AspNetCore.Components.Routing
|
||||||
<Found Context="routeData">
|
@inject Atomx.Admin.Client.Services.ILocalizationProvider LocalizationProvider
|
||||||
<CascadingValue Value="routeData">
|
@inject NavigationManager Navigation
|
||||||
<AuthorizeRouteView RouteData="routeData" DefaultLayout="typeof(Layout.MainLayout)">
|
|
||||||
<NotAuthorized>
|
<div @key="LocalizationProvider.CurrentCulture">
|
||||||
<RedirectToLogin />
|
<Router AppAssembly="typeof(Program).Assembly">
|
||||||
</NotAuthorized>
|
<Found Context="routeData">
|
||||||
</AuthorizeRouteView>
|
<CascadingValue Value="routeData">
|
||||||
</CascadingValue>
|
<AuthorizeRouteView RouteData="routeData" DefaultLayout="typeof(Layout.MainLayout)">
|
||||||
<FocusOnNavigate RouteData="routeData" Selector="h1" />
|
<NotAuthorized>
|
||||||
</Found>
|
<RedirectToLogin />
|
||||||
</Router>
|
</NotAuthorized>
|
||||||
|
</AuthorizeRouteView>
|
||||||
|
</CascadingValue>
|
||||||
|
<FocusOnNavigate RouteData="routeData" Selector="h1" />
|
||||||
|
</Found>
|
||||||
|
</Router>
|
||||||
|
</div>
|
||||||
<AntContainer />
|
<AntContainer />
|
||||||
|
|
||||||
|
@code {
|
||||||
|
private bool _initialized;
|
||||||
|
|
||||||
|
protected override async Task OnInitializedAsync()
|
||||||
|
{
|
||||||
|
// Subscribe to language changes to remount router when needed
|
||||||
|
if (LocalizationProvider != null)
|
||||||
|
{
|
||||||
|
LocalizationProvider.LanguageChanged += OnLanguageChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Subscribe to navigation events so client-side nav to /zh/... or /en/... triggers culture change
|
||||||
|
Navigation.LocationChanged += OnLocationChanged;
|
||||||
|
|
||||||
|
// Try detect first URL segment as short culture (e.g. /zh/ or /en/) and set culture before first render
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var relative = Navigation.ToBaseRelativePath(Navigation.Uri).Trim('/');
|
||||||
|
var segments = string.IsNullOrEmpty(relative) ? Array.Empty<string>() : relative.Split('/', StringSplitOptions.RemoveEmptyEntries);
|
||||||
|
var first = segments.FirstOrDefault();
|
||||||
|
var mapping = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
|
||||||
|
{
|
||||||
|
{ "zh", "zh-Hans" },
|
||||||
|
{ "en", "en-US" }
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(first) && mapping.TryGetValue(first, out var mapped))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (OperatingSystem.IsBrowser())
|
||||||
|
{
|
||||||
|
await LocalizationProvider.SetCultureAsync(mapped);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (LocalizationProvider is Atomx.Admin.Client.Services.LocalizationProvider concrete)
|
||||||
|
{
|
||||||
|
concrete.SetCultureForServer(mapped);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void OnLocationChanged(object? sender, LocationChangedEventArgs args)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var relative = Navigation.ToBaseRelativePath(args.Location).Trim('/');
|
||||||
|
var segments = string.IsNullOrEmpty(relative) ? Array.Empty<string>() : relative.Split('/', StringSplitOptions.RemoveEmptyEntries);
|
||||||
|
var first = segments.FirstOrDefault();
|
||||||
|
if (string.IsNullOrEmpty(first)) return;
|
||||||
|
|
||||||
|
var mapping = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
|
||||||
|
{
|
||||||
|
{ "zh", "zh-Hans" },
|
||||||
|
{ "en", "en-US" }
|
||||||
|
};
|
||||||
|
|
||||||
|
if (mapping.TryGetValue(first, out var mapped))
|
||||||
|
{
|
||||||
|
// if culture already set to same, skip
|
||||||
|
if (string.Equals(LocalizationProvider.CurrentCulture, mapped, StringComparison.OrdinalIgnoreCase)) return;
|
||||||
|
|
||||||
|
// Call async set; ignore errors
|
||||||
|
await LocalizationProvider.SetCultureAsync(mapped);
|
||||||
|
|
||||||
|
// Trigger router remount via StateHasChanged
|
||||||
|
_ = InvokeAsync(StateHasChanged);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||||
|
{
|
||||||
|
if (firstRender && !_initialized)
|
||||||
|
{
|
||||||
|
_initialized = true;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (LocalizationProvider != null)
|
||||||
|
{
|
||||||
|
await LocalizationProvider.InitializeAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnLanguageChanged(object? s, string culture)
|
||||||
|
{
|
||||||
|
// Remount router via @key by triggering StateHasChanged
|
||||||
|
_ = InvokeAsync(StateHasChanged);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (LocalizationProvider != null)
|
||||||
|
{
|
||||||
|
LocalizationProvider.LanguageChanged -= OnLanguageChanged;
|
||||||
|
}
|
||||||
|
Navigation.LocationChanged -= OnLocationChanged;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -72,10 +72,11 @@ namespace Atomx.Admin.Client.Services
|
|||||||
page = 1;
|
page = 1;
|
||||||
}
|
}
|
||||||
url = $"{url}?page={page}&size={size}";
|
url = $"{url}?page={page}&size={size}";
|
||||||
|
var json = data.ToJson();
|
||||||
using var request = new HttpRequestMessage(HttpMethod.Post, url)
|
using var request = new HttpRequestMessage(HttpMethod.Post, url)
|
||||||
{
|
{
|
||||||
Content = JsonContent.Create(data)
|
Content = new StringContent(json, Encoding.UTF8, "application/json")
|
||||||
|
//Content = JsonContent.Create(data)
|
||||||
};
|
};
|
||||||
AttachCookieIfServer(request);
|
AttachCookieIfServer(request);
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,82 @@
|
|||||||
|
using Microsoft.Extensions.Localization;
|
||||||
|
using System.Globalization;
|
||||||
|
|
||||||
|
namespace Atomx.Admin.Client.Services
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 基于 ILocalizationProvider 的 IStringLocalizer 实现:
|
||||||
|
/// 使用 JSON 文件中的键值,未找到返回 key 本身。
|
||||||
|
/// 名称改为 JsonStringLocalizer 避免与框架的 StringLocalizer 冲突。
|
||||||
|
/// </summary>
|
||||||
|
public class JsonStringLocalizer<T> : IStringLocalizer<T>
|
||||||
|
{
|
||||||
|
private readonly ILocalizationProvider _provider;
|
||||||
|
|
||||||
|
public JsonStringLocalizer(ILocalizationProvider provider)
|
||||||
|
{
|
||||||
|
_provider = provider;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LocalizedString this[string name]
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var value = _provider.GetString(name);
|
||||||
|
if (value == null)
|
||||||
|
{
|
||||||
|
// 避免在服务端 prerender 阶段进行同步阻塞。以后台方式启动加载并返回 key。
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_ = _provider.LoadCultureAsync(_provider.CurrentCulture);
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
}
|
||||||
|
|
||||||
|
var result = value ?? name;
|
||||||
|
return new LocalizedString(name, result, resourceNotFound: result == name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public LocalizedString this[string name, params object[] arguments]
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var fmt = _provider.GetString(name);
|
||||||
|
if (fmt == null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_ = _provider.LoadCultureAsync(_provider.CurrentCulture);
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
}
|
||||||
|
|
||||||
|
var format = fmt ?? name;
|
||||||
|
var value = string.Format(format, arguments);
|
||||||
|
return new LocalizedString(name, value, resourceNotFound: format == name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<LocalizedString> GetAllStrings(bool includeParentCultures)
|
||||||
|
{
|
||||||
|
var list = new List<LocalizedString>();
|
||||||
|
var providerType = _provider.GetType();
|
||||||
|
var currentProp = providerType.GetProperty("CurrentCulture");
|
||||||
|
var culture = currentProp?.GetValue(_provider) as string ?? string.Empty;
|
||||||
|
var cacheField = providerType.GetField("_cache", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
|
||||||
|
if (!string.IsNullOrEmpty(culture) && cacheField?.GetValue(_provider) is Dictionary<string, Dictionary<string, string>> cache && cache.TryGetValue(culture, out var dict))
|
||||||
|
{
|
||||||
|
foreach (var kv in dict)
|
||||||
|
{
|
||||||
|
list.Add(new LocalizedString(kv.Key, kv.Value, resourceNotFound: false));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IStringLocalizer WithCulture(CultureInfo culture)
|
||||||
|
{
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
544
Atomx.Admin/Atomx.Admin.Client/Services/LocalizationProvider.cs
Normal file
544
Atomx.Admin/Atomx.Admin.Client/Services/LocalizationProvider.cs
Normal file
@@ -0,0 +1,544 @@
|
|||||||
|
using Microsoft.JSInterop;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Text.Json;
|
||||||
|
|
||||||
|
namespace Atomx.Admin.Client.Services
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// <20><><EFBFBD><EFBFBD><EFBFBD>ṩ<EFBFBD><E1B9A9><EFBFBD><EFBFBD><EFBFBD><EFBFBD> JSON <20>ļ<EFBFBD><C4BC>ļ<EFBFBD><C4BC>ء<EFBFBD><D8A1><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʱ<EFBFBD>л<EFBFBD><D0BB><EFBFBD><EFBFBD>ܡ<EFBFBD>
|
||||||
|
/// <20><>Ҫְ<D2AA><D6B0><EFBFBD><EFBFBD>
|
||||||
|
/// - <20><> Server <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> prerender <20><> Blazor Server<65><72>ʱ<EFBFBD><CAB1><EFBFBD>Դ<EFBFBD> webroot ͬ<><CDAC><EFBFBD><EFBFBD>ȡ<EFBFBD><C8A1><EFBFBD>ػ<EFBFBD> JSON <20>ļ<EFBFBD><C4BC><EFBFBD><EFBFBD><EFBFBD><EFBFBD>棬
|
||||||
|
/// <20>Ա<EFBFBD><D4B1>ڷ<EFBFBD><DAB7><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ⱦ<EFBFBD><EFBFBD><D7B6><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>á<EFBFBD>
|
||||||
|
/// - <20><> WASM <20><><EFBFBD><EFBFBD>ʱʹ<CAB1><CAB9>ע<EFBFBD><D7A2><EFBFBD><EFBFBD> HttpClient <20><> /localization/{culture}.json <20><><EFBFBD>ز<EFBFBD><D8B2><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||||
|
/// - <20><><EFBFBD>л<EFBFBD><D0BB><EFBFBD><EFBFBD><EFBFBD>ʱд<CAB1><D0B4><EFBFBD><EFBFBD>Ϊ `atomx.culture` <20><> cookie<69><65><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>˶<EFBFBD>ȡ<EFBFBD><C8A1><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ҳ<EFBFBD><D2B3><EFBFBD><EFBFBD> HTML lang <20><><EFBFBD>ԡ<EFBFBD>
|
||||||
|
/// - <20>ṩ<EFBFBD>¼<EFBFBD>֪ͨ LanguageChanged<65><64><EFBFBD><EFBFBD> UI <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ӧ<EFBFBD><D3A6><EFBFBD>Ա<EFBFBD><D4B1><EFBFBD><EFBFBD><EFBFBD>
|
||||||
|
///
|
||||||
|
/// ˵<><CBB5><EFBFBD><EFBFBD>Ϊ<EFBFBD>˼<EFBFBD><CBBC><EFBFBD> Server <20><> WASM<53><4D><EFBFBD><EFBFBD> Provider <20>ᾡ<EFBFBD><E1BEA1><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʱ<EFBFBD><CAB1><EFBFBD><EFBFBD>ѡ<EFBFBD><D1A1><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʵ<EFBFBD><CAB5><EFBFBD>Դ<EFBFBD><D4B4><EFBFBD>ط<EFBFBD>ʽ<EFBFBD><CABD>
|
||||||
|
/// </summary>
|
||||||
|
public interface ILocalizationProvider
|
||||||
|
{
|
||||||
|
string CurrentCulture { get; }
|
||||||
|
string? GetString(string key);
|
||||||
|
Task SetCultureAsync(string cultureShortOrFull);
|
||||||
|
Task InitializeAsync();
|
||||||
|
/// <summary>
|
||||||
|
/// <20><>ָ֤<D6A4><D6B8><EFBFBD>Ļ<EFBFBD><C4BB><EFBFBD><EFBFBD><EFBFBD>Դ<EFBFBD>ѱ<EFBFBD><D1B1><EFBFBD><EFBFBD>أ<EFBFBD><D8A3>첽<EFBFBD><ECB2BD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ⲿ<EFBFBD><E2B2BF><EFBFBD><EFBFBD>Ҫʱ<D2AA><CAB1><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ء<EFBFBD>
|
||||||
|
/// </summary>
|
||||||
|
Task LoadCultureAsync(string culture);
|
||||||
|
event EventHandler<string>? LanguageChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// LocalizationProvider <20><>ʵ<EFBFBD>֣<EFBFBD>
|
||||||
|
/// - ά<><CEAC>һ<EFBFBD><D2BB><EFBFBD><EFBFBD>̬<EFBFBD><CCAC><EFBFBD>棬<EFBFBD><E6A3AC><EFBFBD><EFBFBD><EFBFBD>ظ<EFBFBD><D8B8><EFBFBD><EFBFBD><EFBFBD>/<2F><>ȡ<EFBFBD><C8A1>Դ<EFBFBD><D4B4>
|
||||||
|
/// - ֧<>ֶ<EFBFBD><D6B6>루<EFBFBD><EBA3A8> zh / en<65><6E><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> culture<72><65><EFBFBD><EFBFBD> zh-Hans / en-US<55><53>֮<EFBFBD><D6AE><EFBFBD><EFBFBD>ӳ<EFBFBD>䡣
|
||||||
|
/// - <20><> Server <20><>֧<EFBFBD><D6A7>ͬ<EFBFBD><CDAC><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> prerender <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||||
|
/// </summary>
|
||||||
|
public class LocalizationProvider : ILocalizationProvider
|
||||||
|
{
|
||||||
|
private readonly IServiceProvider _sp;
|
||||||
|
private readonly IHttpClientFactory? _httpClientFactory;
|
||||||
|
private readonly IJSRuntime? _jsRuntime;
|
||||||
|
private readonly ILogger<LocalizationProvider> _logger;
|
||||||
|
private readonly ILocalizationService _localizationService;
|
||||||
|
|
||||||
|
// <20><><EFBFBD>棺culture -> translations<6E><73>ʹ<EFBFBD><CAB9> ConcurrentDictionary <20><><EFBFBD>̰߳<DFB3>ȫ<EFBFBD>ع<EFBFBD><D8B9><EFBFBD><EFBFBD><EFBFBD>
|
||||||
|
// ʹ<>þ<EFBFBD>̬<EFBFBD>ֶ<EFBFBD><D6B6><EFBFBD>Ϊ<EFBFBD><CEAA>ʹ<EFBFBD>м<EFBFBD><D0BC><EFBFBD>/<2F><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ͬһ<CDAC><D2BB><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ܹ<EFBFBD><DCB9><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ѽ<EFBFBD><D1BC>صķ<D8B5><C4B7>룬<EFBFBD><EBA3AC><EFBFBD><EFBFBD><EFBFBD>ظ<EFBFBD> I/O<><4F>
|
||||||
|
private static readonly ConcurrentDictionary<string, Dictionary<string, string>> _cache = new();
|
||||||
|
|
||||||
|
// ֧<>ֵĶ<D6B5><C4B6><EFBFBD>ӳ<EFBFBD>䣬<EFBFBD><E4A3AC><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>չ<EFBFBD><D5B9><EFBFBD>ڴ<EFBFBD><DAB4><EFBFBD><EFBFBD>ӡ<EFBFBD>
|
||||||
|
private static readonly Dictionary<string, string> ShortToCulture = new(StringComparer.OrdinalIgnoreCase)
|
||||||
|
{
|
||||||
|
{ "zh", "zh-Hans" },
|
||||||
|
{ "en", "en-US" }
|
||||||
|
};
|
||||||
|
|
||||||
|
// Ĭ<><C4AC><EFBFBD>Ļ<EFBFBD><C4BB><EFBFBD><EFBFBD><EFBFBD>δ<EFBFBD>ܴ<EFBFBD><DCB4><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>/URL/Cookie <20>н<EFBFBD><D0BD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ļ<EFBFBD>ʱʹ<CAB1>ã<EFBFBD>
|
||||||
|
private string _currentCulture = "zh-Hans";
|
||||||
|
|
||||||
|
private const string CookieName = "atomx.culture";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// <20><><EFBFBD>캯<EFBFBD><ECBAAF><EFBFBD><EFBFBD>ͨ<EFBFBD><CDA8><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ע<EFBFBD><D7A2><EFBFBD><EFBFBD>ȡ<EFBFBD><C8A1>Ҫ<EFBFBD><D2AA><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||||
|
/// ע<><EFBFBD>캯<EFBFBD><ECBAAF><EFBFBD>в<EFBFBD>Ӧִ<D3A6>к<EFBFBD>ʱ<EFBFBD><CAB1> JS <20><><EFBFBD>ص<EFBFBD>ͬ<EFBFBD><CDAC><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||||
|
/// </summary>
|
||||||
|
public LocalizationProvider(IServiceProvider sp, ILogger<LocalizationProvider> logger, IHttpClientFactory? httpClientFactory, IJSRuntime? jsRuntime, ILocalizationService localizationService)
|
||||||
|
{
|
||||||
|
_sp = sp;
|
||||||
|
_httpClientFactory = httpClientFactory;
|
||||||
|
_jsRuntime = jsRuntime;
|
||||||
|
_logger = logger;
|
||||||
|
_localizationService = localizationService;
|
||||||
|
|
||||||
|
// <20><><EFBFBD>Ը<EFBFBD><D4B8>ݵ<EFBFBD>ǰ<EFBFBD>߳<EFBFBD> culture <20><>ʼ<EFBFBD><CABC> _currentCulture<72><65><EFBFBD><EFBFBD><EFBFBD>׳<EFBFBD><D7B3>쳣<EFBFBD><ECB3A3>
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var threadUi = CultureInfo.DefaultThreadCurrentUICulture ?? CultureInfo.CurrentUICulture;
|
||||||
|
if (!string.IsNullOrEmpty(threadUi?.Name))
|
||||||
|
{
|
||||||
|
_currentCulture = MapToFullCulture(threadUi!.Name);
|
||||||
|
_logger.LogDebug("LocalizationProvider <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>߳<EFBFBD> UI <20>Ļ<EFBFBD>: {Culture}", _currentCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogDebug(ex, "LocalizationProvider <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ȡ<EFBFBD>߳<EFBFBD><DFB3>Ļ<EFBFBD>ʧ<EFBFBD><CAA7>");
|
||||||
|
}
|
||||||
|
|
||||||
|
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> Server <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>δ<EFBFBD><CEB4>ʹ<EFBFBD><CAB9> JSRuntime<6D><65><EFBFBD><EFBFBD>ζ<EFBFBD>ŷ<EFBFBD><C5B7><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ͬ<EFBFBD><CDAC><EFBFBD><EFBFBD><EFBFBD>ļ<EFBFBD>ϵͳ<CFB5><CDB3>ȡ<EFBFBD><C8A1><EFBFBD>ػ<EFBFBD><D8BB>ļ<EFBFBD><C4BC><EFBFBD>֧<EFBFBD><D6A7> prerender
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var envType = Type.GetType("Microsoft.AspNetCore.Hosting.IWebHostEnvironment, Microsoft.AspNetCore.Hosting.Abstractions")
|
||||||
|
?? Type.GetType("Microsoft.AspNetCore.Hosting.IWebHostEnvironment");
|
||||||
|
if (envType != null)
|
||||||
|
{
|
||||||
|
var env = _sp.GetService(envType);
|
||||||
|
if (env != null && _jsRuntime == null)
|
||||||
|
{
|
||||||
|
var webRootProp = envType.GetProperty("WebRootPath");
|
||||||
|
var contentRootProp = envType.GetProperty("ContentRootPath");
|
||||||
|
var webRoot = webRootProp?.GetValue(env) as string ?? Path.Combine(contentRootProp?.GetValue(env) as string ?? ".", "wwwroot");
|
||||||
|
var path = Path.Combine(webRoot ?? ".", "localization", _currentCulture + ".json");
|
||||||
|
if (File.Exists(path))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var json = File.ReadAllText(path);
|
||||||
|
var dict = JsonSerializer.Deserialize<Dictionary<string, string>>(json) ?? new Dictionary<string, string>();
|
||||||
|
_cache[_currentCulture] = dict;
|
||||||
|
_logger.LogInformation("(Server ͬ<><CDAC>) <20><>·<EFBFBD><C2B7><EFBFBD><EFBFBD><EFBFBD>ر<EFBFBD><D8B1>ػ<EFBFBD><D8BB>ļ<EFBFBD> {Path}<7D><>Culture:{Culture}<7D><><EFBFBD><EFBFBD>Ŀ<EFBFBD><C4BF>:{Count}", path, _currentCulture, dict.Count);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogWarning(ex, "(Server ͬ<><CDAC>) <20><>ȡ<EFBFBD><C8A1><EFBFBD>ػ<EFBFBD><D8BB>ļ<EFBFBD>ʧ<EFBFBD><CAA7>: {Path}", path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger.LogDebug("<22><><EFBFBD>ػ<EFBFBD><D8BB>ļ<EFBFBD>δ<EFBFBD><CEB4>·<EFBFBD><C2B7><EFBFBD>ҵ<EFBFBD>: {Path}", path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogDebug(ex, "LocalizationProvider <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ͬ<EFBFBD><CDAC><EFBFBD>ļ<EFBFBD><C4BC><EFBFBD><EFBFBD>س<EFBFBD><D8B3><EFBFBD>ʧ<EFBFBD><CAA7>");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string CurrentCulture => _currentCulture;
|
||||||
|
|
||||||
|
public event EventHandler<string>? LanguageChanged;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// <20>ӻ<EFBFBD><D3BB><EFBFBD><EFBFBD>ж<EFBFBD>ȡָ<C8A1><D6B8><EFBFBD><EFBFBD><EFBFBD>ı<EFBFBD><C4B1>ػ<EFBFBD><D8BB>ַ<EFBFBD><D6B7><EFBFBD><EFBFBD><EFBFBD>δ<EFBFBD>ҵ<EFBFBD><D2B5><EFBFBD><EFBFBD><EFBFBD> null<6C><6C>
|
||||||
|
/// </summary>
|
||||||
|
public string? GetString(string key)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(key)) return null;
|
||||||
|
|
||||||
|
if (_cache.TryGetValue(_currentCulture, out var dict) && dict.TryGetValue(key, out var val))
|
||||||
|
{
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// <20><>ʼ<EFBFBD><CABC><EFBFBD><EFBFBD><EFBFBD>̣<EFBFBD><CCA3><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ˣ<EFBFBD>
|
||||||
|
/// <20><><EFBFBD>ȼ<EFBFBD><C8BC><EFBFBD>URL <20><><EFBFBD><EFBFBD>ǰ -> Cookie(atomx.culture) -> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> -> Ĭ<><C4AC>
|
||||||
|
/// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>루<EFBFBD><EBA3A8> zh/en<65><6E><EFBFBD><EFBFBD>ӳ<EFBFBD><D3B3>Ϊ<EFBFBD><CEAA><EFBFBD><EFBFBD><EFBFBD>Ļ<EFBFBD><C4BB><EFBFBD><EFBFBD><EFBFBD> zh-Hans/en-US<55><53><EFBFBD><EFBFBD>
|
||||||
|
/// </summary>
|
||||||
|
public async Task InitializeAsync()
|
||||||
|
{
|
||||||
|
_logger.LogDebug("LocalizationProvider.InitializeAsync <20><>ʼ. CurrentCulture={Culture}", _currentCulture);
|
||||||
|
|
||||||
|
string? urlFirstSegment = null;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (_jsRuntime != null && OperatingSystem.IsBrowser())
|
||||||
|
{
|
||||||
|
var path = await _jsRuntime.InvokeAsync<string>("eval", "location.pathname");
|
||||||
|
_logger.LogDebug("JS location.pathname='{Path}'", path);
|
||||||
|
if (!string.IsNullOrEmpty(path))
|
||||||
|
{
|
||||||
|
var trimmed = path.Trim('/');
|
||||||
|
if (!string.IsNullOrEmpty(trimmed))
|
||||||
|
{
|
||||||
|
var seg = trimmed.Split('/', StringSplitOptions.RemoveEmptyEntries).FirstOrDefault();
|
||||||
|
urlFirstSegment = seg;
|
||||||
|
_logger.LogDebug("<22><><EFBFBD> URL <20><EFBFBD>: {Segment}", urlFirstSegment);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogDebug(ex, "<22><>ȡ location.pathname ʧ<><CAA7>");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(urlFirstSegment) && ShortToCulture.TryGetValue(urlFirstSegment, out var mapped))
|
||||||
|
{
|
||||||
|
_logger.LogDebug("URL <20><><EFBFBD><EFBFBD> '{Seg}' ӳ<><D3B3>Ϊ<EFBFBD>Ļ<EFBFBD> '{Culture}'", urlFirstSegment, mapped);
|
||||||
|
await SetCultureInternalAsync(mapped, persistCookie: false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (_jsRuntime != null && OperatingSystem.IsBrowser())
|
||||||
|
{
|
||||||
|
var cookieVal = await _jsRuntime.InvokeAsync<string>("cookies.Read", CookieName);
|
||||||
|
_logger.LogDebug("<22><>ȡ Cookie '{CookieName}'='{CookieVal}'", CookieName, cookieVal);
|
||||||
|
if (!string.IsNullOrEmpty(cookieVal))
|
||||||
|
{
|
||||||
|
if (ShortToCulture.TryGetValue(cookieVal, out var mappedFromCookie))
|
||||||
|
{
|
||||||
|
_logger.LogDebug("Cookie <20><><EFBFBD><EFBFBD> '{Cookie}' ӳ<><D3B3>Ϊ<EFBFBD>Ļ<EFBFBD> {Culture}", cookieVal, mappedFromCookie);
|
||||||
|
await SetCultureInternalAsync(mappedFromCookie, persistCookie: false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// cookie <20>п<EFBFBD><D0BF><EFBFBD><EFBFBD>Ѿ<EFBFBD><D1BE><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> culture<72><65><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ƽ<EFBFBD><C6BD><EFBFBD><EFBFBD>һ<EFBFBD><D2BB>
|
||||||
|
await SetCultureInternalAsync(MapToFullCulture(cookieVal), persistCookie: false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogDebug(ex, "<22><>ȡ Cookie ʧ<><CAA7>");
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (_jsRuntime != null && OperatingSystem.IsBrowser())
|
||||||
|
{
|
||||||
|
var browserLang = await _jsRuntime.InvokeAsync<string>("getBrowserLanguage");
|
||||||
|
_logger.LogDebug("<22><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>: {BrowserLang}", browserLang);
|
||||||
|
if (!string.IsNullOrEmpty(browserLang))
|
||||||
|
{
|
||||||
|
var mappedFromBrowser = MapToFullCulture(browserLang);
|
||||||
|
_logger.LogDebug("<22><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ӳ<EFBFBD><D3B3>Ϊ {Culture}", mappedFromBrowser);
|
||||||
|
await SetCultureInternalAsync(mappedFromBrowser, persistCookie: false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogDebug(ex, "<22><>ȡ<EFBFBD><C8A1><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʧ<EFBFBD><CAA7>");
|
||||||
|
}
|
||||||
|
|
||||||
|
// <20><><EFBFBD>˵<EFBFBD><CBB5><EFBFBD>ǰĬ<C7B0><C4AC><EFBFBD>Ļ<EFBFBD><C4BB><EFBFBD>ȷ<EFBFBD><C8B7><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Դ
|
||||||
|
_logger.LogDebug("InitializeAsync <20><><EFBFBD><EFBFBD>ʹ<EFBFBD>õ<EFBFBD>ǰ<EFBFBD>Ļ<EFBFBD> {Culture}", _currentCulture);
|
||||||
|
await EnsureCultureLoadedAsync(_currentCulture);
|
||||||
|
await SetCultureInternalAsync(_currentCulture, persistCookie: false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// <20><><EFBFBD><EFBFBD><EFBFBD>첽<EFBFBD><ECB2BD><EFBFBD><EFBFBD><EFBFBD>Ļ<EFBFBD><C4BB><EFBFBD><EFBFBD>ɴ<EFBFBD><C9B4><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> culture<72><65><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ѡ<EFBFBD><D1A1><EFBFBD>Ƿ<EFBFBD><C7B7>־û<D6BE><C3BB><EFBFBD> Cookie
|
||||||
|
/// </summary>
|
||||||
|
public async Task SetCultureAsync(string cultureShortOrFull)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(cultureShortOrFull)) return;
|
||||||
|
if (ShortToCulture.TryGetValue(cultureShortOrFull, out var mapped))
|
||||||
|
{
|
||||||
|
await SetCultureInternalAsync(mapped, persistCookie: true);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await SetCultureInternalAsync(MapToFullCulture(cultureShortOrFull), persistCookie: true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task LoadCultureAsync(string culture) => EnsureCultureLoadedAsync(MapToFullCulture(culture));
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Server <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> prerender ʱ<><CAB1>ͬ<EFBFBD><CDAC><EFBFBD><EFBFBD><EFBFBD>ã<EFBFBD><C3A3><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>߳<EFBFBD> Culture <20><><EFBFBD><EFBFBD><EFBFBD>Դ<EFBFBD> webroot ͬ<><CDAC><EFBFBD><EFBFBD><EFBFBD>ر<EFBFBD><D8B1>ػ<EFBFBD><D8BB>ļ<EFBFBD><C4BC><EFBFBD>
|
||||||
|
/// <20>÷<EFBFBD><C3B7><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ᴥ<EFBFBD><E1B4A5> JS <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʺ<EFBFBD><CABA><EFBFBD><EFBFBD>м<EFBFBD><D0BC><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʹ<EFBFBD>á<EFBFBD>
|
||||||
|
/// </summary>
|
||||||
|
public void SetCultureForServer(string cultureShortOrFull)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var cultureFull = MapToFullCulture(cultureShortOrFull);
|
||||||
|
if (string.IsNullOrEmpty(cultureFull)) return;
|
||||||
|
|
||||||
|
// <20><><EFBFBD><EFBFBD><EFBFBD>߳<EFBFBD> culture<72><65>Ӱ<EFBFBD><D3B0><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>˴<EFBFBD><CBB4><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> IStringLocalizer<65><72>
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var ci = new CultureInfo(cultureFull);
|
||||||
|
CultureInfo.DefaultThreadCurrentCulture = ci;
|
||||||
|
CultureInfo.DefaultThreadCurrentUICulture = ci;
|
||||||
|
_currentCulture = cultureFull;
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
|
||||||
|
// ͬ<><CDAC><EFBFBD><EFBFBD> webroot <20><><EFBFBD><EFBFBD> JSON <20><><EFBFBD>ػ<EFBFBD><D8BB>ļ<EFBFBD><C4BC><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ڣ<EFBFBD>
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var envType = Type.GetType("Microsoft.AspNetCore.Hosting.IWebHostEnvironment, Microsoft.AspNetCore.Hosting.Abstractions")
|
||||||
|
?? Type.GetType("Microsoft.AspNetCore.Hosting.IWebHostEnvironment");
|
||||||
|
if (envType != null)
|
||||||
|
{
|
||||||
|
var env = _sp.GetService(envType);
|
||||||
|
if (env != null)
|
||||||
|
{
|
||||||
|
var webRootProp = envType.GetProperty("WebRootPath");
|
||||||
|
var contentRootProp = envType.GetProperty("ContentRootPath");
|
||||||
|
var webRoot = webRootProp?.GetValue(env) as string ?? Path.Combine(contentRootProp?.GetValue(env) as string ?? ".", "wwwroot");
|
||||||
|
var path = Path.Combine(webRoot ?? ".", "localization", cultureFull + ".json");
|
||||||
|
if (File.Exists(path))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var json = File.ReadAllText(path);
|
||||||
|
var dict = JsonSerializer.Deserialize<Dictionary<string, string>>(json) ?? new Dictionary<string, string>();
|
||||||
|
_cache[cultureFull] = dict;
|
||||||
|
_logger.LogInformation("(Server ͬ<><CDAC>) <20><>Ϊ {Culture} <20><>·<EFBFBD><C2B7><EFBFBD><EFBFBD><EFBFBD>ر<EFBFBD><D8B1>ػ<EFBFBD><D8BB>ļ<EFBFBD><C4BC><EFBFBD><EFBFBD><EFBFBD>Ŀ<EFBFBD><C4BF>: {Count}", cultureFull, dict.Count);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogWarning(ex, "(Server ͬ<><CDAC>) <20><>ȡ<EFBFBD><C8A1><EFBFBD>ػ<EFBFBD><D8BB>ļ<EFBFBD>ʧ<EFBFBD><CAA7>: {Path}", path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogDebug(ex, "SetCultureForServer <20><><EFBFBD><EFBFBD><EFBFBD>ļ<EFBFBD>ʱ<EFBFBD><CAB1><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>: {Culture}", cultureFull);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogDebug(ex, "SetCultureForServer ִ<>й<EFBFBD><D0B9><EFBFBD><EFBFBD>з<EFBFBD><D0B7><EFBFBD><EFBFBD>쳣");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// <20>ڲ<EFBFBD><DAB2><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ļ<EFBFBD><C4BB><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ҫʱ<D2AA>־û<D6BE> Cookie<69><65><EFBFBD><EFBFBD><EFBFBD><EFBFBD> localization service <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>¼<EFBFBD><C2BC><EFBFBD>
|
||||||
|
/// </summary>
|
||||||
|
private async Task SetCultureInternalAsync(string cultureFull, bool persistCookie)
|
||||||
|
{
|
||||||
|
//_logger.LogDebug("<22><><EFBFBD><EFBFBD><EFBFBD>ڲ<EFBFBD><DAB2>Ļ<EFBFBD><C4BB>첽<EFBFBD><ECB2BD>ʼ: {Culture}, <20>־û<D6BE>={Persist}", cultureFull, persistCookie);
|
||||||
|
await EnsureCultureLoadedAsync(cultureFull);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var ci = new CultureInfo(cultureFull);
|
||||||
|
CultureInfo.DefaultThreadCurrentCulture = ci;
|
||||||
|
CultureInfo.DefaultThreadCurrentUICulture = ci;
|
||||||
|
_currentCulture = cultureFull;
|
||||||
|
_localizationService.SetLanguage(ci);
|
||||||
|
_logger.LogDebug("<22>Ļ<EFBFBD><C4BB><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ϊ {Culture}", cultureFull);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogWarning(ex, "<22><><EFBFBD><EFBFBD> Culture ʧ<><CAA7>: {Culture}", cultureFull);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (persistCookie && _jsRuntime != null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var shortKey = ShortToCulture.FirstOrDefault(kv => string.Equals(kv.Value, cultureFull, StringComparison.OrdinalIgnoreCase)).Key ?? cultureFull;
|
||||||
|
// <20><> shortKey д<><D0B4> cookie<69><65><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ȡ<EFBFBD><C8A1><EFBFBD><EFBFBD><EFBFBD><EFBFBD> Server ģʽ<C4A3>µ<EFBFBD><C2B5>м<EFBFBD><D0BC><EFBFBD><EFBFBD><EFBFBD>
|
||||||
|
await _jsRuntime.InvokeVoidAsync("cookies.Write", CookieName, shortKey, DateTime.UtcNow.AddYears(1).ToString("o"));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogDebug(ex, "д<><D0B4> Cookie ʧ<><CAA7>");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (_jsRuntime != null)
|
||||||
|
{
|
||||||
|
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> HTML <20><> lang <20><><EFBFBD>ԣ<EFBFBD><D4A3><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ϰ<EFBFBD>/SEO
|
||||||
|
await _jsRuntime.InvokeVoidAsync("setHtmlLang", cultureFull);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
|
||||||
|
// ֪ͨ<CDA8><D6AA><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||||
|
LanguageChanged?.Invoke(this, cultureFull);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ȷ<><C8B7>ָ<EFBFBD><D6B8><EFBFBD>Ļ<EFBFBD><C4BB><EFBFBD> JSON <20>ļ<EFBFBD><C4BC>ѱ<EFBFBD><D1B1><EFBFBD><EFBFBD>ص<EFBFBD><D8B5><EFBFBD><EFBFBD>档<EFBFBD><E6A1A3><EFBFBD><EFBFBD>˳<EFBFBD><CBB3><EFBFBD><EFBFBD>WASM HttpClient -> <20>ļ<EFBFBD>ϵͳ -> <20><><EFBFBD>ֵ<EFBFBD>ռλ<D5BC><CEBB>
|
||||||
|
/// </summary>
|
||||||
|
private async Task EnsureCultureLoadedAsync(string cultureFull)
|
||||||
|
{
|
||||||
|
// <20><>һ<EFBFBD><D2BB><EFBFBD><EFBFBD><EFBFBD>루<EFBFBD><EBA3A8><EFBFBD><EFBFBD> zh -> zh-Hans<6E><73>
|
||||||
|
cultureFull = MapToFullCulture(cultureFull);
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(cultureFull)) return;
|
||||||
|
if (_cache.ContainsKey(cultureFull))
|
||||||
|
{
|
||||||
|
_logger.LogDebug("EnsureCultureLoadedAsync: <20>Ļ<EFBFBD> {Culture} <20>ѻ<EFBFBD><D1BB><EFBFBD>", cultureFull);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>WASM<53><4D><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʹ<EFBFBD><CAB9> HttpClient <20><><EFBFBD>ر<EFBFBD><D8B1>ػ<EFBFBD> JSON
|
||||||
|
if (_jsRuntime != null && OperatingSystem.IsBrowser())
|
||||||
|
{
|
||||||
|
_logger.LogInformation("EnsureCultureLoadedAsync: <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ͨ<EFBFBD><CDA8> HttpClient <20><><EFBFBD><EFBFBD> {Culture}", cultureFull);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var http = _sp.GetService(typeof(HttpClient)) as HttpClient;
|
||||||
|
if (http == null && _httpClientFactory != null)
|
||||||
|
{
|
||||||
|
_logger.LogDebug("δ<><CEB4> ServiceProvider <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> HttpClient<6E><74>ʹ<EFBFBD><CAB9> IHttpClientFactory <20><><EFBFBD><EFBFBD>");
|
||||||
|
http = _httpClientFactory.CreateClient();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger.LogDebug("<22><> ServiceProvider <20><><EFBFBD><EFBFBD> HttpClient: {HasClient}", http != null);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (http != null)
|
||||||
|
{
|
||||||
|
var url = $"/localization/{cultureFull}.json";
|
||||||
|
Uri? requestUri = null;
|
||||||
|
|
||||||
|
// <20><> HttpClient <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> BaseAddress ʱʹ<CAB1>ã<EFBFBD><C3A3><EFBFBD><EFBFBD><EFBFBD>ͨ<EFBFBD><CDA8> JS <20><>ȡ location.origin <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> URI
|
||||||
|
if (http.BaseAddress != null)
|
||||||
|
{
|
||||||
|
requestUri = new Uri(http.BaseAddress, url);
|
||||||
|
}
|
||||||
|
else if (_jsRuntime != null && OperatingSystem.IsBrowser())
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var origin = await _jsRuntime.InvokeAsync<string>("eval", "location.origin");
|
||||||
|
if (!string.IsNullOrEmpty(origin))
|
||||||
|
{
|
||||||
|
requestUri = new Uri(new Uri(origin), url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception jsEx)
|
||||||
|
{
|
||||||
|
_logger.LogDebug(jsEx, "<22><> JS <20><>ȡ location.origin ʧ<><CAA7>");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (requestUri != null)
|
||||||
|
{
|
||||||
|
_logger.LogInformation("<22><> {Url} <20><><EFBFBD>ر<EFBFBD><D8B1>ػ<EFBFBD><D8BB><EFBFBD>Դ", requestUri);
|
||||||
|
var txt = await http.GetStringAsync(requestUri);
|
||||||
|
var dict = JsonSerializer.Deserialize<Dictionary<string, string>>(txt) ?? new Dictionary<string, string>();
|
||||||
|
_cache[cultureFull] = dict;
|
||||||
|
_logger.LogInformation("ͨ<><CDA8> HttpClient Ϊ {Culture} <20><><EFBFBD>ص<EFBFBD><D8B5><EFBFBD><EFBFBD>ػ<EFBFBD><D8BB><EFBFBD><EFBFBD>ݣ<EFBFBD><DDA3><EFBFBD>Ŀ<EFBFBD><C4BF>: {Count}", cultureFull, dict.Count);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger.LogWarning("HttpClient <20><EFBFBD><DEB7><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> URL<52><4C><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ͨ<EFBFBD><CDA8> HttpClient <20><><EFBFBD><EFBFBD> {Culture}", cultureFull);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger.LogWarning("δ<>ҵ<EFBFBD><D2B5><EFBFBD><EFBFBD>õ<EFBFBD> HttpClient <20>Լ<EFBFBD><D4BC><EFBFBD> {Culture}", cultureFull);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogDebug(ex, "ͨ<><CDA8> HttpClient <20><><EFBFBD>ر<EFBFBD><D8B1>ػ<EFBFBD><D8BB>ļ<EFBFBD>ʧ<EFBFBD><CAA7>: {Culture}", cultureFull);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.LogDebug("EnsureCultureLoadedAsync: <20><><EFBFBD><EFBFBD><EFBFBD>ļ<EFBFBD>ϵͳ<CFB5><CDB3><EFBFBD><EFBFBD> {Culture}", cultureFull);
|
||||||
|
// <20><><EFBFBD>ˣ<EFBFBD><CBA3><EFBFBD><EFBFBD><EFBFBD>ͨ<EFBFBD><CDA8> IWebHostEnvironment <20><><EFBFBD>ļ<EFBFBD>ϵͳ<CFB5><CDB3>ȡ<EFBFBD><C8A1>Server ʱ<><CAB1><EFBFBD>ã<EFBFBD>
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var envType = Type.GetType("Microsoft.AspNetCore.Hosting.IWebHostEnvironment, Microsoft.AspNetCore.Hosting.Abstractions")
|
||||||
|
?? Type.GetType("Microsoft.AspNetCore.Hosting.IWebHostEnvironment");
|
||||||
|
if (envType != null)
|
||||||
|
{
|
||||||
|
var env = _sp.GetService(envType);
|
||||||
|
if (env != null)
|
||||||
|
{
|
||||||
|
var webRootProp = envType.GetProperty("WebRootPath");
|
||||||
|
var contentRootProp = envType.GetProperty("ContentRootPath");
|
||||||
|
var webRoot = webRootProp?.GetValue(env) as string ?? Path.Combine(contentRootProp?.GetValue(env) as string ?? ".", "wwwroot");
|
||||||
|
var path = Path.Combine(webRoot ?? ".", "localization", cultureFull + ".json");
|
||||||
|
_logger.LogDebug("<22><><EFBFBD>ұ<EFBFBD><D2B1>ػ<EFBFBD><D8BB>ļ<EFBFBD>·<EFBFBD><C2B7>: {Path}", path);
|
||||||
|
if (File.Exists(path))
|
||||||
|
{
|
||||||
|
var json = await File.ReadAllTextAsync(path);
|
||||||
|
var dict = JsonSerializer.Deserialize<Dictionary<string, string>>(json) ?? new Dictionary<string, string>();
|
||||||
|
_cache[cultureFull] = dict;
|
||||||
|
_logger.LogInformation("<22><><EFBFBD>ļ<EFBFBD>Ϊ {Culture} <20><><EFBFBD>ر<EFBFBD><D8B1>ػ<EFBFBD><D8BB><EFBFBD><EFBFBD><EFBFBD>Ŀ<EFBFBD><C4BF>: {Count}", cultureFull, dict.Count);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger.LogDebug("δ<><CEB4>·<EFBFBD><C2B7><EFBFBD>ҵ<EFBFBD><D2B5><EFBFBD><EFBFBD>ػ<EFBFBD><D8BB>ļ<EFBFBD>: {Path}", path);
|
||||||
|
// <20><><EFBFBD><EFBFBD>·<EFBFBD><C2B7><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʱ<EFBFBD><CAB1><EFBFBD><EFBFBD>Ŀ¼<C4BF><C2BC> wwwroot
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var alt = Path.Combine(AppContext.BaseDirectory ?? ".", "wwwroot", "localization", cultureFull + ".json");
|
||||||
|
_logger.LogDebug("<22><><EFBFBD>ұ<EFBFBD><D2B1><EFBFBD>·<EFBFBD><C2B7>: {AltPath}", alt);
|
||||||
|
if (File.Exists(alt))
|
||||||
|
{
|
||||||
|
var json2 = await File.ReadAllTextAsync(alt);
|
||||||
|
var dict2 = JsonSerializer.Deserialize<Dictionary<string, string>>(json2) ?? new Dictionary<string, string>();
|
||||||
|
_cache[cultureFull] = dict2;
|
||||||
|
_logger.LogInformation("<22>ӱ<EFBFBD><D3B1><EFBFBD>·<EFBFBD><C2B7>Ϊ {Culture} <20><><EFBFBD>ص<EFBFBD><D8B5><EFBFBD><EFBFBD>ػ<EFBFBD><D8BB>ļ<EFBFBD><C4BC><EFBFBD><EFBFBD><EFBFBD>Ŀ<EFBFBD><C4BF>: {Count}", cultureFull, dict2.Count);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger.LogDebug("<22><><EFBFBD><EFBFBD>·<EFBFBD><C2B7>δ<EFBFBD>ҵ<EFBFBD><D2B5><EFBFBD><EFBFBD>ػ<EFBFBD><D8BB>ļ<EFBFBD>: {AltPath}", alt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception exAlt)
|
||||||
|
{
|
||||||
|
_logger.LogDebug(exAlt, "<22><><EFBFBD>鱸<EFBFBD>ñ<EFBFBD><C3B1>ػ<EFBFBD>·<EFBFBD><C2B7>ʱ<EFBFBD><CAB1><EFBFBD><EFBFBD>");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger.LogDebug("<22><EFBFBD><DEB7><EFBFBD> ServiceProvider <20><>ȡ IWebHostEnvironment ʵ<><CAB5>");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger.LogDebug("ͨ<><CDA8><EFBFBD><EFBFBD><EFBFBD><EFBFBD>δ<EFBFBD><CEB4><EFBFBD>ҵ<EFBFBD> IWebHostEnvironment <20><><EFBFBD><EFBFBD>");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogDebug(ex, "ͨ<><CDA8><EFBFBD>ļ<EFBFBD>ϵͳ<CFB5><CDB3><EFBFBD>ر<EFBFBD><D8B1>ػ<EFBFBD><D8BB>ļ<EFBFBD>ʧ<EFBFBD><CAA7>: {Culture}", cultureFull);
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.LogDebug("EnsureCultureLoadedAsync: <20><><EFBFBD><EFBFBD>Ϊ {Culture} <20>Ŀ<EFBFBD><C4BF>ֵ<EFBFBD>ռλ", cultureFull);
|
||||||
|
_cache[cultureFull] = new Dictionary<string, string>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ַ<EFBFBD><D6B7><EFBFBD>ӳ<EFBFBD><D3B3>Ϊ<EFBFBD>ڲ<EFBFBD>ʹ<EFBFBD>õ<EFBFBD><C3B5><EFBFBD><EFBFBD><EFBFBD> culture<72><65><EFBFBD><EFBFBD><EFBFBD><EFBFBD> zh -> zh-Hans<6E><73>
|
||||||
|
/// </summary>
|
||||||
|
private string MapToFullCulture(string culture)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(culture)) return culture;
|
||||||
|
// ֱ<><D6B1>ӳ<EFBFBD><D3B3>
|
||||||
|
if (ShortToCulture.TryGetValue(culture, out var mapped)) return mapped;
|
||||||
|
// <20><><EFBFBD><EFBFBD>ǰ<C7B0><D7BA><EFBFBD><EFBFBD><EFBFBD><EFBFBD> zh-CN -> zh
|
||||||
|
var prefix = culture.Split('-', StringSplitOptions.RemoveEmptyEntries).FirstOrDefault();
|
||||||
|
if (!string.IsNullOrEmpty(prefix) && ShortToCulture.TryGetValue(prefix, out var mapped2)) return mapped2;
|
||||||
|
return culture;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
11
Atomx.Admin/Atomx.Admin.Client/Services/NavigationService.cs
Normal file
11
Atomx.Admin/Atomx.Admin.Client/Services/NavigationService.cs
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
namespace Atomx.Admin.Client.Services
|
||||||
|
{
|
||||||
|
public interface INavigationService
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public class NavigationService
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,11 +1,12 @@
|
|||||||
using Atomx.Admin.Client.Models;
|
using Atomx.Admin.Client.Models;
|
||||||
using FluentValidation;
|
using FluentValidation;
|
||||||
|
using Microsoft.Extensions.Localization;
|
||||||
|
|
||||||
namespace Atomx.Admin.Client.Validators
|
namespace Atomx.Admin.Client.Validators
|
||||||
{
|
{
|
||||||
public class AddressModelValidator : AbstractValidator<AddressModel>
|
public class AddressModelValidator : AbstractValidator<AddressModel>
|
||||||
{
|
{
|
||||||
public AddressModelValidator()
|
public AddressModelValidator(IStringLocalizer<AddressModelValidator> localizer)
|
||||||
{
|
{
|
||||||
RuleFor(p => p.Name).NotEmpty().WithMessage("请填写收件人信息");
|
RuleFor(p => p.Name).NotEmpty().WithMessage("请填写收件人信息");
|
||||||
RuleFor(p => p.Email).EmailAddress().When(p => !string.IsNullOrEmpty(p.Email)).WithMessage("请填写你常用的邮箱地址");
|
RuleFor(p => p.Email).EmailAddress().When(p => !string.IsNullOrEmpty(p.Email)).WithMessage("请填写你常用的邮箱地址");
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
using Atomx.Admin.Client.Models;
|
using Atomx.Admin.Client.Models;
|
||||||
using FluentValidation;
|
using FluentValidation;
|
||||||
|
using Microsoft.Extensions.Localization;
|
||||||
|
|
||||||
namespace Atomx.Admin.Client.Validators
|
namespace Atomx.Admin.Client.Validators
|
||||||
{
|
{
|
||||||
public class AdminModelValidator : AbstractValidator<AdminModel>
|
public class AdminModelValidator : AbstractValidator<AdminModel>
|
||||||
{
|
{
|
||||||
public AdminModelValidator()
|
public AdminModelValidator(IStringLocalizer<AdminModelValidator> localizer)
|
||||||
{
|
{
|
||||||
RuleFor(p => p.Username).NotEmpty().WithMessage("用户名不能为空");
|
RuleFor(p => p.Username).NotEmpty().WithMessage("用户名不能为空");
|
||||||
RuleFor(p => p.Username).Length(2, 64).When(p => !string.IsNullOrEmpty(p.Username)).WithMessage("用户名长度必须再2-64个字符之间");
|
RuleFor(p => p.Username).Length(2, 64).When(p => !string.IsNullOrEmpty(p.Username)).WithMessage("用户名长度必须再2-64个字符之间");
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
using Atomx.Admin.Client.Models;
|
using Atomx.Admin.Client.Models;
|
||||||
using Atomx.Common.Enums;
|
using Atomx.Common.Enums;
|
||||||
using FluentValidation;
|
using FluentValidation;
|
||||||
|
using Microsoft.Extensions.Localization;
|
||||||
|
|
||||||
namespace Atomx.Admin.Client.Validators
|
namespace Atomx.Admin.Client.Validators
|
||||||
{
|
{
|
||||||
public class AppVersionModelValidator : AbstractValidator<AppVersionModel>
|
public class AppVersionModelValidator : AbstractValidator<AppVersionModel>
|
||||||
{
|
{
|
||||||
public AppVersionModelValidator()
|
public AppVersionModelValidator(IStringLocalizer<LoginModelValidator> localizer)
|
||||||
{
|
{
|
||||||
RuleFor(p => p.AppName).NotEmpty().WithMessage("应用名不能为空");
|
RuleFor(p => p.AppName).NotEmpty().WithMessage("应用名不能为空");
|
||||||
RuleFor(p => p.Title).NotEmpty().WithMessage("版本标题不能为空");
|
RuleFor(p => p.Title).NotEmpty().WithMessage("版本标题不能为空");
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
using Atomx.Admin.Client.Models;
|
||||||
|
using FluentValidation;
|
||||||
|
using Microsoft.Extensions.Localization;
|
||||||
|
|
||||||
|
namespace Atomx.Admin.Client.Validators
|
||||||
|
{
|
||||||
|
public class AreaModelValidator : AbstractValidator<AreaModel>
|
||||||
|
{
|
||||||
|
public AreaModelValidator(IStringLocalizer<AreaModelValidator> localizer)
|
||||||
|
{
|
||||||
|
RuleFor(p => p.Name).NotEmpty().WithMessage("请填名称信息");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
using Atomx.Admin.Client.Models;
|
using Atomx.Admin.Client.Models;
|
||||||
using Atomx.Common.Enums;
|
|
||||||
using FluentValidation;
|
using FluentValidation;
|
||||||
|
using Microsoft.Extensions.Localization;
|
||||||
|
|
||||||
namespace Atomx.Admin.Client.Validators
|
namespace Atomx.Admin.Client.Validators
|
||||||
{
|
{
|
||||||
public class CategoryModelValidator : AbstractValidator<CategoryModel>
|
public class CategoryModelValidator : AbstractValidator<CategoryModel>
|
||||||
{
|
{
|
||||||
public CategoryModelValidator()
|
public CategoryModelValidator(IStringLocalizer<CategoryModelValidator> localizer)
|
||||||
{
|
{
|
||||||
RuleFor(p => p.Name).NotEmpty().WithMessage("名称不能为空");
|
RuleFor(p => p.Name).NotEmpty().WithMessage("名称不能为空");
|
||||||
RuleFor(p => p.ParentId).Must((p,id)=> ValidateParent(id,p)).WithMessage("不能选择自己做上级分类");
|
RuleFor(p => p.ParentId).Must((p,id)=> ValidateParent(id,p)).WithMessage("不能选择自己做上级分类");
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
using Atomx.Admin.Client.Models;
|
using Atomx.Admin.Client.Models;
|
||||||
using FluentValidation;
|
using FluentValidation;
|
||||||
|
using Microsoft.Extensions.Localization;
|
||||||
|
|
||||||
namespace Atomx.Admin.Client.Validators
|
namespace Atomx.Admin.Client.Validators
|
||||||
{
|
{
|
||||||
public class CorporationModelValidator : AbstractValidator<CorporationModel>
|
public class CorporationModelValidator : AbstractValidator<CorporationModel>
|
||||||
{
|
{
|
||||||
public CorporationModelValidator()
|
public CorporationModelValidator(IStringLocalizer<CorporationModelValidator> localizer)
|
||||||
{
|
{
|
||||||
RuleFor(p => p.Name).NotEmpty().WithMessage("公司名称不能为空");
|
RuleFor(p => p.Name).NotEmpty().WithMessage("公司名称不能为空");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
using Atomx.Admin.Client.Models;
|
using Atomx.Admin.Client.Models;
|
||||||
using FluentValidation;
|
using FluentValidation;
|
||||||
|
using Microsoft.Extensions.Localization;
|
||||||
|
|
||||||
namespace Atomx.Admin.Client.Validators
|
namespace Atomx.Admin.Client.Validators
|
||||||
{
|
{
|
||||||
public class CorporationStaffModelValidator : AbstractValidator<CorporationStaffModel>
|
public class CorporationStaffModelValidator : AbstractValidator<CorporationStaffModel>
|
||||||
{
|
{
|
||||||
public CorporationStaffModelValidator()
|
public CorporationStaffModelValidator(IStringLocalizer<CorporationStaffModelValidator> localizer)
|
||||||
{
|
{
|
||||||
RuleFor(p => p.Username).NotEmpty().WithMessage("用户名不能为空");
|
RuleFor(p => p.Username).NotEmpty().WithMessage("用户名不能为空");
|
||||||
RuleFor(p => p.Username).Length(2, 64).When(p => !string.IsNullOrEmpty(p.Username)).WithMessage("用户名长度必须再2-64个字符之间");
|
RuleFor(p => p.Username).Length(2, 64).When(p => !string.IsNullOrEmpty(p.Username)).WithMessage("用户名长度必须再2-64个字符之间");
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
using Atomx.Admin.Client.Models;
|
||||||
|
using FluentValidation;
|
||||||
|
using Microsoft.Extensions.Localization;
|
||||||
|
|
||||||
|
namespace Atomx.Admin.Client.Validators
|
||||||
|
{
|
||||||
|
public class CountryModelValidator : AbstractValidator<CountryModel>
|
||||||
|
{
|
||||||
|
public CountryModelValidator(IStringLocalizer<CountryModelValidator> localizer)
|
||||||
|
{
|
||||||
|
RuleFor(p => p.Name).NotEmpty().WithMessage("请填名称信息");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,11 +1,12 @@
|
|||||||
using Atomx.Admin.Client.Models;
|
using Atomx.Admin.Client.Models;
|
||||||
using FluentValidation;
|
using FluentValidation;
|
||||||
|
using Microsoft.Extensions.Localization;
|
||||||
|
|
||||||
namespace Atomx.Admin.Client.Validators
|
namespace Atomx.Admin.Client.Validators
|
||||||
{
|
{
|
||||||
public class CurrencyModelValidator : AbstractValidator<CurrencyModel>
|
public class CurrencyModelValidator : AbstractValidator<CurrencyModel>
|
||||||
{
|
{
|
||||||
public CurrencyModelValidator()
|
public CurrencyModelValidator(IStringLocalizer<CurrencyModelValidator> localizer)
|
||||||
{
|
{
|
||||||
RuleFor(p => p.Name).NotEmpty().WithMessage("请填写货币名称");
|
RuleFor(p => p.Name).NotEmpty().WithMessage("请填写货币名称");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
using Atomx.Common.Configuration;
|
using Atomx.Common.Configuration;
|
||||||
using FluentValidation;
|
using FluentValidation;
|
||||||
|
using Microsoft.Extensions.Localization;
|
||||||
|
|
||||||
namespace Atomx.Admin.Client.Validators
|
namespace Atomx.Admin.Client.Validators
|
||||||
{
|
{
|
||||||
public class GeneralConfigValidator : AbstractValidator<GeneralConfig>
|
public class GeneralConfigValidator : AbstractValidator<GeneralConfig>
|
||||||
{
|
{
|
||||||
public GeneralConfigValidator()
|
public GeneralConfigValidator(IStringLocalizer<GeneralConfigValidator> localizer)
|
||||||
{
|
{
|
||||||
RuleFor(p => p.Name).NotEmpty().WithMessage("网站名称不能为空");
|
RuleFor(p => p.Name).NotEmpty().WithMessage("网站名称不能为空");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
using Atomx.Admin.Client.Models;
|
using Atomx.Admin.Client.Models;
|
||||||
using FluentValidation;
|
using FluentValidation;
|
||||||
|
using Microsoft.Extensions.Localization;
|
||||||
|
|
||||||
namespace Atomx.Admin.Client.Validators
|
namespace Atomx.Admin.Client.Validators
|
||||||
{
|
{
|
||||||
public class LanguageModelValidator : AbstractValidator<LanguageModel>
|
public class LanguageModelValidator : AbstractValidator<LanguageModel>
|
||||||
{
|
{
|
||||||
public LanguageModelValidator()
|
public LanguageModelValidator(IStringLocalizer<LanguageModelValidator> localizer)
|
||||||
{
|
{
|
||||||
RuleFor(p => p.Title).NotEmpty().WithMessage("请填写语言标题");
|
RuleFor(p => p.Title).NotEmpty().WithMessage("请填写语言标题");
|
||||||
RuleFor(p => p.Name).NotEmpty().WithMessage("请填写语言名称");
|
RuleFor(p => p.Name).NotEmpty().WithMessage("请填写语言名称");
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
using Atomx.Admin.Client.Models;
|
using Atomx.Admin.Client.Models;
|
||||||
using FluentValidation;
|
using FluentValidation;
|
||||||
|
using Microsoft.Extensions.Localization;
|
||||||
|
|
||||||
namespace Atomx.Admin.Client.Validators
|
namespace Atomx.Admin.Client.Validators
|
||||||
{
|
{
|
||||||
public class LocaleResourceModelValidator : AbstractValidator<LocaleResourceModel>
|
public class LocaleResourceModelValidator : AbstractValidator<LocaleResourceModel>
|
||||||
{
|
{
|
||||||
public LocaleResourceModelValidator()
|
public LocaleResourceModelValidator(IStringLocalizer<LocaleResourceModelValidator> localizer)
|
||||||
{
|
{
|
||||||
RuleFor(p => p.Name).NotEmpty().WithMessage("本地化多语言信息不能为空");
|
RuleFor(p => p.Name).NotEmpty().WithMessage("本地化多语言信息不能为空");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,25 +1,36 @@
|
|||||||
using Atomx.Admin.Client.Models;
|
using Atomx.Admin.Client.Models;
|
||||||
using FluentValidation;
|
using FluentValidation;
|
||||||
|
using Microsoft.Extensions.Localization;
|
||||||
|
|
||||||
namespace Atomx.Admin.Client.Validators
|
namespace Atomx.Admin.Client.Validators
|
||||||
{
|
{
|
||||||
public class LoginModelValidator : AbstractValidator<LoginModel>
|
public class LoginModelValidator : AbstractValidator<LoginModel>
|
||||||
{
|
{
|
||||||
public LoginModelValidator()
|
public LoginModelValidator(IStringLocalizer<LoginModelValidator> localizer)
|
||||||
{
|
{
|
||||||
RuleFor(p => p.Account).NotEmpty().WithMessage("登录账号不能为空");
|
// helper funcs to get localized text or fallback
|
||||||
RuleFor(p => p.Account).Length(2, 100).When(p => !string.IsNullOrEmpty(p.Account)).WithMessage("用户名长度必须再2-100个字符之间");
|
string AccountEmpty() => localizer?["Login.Account.Empty"].Value ?? "登录账号不能为空";
|
||||||
//RuleFor(p => p.Account).EmailAddress().When(p => !p.Account.Contains("@") && !string.IsNullOrEmpty(p.Account)).WithMessage("电子邮件地址不正确");
|
string AccountLength() => localizer?["Login.Account.Length"].Value ?? "用户名长度必须再2-100个字符之间";
|
||||||
|
string PasswordEmpty() => localizer?["Login.Password.Empty"].Value ?? "请输入登录密码";
|
||||||
|
string PasswordLength() => localizer?["Login.Password.Length"].Value ?? "登录密码必须在6-32位长度之间";
|
||||||
|
|
||||||
//RuleFor(p => p.Username).NotEmpty().WithMessage("用户名不能为空");
|
RuleFor(p => p.Account)
|
||||||
//RuleFor(p => p.Username).Length(2, 50).When(p => !string.IsNullOrEmpty(p.Username)).WithMessage("用户名长度必须再2-50个字符之间");
|
.NotEmpty()
|
||||||
//RuleFor(p => p.Account).NotEmpty().WithMessage("电子邮件地址不能为空");
|
.WithMessage(_ => AccountEmpty());
|
||||||
//RuleFor(p => p.Email).EmailAddress().When(p => !string.IsNullOrEmpty(p.Email)).WithMessage(p => localizer["Form.Email.Invalid"]);
|
|
||||||
//RuleFor(p => p.Email).MaximumLength(128).When(p => !string.IsNullOrEmpty(p.Email)).WithMessage(p => localizer["Form.Email.LengthInvalid"]);
|
RuleFor(p => p.Account)
|
||||||
RuleFor(p => p.Password).NotEmpty().WithMessage("请输入登录密码");
|
.Length(2, 100)
|
||||||
RuleFor(p => p.Password).Length(6, 32).When(p => !string.IsNullOrEmpty(p.Password)).WithMessage("登录密码必须在6-32位长度之间");
|
.When(p => !string.IsNullOrEmpty(p.Account))
|
||||||
//RuleFor(p => p.ConfirmPassword).NotEmpty().WithMessage(p => localizer["Form.ConfirmPassword.Empty"]);
|
.WithMessage(_ => AccountLength());
|
||||||
//RuleFor(p => p.ConfirmPassword).Equal(p => p.Password).When(p => !string.IsNullOrEmpty(p.Password) && !string.IsNullOrEmpty(p.ConfirmPassword)).WithMessage(p => localizer["Form.ConfirmPassword.Different"]);
|
|
||||||
|
RuleFor(p => p.Password)
|
||||||
|
.NotEmpty()
|
||||||
|
.WithMessage(_ => PasswordEmpty());
|
||||||
|
|
||||||
|
RuleFor(p => p.Password)
|
||||||
|
.Length(6, 32)
|
||||||
|
.When(p => !string.IsNullOrEmpty(p.Password))
|
||||||
|
.WithMessage(_ => PasswordLength());
|
||||||
}
|
}
|
||||||
|
|
||||||
public Func<object, string, Task<IEnumerable<string>>> ValidateValue => async (model, propertyName) =>
|
public Func<object, string, Task<IEnumerable<string>>> ValidateValue => async (model, propertyName) =>
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
using Atomx.Admin.Client.Models;
|
using Atomx.Admin.Client.Models;
|
||||||
using FluentValidation;
|
using FluentValidation;
|
||||||
|
using Microsoft.Extensions.Localization;
|
||||||
|
|
||||||
namespace Atomx.Admin.Client.Validators
|
namespace Atomx.Admin.Client.Validators
|
||||||
{
|
{
|
||||||
public class ManufacturerModelValidator : AbstractValidator<ManufacturerModel>
|
public class ManufacturerModelValidator : AbstractValidator<ManufacturerModel>
|
||||||
{
|
{
|
||||||
public ManufacturerModelValidator()
|
public ManufacturerModelValidator(IStringLocalizer<ManufacturerModelValidator> localizer)
|
||||||
{
|
{
|
||||||
RuleFor(p => p.Name).NotEmpty().WithMessage("名称不能为空");
|
RuleFor(p => p.Name).NotEmpty().WithMessage("名称不能为空");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
using Atomx.Common.Entities;
|
|
||||||
using FluentValidation;
|
|
||||||
|
|
||||||
namespace Atomx.Admin.Client.Validators
|
|
||||||
{
|
|
||||||
public class MaterialBatchValidator : AbstractValidator<MaterialBatch>
|
|
||||||
{
|
|
||||||
public MaterialBatchValidator()
|
|
||||||
{
|
|
||||||
RuleFor(p => p.Price).NotEmpty().WithMessage("价格不能为空");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
using Atomx.Common.Entities;
|
|
||||||
using FluentValidation;
|
|
||||||
|
|
||||||
namespace Atomx.Admin.Client.Validators
|
|
||||||
{
|
|
||||||
public class MaterialRecordValidator : AbstractValidator<MaterialRecord>
|
|
||||||
{
|
|
||||||
public MaterialRecordValidator()
|
|
||||||
{
|
|
||||||
RuleFor(p => p.UserId).NotEmpty().WithMessage("名称不能为空");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
using Atomx.Common.Entities;
|
|
||||||
using FluentValidation;
|
|
||||||
|
|
||||||
namespace Atomx.Admin.Client.Validators
|
|
||||||
{
|
|
||||||
public class MaterialValidator : AbstractValidator<Material>
|
|
||||||
{
|
|
||||||
public MaterialValidator()
|
|
||||||
{
|
|
||||||
//RuleFor(p => p.Name).NotEmpty().WithMessage("名称不能为空");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,12 +1,13 @@
|
|||||||
using Atomx.Admin.Client.Models;
|
using Atomx.Admin.Client.Models;
|
||||||
using Atomx.Common.Models;
|
using Atomx.Common.Models;
|
||||||
using FluentValidation;
|
using FluentValidation;
|
||||||
|
using Microsoft.Extensions.Localization;
|
||||||
|
|
||||||
namespace Atomx.Admin.Client.Validators
|
namespace Atomx.Admin.Client.Validators
|
||||||
{
|
{
|
||||||
public class MenuModelValidator : AbstractValidator<MenuModel>
|
public class MenuModelValidator : AbstractValidator<MenuModel>
|
||||||
{
|
{
|
||||||
public MenuModelValidator()
|
public MenuModelValidator(IStringLocalizer<MenuModelValidator> localizer)
|
||||||
{
|
{
|
||||||
RuleFor(p => p.Name).NotEmpty().WithMessage("名称不能为空");
|
RuleFor(p => p.Name).NotEmpty().WithMessage("名称不能为空");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
using Atomx.Admin.Client.Models;
|
using Atomx.Admin.Client.Models;
|
||||||
using FluentValidation;
|
using FluentValidation;
|
||||||
|
using Microsoft.Extensions.Localization;
|
||||||
|
|
||||||
namespace Atomx.Admin.Client.Validators
|
namespace Atomx.Admin.Client.Validators
|
||||||
{
|
{
|
||||||
public class MessageTemplateModelValidator : AbstractValidator<MessageTemplateModel>
|
public class MessageTemplateModelValidator : AbstractValidator<MessageTemplateModel>
|
||||||
{
|
{
|
||||||
public MessageTemplateModelValidator()
|
public MessageTemplateModelValidator(IStringLocalizer<MessageTemplateModelValidator> localizer)
|
||||||
{
|
{
|
||||||
RuleFor(p => p.Name).NotEmpty().WithMessage("消息模板名称不能为空");
|
RuleFor(p => p.Name).NotEmpty().WithMessage("消息模板名称不能为空");
|
||||||
RuleFor(p => p.Title).NotEmpty().WithMessage("消息模板标题不能为空");
|
RuleFor(p => p.Title).NotEmpty().WithMessage("消息模板标题不能为空");
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
using Atomx.Admin.Client.Models;
|
using Atomx.Admin.Client.Models;
|
||||||
using FluentValidation;
|
using FluentValidation;
|
||||||
|
using Microsoft.Extensions.Localization;
|
||||||
|
|
||||||
namespace Atomx.Admin.Client.Validators
|
namespace Atomx.Admin.Client.Validators
|
||||||
{
|
{
|
||||||
public class ProductAddStockModelValidator : AbstractValidator<ProductAddStockModel>
|
public class ProductAddStockModelValidator : AbstractValidator<ProductAddStockModel>
|
||||||
{
|
{
|
||||||
public ProductAddStockModelValidator()
|
public ProductAddStockModelValidator(IStringLocalizer<ProductAddStockModelValidator> localizer)
|
||||||
{
|
{
|
||||||
//RuleFor(p => p.Username).NotEmpty().WithMessage("用户名不能为空");
|
//RuleFor(p => p.Username).NotEmpty().WithMessage("用户名不能为空");
|
||||||
//RuleFor(p => p.Username).Length(2, 64).When(p => !string.IsNullOrEmpty(p.Username)).WithMessage("用户名长度必须再2-64个字符之间");
|
//RuleFor(p => p.Username).Length(2, 64).When(p => !string.IsNullOrEmpty(p.Username)).WithMessage("用户名长度必须再2-64个字符之间");
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
using Atomx.Admin.Client.Models;
|
using Atomx.Admin.Client.Models;
|
||||||
using FluentValidation;
|
using FluentValidation;
|
||||||
|
using Microsoft.Extensions.Localization;
|
||||||
|
|
||||||
namespace Atomx.Admin.Client.Validators
|
namespace Atomx.Admin.Client.Validators
|
||||||
{
|
{
|
||||||
public class ProductAttributeModelValidator : AbstractValidator<ProductAttributeModel>
|
public class ProductAttributeModelValidator : AbstractValidator<ProductAttributeModel>
|
||||||
{
|
{
|
||||||
public ProductAttributeModelValidator()
|
public ProductAttributeModelValidator(IStringLocalizer<ProductAttributeModelValidator> localizer)
|
||||||
{
|
{
|
||||||
RuleFor(p => p.Name).NotEmpty().WithMessage("名称不能为空");
|
RuleFor(p => p.Name).NotEmpty().WithMessage("名称不能为空");
|
||||||
RuleFor(p => p.Status).GreaterThan(0).WithMessage("请设置正确的状态");
|
RuleFor(p => p.Status).GreaterThan(0).WithMessage("请设置正确的状态");
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
using Atomx.Admin.Client.Models;
|
using Atomx.Admin.Client.Models;
|
||||||
using FluentValidation;
|
using FluentValidation;
|
||||||
|
using Microsoft.Extensions.Localization;
|
||||||
|
|
||||||
namespace Atomx.Admin.Client.Validators
|
namespace Atomx.Admin.Client.Validators
|
||||||
{
|
{
|
||||||
public class ProductAttributeOptionModelValidator : AbstractValidator<ProductAttributeOptionModel>
|
public class ProductAttributeOptionModelValidator : AbstractValidator<ProductAttributeOptionModel>
|
||||||
{
|
{
|
||||||
public ProductAttributeOptionModelValidator()
|
public ProductAttributeOptionModelValidator(IStringLocalizer<ProductAttributeOptionModelValidator> localizer)
|
||||||
{
|
{
|
||||||
RuleFor(p => p.Value).NotEmpty().WithMessage("数值不能为空");
|
RuleFor(p => p.Value).NotEmpty().WithMessage("数值不能为空");
|
||||||
RuleFor(p => p.Status).GreaterThan(0).WithMessage("请设置正确的状态");
|
RuleFor(p => p.Status).GreaterThan(0).WithMessage("请设置正确的状态");
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
using Atomx.Admin.Client.Models;
|
using Atomx.Admin.Client.Models;
|
||||||
using FluentValidation;
|
using FluentValidation;
|
||||||
|
using Microsoft.Extensions.Localization;
|
||||||
|
|
||||||
namespace Atomx.Admin.Client.Validators
|
namespace Atomx.Admin.Client.Validators
|
||||||
{
|
{
|
||||||
public class ProductModelValidator : AbstractValidator<ProductModel>
|
public class ProductModelValidator : AbstractValidator<ProductModel>
|
||||||
{
|
{
|
||||||
public ProductModelValidator()
|
public ProductModelValidator(IStringLocalizer<ProductModelValidator> localizer)
|
||||||
{
|
{
|
||||||
RuleFor(p => p.Title).NotEmpty().WithMessage("商品标题不能为空");
|
RuleFor(p => p.Title).NotEmpty().WithMessage("商品标题不能为空");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
using Atomx.Admin.Client.Models;
|
using Atomx.Admin.Client.Models;
|
||||||
using FluentValidation;
|
using FluentValidation;
|
||||||
|
using Microsoft.Extensions.Localization;
|
||||||
|
|
||||||
namespace Atomx.Admin.Client.Validators
|
namespace Atomx.Admin.Client.Validators
|
||||||
{
|
{
|
||||||
public class RoleModelValidator : AbstractValidator<RoleModel>
|
public class RoleModelValidator : AbstractValidator<RoleModel>
|
||||||
{
|
{
|
||||||
public RoleModelValidator()
|
public RoleModelValidator(IStringLocalizer<RoleModelValidator> localizer)
|
||||||
{
|
{
|
||||||
RuleFor(p => p.Name).NotEmpty().WithMessage("角色名称不能为空");
|
RuleFor(p => p.Name).NotEmpty().WithMessage("角色名称不能为空");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
using Atomx.Common.Entities;
|
using Atomx.Common.Entities;
|
||||||
using FluentValidation;
|
using FluentValidation;
|
||||||
|
using Microsoft.Extensions.Localization;
|
||||||
|
|
||||||
namespace Atomx.Admin.Client.Validators
|
namespace Atomx.Admin.Client.Validators
|
||||||
{
|
{
|
||||||
public class RoleValidator : AbstractValidator<Role>
|
public class RoleValidator : AbstractValidator<Role>
|
||||||
{
|
{
|
||||||
public RoleValidator()
|
public RoleValidator(IStringLocalizer<RoleValidator> localizer)
|
||||||
{
|
{
|
||||||
RuleFor(p => p.Name).NotEmpty().WithMessage("角色名称不能为空");
|
RuleFor(p => p.Name).NotEmpty().WithMessage("角色名称不能为空");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
using Atomx.Admin.Client.Models;
|
using Atomx.Admin.Client.Models;
|
||||||
using FluentValidation;
|
using FluentValidation;
|
||||||
|
using Microsoft.Extensions.Localization;
|
||||||
|
|
||||||
namespace Atomx.Admin.Client.Validators
|
namespace Atomx.Admin.Client.Validators
|
||||||
{
|
{
|
||||||
public class SiteAppModelValidator : AbstractValidator<SiteAppModel>
|
public class SiteAppModelValidator : AbstractValidator<SiteAppModel>
|
||||||
{
|
{
|
||||||
public SiteAppModelValidator()
|
public SiteAppModelValidator(IStringLocalizer<SiteAppModelValidator> localizer)
|
||||||
{
|
{
|
||||||
RuleFor(p => p.Name).NotEmpty().WithMessage("请填写网站应用名称");
|
RuleFor(p => p.Name).NotEmpty().WithMessage("请填写网站应用名称");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
using Atomx.Admin.Client.Models;
|
using Atomx.Admin.Client.Models;
|
||||||
using FluentValidation;
|
using FluentValidation;
|
||||||
|
using Microsoft.Extensions.Localization;
|
||||||
|
|
||||||
namespace Atomx.Admin.Client.Validators
|
namespace Atomx.Admin.Client.Validators
|
||||||
{
|
{
|
||||||
public class SpecificationAttributeModelValidator : AbstractValidator<SpecificationAttributeModel>
|
public class SpecificationAttributeModelValidator : AbstractValidator<SpecificationAttributeModel>
|
||||||
{
|
{
|
||||||
public SpecificationAttributeModelValidator()
|
public SpecificationAttributeModelValidator(IStringLocalizer<SpecificationAttributeModelValidator> localizer)
|
||||||
{
|
{
|
||||||
RuleFor(p => p.Name).NotEmpty().WithMessage("名称不能为空");
|
RuleFor(p => p.Name).NotEmpty().WithMessage("名称不能为空");
|
||||||
RuleFor(p => p.Status).GreaterThan(0).WithMessage("请设置正确的状态");
|
RuleFor(p => p.Status).GreaterThan(0).WithMessage("请设置正确的状态");
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
using Atomx.Admin.Client.Models;
|
using Atomx.Admin.Client.Models;
|
||||||
using FluentValidation;
|
using FluentValidation;
|
||||||
|
using Microsoft.Extensions.Localization;
|
||||||
|
|
||||||
namespace Atomx.Admin.Client.Validators
|
namespace Atomx.Admin.Client.Validators
|
||||||
{
|
{
|
||||||
public class SpecificationAttributeOptionModelValidator : AbstractValidator<SpecificationAttributeOptionModel>
|
public class SpecificationAttributeOptionModelValidator : AbstractValidator<SpecificationAttributeOptionModel>
|
||||||
{
|
{
|
||||||
public SpecificationAttributeOptionModelValidator()
|
public SpecificationAttributeOptionModelValidator(IStringLocalizer<SpecificationAttributeOptionModelValidator> localizer)
|
||||||
{
|
{
|
||||||
RuleFor(p => p.Value).NotEmpty().WithMessage("数值不能为空");
|
RuleFor(p => p.Value).NotEmpty().WithMessage("数值不能为空");
|
||||||
RuleFor(p => p.Status).GreaterThan(0).WithMessage("请设置正确的状态");
|
RuleFor(p => p.Status).GreaterThan(0).WithMessage("请设置正确的状态");
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
using Atomx.Admin.Client.Models;
|
||||||
|
using FluentValidation;
|
||||||
|
using Microsoft.Extensions.Localization;
|
||||||
|
|
||||||
|
namespace Atomx.Admin.Client.Validators
|
||||||
|
{
|
||||||
|
public class StateProvinceValidator : AbstractValidator<StateProvinceModel>
|
||||||
|
{
|
||||||
|
public StateProvinceValidator(IStringLocalizer<StateProvinceValidator> localizer)
|
||||||
|
{
|
||||||
|
RuleFor(p => p.Name).NotEmpty().WithMessage("请填名称信息");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,11 +1,12 @@
|
|||||||
using Atomx.Admin.Client.Models;
|
using Atomx.Admin.Client.Models;
|
||||||
using FluentValidation;
|
using FluentValidation;
|
||||||
|
using Microsoft.Extensions.Localization;
|
||||||
|
|
||||||
namespace Atomx.Admin.Client.Validators
|
namespace Atomx.Admin.Client.Validators
|
||||||
{
|
{
|
||||||
public class UploadFileModelValidator : AbstractValidator<UploadFileModel>
|
public class UploadFileModelValidator : AbstractValidator<UploadFileModel>
|
||||||
{
|
{
|
||||||
public UploadFileModelValidator()
|
public UploadFileModelValidator(IStringLocalizer<UploadFileModelValidator> localizer)
|
||||||
{
|
{
|
||||||
RuleFor(p => p.Name).NotEmpty().WithMessage("文件名称不能为空");
|
RuleFor(p => p.Name).NotEmpty().WithMessage("文件名称不能为空");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
using Atomx.Common.Entities;
|
using Atomx.Common.Entities;
|
||||||
using FluentValidation;
|
using FluentValidation;
|
||||||
|
using Microsoft.Extensions.Localization;
|
||||||
|
|
||||||
namespace Atomx.Admin.Client.Validators
|
namespace Atomx.Admin.Client.Validators
|
||||||
{
|
{
|
||||||
public class UserValidator : AbstractValidator<User>
|
public class UserValidator : AbstractValidator<User>
|
||||||
{
|
{
|
||||||
public UserValidator()
|
public UserValidator(IStringLocalizer<UserValidator> localizer)
|
||||||
{
|
{
|
||||||
RuleFor(p => p.Name).NotEmpty().WithMessage("名称不能为空");
|
RuleFor(p => p.Name).NotEmpty().WithMessage("名称不能为空");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
namespace Atomx.Admin.Client.Validators
|
||||||
|
{
|
||||||
|
public sealed class ValidatorsMarker
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,11 +1,12 @@
|
|||||||
using Atomx.Admin.Client.Models;
|
using Atomx.Admin.Client.Models;
|
||||||
using FluentValidation;
|
using FluentValidation;
|
||||||
|
using Microsoft.Extensions.Localization;
|
||||||
|
|
||||||
namespace Atomx.Admin.Client.Validators
|
namespace Atomx.Admin.Client.Validators
|
||||||
{
|
{
|
||||||
public class WarehouseModelValidator : AbstractValidator<WarehouseModel>
|
public class WarehouseModelValidator : AbstractValidator<WarehouseModel>
|
||||||
{
|
{
|
||||||
public WarehouseModelValidator()
|
public WarehouseModelValidator(IStringLocalizer<WarehouseModelValidator> localizer)
|
||||||
{
|
{
|
||||||
RuleFor(p => p.Name).NotEmpty().WithMessage("用户名不能为空");
|
RuleFor(p => p.Name).NotEmpty().WithMessage("用户名不能为空");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
@using System.Net.Http
|
@using System.Net.Http
|
||||||
@using System.Net.Http.Json
|
@using System.Net.Http.Json
|
||||||
|
@using System.Text.Json
|
||||||
@using System.Security.Claims
|
@using System.Security.Claims
|
||||||
@using Microsoft.AspNetCore.Authorization
|
@using Microsoft.AspNetCore.Authorization
|
||||||
@using Microsoft.AspNetCore.Components.Authorization
|
@using Microsoft.AspNetCore.Components.Authorization
|
||||||
@@ -32,8 +33,9 @@
|
|||||||
@using Blazored.FluentValidation
|
@using Blazored.FluentValidation
|
||||||
|
|
||||||
|
|
||||||
|
@inject IJSRuntime JS
|
||||||
@inject ILocalStorageService localStorage
|
@inject ILocalStorageService localStorage
|
||||||
|
@inject ILocalizationProvider LocalizationProvider
|
||||||
@inject AuthenticationStateProvider AuthStateProvider
|
@inject AuthenticationStateProvider AuthStateProvider
|
||||||
@inject NavigationManager Navigation
|
@inject NavigationManager Navigation
|
||||||
@inject HttpService HttpService
|
@inject HttpService HttpService
|
||||||
|
|||||||
58
Atomx.Admin/Atomx.Admin.Client/wwwroot/js/common.js
Normal file
58
Atomx.Admin/Atomx.Admin.Client/wwwroot/js/common.js
Normal file
@@ -0,0 +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 '';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
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' };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
window.getBrowserLanguage = function () {
|
||||||
|
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) { }
|
||||||
|
};
|
||||||
@@ -8,30 +8,34 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Remove="Resources\**" />
|
<Compile Remove="Resources\**" />
|
||||||
|
<Compile Remove="wwwroot\js\**" />
|
||||||
<Content Remove="Resources\**" />
|
<Content Remove="Resources\**" />
|
||||||
|
<Content Remove="wwwroot\js\**" />
|
||||||
<EmbeddedResource Remove="Resources\**" />
|
<EmbeddedResource Remove="Resources\**" />
|
||||||
|
<EmbeddedResource Remove="wwwroot\js\**" />
|
||||||
<None Remove="Resources\**" />
|
<None Remove="Resources\**" />
|
||||||
|
<None Remove="wwwroot\js\**" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\..\Atomx.Core\Atomx.Core.csproj" />
|
||||||
<ProjectReference Include="..\..\Atomx.Data\Atomx.Data.csproj" />
|
<ProjectReference Include="..\..\Atomx.Data\Atomx.Data.csproj" />
|
||||||
<ProjectReference Include="..\..\Atomx.Utils\Atomx.Utils.csproj" />
|
<ProjectReference Include="..\..\Atomx.Utils\Atomx.Utils.csproj" />
|
||||||
<ProjectReference Include="..\Atomx.Admin.Client\Atomx.Admin.Client.csproj" />
|
<ProjectReference Include="..\Atomx.Admin.Client\Atomx.Admin.Client.csproj" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="10.0.0" />
|
<PackageReference Include="Hangfire" Version="1.8.22" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="10.0.0" />
|
<PackageReference Include="Hangfire.PostgreSql" Version="1.20.13" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.0" />
|
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="10.0.1" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="10.0.0">
|
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="10.0.1" />
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.1" />
|
||||||
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="10.0.1">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Microsoft.Extensions.Caching.StackExchangeRedis" Version="10.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Caching.StackExchangeRedis" Version="10.0.1" />
|
||||||
<PackageReference Include="Scalar.AspNetCore" Version="2.11.0" />
|
<PackageReference Include="Scalar.AspNetCore" Version="2.11.7" />
|
||||||
<PackageReference Include="Serilog.Settings.Configuration" Version="10.0.0" />
|
<PackageReference Include="Serilog.Settings.Configuration" Version="10.0.0" />
|
||||||
<PackageReference Include="Serilog.Sinks.Console" Version="6.1.1" />
|
<PackageReference Include="Serilog.Sinks.Console" Version="6.1.1" />
|
||||||
<PackageReference Include="Serilog.Sinks.Seq" Version="9.0.0" />
|
<PackageReference Include="Serilog.Sinks.Seq" Version="9.0.0" />
|
||||||
<PackageReference Include="TickerQ" Version="10.0.1" />
|
|
||||||
<PackageReference Include="TickerQ.Dashboard" Version="10.0.1" />
|
|
||||||
<PackageReference Include="TickerQ.EntityFrameworkCore" Version="10.0.1" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user