diff --git a/src/BootstrapBlazor.Server/Components/App.razor b/src/BootstrapBlazor.Server/Components/App.razor
index 14fc245376e..08f502ccaef 100644
--- a/src/BootstrapBlazor.Server/Components/App.razor
+++ b/src/BootstrapBlazor.Server/Components/App.razor
@@ -23,7 +23,6 @@
-
@Localizer["Title"]
diff --git a/src/BootstrapBlazor.Server/Components/Components/Header.razor.cs b/src/BootstrapBlazor.Server/Components/Components/Header.razor.cs
index e6b4ca0fe85..cf617bae761 100644
--- a/src/BootstrapBlazor.Server/Components/Components/Header.razor.cs
+++ b/src/BootstrapBlazor.Server/Components/Components/Header.razor.cs
@@ -57,5 +57,5 @@ protected override void OnInitialized()
_versionString = $"v{PackageVersionService.Version}";
}
- private Task OnThemeChangedAsync(string themeName) => InvokeVoidAsync("updateTheme", themeName);
+ private Task OnThemeChangedAsync(ThemeValue themeName) => InvokeVoidAsync("updateTheme", themeName);
}
diff --git a/src/BootstrapBlazor.Server/Controllers/Api/LoginController.cs b/src/BootstrapBlazor.Server/Controllers/Api/LoginController.cs
index 65dcbb0d749..9c0c1711d02 100644
--- a/src/BootstrapBlazor.Server/Controllers/Api/LoginController.cs
+++ b/src/BootstrapBlazor.Server/Controllers/Api/LoginController.cs
@@ -9,7 +9,7 @@
namespace BootstrapBlazor.Server.Controllers.Api;
///
-///
+/// 登录控制器
///
[Route("api/[controller]")]
[AllowAnonymous]
@@ -17,22 +17,10 @@ namespace BootstrapBlazor.Server.Controllers.Api;
public class LoginController : ControllerBase
{
///
- ///
+ /// 认证方法
///
///
///
[HttpPost]
- public IActionResult Post(User user)
- {
- IActionResult? response;
- if (user.UserName == "admin" && user.Password == "123456")
- {
- response = new JsonResult(new { Code = 200, Message = "登录成功" });
- }
- else
- {
- response = new JsonResult(new { Code = 500, Message = "用户名或密码错误" });
- }
- return response;
- }
+ public IActionResult Post(User user) => user is { UserName: "admin", Password: "123456" } ? new JsonResult(new { Code = 200, Message = "登录成功" }) : new JsonResult(new { Code = 500, Message = "用户名或密码错误" });
}
diff --git a/src/BootstrapBlazor/Components/ThemeProvider/ThemeProvider.razor.cs b/src/BootstrapBlazor/Components/ThemeProvider/ThemeProvider.razor.cs
index f5c57537be0..9c5c428a7f4 100644
--- a/src/BootstrapBlazor/Components/ThemeProvider/ThemeProvider.razor.cs
+++ b/src/BootstrapBlazor/Components/ThemeProvider/ThemeProvider.razor.cs
@@ -69,7 +69,19 @@ public partial class ThemeProvider
/// 获得/设置 主题切换回调方法
///
[Parameter]
- public Func? OnThemeChangedAsync { get; set; }
+ public Func? OnThemeChangedAsync { get; set; }
+
+ ///
+ /// 主题类型
+ ///
+ [Parameter]
+ public ThemeValue ThemeValue { get; set; } = ThemeValue.UseLocalStorage;
+
+ ///
+ /// 主题类型改变回调方法
+ ///
+ [Parameter]
+ public EventCallback ThemeValueChanged { get; set; }
[Inject, NotNull]
private IIconTheme? IconTheme { get; set; }
@@ -107,7 +119,7 @@ protected override void OnParametersSet()
///
///
///
- protected override Task InvokeInitAsync() => InvokeVoidAsync("init", Id, Interop, OnThemeChangedAsync != null ? nameof(OnThemeChanged) : null);
+ protected override Task InvokeInitAsync() => InvokeVoidAsync("init", Id, Interop, ThemeValue, nameof(OnThemeChanged));
///
/// JavaScript 回调方法
@@ -115,8 +127,13 @@ protected override void OnParametersSet()
///
///
[JSInvokable]
- public async Task OnThemeChanged(string name)
+ public async Task OnThemeChanged(ThemeValue name)
{
+ if (ThemeValueChanged.HasDelegate)
+ {
+ await ThemeValueChanged.InvokeAsync(name);
+ }
+
if (OnThemeChangedAsync != null)
{
await OnThemeChangedAsync(name);
diff --git a/src/BootstrapBlazor/Components/ThemeProvider/ThemeProvider.razor.js b/src/BootstrapBlazor/Components/ThemeProvider/ThemeProvider.razor.js
index e221e4c36a8..6f22cb48456 100644
--- a/src/BootstrapBlazor/Components/ThemeProvider/ThemeProvider.razor.js
+++ b/src/BootstrapBlazor/Components/ThemeProvider/ThemeProvider.razor.js
@@ -1,37 +1,56 @@
-import { getAutoThemeValue, getPreferredTheme, setActiveTheme, switchTheme } from "../../modules/utility.js"
+import { getPreferredTheme, setTheme, switchTheme } from "../../modules/utility.js"
import EventHandler from "../../modules/event-handler.js"
+import Data from "../../modules/data.js"
-export function init(id, invoke, callback) {
+export function init(id, invoke, themeValue, callback) {
const el = document.getElementById(id);
- if (el) {
- const currentTheme = getPreferredTheme();
- const activeItem = el.querySelector(`.dropdown-item[data-bb-theme-value="${currentTheme}"]`);
- if (activeItem) {
- setActiveTheme(el, activeItem);
- }
+ if (el === null) {
+ return;
+ }
+
+ const theme = { el };
+ Data.set(id, theme);
+
+ const darkModeMediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
+ EventHandler.on(darkModeMediaQuery, 'change', () => changeTheme(id));
+ theme.mediaQueryList = darkModeMediaQuery;
- const items = el.querySelectorAll('.dropdown-item');
- items.forEach(item => {
- EventHandler.on(item, 'click', () => {
- setActiveTheme(el, item);
-
- let theme = item.getAttribute('data-bb-theme-value');
- if (theme === 'auto') {
- theme = getAutoThemeValue();
- }
- switchTheme(theme, window.innerWidth, 0);
- if (callback) {
- invoke.invokeMethodAsync(callback, theme);
- }
- });
- });
+ let currentTheme = themeValue;
+ if (currentTheme === 'useLocalStorage') {
+ currentTheme = getPreferredTheme();
}
+ setTheme(currentTheme, true);
+ theme.currentTheme = currentTheme;
+
+ EventHandler.on(el, 'click', '.dropdown-item', e => {
+ const activeTheme = e.delegateTarget.getAttribute('data-bb-theme-value');
+ theme.currentTheme = activeTheme;
+ switchTheme(activeTheme, window.innerWidth, 0);
+ if (callback) {
+ invoke.invokeMethodAsync(callback, activeTheme);
+ }
+ });
}
export function dispose(id) {
- const el = document.getElementById(id);
- const items = el.querySelectorAll('.dropdown-item');
- items.forEach(item => {
- EventHandler.off(item, 'click');
- });
+ const theme = Data.get(id);
+ if (theme === null) {
+ return;
+ }
+ Data.remove(id);
+
+ const { el, darkModeMediaQuery } = theme;
+ EventHandler.off(el, 'click');
+ EventHandler.off(darkModeMediaQuery, 'change');
+}
+
+const changeTheme = id => {
+ const theme = Data.get(id);
+ if (theme === null) {
+ return;
+ }
+
+ if (theme.currentTheme === 'auto') {
+ switchTheme('auto', window.innerWidth, 0);
+ }
}
diff --git a/src/BootstrapBlazor/Components/ThemeProvider/ThemeValue.cs b/src/BootstrapBlazor/Components/ThemeProvider/ThemeValue.cs
new file mode 100644
index 00000000000..56b3a992e6b
--- /dev/null
+++ b/src/BootstrapBlazor/Components/ThemeProvider/ThemeValue.cs
@@ -0,0 +1,34 @@
+// Copyright (c) Argo Zhang (argo@163.com). All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+// Website: https://www.blazor.zone or https://argozhang.github.io/
+
+using BootstrapBlazor.Core.Converter;
+
+namespace BootstrapBlazor.Components;
+
+///
+/// 主题选项
+///
+[JsonEnumConverter(true)]
+public enum ThemeValue
+{
+ ///
+ /// 自动
+ ///
+ Auto,
+
+ ///
+ /// 明亮主题
+ ///
+ Light,
+
+ ///
+ /// 暗黑主题
+ ///
+ Dark,
+
+ ///
+ /// 使用本地保存选项
+ ///
+ UseLocalStorage,
+}
diff --git a/src/BootstrapBlazor/Converter/JsonEnumConverter.cs b/src/BootstrapBlazor/Converter/JsonEnumConverter.cs
index 0fb3461aa72..4e7564cfc86 100644
--- a/src/BootstrapBlazor/Converter/JsonEnumConverter.cs
+++ b/src/BootstrapBlazor/Converter/JsonEnumConverter.cs
@@ -12,34 +12,26 @@ namespace BootstrapBlazor.Core.Converter;
///
public class JsonEnumConverter : JsonConverterAttribute
{
- ///
- /// 构造函数
- ///
- public JsonEnumConverter() : base()
- {
-
- }
-
///
/// 构造函数
///
/// Optional naming policy for writing enum values.
/// True to allow undefined enum values. When true, if an enum value isn't defined it will output as a number rather than a string.
- public JsonEnumConverter(bool camelCase, bool allowIntegerValues = true) : this()
+ public JsonEnumConverter(bool camelCase = false, bool allowIntegerValues = true)
{
- CamelCase = camelCase;
- AllowIntegerValues = allowIntegerValues;
+ _camelCase = camelCase;
+ _allowIntegerValues = allowIntegerValues;
}
///
/// naming policy for writing enum values
///
- public bool CamelCase { get; private set; }
+ private readonly bool _camelCase;
///
/// True to allow undefined enum values. When true, if an enum value isn't defined it will output as a number rather than a string
///
- public bool AllowIntegerValues { get; private set; } = true;
+ private readonly bool _allowIntegerValues;
///
///
@@ -48,9 +40,9 @@ public JsonEnumConverter(bool camelCase, bool allowIntegerValues = true) : this(
///
public override JsonConverter? CreateConverter(Type typeToConvert)
{
- var converter = CamelCase
- ? new JsonStringEnumConverter(JsonNamingPolicy.CamelCase, AllowIntegerValues)
- : new JsonStringEnumConverter(null, AllowIntegerValues);
+ var converter = _camelCase
+ ? new JsonStringEnumConverter(JsonNamingPolicy.CamelCase, _allowIntegerValues)
+ : new JsonStringEnumConverter(null, _allowIntegerValues);
return converter;
}
}
diff --git a/src/BootstrapBlazor/wwwroot/modules/utility.js b/src/BootstrapBlazor/wwwroot/modules/utility.js
index 33948b6a754..79ef5674607 100644
--- a/src/BootstrapBlazor/wwwroot/modules/utility.js
+++ b/src/BootstrapBlazor/wwwroot/modules/utility.js
@@ -716,7 +716,9 @@ export function getTheme() {
}
export function saveTheme(theme) {
- localStorage.setItem('theme', theme)
+ if (localStorage) {
+ localStorage.setItem('theme', theme);
+ }
}
export function getAutoThemeValue() {
diff --git a/test/UnitTest/Components/ThemeProviderTest.cs b/test/UnitTest/Components/ThemeProviderTest.cs
index 84c0a923474..bf9816d584a 100644
--- a/test/UnitTest/Components/ThemeProviderTest.cs
+++ b/test/UnitTest/Components/ThemeProviderTest.cs
@@ -21,17 +21,29 @@ public void ThemeProvider_Ok()
[Fact]
public async Task OnThemeChanged_Ok()
{
- var name = "";
+ var v = ThemeValue.Auto;
var cut = Context.RenderComponent(pb =>
{
- pb.Add(a => a.OnThemeChangedAsync, t =>
+ pb.Add(a => a.OnThemeChangedAsync, val =>
{
- name = t;
+ v = val;
return Task.CompletedTask;
});
- pb.Add(a => a.Alignment, Alignment.Center);
});
- await cut.Instance.OnThemeChanged("dark");
- Assert.Equal("dark", name);
+ await cut.Instance.OnThemeChanged(ThemeValue.Dark);
+ Assert.Equal(ThemeValue.Dark, v);
+ }
+
+ [Fact]
+ public async Task ThemeValueChanged_Ok()
+ {
+ var v = ThemeValue.Auto;
+ var cut = Context.RenderComponent(pb =>
+ {
+ pb.Add(a => a.ThemeValue, ThemeValue.Light);
+ pb.Add(a => a.ThemeValueChanged, EventCallback.Factory.Create(this, val => v = val));
+ });
+ await cut.Instance.OnThemeChanged(ThemeValue.Dark);
+ Assert.Equal(ThemeValue.Dark, v);
}
}