暂时更改一部分Skill

This commit is contained in:
chfei committed 2026-10-27 19:01:56 +08:00
1 parent 84b28f9672
commit 6d4629e4ac
53 files changed
+5293 -10

No files matched your search

+44 -1
View File
@@ -1,4 +1,5 @@
using Prism.Ioc;
using Prism.Services.Dialogs;
using MAX.Notification;
using Store.Management.Notifications;
using Store.Management.Framework;
@@ -6,12 +7,47 @@ using Store.Management.Services;
using Store.Management.ViewModels;
using Store.Management.Views;
using System.Windows;
using Store.Management.Api;
namespace Store.Management;
public partial class App : FrameworkApplication
{
protected override Window CreateShell() => Container.Resolve<MainWindow>();
private readonly StoreApiClient? _apiClient;
public App() { }
public App(StoreApiClient apiClient) => _apiClient = apiClient ?? throw new ArgumentNullException(nameof(apiClient));
private StoreApiClient CreateApiClient() => _apiClient ?? new(new ApiClientOptions
{
BaseUrl = new Uri("http://localhost:14312/api/")
});
protected override Window CreateShell() => null!;
protected override void OnInitialized()
{
base.OnInitialized();
// 登录弹窗关闭与主窗口创建之间不触发 WPF 的最后窗口退出。
ShutdownMode = ShutdownMode.OnExplicitShutdown;
Container.Resolve<IDialogService>().ShowDialog(
nameof(LoginWindow), new DialogParameters(), result =>
{
if (result.Result != ButtonResult.OK)
{
Shutdown();
return;
}
MainWindow = Container.Resolve<MainWindow>();
ShutdownMode = ShutdownMode.OnMainWindowClose;
MainWindow.Show();
});
}
protected override void RegisterTypes(IContainerRegistry registry)
{
registry.RegisterInstance(CreateApiClient());
registry.Register<LoginViewModel>(provider => new LoginViewModel(provider.Resolve<StoreApiClient>().Auth));
registry.RegisterSingleton<SingletonProbe>();
registry.Register<TransientProbe>();
registry.RegisterSingleton<NotificationExampleState>();
@@ -24,6 +60,13 @@ public partial class App : FrameworkApplication
new ProductCacheHandler(provider.Resolve<NotificationExampleState>()),
new ProductAuditHandler(provider.Resolve<NotificationExampleState>())
});
registry.RegisterDialog<LoginWindow, LoginViewModel>();
registry.RegisterDialog<FrameworkTestDialog, FrameworkTestDialogViewModel>();
}
protected override void OnExit(ExitEventArgs e)
{
try { Container?.Resolve<StoreApiClient>().Dispose(); }
finally { base.OnExit(e); }
}
}
@@ -0,0 +1,139 @@
using System.Security.Cryptography;
using System.Text;
using Prism.Commands;
using MAX.Framework.Controls;
using Prism.Services.Dialogs;
using System.Windows.Input;
using Store.Management.Api.Authentication;
using Store.Management.Api.Transport;
using Store.Management.Framework.ViewModels;
namespace Store.Management.ViewModels;
/// <summary>登录校验、认证请求和窗口生命周期内的请求取消。</summary>
public sealed class LoginViewModel(IAuthApi auth) : ConfirmDialogViewModel, IDisposable
{
private readonly IAuthApi _auth = auth ?? throw new ArgumentNullException(nameof(auth));
private readonly CancellationTokenSource _lifetime = new();
private string _userName = "";
private string _passwordHash = "";
private bool _disposed;
private bool _authenticated;
public const int InputMaxLength = 250;
public LoadingStatus LoadingStatus { get; } = new();
public string UserName { get => _userName; set => SetProperty(ref _userName, value); }
public bool CanEdit => !LoadingStatus.IsLoading && !_disposed && !_authenticated;
public string LoginButtonText => LoadingStatus.IsLoading ? "正在登录…" : "登录";
public event EventHandler? ClearPasswordRequested;
/// <summary>仅由密码控件桥接调用,不保存明文密码。</summary>
public void UpdatePassword(string password)
{
if (!CanEdit)
{
return;
}
// 服务端要求 UTF-8 密码的 32 位大写 MD5,保留首尾空格。
_passwordHash = string.IsNullOrEmpty(password)
? "" : Convert.ToHexString(MD5.HashData(Encoding.UTF8.GetBytes(password)));
}
public override ICommand ConfirmCommand => LoginCommand;
public override void OnDialogOpened(IDialogParameters parameters)
{
Title = "门店管理 · 登录";
}
public override void OnDialogClosed() => Dispose();
public AsyncDelegateCommand LoginCommand => field ??= new AsyncDelegateCommand(LoginAsync, () => CanEdit);
private async Task LoginAsync()
{
if (!CanEdit)
{
return;
}
if (string.IsNullOrWhiteSpace(UserName))
{
LoadingStatus.Error("请输入用户名。");
return;
}
if (string.IsNullOrEmpty(_passwordHash))
{
LoadingStatus.Error("请输入密码。");
return;
}
LoadingStatus.Initialize("正在验证账号,请稍候…");
NotifyStateChanged();
try
{
await _auth.LoginAsync(new LoginRequest
{
UserName = UserName.Trim(),
Password = _passwordHash
}, _lifetime.Token);
if (!_disposed)
{
_authenticated = true;
OnRequestClose(new DialogResult(ButtonResult.OK));
}
}
catch (OperationCanceledException) when (_disposed)
{
// 关闭窗口产生的取消无需提示。
}
catch (ApiException ex)
{
if (!_disposed)
{
LoadingStatus.Error(ex.Message);
}
}
catch (OperationCanceledException)
{
LoadingStatus.Error("登录已取消,请重试。");
}
catch (Exception)
{
if (!_disposed)
{
LoadingStatus.Error("登录失败,请稍后重试。");
}
}
finally
{
_passwordHash = "";
if (!_disposed)
{
LoadingStatus.Done();
ClearPasswordRequested?.Invoke(this, EventArgs.Empty);
NotifyStateChanged();
}
}
}
private void NotifyStateChanged()
{
RaisePropertyChanged(nameof(CanEdit));
RaisePropertyChanged(nameof(LoginButtonText));
LoginCommand.RaiseCanExecuteChanged();
}
public void Dispose()
{
if (_disposed)
{
return;
}
_disposed = true;
_passwordHash = "";
_lifetime.Cancel();
_lifetime.Dispose();
}
}
@@ -0,0 +1,105 @@
<UserControl
x:Class="Store.Management.Views.LoginWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="clr-namespace:Store.Management.ViewModels"
Width="520"
Height="620"
MinWidth="440"
MinHeight="540"
d:DataContext="{d:DesignInstance Type=vm:LoginViewModel}"
Background="{DynamicResource BackgroundBrush}"
Foreground="{DynamicResource PrimaryTextBrush}"
Loaded="OnLoaded"
Unloaded="OnUnloaded"
mc:Ignorable="d">
<ScrollViewer HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto">
<Grid Margin="32" VerticalAlignment="Center">
<StackPanel MaxWidth="360">
<!-- 系统标识与登录说明 -->
<TextBlock
FontSize="14"
FontWeight="SemiBold"
Foreground="{DynamicResource PrimaryBrush}"
Text="STORE MANAGEMENT" />
<TextBlock
Margin="0,12,0,0"
FontSize="28"
FontWeight="SemiBold"
Text="登录门店管理" />
<TextBlock
Margin="0,8,0,28"
Foreground="{DynamicResource SecondaryTextBrush}"
Text="请输入您的用户名和密码"
TextWrapping="Wrap" />
<!-- 登录表单:密码只保留在 PasswordBox 中 -->
<Border
Padding="24"
Background="{DynamicResource RegionBrush}"
BorderBrush="{DynamicResource BorderBrush}"
BorderThickness="1"
CornerRadius="8">
<StackPanel>
<Label
Padding="0,0,0,8"
Content="用户名"
Target="{Binding ElementName=UserNameBox, Mode=OneWay}" />
<TextBox
x:Name="UserNameBox"
Height="40"
VerticalContentAlignment="Center"
AutomationProperties.Name="用户名"
IsEnabled="{Binding CanEdit, Mode=OneWay}"
MaxLength="{x:Static vm:LoginViewModel.InputMaxLength}"
Text="{Binding UserName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
<Label
Padding="0,20,0,8"
Content="密码"
Target="{Binding ElementName=PasswordBox, Mode=OneWay}" />
<PasswordBox
x:Name="PasswordBox"
PasswordChanged="OnPasswordChanged"
Height="40"
VerticalContentAlignment="Center"
AutomationProperties.Name="密码"
IsEnabled="{Binding CanEdit, Mode=OneWay}"
MaxLength="{x:Static vm:LoginViewModel.InputMaxLength}"
/>
<TextBlock
Margin="0,12,0,0"
AutomationProperties.LiveSetting="Polite"
Foreground="{DynamicResource SecondaryTextBrush}"
Text="{Binding LoadingStatus.Message, Mode=OneWay}"
TextWrapping="Wrap" />
<!-- 保留错误提示空间,避免登录失败时表单跳动 -->
<TextBlock
x:Name="ErrorText"
MinHeight="42"
Margin="0,12,0,8"
AutomationProperties.LiveSetting="Polite"
Foreground="{DynamicResource DangerBrush}"
Text="{Binding LoadingStatus.ErrorMessage, Mode=OneWay}"
TextWrapping="Wrap" />
<Button
x:Name="LoginButton"
Height="40"
Command="{Binding LoginCommand, Mode=OneWay}"
Content="{Binding LoginButtonText, Mode=OneWay}"
IsDefault="True"
Style="{StaticResource ButtonPrimary}" />
</StackPanel>
</Border>
<TextBlock
Margin="0,20,0,0"
HorizontalAlignment="Center"
Foreground="{DynamicResource SecondaryTextBrush}"
Text="请使用管理员分配的账号登录"
TextWrapping="Wrap" />
</StackPanel>
</Grid>
</ScrollViewer>
</UserControl>
@@ -0,0 +1,42 @@
using Store.Management.ViewModels;
using System.Windows;
using System.Windows.Controls;
namespace Store.Management.Views;
public partial class LoginWindow : UserControl
{
public LoginWindow() => InitializeComponent();
private void OnLoaded(object sender, RoutedEventArgs e)
{
if (DataContext is LoginViewModel viewModel)
{
viewModel.ClearPasswordRequested += OnClearPasswordRequested;
}
UserNameBox.Focus();
}
private void OnPasswordChanged(object sender, RoutedEventArgs e)
{
if (DataContext is LoginViewModel viewModel)
{
viewModel.UpdatePassword(PasswordBox.Password);
}
}
private void OnClearPasswordRequested(object? sender, EventArgs e)
{
PasswordBox.Clear();
PasswordBox.Focus();
}
private void OnUnloaded(object sender, RoutedEventArgs e)
{
if (DataContext is LoginViewModel viewModel)
{
viewModel.ClearPasswordRequested -= OnClearPasswordRequested;
}
PasswordBox.Clear();
}
}