增加MessageBoxService服务

This commit is contained in:
chfei committed 2026-11-13 10:16:43 +08:00
1 parent 3dcb6a1c34
commit 30a6a5ea24
15 files changed
+517 -153

No files matched your search

@@ -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">
<sys:Double x:Key="DataGridExFontSize">12</sys:Double>
@@ -498,6 +498,11 @@
</Setter>
</Style>
</Style.Resources>
<Setter Property="PageSizes">
<Setter.Value>
<Int32Collection>50,100,500,1000,2000</Int32Collection>
</Setter.Value>
</Setter>
<Setter Property="UseLayoutRounding" Value="True" />
<Setter Property="SnapsToDevicePixels" Value="True" />
<Setter Property="Background" Value="White" />
@@ -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<Exception>? 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<Exception>? 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;
}
/// <summary>返回 null 可允许多开;默认限制同一登录会话中的应用实例。</summary>
public virtual string? GetMutexName() => @"Local\Store.Management";
/// <summary>返回 null 可允许多开;默认限制同一登录会话中的应用实例。</summary>
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<IStatusBarService>();
MessageBoxService = Container?.Resolve<IMessageBoxService>();
}
protected override void OnInitialized()
{
base.OnInitialized();
IsAppInitialized = true;
NLogger.Instance.Info("软件初始化成功,进入主页面");
}
protected override void RegisterRequiredTypes(IContainerRegistry registry)
{
base.RegisterRequiredTypes(registry);
registry.RegisterDialogWindow<Controls.DialogWindow>();
}
protected override void RegisterRequiredTypes(IContainerRegistry registry)
{
base.RegisterRequiredTypes(registry);
registry.RegisterDialogWindow<Controls.DialogWindow>();
}
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();
}
/// <summary>先同步保存报告,再通知订阅者并关闭应用。</summary>
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<Exception> 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);
}
/// <summary>先同步保存报告,再通知订阅者并关闭应用。</summary>
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<Exception> 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);
}
/// <summary>通过 WPF 正常退出,执行退出清理。</summary>
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)));
}
/// <summary>通过 WPF 正常退出,执行退出清理。</summary>
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<StoreApiClient>().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<StoreApiClient>().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(); }
}
}
}
}
@@ -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);
}
}
@@ -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);
}
}
@@ -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;
}
}
}
}
@@ -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<Window>().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);
}
}
}
@@ -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));
}
}
}
@@ -0,0 +1,7 @@
namespace Store.Management.Framework
{
public enum MessageButton
{
OK, OKCancel, YesNoCancel, YesNo,
}
}
@@ -0,0 +1,8 @@
namespace Store.Management.Framework
{
public enum MessageIcon
{
None, Error, Question, Warning, Information,
Hand = Error, Stop = Error, Exclamation = Warning, Asterisk = Information
}
}
@@ -0,0 +1,7 @@
namespace Store.Management.Framework
{
public enum MessageResult
{
None, OK, Cancel, Yes, No,
}
}
@@ -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<PageResult<UserInfo>> SearchAsync(UserSearchRequest request, CancellationToken cancellationToken = default);
Task<UserInfo> CreateAsync(UserCreateRequest request, CancellationToken cancellationToken = default);
Task<UserInfo> UpdateAsync(UserUpdateRequest request, CancellationToken cancellationToken = default);
Task DeleteAsync(int id, CancellationToken cancellationToken = default);
}
internal sealed class UserService(StoreApiClient client) : IUserService, ISingletonDependency
{
public Task<PageResult<UserInfo>> SearchAsync(UserSearchRequest request, CancellationToken cancellationToken = default)
=> client.Users.SearchAsync(request, cancellationToken);
public Task<UserInfo> CreateAsync(UserCreateRequest request, CancellationToken cancellationToken = default)
=> client.Users.CreateAsync(request, cancellationToken);
public Task<UserInfo> UpdateAsync(UserUpdateRequest request, CancellationToken cancellationToken = default)
=> client.Users.UpdateAsync(request, cancellationToken);
public Task DeleteAsync(int id, CancellationToken cancellationToken = default)
=> client.Users.DeleteAsync(id, cancellationToken);
}
@@ -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)
@@ -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<UserInfo> result = await client.Users.SearchAsync(request);
PageResult<UserInfo> 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)
{
@@ -127,6 +127,9 @@
ShowRowNumber="True"
ShowToolbar="True"
TotalItemCount="{Binding TotalCount, Mode=OneWay}">
<!--<f:DataGridEx.PageSizes>
<Int32Collection>50,100,500,1000</Int32Collection>
</f:DataGridEx.PageSizes>-->
<f:DataGridEx.ToolbarContent>
<ItemsControl>
<ItemsControl.ItemsPanel>
@@ -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<IDialogService>(), status);
var usersExtension = new Prism.Autofac.AutofacContainerExtension();
usersExtension.RegisterInstance(typeof(StoreApiClient), usersClient);
usersExtension.FinalizeExtension();
using var usersContainer = usersExtension.Instance;
var userService = usersContainer.Resolve<IUserService>();
var usersVm = new UserManagementPageViewModel(userService, app.Container.Resolve<IDialogService>(), 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);
}
}
}