diff --git a/Source/Store.Management.Framework/Controls/DataGridEx/DataGridEx.xaml b/Source/Store.Management.Framework/Controls/DataGridEx/DataGridEx.xaml
index bbfa67d..19565a5 100644
--- a/Source/Store.Management.Framework/Controls/DataGridEx/DataGridEx.xaml
+++ b/Source/Store.Management.Framework/Controls/DataGridEx/DataGridEx.xaml
@@ -4,8 +4,8 @@
xmlns:ext="clr-namespace:Store.Management.Framework.Controls"
xmlns:f="clr-namespace:Store.Management.Framework.Controls.Attach;assembly=Store.Management.Framework"
xmlns:local="clr-namespace:Store.Management.Framework.Controls"
- xmlns:wpf="MAX.WPF"
- xmlns:sys="clr-namespace:System;assembly=mscorlib">
+ xmlns:sys="clr-namespace:System;assembly=mscorlib"
+ xmlns:wpf="MAX.WPF">
12
@@ -498,6 +498,11 @@
+
+
+ 50,100,500,1000,2000
+
+
diff --git a/Source/Store.Management.Framework/FrameworkApplication.cs b/Source/Store.Management.Framework/FrameworkApplication.cs
index e0c5f91..4d548b1 100644
--- a/Source/Store.Management.Framework/FrameworkApplication.cs
+++ b/Source/Store.Management.Framework/FrameworkApplication.cs
@@ -1,3 +1,4 @@
+using MAX.Common.Helpers;
using MAX.Framework.Logger;
using NLog;
using Prism.Autofac;
@@ -15,141 +16,155 @@ namespace Store.Management.Framework;
public abstract class FrameworkApplication : PrismApplication
{
- private Mutex? _mutex;
- private bool _ownsMutex;
- private int _fatalHandling;
- private int _shutdownRequested;
- public static bool ShuttingDown { get; private set; }
- public static bool IsAppInitialized { get; private set; }
- public static event Action? FatalErrorRaised;
+ private Mutex? _mutex;
+ private bool _ownsMutex;
+ private int _fatalHandling;
+ private int _shutdownRequested;
+ public static bool ShuttingDown { get; private set; }
+ public static bool IsAppInitialized { get; private set; }
+ public static event Action? FatalErrorRaised;
+ private IStatusBarService StatusBarService;
+ private IMessageBoxService MessageBoxService;
+ private static bool FatalShutdown = false;
+ protected FrameworkApplication()
+ {
+ DispatcherUnhandledException += OnDispatcherUnhandledException;
+ AppDomain.CurrentDomain.UnhandledException += OnDomainUnhandledException;
+ TaskScheduler.UnobservedTaskException += OnUnobservedTaskException;
+ }
- protected FrameworkApplication()
- {
- DispatcherUnhandledException += OnDispatcherUnhandledException;
- AppDomain.CurrentDomain.UnhandledException += OnDomainUnhandledException;
- TaskScheduler.UnobservedTaskException += OnUnobservedTaskException;
- }
+ /// 返回 null 可允许多开;默认限制同一登录会话中的应用实例。
+ public virtual string? GetMutexName() => @"Local\Store.Management";
- /// 返回 null 可允许多开;默认限制同一登录会话中的应用实例。
- public virtual string? GetMutexName() => @"Local\Store.Management";
+ protected override void OnStartup(StartupEventArgs e)
+ {
+ try
+ {
+ //string? name = GetMutexName();
+ //if (!string.IsNullOrWhiteSpace(name))
+ //{
+ // _mutex = new Mutex(true, name, out _ownsMutex);
+ // if (!_ownsMutex)
+ // {
+ // MessageBox.Show("程序已在运行。", Config.AppName, MessageBoxButton.OK, MessageBoxImage.Information);
+ // Shutdown();
+ // return;
+ // }
+ //}
+ FolderConfig.Initialize();
+ base.OnStartup(e);
+ ApplicationLogService.Initialize();
+ NLogger.Instance.Info($"软件启动:{Config.AppFullName},版本:{Config.GetFullVersionString()},会话:{Config.SessionID},启动时间:{Config.AppStartTime:O},Windows:{Config.WindowsVersion}");
+ DISource.Resolver = Resolve;
+ }
+ catch (Exception ex) { Fatal(ex); }
+ }
- protected override void OnStartup(StartupEventArgs e)
- {
- try
- {
- //string? name = GetMutexName();
- //if (!string.IsNullOrWhiteSpace(name))
- //{
- // _mutex = new Mutex(true, name, out _ownsMutex);
- // if (!_ownsMutex)
- // {
- // MessageBox.Show("程序已在运行。", Config.AppName, MessageBoxButton.OK, MessageBoxImage.Information);
- // Shutdown();
- // return;
- // }
- //}
- FolderConfig.Initialize();
- base.OnStartup(e);
- ApplicationLogService.Initialize();
- NLogger.Instance.Info($"软件启动:{Config.AppFullName},版本:{Config.GetFullVersionString()},会话:{Config.SessionID},启动时间:{Config.AppStartTime:O},Windows:{Config.WindowsVersion}");
- DISource.Resolver = Resolve;
- }
- catch (Exception ex) { Fatal(ex); }
- }
+ protected override void OnInitialized()
+ {
+ base.OnInitialized();
+ IsAppInitialized = true;
+ NLogger.Instance.Info("软件初始化成功,进入主页面");
+ StatusBarService = Container?.Resolve();
+ MessageBoxService = Container?.Resolve();
+ }
- protected override void OnInitialized()
- {
- base.OnInitialized();
- IsAppInitialized = true;
- NLogger.Instance.Info("软件初始化成功,进入主页面");
- }
+ protected override void RegisterRequiredTypes(IContainerRegistry registry)
+ {
+ base.RegisterRequiredTypes(registry);
+ registry.RegisterDialogWindow();
+ }
- protected override void RegisterRequiredTypes(IContainerRegistry registry)
- {
- base.RegisterRequiredTypes(registry);
- registry.RegisterDialogWindow();
- }
+ private object Resolve(Type type, string name)
+ {
+ if (type == null || Container is null) return null!;
+ return name != null ? Container.Resolve(type, name) : Container.Resolve(type);
+ }
- private object Resolve(Type type, string name)
- {
- if (type == null || Container is null) return null!;
- return name != null ? Container.Resolve(type, name) : Container.Resolve(type);
- }
+ private void OnDispatcherUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e)
+ {
+ e.Handled = true;
+ StatusBarService.Report(e.Exception.Message, StatusLevel.Error);
+ Fatal(e.Exception);
- private void OnDispatcherUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e)
- {
- e.Handled = true;
- Fatal(e.Exception);
- }
+ }
- private void OnDomainUnhandledException(object sender, UnhandledExceptionEventArgs e)
- {
- Exception exception = e.ExceptionObject as Exception ?? new Exception(e.ExceptionObject?.ToString());
- // CLR 终止期间同步记录,不等待 UI 线程。
- if (e.IsTerminating) ApplicationLogService.WriteFatal(exception, "AppDomain.UnhandledException");
- else Fatal(exception);
- }
+ private void OnDomainUnhandledException(object sender, UnhandledExceptionEventArgs e)
+ {
+ Exception exception = e.ExceptionObject as Exception ?? new Exception(e.ExceptionObject?.ToString());
+ StatusBarService.Report(exception.Message, StatusLevel.Error);
+ // CLR 终止期间同步记录,不等待 UI 线程。
+ if (e.IsTerminating) ApplicationLogService.WriteFatal(exception, "AppDomain.UnhandledException");
+ else Fatal(exception);
+ }
- private void OnUnobservedTaskException(object? sender, UnobservedTaskExceptionEventArgs e)
- {
- ApplicationLogService.WriteFatal(e.Exception, "TaskScheduler.UnobservedTaskException");
- e.SetObserved();
- }
+ private void OnUnobservedTaskException(object? sender, UnobservedTaskExceptionEventArgs e)
+ {
+ ApplicationLogService.WriteFatal(e.Exception, "TaskScheduler.UnobservedTaskException");
+ e.SetObserved();
+ }
- /// 先同步保存报告,再通知订阅者并关闭应用。
- public static void Fatal(Exception exception)
- {
- ArgumentNullException.ThrowIfNull(exception);
- string? path = ApplicationLogService.WriteFatal(exception, "Fatal");
- if (Current is not FrameworkApplication app || Interlocked.Exchange(ref app._fatalHandling, 1) != 0) return;
- foreach (Action handler in FatalErrorRaised?.GetInvocationList() ?? [])
- {
- try { handler(exception); }
- catch (Exception ex) { ApplicationLogService.WriteFatal(ex, "FatalErrorRaised"); }
- }
- void ShowErrorAndShutdown()
- {
- try
- {
- MessageBox.Show($"软件发生致命错误,即将关闭。\n{exception.Message}\n\n日志:{path ?? "写入失败,请查看调试输出"}",
- Config.AppName, MessageBoxButton.OK, MessageBoxImage.Error);
- }
- finally { TotalShutdown(-1); }
- }
- if (app.Dispatcher.CheckAccess()) ShowErrorAndShutdown();
- else if (!app.Dispatcher.HasShutdownStarted) app.Dispatcher.BeginInvoke((Action)ShowErrorAndShutdown);
- }
+ /// 先同步保存报告,再通知订阅者并关闭应用。
+ public void Fatal(Exception exception)
+ {
+ ArgumentNullException.ThrowIfNull(exception);
+ string? path = ApplicationLogService.WriteFatal(exception, "Fatal");
+ if (Current is not FrameworkApplication app || Interlocked.Exchange(ref app._fatalHandling, 1) != 0) return;
+ foreach (Action handler in FatalErrorRaised?.GetInvocationList() ?? [])
+ {
+ try { handler(exception); }
+ catch (Exception ex) { ApplicationLogService.WriteFatal(ex, "FatalErrorRaised"); }
+ }
+ void ShowErrorAndShutdown()
+ {
+ if (FatalShutdown)
+ {
+ try
+ {
+ MessageBox.Show($"软件发生致命错误,即将关闭。\n{exception.Message}\n\n日志:{path ?? "写入失败,请查看调试输出"}",
+ Config.AppName, MessageBoxButton.OK, MessageBoxImage.Error);
+ }
+ finally { TotalShutdown(-1); }
+ }
+ else
+ {
+ MessageBoxService.ShowError(exception.Message);
+ }
+ }
+ if (app.Dispatcher.CheckAccess()) ShowErrorAndShutdown();
+ else if (!app.Dispatcher.HasShutdownStarted) app.Dispatcher.BeginInvoke((Action)ShowErrorAndShutdown);
+ }
- /// 通过 WPF 正常退出,执行退出清理。
- public static void TotalShutdown(int exitCode = 0)
- {
- if (Current is not FrameworkApplication app || Interlocked.Exchange(ref app._shutdownRequested, 1) != 0) return;
- ShuttingDown = true;
- if (app.Dispatcher.CheckAccess()) app.Shutdown(exitCode);
- else if (!app.Dispatcher.HasShutdownStarted) app.Dispatcher.BeginInvoke((Action)(() => app.Shutdown(exitCode)));
- }
+ /// 通过 WPF 正常退出,执行退出清理。
+ public static void TotalShutdown(int exitCode = 0)
+ {
+ if (Current is not FrameworkApplication app || Interlocked.Exchange(ref app._shutdownRequested, 1) != 0) return;
+ ShuttingDown = true;
+ if (app.Dispatcher.CheckAccess()) app.Shutdown(exitCode);
+ else if (!app.Dispatcher.HasShutdownStarted) app.Dispatcher.BeginInvoke((Action)(() => app.Shutdown(exitCode)));
+ }
- protected override void OnExit(ExitEventArgs e)
- {
- ShuttingDown = true;
- try
- {
- try { Container?.Resolve().Dispose(); }
- finally { base.OnExit(e); }
- }
- finally
- {
- DispatcherUnhandledException -= OnDispatcherUnhandledException;
- AppDomain.CurrentDomain.UnhandledException -= OnDomainUnhandledException;
- TaskScheduler.UnobservedTaskException -= OnUnobservedTaskException;
- DISource.Resolver = null!;
- try { if (_ownsMutex) _mutex?.ReleaseMutex(); }
- finally
- {
- _mutex?.Dispose();
- try { NLogger.Instance.Info($"软件关闭,退出码:{e.ApplicationExitCode}"); }
- finally { LogManager.Shutdown(); }
- }
- }
- }
+ protected override void OnExit(ExitEventArgs e)
+ {
+ ShuttingDown = true;
+ try
+ {
+ try { Container?.Resolve().Dispose(); }
+ finally { base.OnExit(e); }
+ }
+ finally
+ {
+ DispatcherUnhandledException -= OnDispatcherUnhandledException;
+ AppDomain.CurrentDomain.UnhandledException -= OnDomainUnhandledException;
+ TaskScheduler.UnobservedTaskException -= OnUnobservedTaskException;
+ DISource.Resolver = null!;
+ try { if (_ownsMutex) _mutex?.ReleaseMutex(); }
+ finally
+ {
+ _mutex?.Dispose();
+ try { NLogger.Instance.Info($"软件关闭,退出码:{e.ApplicationExitCode}"); }
+ finally { LogManager.Shutdown(); }
+ }
+ }
+ }
}
diff --git a/Source/Store.Management.Framework/Interfaces/IMessageBoxService.cs b/Source/Store.Management.Framework/Interfaces/IMessageBoxService.cs
deleted file mode 100644
index 03a67c3..0000000
--- a/Source/Store.Management.Framework/Interfaces/IMessageBoxService.cs
+++ /dev/null
@@ -1,15 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Windows;
-using System.ComponentModel;
-
-namespace Store.Management.Framework.Interfaces
-{
-
- public interface IMessageBoxService
- {
-
- //MessageResult Show(string messageBoxText, string caption, MessageBoxButton button, MessageIcon icon, MessageResult defaultResult);
- }
-}
diff --git a/Source/Store.Management.Framework/Services/MessageBoxService/IMessageBoxService.cs b/Source/Store.Management.Framework/Services/MessageBoxService/IMessageBoxService.cs
new file mode 100644
index 0000000..54d26a6
--- /dev/null
+++ b/Source/Store.Management.Framework/Services/MessageBoxService/IMessageBoxService.cs
@@ -0,0 +1,27 @@
+using MAX.Core.Commons;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Store.Management.Framework
+{
+ public interface IMessageBoxService
+ {
+ MessageResult Show(string messageBoxText, string caption, MessageButton button, MessageIcon icon, MessageResult defaultResult);
+
+
+ void ShowInformation(string content);
+ void ShowWarning(string content);
+ void ShowError(string content);
+ void ShowError(string content, Exception exception);
+ bool ShowYesNoQuestion(string content);
+ bool ShowYesNoWarning(string content);
+ bool ShowYesNoError(string content);
+ bool ShowOkCancelQuestion(string content);
+ bool ShowOkCancelWarning(string content);
+ bool ShowOkCancelError(string content);
+ MessageResult ShowYesNoCancelQuestion(string content);
+ MessageResult ShowYesNoCancelWarning(string content);
+ MessageResult ShowYesNoCancelError(string content);
+ }
+}
diff --git a/Source/Store.Management.Framework/Services/MessageBoxService/MessageBoxEnumsConverter.cs b/Source/Store.Management.Framework/Services/MessageBoxService/MessageBoxEnumsConverter.cs
new file mode 100644
index 0000000..07e3f33
--- /dev/null
+++ b/Source/Store.Management.Framework/Services/MessageBoxService/MessageBoxEnumsConverter.cs
@@ -0,0 +1,99 @@
+using System.ComponentModel;
+using System.Windows;
+
+namespace Store.Management.Framework
+{
+ public static class MessageBoxEnumsConverter
+ {
+ [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
+ public static bool? ToBool(this MessageBoxResult result)
+ {
+ switch (result)
+ {
+ case MessageBoxResult.Cancel: return null;
+ case MessageBoxResult.No: return false;
+ default: return true;
+ }
+ }
+ [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
+ public static bool? ToBool(this MessageResult result)
+ {
+ switch (result)
+ {
+ case MessageResult.Cancel: return null;
+ case MessageResult.No: return false;
+ default: return true;
+ }
+ }
+ [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
+ public static MessageBoxResult ToMessageBoxResult(this MessageResult result)
+ {
+ switch (result)
+ {
+ case MessageResult.Cancel: return MessageBoxResult.Cancel;
+ case MessageResult.No: return MessageBoxResult.No;
+ case MessageResult.Yes: return MessageBoxResult.Yes;
+ case MessageResult.OK: return MessageBoxResult.OK;
+ default: return MessageBoxResult.None;
+ }
+ }
+ [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
+ public static MessageResult ToMessageResult(this MessageBoxResult result)
+ {
+ switch (result)
+ {
+ case MessageBoxResult.Cancel: return MessageResult.Cancel;
+ case MessageBoxResult.No: return MessageResult.No;
+ case MessageBoxResult.Yes: return MessageResult.Yes;
+ case MessageBoxResult.OK: return MessageResult.OK;
+ default: return MessageResult.None;
+ }
+ }
+ [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
+ public static MessageBoxButton ToMessageBoxButton(this MessageButton button)
+ {
+ switch (button)
+ {
+ case MessageButton.OKCancel: return MessageBoxButton.OKCancel;
+ case MessageButton.YesNo: return MessageBoxButton.YesNo;
+ case MessageButton.YesNoCancel: return MessageBoxButton.YesNoCancel;
+ default: return MessageBoxButton.OK;
+ }
+ }
+ [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
+ public static MessageButton ToMessageButton(this MessageBoxButton button)
+ {
+ switch (button)
+ {
+ case MessageBoxButton.OKCancel: return MessageButton.OKCancel;
+ case MessageBoxButton.YesNo: return MessageButton.YesNo;
+ case MessageBoxButton.YesNoCancel: return MessageButton.YesNoCancel;
+ default: return MessageButton.OK;
+ }
+ }
+ [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
+ public static MessageBoxImage ToMessageBoxImage(this MessageIcon icon)
+ {
+ switch (icon)
+ {
+ case MessageIcon.Error: return MessageBoxImage.Error;
+ case MessageIcon.Information: return MessageBoxImage.Information;
+ case MessageIcon.Question: return MessageBoxImage.Question;
+ case MessageIcon.Warning: return MessageBoxImage.Warning;
+ default: return MessageBoxImage.None;
+ }
+ }
+ [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
+ public static MessageIcon ToMessageIcon(this MessageBoxImage icon)
+ {
+ switch (icon)
+ {
+ case MessageBoxImage.Error: return MessageIcon.Error;
+ case MessageBoxImage.Information: return MessageIcon.Information;
+ case MessageBoxImage.Question: return MessageIcon.Question;
+ case MessageBoxImage.Warning: return MessageIcon.Warning;
+ default: return MessageIcon.None;
+ }
+ }
+ }
+}
diff --git a/Source/Store.Management.Framework/Services/MessageBoxService/MessageBoxService.cs b/Source/Store.Management.Framework/Services/MessageBoxService/MessageBoxService.cs
new file mode 100644
index 0000000..128b946
--- /dev/null
+++ b/Source/Store.Management.Framework/Services/MessageBoxService/MessageBoxService.cs
@@ -0,0 +1,124 @@
+using MAX.Core.DependencyInjection;
+using Store.Management.Framework.Configs;
+using System.Windows;
+using MessageBox = HandyControl.Controls.MessageBox;
+
+namespace Store.Management.Framework
+{
+ public class MessageBoxService : IMessageBoxService, ISingletonDependency
+ {
+ protected readonly Guid id = Guid.NewGuid();
+
+ public string MessageTitle { get; set; } = string.Empty;
+
+ public MessageBoxService()
+ {
+ MessageTitle = AppConfig.AppDisplayName;
+ }
+
+ public virtual MessageResult Show(string messageBoxText, string caption, MessageButton button, MessageIcon icon, MessageResult defaultResult)
+ {
+ Window? window = Application.Current?.Windows.OfType().FirstOrDefault(w => w.IsActive);
+ if (window is null)
+ {
+ return MessageBox.Show(
+ messageBoxText: messageBoxText,
+ caption: caption,
+ button: button.ToMessageBoxButton(),
+ icon: icon.ToMessageBoxImage(),
+ defaultResult: defaultResult.ToMessageBoxResult()
+ ).ToMessageResult();
+ }
+ else
+ {
+ return MessageBox.Show(
+ owner: window,
+ messageBoxText: messageBoxText,
+ caption: caption,
+ button: button.ToMessageBoxButton(),
+ icon: icon.ToMessageBoxImage(),
+ defaultResult: defaultResult.ToMessageBoxResult()
+ ).ToMessageResult();
+ }
+ }
+
+ public void ShowInformation(string content)
+ {
+ this.ShowMessage(content, MessageTitle, MessageButton.OK, MessageIcon.Information, MessageResult.OK);
+ }
+
+ public void ShowWarning(string content)
+ {
+ this.ShowMessage(content, MessageTitle, MessageButton.OK, MessageIcon.Warning, MessageResult.OK);
+ }
+
+ public void ShowError(string content, Exception exception)
+ {
+ if (exception is null)
+ {
+ ShowError(content);
+ }
+ else
+ {
+ this.ShowMessage(content + "\n" + exception.Message, MessageTitle, MessageButton.OK, MessageIcon.Error, MessageResult.OK);
+ }
+ }
+
+ public void ShowError(string content)
+ {
+ this.ShowMessage(content, MessageTitle, MessageButton.OK, MessageIcon.Error, MessageResult.OK);
+ }
+
+ public bool ShowYesNoQuestion(string content)
+ {
+ MessageResult result = this.ShowMessage(content, MessageTitle, MessageButton.YesNo, MessageIcon.Question, MessageResult.No);
+ return result == MessageResult.Yes;
+ }
+
+ public bool ShowYesNoWarning(string content)
+ {
+ MessageResult result = this.ShowMessage(content, MessageTitle, MessageButton.YesNo, MessageIcon.Warning, MessageResult.No);
+ return result == MessageResult.Yes;
+ }
+
+ public bool ShowYesNoError(string content)
+ {
+ MessageResult result = this.ShowMessage(content, MessageTitle, MessageButton.YesNo, MessageIcon.Error, MessageResult.No);
+ return result == MessageResult.Yes;
+ }
+
+ public bool ShowOkCancelQuestion(string content)
+ {
+ MessageResult result = this.ShowMessage(content, MessageTitle, MessageButton.OKCancel, MessageIcon.Question, MessageResult.OK);
+ return result == MessageResult.OK;
+ }
+
+ public bool ShowOkCancelWarning(string content)
+ {
+ MessageResult result = this.ShowMessage(content, MessageTitle, MessageButton.OKCancel, MessageIcon.Warning, MessageResult.OK);
+ return result == MessageResult.OK;
+ }
+
+ public bool ShowOkCancelError(string content)
+ {
+ MessageResult result = this.ShowMessage(content, MessageTitle, MessageButton.OKCancel, MessageIcon.Error, MessageResult.OK);
+ return result == MessageResult.OK;
+ }
+
+ public MessageResult ShowYesNoCancelQuestion(string content)
+ {
+ return this.ShowMessage(content, MessageTitle, MessageButton.YesNoCancel, MessageIcon.Question, MessageResult.Cancel);
+ }
+
+ public MessageResult ShowYesNoCancelWarning(string content)
+ {
+ return this.ShowMessage(content, MessageTitle, MessageButton.YesNoCancel, MessageIcon.Warning, MessageResult.Cancel);
+ }
+
+ public MessageResult ShowYesNoCancelError(string content)
+ {
+ return this.ShowMessage(content, MessageTitle, MessageButton.YesNoCancel, MessageIcon.Error, MessageResult.Cancel);
+ }
+
+ }
+}
diff --git a/Source/Store.Management.Framework/Services/MessageBoxService/MessageBoxServiceExtensions.cs b/Source/Store.Management.Framework/Services/MessageBoxService/MessageBoxServiceExtensions.cs
new file mode 100644
index 0000000..90108ed
--- /dev/null
+++ b/Source/Store.Management.Framework/Services/MessageBoxService/MessageBoxServiceExtensions.cs
@@ -0,0 +1,37 @@
+namespace Store.Management.Framework
+{
+ public static class MessageBoxServiceExtensions
+ {
+ extension(IMessageBoxService service)
+ {
+ public MessageResult ShowMessage(string messageBoxText, string caption, MessageButton button, MessageIcon icon, MessageResult defaultResult)
+ {
+ VerifyService(service);
+ return service.Show(messageBoxText, caption, button, icon, defaultResult);
+ }
+ public bool? ShowMessage(string messageBoxText)
+ {
+ return service.ShowMessage(messageBoxText, string.Empty);
+ }
+ public bool? ShowMessage(string messageBoxText, string caption)
+ {
+ return service.ShowMessage(messageBoxText, caption, MessageButton.OK).ToBool();
+ }
+ public MessageResult ShowMessage(string messageBoxText, string caption, MessageButton button)
+ {
+ return service.ShowMessage(messageBoxText, caption, button, MessageIcon.None);
+ }
+ public MessageResult ShowMessage(string messageBoxText, string caption, MessageButton button, MessageIcon icon)
+ {
+ VerifyService(service);
+ return service.Show(messageBoxText, caption, button, icon, MessageResult.None);
+ }
+ }
+
+ static void VerifyService(IMessageBoxService service)
+ {
+ if (service == null)
+ throw new ArgumentNullException(nameof(service));
+ }
+ }
+}
diff --git a/Source/Store.Management.Framework/Services/MessageBoxService/MessageButton.cs b/Source/Store.Management.Framework/Services/MessageBoxService/MessageButton.cs
new file mode 100644
index 0000000..1417a41
--- /dev/null
+++ b/Source/Store.Management.Framework/Services/MessageBoxService/MessageButton.cs
@@ -0,0 +1,7 @@
+namespace Store.Management.Framework
+{
+ public enum MessageButton
+ {
+ OK, OKCancel, YesNoCancel, YesNo,
+ }
+}
diff --git a/Source/Store.Management.Framework/Services/MessageBoxService/MessageIcon.cs b/Source/Store.Management.Framework/Services/MessageBoxService/MessageIcon.cs
new file mode 100644
index 0000000..a9b8b71
--- /dev/null
+++ b/Source/Store.Management.Framework/Services/MessageBoxService/MessageIcon.cs
@@ -0,0 +1,8 @@
+namespace Store.Management.Framework
+{
+ public enum MessageIcon
+ {
+ None, Error, Question, Warning, Information,
+ Hand = Error, Stop = Error, Exclamation = Warning, Asterisk = Information
+ }
+}
diff --git a/Source/Store.Management.Framework/Services/MessageBoxService/MessageResult.cs b/Source/Store.Management.Framework/Services/MessageBoxService/MessageResult.cs
new file mode 100644
index 0000000..e1df9cb
--- /dev/null
+++ b/Source/Store.Management.Framework/Services/MessageBoxService/MessageResult.cs
@@ -0,0 +1,7 @@
+namespace Store.Management.Framework
+{
+ public enum MessageResult
+ {
+ None, OK, Cancel, Yes, No,
+ }
+}
diff --git a/Source/Store.Management.Framework/Services/UserService.cs b/Source/Store.Management.Framework/Services/UserService.cs
new file mode 100644
index 0000000..51d7b95
--- /dev/null
+++ b/Source/Store.Management.Framework/Services/UserService.cs
@@ -0,0 +1,29 @@
+using MAX.Core.DependencyInjection;
+using Store.Management.Api;
+using Store.Management.Api.Models;
+using Store.Management.Api.Users;
+
+namespace Store.Management.Framework.Services;
+
+public interface IUserService
+{
+ Task> SearchAsync(UserSearchRequest request, CancellationToken cancellationToken = default);
+ Task CreateAsync(UserCreateRequest request, CancellationToken cancellationToken = default);
+ Task UpdateAsync(UserUpdateRequest request, CancellationToken cancellationToken = default);
+ Task DeleteAsync(int id, CancellationToken cancellationToken = default);
+}
+
+internal sealed class UserService(StoreApiClient client) : IUserService, ISingletonDependency
+{
+ public Task> SearchAsync(UserSearchRequest request, CancellationToken cancellationToken = default)
+ => client.Users.SearchAsync(request, cancellationToken);
+
+ public Task CreateAsync(UserCreateRequest request, CancellationToken cancellationToken = default)
+ => client.Users.CreateAsync(request, cancellationToken);
+
+ public Task UpdateAsync(UserUpdateRequest request, CancellationToken cancellationToken = default)
+ => client.Users.UpdateAsync(request, cancellationToken);
+
+ public Task DeleteAsync(int id, CancellationToken cancellationToken = default)
+ => client.Users.DeleteAsync(id, cancellationToken);
+}
diff --git a/Source/Store.Management/ViewModels/UserEditorDialogViewModel.cs b/Source/Store.Management/ViewModels/UserEditorDialogViewModel.cs
index 6452a10..d3eb287 100644
--- a/Source/Store.Management/ViewModels/UserEditorDialogViewModel.cs
+++ b/Source/Store.Management/ViewModels/UserEditorDialogViewModel.cs
@@ -1,15 +1,15 @@
using Prism.Commands;
using Prism.Services.Dialogs;
-using Store.Management.Api;
using Store.Management.Api.Transport;
using Store.Management.Api.Users;
using Store.Management.Framework.Controls;
+using Store.Management.Framework.Services;
using System.ComponentModel.DataAnnotations;
using System.Windows.Input;
namespace Store.Management.ViewModels;
-public sealed class UserEditorDialogViewModel(StoreApiClient client) : ConfirmDialogViewModel
+public sealed class UserEditorDialogViewModel(IUserService userService) : ConfirmDialogViewModel
{
public const int UserNameLimit = 20;
public const int PasswordLimit = 128;
@@ -65,11 +65,11 @@ public sealed class UserEditorDialogViewModel(StoreApiClient client) : ConfirmDi
{
if (Draft is UserCreateRequest newUser)
{
- await client.Users.CreateAsync(newUser);
+ await userService.CreateAsync(newUser);
}
else
{
- await client.Users.UpdateAsync((UserUpdateRequest)Draft);
+ await userService.UpdateAsync((UserUpdateRequest)Draft);
}
}
catch (ApiException ex)
diff --git a/Source/Store.Management/ViewModels/UserManagementPageViewModel.cs b/Source/Store.Management/ViewModels/UserManagementPageViewModel.cs
index abe95fb..6130c30 100644
--- a/Source/Store.Management/ViewModels/UserManagementPageViewModel.cs
+++ b/Source/Store.Management/ViewModels/UserManagementPageViewModel.cs
@@ -1,7 +1,6 @@
using Prism.Commands;
using Prism.Mvvm;
using Prism.Services.Dialogs;
-using Store.Management.Api;
using Store.Management.Api.Models;
using Store.Management.Api.Transport;
using Store.Management.Api.Users;
@@ -13,7 +12,7 @@ using System.Windows.Controls.Primitives;
namespace Store.Management.ViewModels;
-public sealed class UserManagementPageViewModel(StoreApiClient client, IDialogService dialogs, IStatusBarService statusBar) : BindableBase
+public sealed class UserManagementPageViewModel(IUserService userService, IDialogService dialogs, IStatusBarService statusBar) : BindableBase
{
public const int KeywordLimit = 100;
private string appliedKeyword = "";
@@ -59,13 +58,13 @@ public sealed class UserManagementPageViewModel(StoreApiClient client, IDialogSe
{
statusBar.Report("正在查询用户…");
UserSearchRequest request = new() { Keyword = keyword, Page = page, PageSize = size };
- PageResult result = await client.Users.SearchAsync(request);
+ PageResult result = await userService.SearchAsync(request);
// 删除末页最后一条记录后,按服务端总数回到有效页。
int lastPage = (int)Math.Clamp((result.Total - 1) / size + 1, 1, page);
if (page > lastPage)
{
request.Page = lastPage;
- result = await client.Users.SearchAsync(request);
+ result = await userService.SearchAsync(request);
}
appliedKeyword = keyword;
pageSize = size;
@@ -86,6 +85,7 @@ public sealed class UserManagementPageViewModel(StoreApiClient client, IDialogSe
pageSize = size;
statusBar.Report(ex.Message, StatusLevel.Error);
}
+
finally
{
CanInteract = true;
@@ -115,7 +115,7 @@ public sealed class UserManagementPageViewModel(StoreApiClient client, IDialogSe
try
{
statusBar.Report("正在删除用户…");
- await client.Users.DeleteAsync(user.Id);
+ await userService.DeleteAsync(user.Id);
}
catch (ApiException ex)
{
diff --git a/Source/Store.Management/Views/DataGridTestView.xaml b/Source/Store.Management/Views/DataGridTestView.xaml
index 1c93a2e..5a28be1 100644
--- a/Source/Store.Management/Views/DataGridTestView.xaml
+++ b/Source/Store.Management/Views/DataGridTestView.xaml
@@ -127,6 +127,9 @@
ShowRowNumber="True"
ShowToolbar="True"
TotalItemCount="{Binding TotalCount, Mode=OneWay}">
+
diff --git a/Tests/Store.Management.App.SmokeTests/Program.cs b/Tests/Store.Management.App.SmokeTests/Program.cs
index f5d353e..7e6aec6 100644
--- a/Tests/Store.Management.App.SmokeTests/Program.cs
+++ b/Tests/Store.Management.App.SmokeTests/Program.cs
@@ -76,6 +76,11 @@ class Program
TextBlock text = (TextBlock)shell.FindName("StatusText");
ProgressBar progress = (ProgressBar)shell.FindName("StatusProgress");
Check(text.Text == "就绪" && progress.Visibility == Visibility.Collapsed, "初始状态");
+ _ = app.Dispatcher.BeginInvoke(new Action(() =>
+ throw new System.ComponentModel.DataAnnotations.ValidationException("分页参数无效")));
+ await Dispatcher.Yield(DispatcherPriority.ApplicationIdle);
+ Check(shell.IsVisible && text.Text == "分页参数无效" && status.Current.Level == StatusLevel.Error,
+ "Dispatcher 异常后主窗口继续运行并显示错误");
foreach ((StatusLevel level, string brush, int value) in new[] {
(StatusLevel.Info, "PrimaryTextBrush", 0), (StatusLevel.Warning, "WarningBrush", 50),
(StatusLevel.Error, "DangerBrush", 75), (StatusLevel.Success, "SuccessBrush", 100) })
@@ -90,7 +95,12 @@ class Program
Check(text.Text == "完成" && progress.Visibility == Visibility.Collapsed, "纯文本状态隐藏进度");
using var usersClient = new StoreApiClient(new ApiClientOptions { BaseUrl = new Uri("http://localhost:14312/api/v1/") }, httpClient);
usersClient.Session.Clear();
- var usersVm = new UserManagementPageViewModel(usersClient, app.Container.Resolve(), status);
+ var usersExtension = new Prism.Autofac.AutofacContainerExtension();
+ usersExtension.RegisterInstance(typeof(StoreApiClient), usersClient);
+ usersExtension.FinalizeExtension();
+ using var usersContainer = usersExtension.Instance;
+ var userService = usersContainer.Resolve();
+ var usersVm = new UserManagementPageViewModel(userService, app.Container.Resolve(), status);
await usersVm.SearchCommand.Execute();
Check(status.Current.Level == StatusLevel.Error && usersVm.CanInteract, "查询失败后显示错误并允许重试");
await usersClient.Auth.LoginAsync(new() { UserName = "admin", Password = "320BB1D43EC042096A83DBAC6DC9294A" });
@@ -100,6 +110,7 @@ class Program
Check(usersVm.CanInteract && usersVm.Total == 51 && status.Current.Level == StatusLevel.Success, "重试查询成功");
await usersVm.PageCommand.Execute(new DataGridPageRequest(2, 50));
Check(usersVm.Page == 2 && usersVm.CanInteract && status.Current.Level == StatusLevel.Success, "分页查询成功");
+ await userService.DeleteAsync(1);
Console.WriteLine("PASS: status bar bindings, colors and background progress reports.");
return;
}
@@ -230,6 +241,13 @@ sealed class LoginHandler : HttpMessageHandler
Content = new StringContent("""{"code":200,"data":{"total":51,"items":[{"id":1,"userName":"admin"}]}}""", Encoding.UTF8, "application/json")
};
}
+ if (request.Method == HttpMethod.Post && request.RequestUri?.PathAndQuery == "/api/v1/user/DelUser?id=1")
+ {
+ return new HttpResponseMessage(HttpStatusCode.OK)
+ {
+ Content = new StringContent("""{"code":200}""", Encoding.UTF8, "application/json")
+ };
+ }
if (request.RequestUri?.AbsoluteUri != "http://localhost:14312/api/v1/Auth/Login")
throw new Exception("登录路由错误");
var body = System.Text.Json.JsonDocument.Parse(await request.Content!.ReadAsStringAsync(cancellationToken));
@@ -258,4 +276,4 @@ sealed class TestApp(StoreApiClient client) : App
base.RegisterTypes(containerRegistry);
containerRegistry.RegisterInstance(client);
}
-}
\ No newline at end of file
+}