A:调整页面展示和引入方式
This commit is contained in:
1 parent
4ec76b7855
commit
dd0681ba90
10 files changed
+786
-517
No files matched your search
@@ -1,9 +1,13 @@
|
||||
<Application x:Class="Server.App"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="clr-namespace:Server"
|
||||
StartupUri="MainWindow.xaml">
|
||||
xmlns:local="clr-namespace:Server">
|
||||
<Application.Resources>
|
||||
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="pack://application:,,,/HandyControl;component/Themes/SkinDefault.xaml" />
|
||||
<ResourceDictionary Source="pack://application:,,,/HandyControl;component/Themes/Theme.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</Application.Resources>
|
||||
</Application>
|
||||
+120
-83
@@ -1,12 +1,19 @@
|
||||
using System.Windows;
|
||||
using System;
|
||||
using System.Windows;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Config;
|
||||
using Sharp.Log;
|
||||
using Sharp.Log.Extensions;
|
||||
using Sharp.Data;
|
||||
using Sharp.Data.Context;
|
||||
using Sharp.Wpf.Services;
|
||||
using Sharp.Wpf.Abstractions;
|
||||
using Server.Config;
|
||||
using Server.Services;
|
||||
using Server.Data;
|
||||
using Server.ViewModels;
|
||||
using Server.Views;
|
||||
|
||||
namespace Server
|
||||
{
|
||||
@@ -15,32 +22,124 @@ namespace Server
|
||||
/// </summary>
|
||||
public partial class App : Application
|
||||
{
|
||||
public ConfigManager? ConfigManager { get; private set; }
|
||||
public AppConfig? AppConfig { get; private set; }
|
||||
public ILoggerFactory? LoggerFactory { get; private set; }
|
||||
public ServerService? ServerService { get; private set; }
|
||||
public IDataContext? DataContext { get; private set; }
|
||||
private ServiceProvider? _serviceProvider;
|
||||
|
||||
protected override void OnStartup(StartupEventArgs e)
|
||||
{
|
||||
base.OnStartup(e);
|
||||
|
||||
// 1. Global Exception Handling
|
||||
DispatcherUnhandledException += (s, args) =>
|
||||
{
|
||||
// Safe logging: handle case where DI failed
|
||||
if (_serviceProvider != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var logger = _serviceProvider.GetService<ILogger<App>>();
|
||||
logger?.LogError(args.Exception, "UI线程未处理异常");
|
||||
}
|
||||
catch { /* Ignore logging failure during crash */ }
|
||||
}
|
||||
|
||||
MessageBox.Show($"发生未处理异常: {args.Exception.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
args.Handled = true; // Prevent crash
|
||||
};
|
||||
|
||||
TaskScheduler.UnobservedTaskException += (s, args) =>
|
||||
{
|
||||
if (_serviceProvider != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var logger = _serviceProvider.GetService<ILogger<App>>();
|
||||
logger?.LogError(args.Exception, "任务未观察到的异常");
|
||||
}
|
||||
catch { /* Ignore */ }
|
||||
}
|
||||
args.SetObserved();
|
||||
};
|
||||
|
||||
AppDomain.CurrentDomain.UnhandledException += (s, args) =>
|
||||
{
|
||||
var ex = args.ExceptionObject as Exception;
|
||||
if (_serviceProvider != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var logger = _serviceProvider.GetService<ILogger<App>>();
|
||||
logger?.LogCritical(ex, "AppDomain未处理异常 (即将终止: {IsTerminating})", args.IsTerminating);
|
||||
}
|
||||
catch { /* Ignore */ }
|
||||
}
|
||||
|
||||
if (args.IsTerminating)
|
||||
{
|
||||
MessageBox.Show($"发生严重错误,程序即将退出: {ex?.Message}", "严重错误", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
// 1. 初始化配置
|
||||
InitializeConfiguration();
|
||||
// 2. 构建配置
|
||||
var cm = ConfigDefaults.BuildDefault(defaults =>
|
||||
{
|
||||
defaults.MainFile = "appsettings.json";
|
||||
defaults.IncludeEnvironmentVariables = true;
|
||||
}, configure =>
|
||||
{
|
||||
configure.ReloadOnChange = true;
|
||||
});
|
||||
|
||||
// 2. 初始化日志系统
|
||||
InitializeLogging();
|
||||
// 3. 构建依赖注入容器
|
||||
var services = new ServiceCollection();
|
||||
|
||||
// 3. 初始化数据库
|
||||
InitializeDatabase();
|
||||
// 配置管理
|
||||
services.AddSingleton(cm);
|
||||
services.AddSingleton<IConfiguration>(cm.Configuration);
|
||||
var appConfig = cm.Get<AppConfig>(validate: true);
|
||||
services.AddSingleton(appConfig);
|
||||
services.AddSingleton(appConfig.Server);
|
||||
|
||||
// 4. 初始化服务器服务
|
||||
InitializeServerService();
|
||||
// 日志
|
||||
services.AddLogging(b => b.AddSharpLog());
|
||||
|
||||
var logger = LoggerFactory?.CreateLogger<App>();
|
||||
logger?.LogInformation("应用程序启动成功");
|
||||
// 数据库
|
||||
services.AddSingleton<IDataContext>(sp =>
|
||||
{
|
||||
var builder = new DataContextBuilder<AppDbContext>();
|
||||
builder.UseSqlite("Data Source=server.db")
|
||||
.UseAudit(true)
|
||||
.UseSoftDelete(true)
|
||||
.UseCurrentUser("system")
|
||||
.EnableDetailedErrors(true);
|
||||
return builder.Build();
|
||||
});
|
||||
|
||||
// 服务器服务
|
||||
services.AddSingleton<ServerService>();
|
||||
|
||||
// WPF服务
|
||||
services.AddSingleton<IDialogService, DialogService>();
|
||||
services.AddSingleton<IDispatcher>(new DispatcherService(Dispatcher));
|
||||
|
||||
// ViewModels
|
||||
services.AddTransient<MainViewModel>();
|
||||
services.AddTransient<UserManagementViewModel>();
|
||||
|
||||
// Windows
|
||||
services.AddTransient<MainWindow>();
|
||||
services.AddTransient<UserManagementWindow>();
|
||||
|
||||
_serviceProvider = services.BuildServiceProvider();
|
||||
|
||||
// 4. 显示主窗口
|
||||
var mainWindow = _serviceProvider.GetRequiredService<MainWindow>();
|
||||
Current.MainWindow = mainWindow;
|
||||
mainWindow.Show();
|
||||
|
||||
var logger = _serviceProvider.GetRequiredService<ILogger<App>>();
|
||||
logger.LogInformation("应用程序启动成功");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -54,75 +153,14 @@ namespace Server
|
||||
}
|
||||
}
|
||||
|
||||
private void InitializeConfiguration()
|
||||
{
|
||||
ConfigManager = ConfigDefaults.BuildDefault();
|
||||
AppConfig = ConfigManager.Get<AppConfig>(validate: true);
|
||||
}
|
||||
|
||||
private void InitializeLogging()
|
||||
{
|
||||
if (ConfigManager == null)
|
||||
{
|
||||
throw new InvalidOperationException("配置未初始化");
|
||||
}
|
||||
|
||||
LoggerFactory = SharpLogDefaults.CreateLoggerFactory(
|
||||
configuration: ConfigManager.Configuration,
|
||||
configure: options =>
|
||||
{
|
||||
options.Enrich.Application ??= "SharpServer";
|
||||
});
|
||||
}
|
||||
|
||||
private void InitializeDatabase()
|
||||
{
|
||||
if (LoggerFactory == null)
|
||||
{
|
||||
throw new InvalidOperationException("日志系统未初始化");
|
||||
}
|
||||
|
||||
var logger = LoggerFactory.CreateLogger<App>();
|
||||
logger.LogInformation("正在初始化数据库...");
|
||||
|
||||
try
|
||||
{
|
||||
var builder = new DataContextBuilder<AppDbContext>();
|
||||
builder.UseSqlite("Data Source=server.db")
|
||||
.UseAudit(true)
|
||||
.UseSoftDelete(true)
|
||||
.UseCurrentUser("system")
|
||||
.EnableDetailedErrors(true);
|
||||
|
||||
DataContext = builder.Build();
|
||||
|
||||
logger.LogInformation("数据库初始化成功");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "数据库初始化失败");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private void InitializeServerService()
|
||||
{
|
||||
if (AppConfig == null || LoggerFactory == null)
|
||||
{
|
||||
throw new InvalidOperationException("配置或日志系统未初始化");
|
||||
}
|
||||
|
||||
ServerService = new ServerService(AppConfig, LoggerFactory);
|
||||
}
|
||||
|
||||
protected override void OnExit(ExitEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
ServerService?.Dispose();
|
||||
DataContext?.Dispose();
|
||||
_serviceProvider?.GetService<ServerService>()?.Dispose();
|
||||
_serviceProvider?.GetService<IDataContext>()?.Dispose();
|
||||
|
||||
var logger = LoggerFactory?.CreateLogger<App>();
|
||||
var logger = _serviceProvider?.GetService<ILogger<App>>();
|
||||
logger?.LogInformation("应用程序正常退出");
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -132,11 +170,10 @@ namespace Server
|
||||
}
|
||||
finally
|
||||
{
|
||||
LoggerFactory?.Dispose();
|
||||
_serviceProvider?.Dispose();
|
||||
}
|
||||
|
||||
base.OnExit(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
<Window x:Class="Server.MainWindow"
|
||||
<hc:GlowWindow x:Class="Server.MainWindow"
|
||||
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:local="clr-namespace:Server"
|
||||
xmlns:hc="https://handyorg.github.io/handycontrol"
|
||||
mc:Ignorable="d"
|
||||
Title="Sharp HTTP Server" Height="600" Width="900"
|
||||
WindowStartupLocation="CenterScreen">
|
||||
@@ -38,25 +39,33 @@
|
||||
|
||||
<!-- 服务器信息 -->
|
||||
<TextBlock Grid.Row="0" Grid.Column="0" Text="服务器地址:" VerticalAlignment="Center" Margin="0,0,10,5"/>
|
||||
<TextBlock Grid.Row="0" Grid.Column="1" x:Name="ServerUrlTextBlock" Text="http://localhost:8080"
|
||||
<TextBlock Grid.Row="0" Grid.Column="1" Text="{Binding ServerUrl}"
|
||||
FontWeight="Bold" VerticalAlignment="Center" Margin="0,0,0,5"/>
|
||||
|
||||
<TextBlock Grid.Row="1" Grid.Column="0" Text="服务器状态:" VerticalAlignment="Center" Margin="0,0,10,5"/>
|
||||
<TextBlock Grid.Row="1" Grid.Column="1" x:Name="StatusTextBlock" Text="未启动"
|
||||
Foreground="Gray" FontWeight="Bold" VerticalAlignment="Center" Margin="0,0,0,5"/>
|
||||
<TextBlock Grid.Row="1" Grid.Column="1" Text="{Binding StatusText}"
|
||||
Foreground="{Binding StatusColor}" FontWeight="Bold" VerticalAlignment="Center" Margin="0,0,0,5"/>
|
||||
|
||||
<!-- 控制按钮 -->
|
||||
<StackPanel Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="2" Orientation="Horizontal" Margin="0,10,0,0">
|
||||
<Button x:Name="StartButton" Content="启动服务器" Width="100" Height="32" Margin="0,0,10,0" Click="StartButton_Click"
|
||||
Background="#4CAF50" Foreground="White" BorderThickness="0" FontWeight="Bold" Cursor="Hand"/>
|
||||
<Button x:Name="StopButton" Content="停止服务器" Width="100" Height="32" Margin="0,0,10,0" Click="StopButton_Click"
|
||||
Background="#F44336" Foreground="White" BorderThickness="0" FontWeight="Bold" Cursor="Hand" IsEnabled="False"/>
|
||||
<Button x:Name="RestartButton" Content="重启服务器" Width="100" Height="32" Margin="0,0,10,0" Click="RestartButton_Click"
|
||||
Background="#FF9800" Foreground="White" BorderThickness="0" FontWeight="Bold" Cursor="Hand" IsEnabled="False"/>
|
||||
<Button x:Name="DataManagementButton" Content="数据管理" Width="100" Height="32" Margin="0,0,10,0" Click="DataManagementButton_Click"
|
||||
Background="#673AB7" Foreground="White" BorderThickness="0" FontWeight="Bold" Cursor="Hand"/>
|
||||
<Button x:Name="ClearLogButton" Content="清空日志" Width="100" Height="32" Click="ClearLogButton_Click"
|
||||
Background="#9E9E9E" Foreground="White" BorderThickness="0" FontWeight="Bold" Cursor="Hand"/>
|
||||
<Button Content="➕ 启动服务器" Width="120" Height="32" Margin="0,0,10,0"
|
||||
Command="{Binding StartCommand}"
|
||||
IsEnabled="{Binding CanStart}"
|
||||
Style="{StaticResource ButtonSuccess}"/>
|
||||
<Button Content="⏹ 停止服务器" Width="120" Height="32" Margin="0,0,10,0"
|
||||
Command="{Binding StopCommand}"
|
||||
IsEnabled="{Binding IsRunning}"
|
||||
Style="{StaticResource ButtonDanger}"/>
|
||||
<Button Content="🔄 重启服务器" Width="120" Height="32" Margin="0,0,10,0"
|
||||
Command="{Binding RestartCommand}"
|
||||
IsEnabled="{Binding IsRunning}"
|
||||
Style="{StaticResource ButtonWarning}"/>
|
||||
<Button Content="📊 数据管理" Width="120" Height="32" Margin="0,0,10,0"
|
||||
Command="{Binding OpenDataManagementCommand}"
|
||||
Style="{StaticResource ButtonPrimary}"/>
|
||||
<Button Content="🗑 清空日志" Width="100" Height="32"
|
||||
Command="{Binding ClearLogCommand}"
|
||||
Style="{StaticResource ButtonDefault}"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Border>
|
||||
@@ -72,7 +81,7 @@
|
||||
<TextBlock Grid.Row="0" Text="日志信息" FontSize="14" FontWeight="Bold" Margin="0,0,0,10"/>
|
||||
|
||||
<ScrollViewer Grid.Row="1" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto">
|
||||
<TextBox x:Name="LogTextBox"
|
||||
<TextBox Text="{Binding LogText, Mode=OneWay}"
|
||||
IsReadOnly="True"
|
||||
TextWrapping="Wrap"
|
||||
Background="#F5F5F5"
|
||||
@@ -110,5 +119,4 @@
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</Grid>
|
||||
</Window>
|
||||
|
||||
</hc:GlowWindow>
|
||||
@@ -1,203 +1,24 @@
|
||||
using System.Windows;
|
||||
using Server.Services;
|
||||
using Server.Config;
|
||||
using Server.Views;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using HandyControl.Controls;
|
||||
using Server.ViewModels;
|
||||
|
||||
namespace Server
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for MainWindow.xaml
|
||||
/// </summary>
|
||||
public partial class MainWindow : Window
|
||||
public partial class MainWindow : GlowWindow
|
||||
{
|
||||
private ServerService? _serverService;
|
||||
private AppConfig? _config;
|
||||
private ILogger<MainWindow>? _logger;
|
||||
|
||||
public MainWindow()
|
||||
public MainWindow(MainViewModel viewModel)
|
||||
{
|
||||
InitializeComponent();
|
||||
Loaded += MainWindow_Loaded;
|
||||
Closing += MainWindow_Closing;
|
||||
}
|
||||
DataContext = viewModel;
|
||||
|
||||
private void MainWindow_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
try
|
||||
// Handle window closing event
|
||||
Closing += (s, e) =>
|
||||
{
|
||||
// 从App获取依赖
|
||||
var app = (App)Application.Current;
|
||||
_config = app.AppConfig;
|
||||
_logger = app.LoggerFactory?.CreateLogger<MainWindow>();
|
||||
_serverService = app.ServerService;
|
||||
|
||||
if (_config != null && _serverService != null)
|
||||
{
|
||||
// 更新UI显示服务器地址
|
||||
ServerUrlTextBlock.Text = $"http://{_config.Server.Host}:{_config.Server.Port}";
|
||||
|
||||
// 订阅服务器状态变化事件
|
||||
_serverService.StatusChanged += OnServerStatusChanged;
|
||||
|
||||
AppendLog("应用程序已启动");
|
||||
AppendLog($"配置已加载: {_config.Server.Host}:{_config.Server.Port}");
|
||||
}
|
||||
else
|
||||
{
|
||||
AppendLog("错误: 无法初始化服务器服务");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger?.LogError(ex, "MainWindow初始化失败");
|
||||
AppendLog($"初始化失败: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private void MainWindow_Closing(object? sender, System.ComponentModel.CancelEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_serverService?.IsRunning == true)
|
||||
{
|
||||
var result = MessageBox.Show(
|
||||
"服务器正在运行,是否停止服务器并退出?",
|
||||
"确认退出",
|
||||
MessageBoxButton.YesNo,
|
||||
MessageBoxImage.Question);
|
||||
|
||||
if (result == MessageBoxResult.Yes)
|
||||
{
|
||||
_serverService.Stop();
|
||||
AppendLog("服务器已停止");
|
||||
}
|
||||
else
|
||||
{
|
||||
e.Cancel = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger?.LogError(ex, "关闭窗口时出错");
|
||||
AppendLog($"关闭出错: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private void StartButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
_serverService?.Start();
|
||||
UpdateButtonStates(isRunning: true);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger?.LogError(ex, "启动服务器失败");
|
||||
AppendLog($"启动失败: {ex.Message}");
|
||||
MessageBox.Show($"启动服务器失败: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private void StopButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
_serverService?.Stop();
|
||||
UpdateButtonStates(isRunning: false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger?.LogError(ex, "停止服务器失败");
|
||||
AppendLog($"停止失败: {ex.Message}");
|
||||
MessageBox.Show($"停止服务器失败: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private void RestartButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
_serverService?.Restart();
|
||||
UpdateButtonStates(isRunning: true);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger?.LogError(ex, "重启服务器失败");
|
||||
AppendLog($"重启失败: {ex.Message}");
|
||||
MessageBox.Show($"重启服务器失败: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private void ClearLogButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
LogTextBox.Clear();
|
||||
}
|
||||
|
||||
private void DataManagementButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger?.LogInformation("打开数据管理窗口");
|
||||
var window = new UserManagementWindow
|
||||
{
|
||||
Owner = this
|
||||
};
|
||||
window.ShowDialog();
|
||||
_logger?.LogInformation("数据管理窗口已关闭");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger?.LogError(ex, "打开数据管理窗口失败");
|
||||
AppendLog($"打开数据管理窗口失败: {ex.Message}");
|
||||
MessageBox.Show($"打开数据管理窗口失败: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnServerStatusChanged(object? sender, string status)
|
||||
{
|
||||
Dispatcher.Invoke(() =>
|
||||
{
|
||||
StatusTextBlock.Text = status;
|
||||
|
||||
// 根据状态更新颜色
|
||||
if (status.Contains("已启动"))
|
||||
{
|
||||
StatusTextBlock.Foreground = System.Windows.Media.Brushes.Green;
|
||||
}
|
||||
else if (status.Contains("已停止"))
|
||||
{
|
||||
StatusTextBlock.Foreground = System.Windows.Media.Brushes.Gray;
|
||||
}
|
||||
else if (status.Contains("失败") || status.Contains("错误"))
|
||||
{
|
||||
StatusTextBlock.Foreground = System.Windows.Media.Brushes.Red;
|
||||
}
|
||||
else
|
||||
{
|
||||
StatusTextBlock.Foreground = System.Windows.Media.Brushes.Orange;
|
||||
}
|
||||
|
||||
AppendLog(status);
|
||||
});
|
||||
}
|
||||
|
||||
private void UpdateButtonStates(bool isRunning)
|
||||
{
|
||||
StartButton.IsEnabled = !isRunning;
|
||||
StopButton.IsEnabled = isRunning;
|
||||
RestartButton.IsEnabled = isRunning;
|
||||
}
|
||||
|
||||
private void AppendLog(string message)
|
||||
{
|
||||
Dispatcher.Invoke(() =>
|
||||
{
|
||||
var timestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
|
||||
LogTextBox.AppendText($"[{timestamp}] {message}\n");
|
||||
LogTextBox.ScrollToEnd();
|
||||
});
|
||||
// Check if we need to stop the server before closing
|
||||
e.Cancel = !viewModel.CheckBeforeClose();
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -28,6 +28,7 @@
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="9.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="9.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.0" />
|
||||
<PackageReference Include="HandyControl" Version="3.5.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -0,0 +1,278 @@
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Sharp.Wpf.Mvvm;
|
||||
using Sharp.Wpf.Commands;
|
||||
using Server.Config;
|
||||
using Server.Services;
|
||||
using Server.Views;
|
||||
|
||||
namespace Server.ViewModels
|
||||
{
|
||||
/// <summary>
|
||||
/// 主窗口ViewModel
|
||||
/// </summary>
|
||||
public class MainViewModel : ViewModelBase
|
||||
{
|
||||
private readonly ServerService _serverService;
|
||||
private readonly ILogger<MainViewModel> _logger;
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
|
||||
private string _statusText = "未启动";
|
||||
private Brush _statusColor = Brushes.Gray;
|
||||
private string _logText = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// </summary>
|
||||
public MainViewModel(
|
||||
ServerService serverService,
|
||||
AppConfig config,
|
||||
ILogger<MainViewModel> logger,
|
||||
IServiceProvider serviceProvider)
|
||||
{
|
||||
_serverService = serverService ?? throw new ArgumentNullException(nameof(serverService));
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
_serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider));
|
||||
|
||||
ServerUrl = $"http://{config.Server.Host}:{config.Server.Port}";
|
||||
|
||||
// 初始化命令
|
||||
StartCommand = new RelayCommand(_ => Start(), _ => CanStart);
|
||||
StopCommand = new RelayCommand(_ => Stop(), _ => IsRunning);
|
||||
RestartCommand = new RelayCommand(_ => Restart(), _ => IsRunning);
|
||||
ClearLogCommand = new RelayCommand(_ => ClearLog());
|
||||
OpenDataManagementCommand = new RelayCommand(_ => OpenDataManagement());
|
||||
|
||||
// 订阅服务器状态变化事件
|
||||
_serverService.StatusChanged += OnServerStatusChanged;
|
||||
|
||||
AppendLog("应用程序已启动");
|
||||
AppendLog($"配置已加载: {config.Server.Host}:{config.Server.Port}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 服务器URL
|
||||
/// </summary>
|
||||
public string ServerUrl { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 状态文本
|
||||
/// </summary>
|
||||
public string StatusText
|
||||
{
|
||||
get => _statusText;
|
||||
set => SetProperty(ref _statusText, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 状态颜色
|
||||
/// </summary>
|
||||
public Brush StatusColor
|
||||
{
|
||||
get => _statusColor;
|
||||
set => SetProperty(ref _statusColor, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 日志文本
|
||||
/// </summary>
|
||||
public string LogText
|
||||
{
|
||||
get => _logText;
|
||||
set => SetProperty(ref _logText, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 服务器是否正在运行
|
||||
/// </summary>
|
||||
public bool IsRunning => _serverService.IsRunning;
|
||||
|
||||
/// <summary>
|
||||
/// 能否启动服务器
|
||||
/// </summary>
|
||||
public bool CanStart => !IsRunning;
|
||||
|
||||
/// <summary>
|
||||
/// 启动命令
|
||||
/// </summary>
|
||||
public ICommand StartCommand { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 停止命令
|
||||
/// </summary>
|
||||
public ICommand StopCommand { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 重启命令
|
||||
/// </summary>
|
||||
public ICommand RestartCommand { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 清除日志命令
|
||||
/// </summary>
|
||||
public ICommand ClearLogCommand { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 打开数据管理命令
|
||||
/// </summary>
|
||||
public ICommand OpenDataManagementCommand { get; }
|
||||
|
||||
private void Start()
|
||||
{
|
||||
try
|
||||
{
|
||||
_serverService.Start();
|
||||
UpdateButtonStates();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "启动服务器失败");
|
||||
AppendLog($"启动失败: {ex.Message}");
|
||||
MessageBox.Show($"启动服务器失败: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private void Stop()
|
||||
{
|
||||
try
|
||||
{
|
||||
_serverService.Stop();
|
||||
UpdateButtonStates();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "停止服务器失败");
|
||||
AppendLog($"停止失败: {ex.Message}");
|
||||
MessageBox.Show($"停止服务器失败: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private void Restart()
|
||||
{
|
||||
try
|
||||
{
|
||||
_serverService.Restart();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "重启服务器失败");
|
||||
AppendLog($"重启失败: {ex.Message}");
|
||||
MessageBox.Show($"重启服务器失败: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private void ClearLog()
|
||||
{
|
||||
LogText = string.Empty;
|
||||
}
|
||||
|
||||
private void OpenDataManagement()
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.LogInformation("打开数据管理窗口");
|
||||
var window = _serviceProvider.GetRequiredService<UserManagementWindow>();
|
||||
window.Owner = Application.Current.MainWindow;
|
||||
window.ShowDialog();
|
||||
_logger.LogInformation("数据管理窗口已关闭");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "打开数据管理窗口失败");
|
||||
AppendLog($"打开数据管理窗口失败: {ex.Message}");
|
||||
MessageBox.Show($"打开数据管理窗口失败: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnServerStatusChanged(object? sender, string status)
|
||||
{
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
StatusText = status;
|
||||
|
||||
// 根据状态更新颜色
|
||||
if (status.Contains("已启动"))
|
||||
{
|
||||
StatusColor = Brushes.Green;
|
||||
}
|
||||
else if (status.Contains("已停止"))
|
||||
{
|
||||
StatusColor = Brushes.Gray;
|
||||
}
|
||||
else if (status.Contains("失败") || status.Contains("错误"))
|
||||
{
|
||||
StatusColor = Brushes.Red;
|
||||
}
|
||||
else
|
||||
{
|
||||
StatusColor = Brushes.Orange;
|
||||
}
|
||||
|
||||
UpdateButtonStates();
|
||||
AppendLog(status);
|
||||
});
|
||||
}
|
||||
|
||||
private void UpdateButtonStates()
|
||||
{
|
||||
OnPropertyChanged(nameof(IsRunning));
|
||||
OnPropertyChanged(nameof(CanStart));
|
||||
}
|
||||
|
||||
private void AppendLog(string message)
|
||||
{
|
||||
var timestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
|
||||
LogText += $"[{timestamp}] {message}\n";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查是否需要停止服务器
|
||||
/// </summary>
|
||||
/// <returns>true表示可以关闭,false表示用户取消</returns>
|
||||
public bool CheckBeforeClose()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_serverService.IsRunning)
|
||||
{
|
||||
var result = MessageBox.Show(
|
||||
"服务器正在运行,是否停止服务器并退出?",
|
||||
"确认退出",
|
||||
MessageBoxButton.YesNo,
|
||||
MessageBoxImage.Question);
|
||||
|
||||
if (result == MessageBoxResult.Yes)
|
||||
{
|
||||
_serverService.Stop();
|
||||
AppendLog("服务器已停止");
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "关闭窗口时出错");
|
||||
AppendLog($"关闭出错: {ex.Message}");
|
||||
return true; // 即使出错也允许关闭
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 释放资源
|
||||
/// </summary>
|
||||
public override void Dispose()
|
||||
{
|
||||
_serverService.StatusChanged -= OnServerStatusChanged;
|
||||
base.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,308 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Sharp.Data.Context;
|
||||
using Sharp.Wpf.Mvvm;
|
||||
using Sharp.Wpf.Commands;
|
||||
using Sharp.Wpf.Abstractions;
|
||||
using Sharp.Wpf.Services;
|
||||
using Server.Data;
|
||||
using Server.Views;
|
||||
|
||||
namespace Server.ViewModels
|
||||
{
|
||||
/// <summary>
|
||||
/// 用户管理ViewModel
|
||||
/// </summary>
|
||||
public class UserManagementViewModel : ViewModelBase
|
||||
{
|
||||
private readonly IDataContext _dataContext;
|
||||
private readonly ILogger<UserManagementViewModel> _logger;
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
private readonly IDialogService _dialogService;
|
||||
|
||||
private User? _selectedUser;
|
||||
private string _searchText = string.Empty;
|
||||
private string _statusText = string.Empty;
|
||||
private string _recordCount = "记录数: 0";
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// </summary>
|
||||
public UserManagementViewModel(
|
||||
IDataContext dataContext,
|
||||
ILogger<UserManagementViewModel> logger,
|
||||
IServiceProvider serviceProvider,
|
||||
IDialogService dialogService)
|
||||
{
|
||||
_dataContext = dataContext ?? throw new ArgumentNullException(nameof(dataContext));
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
_serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider));
|
||||
_dialogService = dialogService ?? throw new ArgumentNullException(nameof(dialogService));
|
||||
|
||||
Users = new ObservableCollection<User>();
|
||||
|
||||
// 初始化命令
|
||||
LoadDataCommand = new AsyncCommand(async () => await LoadDataAsync());
|
||||
SearchCommand = new AsyncCommand(async () => await LoadDataAsync(SearchText.Trim()));
|
||||
ClearSearchCommand = new AsyncCommand(async () =>
|
||||
{
|
||||
SearchText = string.Empty;
|
||||
await LoadDataAsync();
|
||||
});
|
||||
AddCommand = new RelayCommand(_ => Add());
|
||||
EditCommand = new RelayCommand(_ => Edit(), _ => SelectedUser != null);
|
||||
DeleteCommand = new AsyncCommand(async () => await DeleteAsync(), () => SelectedUser != null);
|
||||
RefreshCommand = new AsyncCommand(async () => await LoadDataAsync(SearchText.Trim()));
|
||||
|
||||
// 加载初始数据
|
||||
_ = LoadDataAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 用户列表
|
||||
/// </summary>
|
||||
public ObservableCollection<User> Users { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 选中的用户
|
||||
/// </summary>
|
||||
public User? SelectedUser
|
||||
{
|
||||
get => _selectedUser;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _selectedUser, value))
|
||||
{
|
||||
((RelayCommand)EditCommand).RaiseCanExecuteChanged();
|
||||
((AsyncCommand)DeleteCommand).RaiseCanExecuteChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 搜索文本
|
||||
/// </summary>
|
||||
public string SearchText
|
||||
{
|
||||
get => _searchText;
|
||||
set => SetProperty(ref _searchText, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 状态文本
|
||||
/// </summary>
|
||||
public string StatusText
|
||||
{
|
||||
get => _statusText;
|
||||
set => SetProperty(ref _statusText, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 记录数
|
||||
/// </summary>
|
||||
public string RecordCount
|
||||
{
|
||||
get => _recordCount;
|
||||
set => SetProperty(ref _recordCount, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 加载数据命令
|
||||
/// </summary>
|
||||
public ICommand LoadDataCommand { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 搜索命令
|
||||
/// </summary>
|
||||
public ICommand SearchCommand { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 清除搜索命令
|
||||
/// </summary>
|
||||
public ICommand ClearSearchCommand { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 添加命令
|
||||
/// </summary>
|
||||
public ICommand AddCommand { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 编辑命令
|
||||
/// </summary>
|
||||
public ICommand EditCommand { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 删除命令
|
||||
/// </summary>
|
||||
public ICommand DeleteCommand { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 刷新命令
|
||||
/// </summary>
|
||||
public ICommand RefreshCommand { get; }
|
||||
|
||||
private async Task LoadDataAsync(string? searchText = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
StatusText = "正在加载数据...";
|
||||
_logger.LogInformation("开始加载用户数据,搜索条件: {SearchText}", searchText ?? "无");
|
||||
|
||||
var repo = _dataContext.Repository<User>();
|
||||
|
||||
List<User> users;
|
||||
if (string.IsNullOrWhiteSpace(searchText))
|
||||
{
|
||||
users = await repo.GetAllAsync();
|
||||
}
|
||||
else
|
||||
{
|
||||
users = await repo.FindAsync(u =>
|
||||
u.UserName.Contains(searchText) ||
|
||||
u.Email.Contains(searchText) ||
|
||||
(u.Remarks != null && u.Remarks.Contains(searchText)));
|
||||
}
|
||||
|
||||
Users.Clear();
|
||||
foreach (var user in users.OrderByDescending(u => u.CreatedAt))
|
||||
{
|
||||
Users.Add(user);
|
||||
}
|
||||
|
||||
RecordCount = $"记录数: {users.Count}";
|
||||
StatusText = string.IsNullOrWhiteSpace(searchText)
|
||||
? $"加载完成,共 {users.Count} 条记录"
|
||||
: $"搜索完成,找到 {users.Count} 条记录";
|
||||
|
||||
_logger.LogInformation("加载完成,共 {Count} 条记录", users.Count);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "加载数据失败");
|
||||
StatusText = "加载失败";
|
||||
await _dialogService.ShowErrorAsync($"加载数据失败: {ex.Message}", "错误");
|
||||
}
|
||||
}
|
||||
|
||||
private void Add()
|
||||
{
|
||||
var dialog = new UserEditDialog();
|
||||
if (dialog.ShowDialog() == true)
|
||||
{
|
||||
_ = SaveUserAsync(dialog.UserData);
|
||||
}
|
||||
}
|
||||
|
||||
private void Edit()
|
||||
{
|
||||
if (SelectedUser == null)
|
||||
{
|
||||
_ = _dialogService.ShowInfoAsync("请选择要编辑的用户", "提示");
|
||||
return;
|
||||
}
|
||||
|
||||
var dialog = new UserEditDialog(SelectedUser);
|
||||
if (dialog.ShowDialog() == true)
|
||||
{
|
||||
_ = UpdateUserAsync(dialog.UserData);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task DeleteAsync()
|
||||
{
|
||||
if (SelectedUser == null)
|
||||
{
|
||||
await _dialogService.ShowInfoAsync("请选择要删除的用户", "提示");
|
||||
return;
|
||||
}
|
||||
|
||||
var confirmed = await _dialogService.ShowConfirmAsync(
|
||||
$"确定要删除用户 '{SelectedUser.UserName}' 吗?\n\n(注意:这是软删除,数据会被标记为已删除但不会真正删除)",
|
||||
"确认删除");
|
||||
|
||||
if (confirmed)
|
||||
{
|
||||
await DeleteUserAsync(SelectedUser.Id);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SaveUserAsync(User user)
|
||||
{
|
||||
try
|
||||
{
|
||||
StatusText = "正在保存...";
|
||||
_logger.LogInformation("新增用户: {UserName}", user.UserName);
|
||||
|
||||
var repo = _dataContext.Repository<User>();
|
||||
await repo.AddAsync(user);
|
||||
await _dataContext.SaveChangesAsync();
|
||||
|
||||
_logger.LogInformation("用户新增成功: {UserId}", user.Id);
|
||||
await _dialogService.ShowInfoAsync("用户新增成功!", "成功");
|
||||
|
||||
await LoadDataAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "新增用户失败");
|
||||
StatusText = "保存失败";
|
||||
await _dialogService.ShowErrorAsync($"新增用户失败: {ex.Message}", "错误");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task UpdateUserAsync(User user)
|
||||
{
|
||||
try
|
||||
{
|
||||
StatusText = "正在更新...";
|
||||
_logger.LogInformation("更新用户: {UserId}", user.Id);
|
||||
|
||||
var repo = _dataContext.Repository<User>();
|
||||
repo.Update(user);
|
||||
await _dataContext.SaveChangesAsync();
|
||||
|
||||
_logger.LogInformation("用户更新成功: {UserId}", user.Id);
|
||||
await _dialogService.ShowInfoAsync("用户更新成功!", "成功");
|
||||
|
||||
await LoadDataAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "更新用户失败");
|
||||
StatusText = "更新失败";
|
||||
await _dialogService.ShowErrorAsync($"更新用户失败: {ex.Message}", "错误");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task DeleteUserAsync(Guid userId)
|
||||
{
|
||||
try
|
||||
{
|
||||
StatusText = "正在删除...";
|
||||
_logger.LogInformation("软删除用户: {UserId}", userId);
|
||||
|
||||
var repo = _dataContext.Repository<User>();
|
||||
await repo.RemoveAsync(userId); // 软删除
|
||||
await _dataContext.SaveChangesAsync();
|
||||
|
||||
_logger.LogInformation("用户软删除成功: {UserId}", userId);
|
||||
await _dialogService.ShowInfoAsync("用户已标记为删除(软删除)!", "成功");
|
||||
|
||||
await LoadDataAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "删除用户失败");
|
||||
StatusText = "删除失败";
|
||||
await _dialogService.ShowErrorAsync($"删除用户失败: {ex.Message}", "错误");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,9 @@
|
||||
<Window x:Class="Server.Views.UserManagementWindow"
|
||||
<hc:GlowWindow x:Class="Server.Views.UserManagementWindow"
|
||||
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:hc="https://handyorg.github.io/handycontrol"
|
||||
mc:Ignorable="d"
|
||||
Title="用户数据管理 - Sharp.Data 示例" Height="700" Width="1200"
|
||||
WindowStartupLocation="CenterScreen">
|
||||
@@ -34,35 +35,43 @@
|
||||
<!-- 搜索框 -->
|
||||
<StackPanel Grid.Column="0" Orientation="Horizontal" Margin="0,0,15,0">
|
||||
<TextBlock Text="搜索:" VerticalAlignment="Center" Margin="0,0,10,0"/>
|
||||
<TextBox x:Name="SearchTextBox" Width="250" Height="32" VerticalContentAlignment="Center"
|
||||
<TextBox Text="{Binding SearchText, UpdateSourceTrigger=PropertyChanged}"
|
||||
Width="250" Height="32" VerticalContentAlignment="Center"
|
||||
Padding="8,0" BorderBrush="#BDBDBD" BorderThickness="1"/>
|
||||
<Button Content="搜索" Width="80" Height="32" Margin="10,0,0,0" Click="SearchButton_Click"
|
||||
Background="#2196F3" Foreground="White" BorderThickness="0" FontWeight="Bold" Cursor="Hand"/>
|
||||
<Button Content="清空" Width="80" Height="32" Margin="5,0,0,0" Click="ClearSearchButton_Click"
|
||||
Background="#9E9E9E" Foreground="White" BorderThickness="0" FontWeight="Bold" Cursor="Hand"/>
|
||||
<Button Content="搜索" Width="80" Height="32" Margin="10,0,0,0"
|
||||
Command="{Binding SearchCommand}"
|
||||
Style="{StaticResource ButtonPrimary}"/>
|
||||
<Button Content="清空" Width="80" Height="32" Margin="5,0,0,0"
|
||||
Command="{Binding ClearSearchCommand}"
|
||||
Style="{StaticResource ButtonDefault}"/>
|
||||
</StackPanel>
|
||||
|
||||
<!-- 状态信息 -->
|
||||
<TextBlock Grid.Column="1" x:Name="StatusTextBlock" Text="就绪" VerticalAlignment="Center"
|
||||
<TextBlock Grid.Column="1" Text="{Binding StatusText}" VerticalAlignment="Center"
|
||||
Foreground="#666" Margin="15,0"/>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<StackPanel Grid.Column="2" Orientation="Horizontal">
|
||||
<Button Content="➕ 新增" Width="90" Height="32" Margin="0,0,5,0" Click="AddButton_Click"
|
||||
Background="#4CAF50" Foreground="White" BorderThickness="0" FontWeight="Bold" Cursor="Hand"/>
|
||||
<Button Content="✏️ 编辑" Width="90" Height="32" Margin="0,0,5,0" Click="EditButton_Click"
|
||||
Background="#FF9800" Foreground="White" BorderThickness="0" FontWeight="Bold" Cursor="Hand"/>
|
||||
<Button Content="🗑️ 删除" Width="90" Height="32" Click="DeleteButton_Click"
|
||||
Background="#F44336" Foreground="White" BorderThickness="0" FontWeight="Bold" Cursor="Hand"/>
|
||||
<Button Content="🔄 刷新" Width="90" Height="32" Margin="5,0,0,0" Click="RefreshButton_Click"
|
||||
Background="#607D8B" Foreground="White" BorderThickness="0" FontWeight="Bold" Cursor="Hand"/>
|
||||
<Button Content="➕ 新增" Width="90" Height="32" Margin="0,0,5,0"
|
||||
Command="{Binding AddCommand}"
|
||||
Style="{StaticResource ButtonSuccess}"/>
|
||||
<Button Content="✏️ 编辑" Width="90" Height="32" Margin="0,0,5,0"
|
||||
Command="{Binding EditCommand}"
|
||||
Style="{StaticResource ButtonWarning}"/>
|
||||
<Button Content="🗑️ 删除" Width="90" Height="32"
|
||||
Command="{Binding DeleteCommand}"
|
||||
Style="{StaticResource ButtonDanger}"/>
|
||||
<Button Content="🔄 刷新" Width="90" Height="32" Margin="5,0,0,0"
|
||||
Command="{Binding RefreshCommand}"
|
||||
Style="{StaticResource ButtonInfo}"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<!-- 数据列表 -->
|
||||
<Border Grid.Row="2" BorderBrush="#E0E0E0" BorderThickness="1" CornerRadius="5" Padding="10">
|
||||
<DataGrid x:Name="UsersDataGrid"
|
||||
<DataGrid ItemsSource="{Binding Users}"
|
||||
SelectedItem="{Binding SelectedUser}"
|
||||
AutoGenerateColumns="False"
|
||||
IsReadOnly="True"
|
||||
SelectionMode="Single"
|
||||
@@ -109,9 +118,9 @@
|
||||
|
||||
<StackPanel Grid.Column="1" VerticalAlignment="Center">
|
||||
<TextBlock Text="数据库: SQLite (server.db)" FontSize="11" FontWeight="Bold"/>
|
||||
<TextBlock x:Name="RecordCountTextBlock" Text="记录数: 0" FontSize="11"/>
|
||||
<TextBlock Text="{Binding RecordCount}" FontSize="11"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Border>
|
||||
</Grid>
|
||||
</Window>
|
||||
</hc:GlowWindow>
|
||||
@@ -1,202 +1,17 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using Sharp.Data.Context;
|
||||
using Server.Data;
|
||||
using Common.ExpandFun;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using HandyControl.Controls;
|
||||
using Server.ViewModels;
|
||||
|
||||
namespace Server.Views;
|
||||
|
||||
public partial class UserManagementWindow : Window
|
||||
namespace Server.Views
|
||||
{
|
||||
private readonly IDataContext _dataContext;
|
||||
private readonly ILogger<UserManagementWindow> _logger;
|
||||
|
||||
public UserManagementWindow()
|
||||
/// <summary>
|
||||
/// Interaction logic for UserManagementWindow.xaml
|
||||
/// </summary>
|
||||
public partial class UserManagementWindow : GlowWindow
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
var app = (App)Application.Current;
|
||||
_dataContext = app.DataContext ?? throw new InvalidOperationException("数据库未初始化");
|
||||
_logger = app.LoggerFactory?.CreateLogger<UserManagementWindow>()
|
||||
?? throw new InvalidOperationException("日志系统未初始化");
|
||||
|
||||
Loaded += UserManagementWindow_Loaded;
|
||||
}
|
||||
|
||||
private async void UserManagementWindow_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
await LoadDataAsync();
|
||||
}
|
||||
|
||||
private async Task LoadDataAsync(string? searchText = null)
|
||||
{
|
||||
try
|
||||
public UserManagementWindow(UserManagementViewModel viewModel)
|
||||
{
|
||||
StatusTextBlock.Text = "正在加载数据...";
|
||||
_logger.LogInformation("开始加载用户数据,搜索条件: {SearchText}", searchText ?? "无");
|
||||
|
||||
var repo = _dataContext.Repository<User>();
|
||||
|
||||
List<User> users;
|
||||
if (string.IsNullOrWhiteSpace(searchText))
|
||||
{
|
||||
users = await repo.GetAllAsync();
|
||||
}
|
||||
else
|
||||
{
|
||||
users = await repo.FindAsync(u =>
|
||||
u.UserName.Contains(searchText) ||
|
||||
u.Email.Contains(searchText) ||
|
||||
(u.Remarks != null && u.Remarks.Contains(searchText)));
|
||||
}
|
||||
|
||||
UsersDataGrid.ItemsSource = users.OrderByDescending(u => u.CreatedAt).ToList();
|
||||
RecordCountTextBlock.Text = $"记录数: {users.Count}";
|
||||
StatusTextBlock.Text = string.IsNullOrWhiteSpace(searchText)
|
||||
? $"加载完成,共 {users.Count} 条记录"
|
||||
: $"搜索完成,找到 {users.Count} 条记录";
|
||||
|
||||
_logger.LogInformation("加载完成,共 {Count} 条记录", users.Count);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "加载数据失败");
|
||||
StatusTextBlock.Text = "加载失败";
|
||||
MessageBox.Show($"加载数据失败: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private async void SearchButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var searchText = SearchTextBox.Text.Trim();
|
||||
await LoadDataAsync(searchText);
|
||||
}
|
||||
|
||||
private async void ClearSearchButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
SearchTextBox.Clear();
|
||||
await LoadDataAsync();
|
||||
}
|
||||
|
||||
private void AddButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var dialog = new UserEditDialog();
|
||||
if (dialog.ShowDialog() == true)
|
||||
{
|
||||
_ = SaveUserAsync(dialog.UserData);
|
||||
}
|
||||
}
|
||||
|
||||
private void EditButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (UsersDataGrid.SelectedItem is not User selectedUser)
|
||||
{
|
||||
MessageBox.Show("请选择要编辑的用户", "提示", MessageBoxButton.OK, MessageBoxImage.Information);
|
||||
return;
|
||||
}
|
||||
|
||||
var dialog = new UserEditDialog(selectedUser);
|
||||
if (dialog.ShowDialog() == true)
|
||||
{
|
||||
_ = UpdateUserAsync(dialog.UserData);
|
||||
}
|
||||
}
|
||||
|
||||
private async void DeleteButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (UsersDataGrid.SelectedItem is not User selectedUser)
|
||||
{
|
||||
MessageBox.Show("请选择要删除的用户", "提示", MessageBoxButton.OK, MessageBoxImage.Information);
|
||||
return;
|
||||
}
|
||||
|
||||
var result = MessageBox.Show(
|
||||
$"确定要删除用户 '{selectedUser.UserName}' 吗?\n\n(注意:这是软删除,数据会被标记为已删除但不会真正删除)",
|
||||
"确认删除",
|
||||
MessageBoxButton.YesNo,
|
||||
MessageBoxImage.Question);
|
||||
|
||||
if (result == MessageBoxResult.Yes)
|
||||
{
|
||||
await DeleteUserAsync(selectedUser.Id);
|
||||
}
|
||||
}
|
||||
|
||||
private async void RefreshButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
await LoadDataAsync(SearchTextBox.Text.Trim());
|
||||
}
|
||||
|
||||
private async Task SaveUserAsync(User user)
|
||||
{
|
||||
try
|
||||
{
|
||||
StatusTextBlock.Text = "正在保存...";
|
||||
_logger.LogInformation("新增用户: {UserName}", user.UserName);
|
||||
|
||||
var repo = _dataContext.Repository<User>();
|
||||
await repo.AddAsync(user);
|
||||
await _dataContext.SaveChangesAsync();
|
||||
|
||||
_logger.LogInformation("用户新增成功: {UserId}", user.Id);
|
||||
MessageBox.Show("用户新增成功!", "成功", MessageBoxButton.OK, MessageBoxImage.Information);
|
||||
|
||||
await LoadDataAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "新增用户失败");
|
||||
StatusTextBlock.Text = "保存失败";
|
||||
MessageBox.Show($"新增用户失败: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task UpdateUserAsync(User user)
|
||||
{
|
||||
try
|
||||
{
|
||||
StatusTextBlock.Text = "正在更新...";
|
||||
_logger.LogInformation("更新用户: {UserId}", user.Id);
|
||||
|
||||
var repo = _dataContext.Repository<User>();
|
||||
repo.Update(user);
|
||||
await _dataContext.SaveChangesAsync();
|
||||
|
||||
_logger.LogInformation("用户更新成功: {UserId}", user.Id);
|
||||
MessageBox.Show("用户更新成功!", "成功", MessageBoxButton.OK, MessageBoxImage.Information);
|
||||
|
||||
await LoadDataAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "更新用户失败");
|
||||
StatusTextBlock.Text = "更新失败";
|
||||
MessageBox.Show($"更新用户失败: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task DeleteUserAsync(Guid userId)
|
||||
{
|
||||
try
|
||||
{
|
||||
StatusTextBlock.Text = "正在删除...";
|
||||
_logger.LogInformation("软删除用户: {UserId}", userId);
|
||||
|
||||
var repo = _dataContext.Repository<User>();
|
||||
await repo.RemoveAsync(userId); // 软删除
|
||||
await _dataContext.SaveChangesAsync();
|
||||
|
||||
_logger.LogInformation("用户软删除成功: {UserId}", userId);
|
||||
MessageBox.Show("用户已标记为删除(软删除)!", "成功", MessageBoxButton.OK, MessageBoxImage.Information);
|
||||
|
||||
await LoadDataAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "删除用户失败");
|
||||
StatusTextBlock.Text = "删除失败";
|
||||
MessageBox.Show($"删除用户失败: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
InitializeComponent();
|
||||
DataContext = viewModel;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -111,15 +111,3 @@ var folder = await new FileDialogService().SelectFolderAsync();
|
||||
- 若要禁用全局异常弹窗,可在 `ExceptionHandlingOptions` 中设置 `ShowDialog = false` 并仅使用 `Log` 钩子记录。
|
||||
- `EventToCommand` 通过反射订阅事件,适用于大多数 Routed 事件;如需传递事件参数,可设置 `PassEventArgsToCommand="True"`。
|
||||
|
||||
我们主要使用Data模块,Repository模块不是非常常用。增加你提到的功能,并在Client模块提供对应功能测试、使用案例,使用Sqlite数据库作为测试数据库。
|
||||
|
||||
对于NetServer增加JWT验证选项(可选使用),增加对于WebSocket的支持、API版本支持、健康端点检查、模型验证等,并且Server模块增加对应功能的测试、使用案例,在Client进行连接通信
|
||||
|
||||
对于PLC模块增加Modbus的支持,并在Server中提供使用示例。
|
||||
|
||||
Ports 模块增加ModbusRTU功能的支持、帧同步器、虚拟串口,并且在Client中增加对应功能的演示
|
||||
|
||||
|
||||
|
||||
这么多模块和功能,他们分别是客户端和服务端的功能,能不能让Client和Server集成他们需要集成的内容,尽可能的内容复用,各个模块之间也进行引用减少重复代码。从这个角度审视目前的所有的代码,并重新设计并在Client和Server中进行集成。并且
|
||||
Client和Server页面也使用HandyControl
|
||||
Reference in new issue
Block a user