A:增加页面集中管理

This commit is contained in:
jimmy committed 2025-12-24 19:36:42 +08:00
1 parent 2943485a2a
commit 974d16833c
61 files changed
+7062 -1

No files matched your search

+7
View File
@@ -0,0 +1,7 @@
<Application x:Class="Sharp.Demo.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Application.Resources>
</Application.Resources>
</Application>
+233
View File
@@ -0,0 +1,233 @@
using System;
using System.Windows;
using System.Windows.Interop;
using System.Windows.Media;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Configuration;
using Common;
using Config;
using Sharp.Log.Extensions;
using Sharp.Wpf.Services;
using Sharp.Wpf.Abstractions;
using Sharp.Wpf.HandyControl.DependencyInjection;
using Sharp.Wpf.DevTools.DependencyInjection;
using Sharp.NetServer.DependencyInjection;
using NetClient.DependencyInjection;
using Plc.Siemens.DependencyInjection;
using Sharp.Process.DependencyInjection;
using Sharp.Python.DependencyInjection;
using Sharp.Data;
using Sharp.Data.Context;
using Sharp.Demo.ViewModels;
using Sharp.Demo.ViewModels.Pages;
using Sharp.Demo.Services;
using Sharp.Demo.Data;
using Sharp.Demo.Views;
namespace Sharp.Demo
{
/// <summary>
/// Sharp Framework Demo 应用程序
/// 模块化演示平台 - 每个页面展示一个 Sharp 模块的完整功能
/// </summary>
public partial class App : Application
{
private ServiceProvider? _serviceProvider;
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
// 设置页面渲染方式
RenderOptions.ProcessRenderMode = RenderMode.SoftwareOnly;
// === 1. 全局异常处理 ===
RegisterExceptionHandlers();
// === 2. 配置管理 ===
var cm = ConfigBuilder.BuildFromFiles(
new[] { "appsettings.json" },
reloadOnChange: true,
includeEnvironment: true,
basePath: AppContext.BaseDirectory
);
// === 3. 依赖注入容器 ===
var services = new ServiceCollection();
// 配置
services.AddSingleton(cm);
services.AddSingleton<IConfiguration>(cm.Configuration);
// 日志
services.AddLogging(b => b.AddSharpLog());
// 数据库
services.AddSingleton<IDataContext>(sp =>
{
var builder = new DataContextBuilder<AppDbContext>();
builder.UseSqlite("Data Source=demo.db");
builder.UseAudit(true);
builder.UseSoftDelete(true);
builder.UseCurrentUser("system");
builder.EnableDetailedErrors(true);
return builder.Build();
});
// 网络模块
services.AddNetClient(cm);
services.AddNetServer(cm);
// 工业控制
services.AddSiemensPlc(options =>
{
options.IpAddress = "127.0.0.1";
options.CpuType = S7.Net.CpuType.S71200;
options.MonitorInterval = 200;
});
// Process & Python
services.AddSharpProcess();
services.AddSharpPython();
// WPF 服务
services.AddSharpHandyControl(); // HandyControl 对话框服务
services.AddSharpDevTools(); // 开发者工具
services.AddSingleton<IDispatcher>(new DispatcherService(Dispatcher));
// 业务服务(单例)
services.AddSingleton<ServerService>();
services.AddSingleton<WindowManager>(); // 窗口管理器
// ViewModels(瞬态 - 每次导航创建新实例)
services.AddTransient<LauncherViewModel>(); // 启动器 ViewModel
services.AddTransient<MainViewModel>();
services.AddTransient<HomeViewModel>();
// P0 页面 ViewModels(优先实现)
services.AddTransient<HttpServerViewModel>();
services.AddTransient<DatabaseViewModel>();
services.AddTransient<PlcDebugViewModel>();
// P1 页面 ViewModels(已实现)
services.AddTransient<SerialPortViewModel>();
services.AddTransient<ProcessTestViewModel>();
// 新增页面 ViewModels
services.AddTransient<HttpClientViewModel>();
services.AddTransient<LogDemoViewModel>();
services.AddTransient<ConfigDemoViewModel>();
services.AddTransient<WpfServicesViewModel>();
// Views(瞬态)
services.AddTransient<LauncherWindow>(); // 启动器窗口
services.AddTransient<MainWindow>();
// 模块窗口(瞬态 - 每次打开创建新实例)
services.AddTransient<Sharp.Demo.Views.Windows.HttpServerWindow>();
services.AddTransient<Sharp.Demo.Views.Windows.HttpClientWindow>();
services.AddTransient<Sharp.Demo.Views.Windows.PlcDebugWindow>();
// TODO: 添加其他窗口注册
// services.AddTransient<Sharp.Demo.Views.Windows.DatabaseWindow>();
// services.AddTransient<Sharp.Demo.Views.Windows.SerialPortWindow>();
// etc...
// 模块窗口 ViewModels(瞬态)
services.AddTransient<Sharp.Demo.ViewModels.Windows.HttpServerViewModel>();
services.AddTransient<Sharp.Demo.ViewModels.Windows.HttpClientViewModel>();
services.AddTransient<Sharp.Demo.ViewModels.Windows.PlcDebugViewModel>();
// TODO: 添加其他 ViewModel 注册
// === 4. 构建容器 ===
_serviceProvider = services.BuildServiceProvider();
// === 5. 全局容器(兼容旧代码) ===
Global.RegisterSingleton<IServiceProvider>(_serviceProvider);
Global.RegisterSingleton(cm);
Global.RegisterSingleton(_serviceProvider.GetRequiredService<IDispatcher>());
// === 6. 显示启动器窗口 ===
var launcherWindow = _serviceProvider.GetRequiredService<LauncherWindow>();
Current.MainWindow = launcherWindow;
launcherWindow.Show();
}
/// <summary>
/// 注册全局异常处理
/// </summary>
private void RegisterExceptionHandlers()
{
// UI 线程未处理异常
DispatcherUnhandledException += (s, args) =>
{
if (_serviceProvider != null)
{
try
{
var logger = _serviceProvider.GetService<ILogger<App>>();
logger?.LogError(args.Exception, "UI 线程未处理异常");
}
catch { /* 忽略日志失败 */ }
}
MessageBox.Show(
$"发生未处理异常: {args.Exception.Message}",
"错误",
MessageBoxButton.OK,
MessageBoxImage.Error
);
args.Handled = true; // 防止崩溃
};
// Task 未观察异常
TaskScheduler.UnobservedTaskException += (s, args) =>
{
if (_serviceProvider != null)
{
try
{
var logger = _serviceProvider.GetService<ILogger<App>>();
logger?.LogError(args.Exception, "Task 未观察异常");
}
catch { /* 忽略 */ }
}
args.SetObserved();
};
// AppDomain 未处理异常
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 { /* 忽略 */ }
}
if (args.IsTerminating)
{
MessageBox.Show(
$"发生严重错误,程序即将退出: {ex?.Message}",
"严重错误",
MessageBoxButton.OK,
MessageBoxImage.Error
);
}
};
}
protected override void OnExit(ExitEventArgs e)
{
_serviceProvider?.Dispose();
base.OnExit(e);
}
}
}
+6
View File
@@ -0,0 +1,6 @@
using System.Windows;
[assembly: ThemeInfo(
ResourceDictionaryLocation.None,
ResourceDictionaryLocation.SourceAssembly
)]
@@ -0,0 +1,57 @@
using Sharp.NetServer.Attributes;
using Sharp.NetServer.Controllers;
using Sharp.NetServer.Models;
namespace Sharp.Demo.Controllers;
/// <summary>
/// 演示控制器 - 提供各种测试 API
/// </summary>
[Route("api/demo")]
public class DemoController : BaseController
{
/// <summary>
/// Hello World
/// GET /api/demo/hello
/// </summary>
[HttpGet("hello")]
public async Task GetHello(HttpContext context)
{
await WriteSuccessResponseAsync(context, new
{
message = "Hello from Sharp.Demo!",
timestamp = DateTime.Now
});
}
/// <summary>
/// Echo 测试
/// POST /api/demo/echo
/// </summary>
[HttpPost("echo")]
public async Task PostEcho(HttpContext context)
{
var body = await ReadRequestBodyAsync(context);
await WriteSuccessResponseAsync(context, new
{
echo = body,
length = body.Length,
timestamp = DateTime.Now
});
}
/// <summary>
/// 获取服务器时间
/// GET /api/demo/time
/// </summary>
[HttpGet("time")]
public async Task GetTime(HttpContext context)
{
await WriteSuccessResponseAsync(context, new
{
utc = DateTime.UtcNow,
local = DateTime.Now,
timezone = TimeZoneInfo.Local.DisplayName
});
}
}
@@ -0,0 +1,51 @@
using Sharp.NetServer.Attributes;
using Sharp.NetServer.Controllers;
using Sharp.NetServer.Models;
namespace Sharp.Demo.Controllers;
/// <summary>
/// 健康检查控制器 - 提供服务器健康状态 API
/// </summary>
[Route("api/health")]
public class HealthController : BaseController
{
/// <summary>
/// 健康检查端点
/// GET /api/health
/// </summary>
[HttpGet]
public async Task GetHealth(HttpContext context)
{
await WriteSuccessResponseAsync(context, new
{
status = "healthy",
timestamp = DateTime.Now,
version = "1.0.0",
server = "Sharp.Demo"
});
}
/// <summary>
/// 详细健康检查
/// GET /api/health/detailed
/// </summary>
[HttpGet("detailed")]
public async Task GetDetailedHealth(HttpContext context)
{
await WriteSuccessResponseAsync(context, new
{
status = "healthy",
timestamp = DateTime.Now,
version = "1.0.0",
server = "Sharp.Demo",
uptime = DateTime.Now.ToString(),
checks = new
{
database = "ok",
memory = "ok",
disk = "ok"
}
});
}
}
+106
View File
@@ -0,0 +1,106 @@
using System;
using Microsoft.EntityFrameworkCore;
using Sharp.Data.Entities;
namespace Sharp.Demo.Data
{
/// <summary>
/// 示例用户实体 - 使用审计基类
/// </summary>
public class User : AuditableEntity
{
/// <summary>
/// 用户名
/// </summary>
public string UserName { get; set; } = string.Empty;
/// <summary>
/// 邮箱
/// </summary>
public string Email { get; set; } = string.Empty;
/// <summary>
/// 年龄
/// </summary>
public int Age { get; set; }
/// <summary>
/// 是否激活
/// </summary>
public bool IsActive { get; set; } = true;
/// <summary>
/// 备注
/// </summary>
public string? Remarks { get; set; }
}
/// <summary>
/// 应用数据库上下文
/// </summary>
public class AppDbContext : DbContext
{
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options)
{
}
public DbSet<User> Users => Set<User>();
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
// 配置 User 实体
modelBuilder.Entity<User>(entity =>
{
entity.HasKey(e => e.Id);
entity.Property(e => e.UserName).IsRequired().HasMaxLength(50);
entity.Property(e => e.Email).IsRequired().HasMaxLength(100);
entity.Property(e => e.Remarks).HasMaxLength(500);
// 添加索引
entity.HasIndex(e => e.UserName);
entity.HasIndex(e => e.Email).IsUnique();
entity.HasIndex(e => e.IsActive);
entity.HasIndex(e => e.CreatedAt);
});
// 添加示例数据
modelBuilder.Entity<User>().HasData(
new User
{
Id = Guid.NewGuid(),
UserName = "admin",
Email = "admin@example.com",
Age = 30,
IsActive = true,
Remarks = "系统管理员",
CreatedAt = DateTime.UtcNow,
CreatedBy = "system"
},
new User
{
Id = Guid.NewGuid(),
UserName = "zhangsan",
Email = "zhangsan@example.com",
Age = 25,
IsActive = true,
Remarks = "测试用户1",
CreatedAt = DateTime.UtcNow,
CreatedBy = "system"
},
new User
{
Id = Guid.NewGuid(),
UserName = "lisi",
Email = "lisi@example.com",
Age = 28,
IsActive = true,
Remarks = "测试用户2",
CreatedAt = DateTime.UtcNow,
CreatedBy = "system"
}
);
}
}
}
+208
View File
@@ -0,0 +1,208 @@
<hc:GlowWindow x:Class="Sharp.Demo.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:hc="https://handyorg.github.io/handycontrol"
xmlns:vm="clr-namespace:Sharp.Demo.ViewModels.Pages"
xmlns:pages="clr-namespace:Sharp.Demo.Views.Pages"
mc:Ignorable="d"
Title="Sharp Framework Demo"
Height="720"
Width="1280"
WindowStartupLocation="CenterScreen">
<!-- 快捷键绑定 -->
<hc:GlowWindow.InputBindings>
<KeyBinding Key="F12" Command="{Binding OpenDevToolsCommand}" />
<KeyBinding Key="F11" Command="{Binding OpenLogViewerCommand}" />
<KeyBinding Key="F10" Command="{Binding OpenPerformanceMonitorCommand}" />
</hc:GlowWindow.InputBindings>
<!-- 页面 ViewModel 到 View 的映射 -->
<hc:GlowWindow.Resources>
<DataTemplate DataType="{x:Type vm:HomeViewModel}">
<pages:HomePage />
</DataTemplate>
<DataTemplate DataType="{x:Type vm:HttpServerViewModel}">
<pages:HttpServerPage />
</DataTemplate>
<DataTemplate DataType="{x:Type vm:DatabaseViewModel}">
<pages:DatabasePage />
</DataTemplate>
<DataTemplate DataType="{x:Type vm:PlcDebugViewModel}">
<pages:PlcDebugPage />
</DataTemplate>
<DataTemplate DataType="{x:Type vm:SerialPortViewModel}">
<pages:SerialPortPage />
</DataTemplate>
<DataTemplate DataType="{x:Type vm:ProcessTestViewModel}">
<pages:ProcessTestPage />
</DataTemplate>
<DataTemplate DataType="{x:Type vm:HttpClientViewModel}">
<pages:HttpClientPage />
</DataTemplate>
<DataTemplate DataType="{x:Type vm:LogDemoViewModel}">
<pages:LogDemoPage />
</DataTemplate>
<DataTemplate DataType="{x:Type vm:ConfigDemoViewModel}">
<pages:ConfigDemoPage />
</DataTemplate>
<DataTemplate DataType="{x:Type vm:WpfServicesViewModel}">
<pages:WpfServicesPage />
</DataTemplate>
<!-- 后续页面映射会在这里添加 -->
</hc:GlowWindow.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="220" />
<!-- 左侧导航 -->
<ColumnDefinition Width="Auto" />
<!-- 分隔线 -->
<ColumnDefinition Width="*" />
<!-- 右侧内容 -->
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
<!-- 状态栏 -->
</Grid.RowDefinitions>
<!-- 左侧导航面板 -->
<Border
Grid.Row="0"
Grid.Column="0"
Background="{DynamicResource RegionBrush}"
BorderBrush="{DynamicResource BorderBrush}"
BorderThickness="0,0,1,0">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<!-- Logo/标题 -->
<RowDefinition Height="*" />
<!-- 导航菜单 -->
<RowDefinition Height="Auto" />
<!-- 底部工具 -->
</Grid.RowDefinitions>
<!-- Logo 区域 -->
<StackPanel Grid.Row="0" Margin="20,30,20,20">
<TextBlock
FontSize="20"
FontWeight="Bold"
Text="Sharp Framework" />
<TextBlock
Margin="0,5,0,0"
FontSize="12"
Foreground="{DynamicResource SecondaryTextBrush}"
Text="模块演示平台" />
</StackPanel>
<!-- 导航菜单(TreeView) -->
<TreeView
Grid.Row="1"
BorderThickness="0"
Margin="10,0"
SelectedItemChanged="TreeView_SelectedItemChanged">
<!-- 概览 -->
<TreeViewItem Header="概览" IsExpanded="True">
<TreeViewItem Header="主页" Tag="Home" />
</TreeViewItem>
<!-- 网络通信 -->
<TreeViewItem Header="网络通信" IsExpanded="True">
<TreeViewItem Header="HTTP 服务器" Tag="HttpServer" />
<TreeViewItem Header="HTTP 客户端" Tag="HttpClient" />
</TreeViewItem>
<!-- 数据管理 -->
<TreeViewItem Header="数据管理" IsExpanded="True">
<TreeViewItem Header="数据库 (EF Core)" Tag="Database" />
</TreeViewItem>
<!-- 工业控制 -->
<TreeViewItem Header="工业控制" IsExpanded="True">
<TreeViewItem Header="PLC 调试" Tag="PlcDebug" />
<TreeViewItem Header="串口调试" Tag="SerialPort" />
</TreeViewItem>
<!-- 进程管理 -->
<TreeViewItem Header="进程管理" IsExpanded="False">
<TreeViewItem Header="Process 执行" Tag="ProcessTest" />
<TreeViewItem Header="Python 集成" Tag="PythonTest" />
</TreeViewItem>
<!-- 基础设施 -->
<TreeViewItem Header="基础设施" IsExpanded="False">
<TreeViewItem Header="日志管理" Tag="LogDemo" />
<TreeViewItem Header="配置管理" Tag="ConfigDemo" />
<TreeViewItem Header="WPF 服务" Tag="WpfServices" />
</TreeViewItem>
</TreeView>
<!-- 底部工具栏 -->
<StackPanel Grid.Row="2" Margin="15,10">
<Button
Margin="0,0,0,5"
Command="{Binding OpenDevToolsCommand}"
Content="开发者工具 (F12)" />
<Button
Margin="0,0,0,5"
Command="{Binding OpenLogViewerCommand}"
Content="日志查看 (F11)" />
<Button
Command="{Binding OpenPerformanceMonitorCommand}"
Content="性能监控 (F10)" />
</StackPanel>
</Grid>
</Border>
<!-- 分隔线 -->
<GridSplitter
Grid.Row="0"
Grid.Column="1"
Width="1"
HorizontalAlignment="Stretch"
Background="{DynamicResource BorderBrush}" />
<!-- 右侧内容区域 -->
<Border
Grid.Row="0"
Grid.Column="2"
Background="{DynamicResource SecondaryRegionBrush}">
<ContentControl Content="{Binding CurrentPage}" Margin="20" />
</Border>
<!-- 状态栏 -->
<StatusBar
Grid.Row="1"
Grid.Column="0"
Grid.ColumnSpan="3"
Background="{DynamicResource RegionBrush}"
BorderBrush="{DynamicResource BorderBrush}"
BorderThickness="0,1,0,0">
<StatusBarItem>
<TextBlock>
<Run Text="HTTP Server: " />
<Run FontWeight="Bold" Text="{Binding ServerStatus, Mode=OneWay}" />
</TextBlock>
</StatusBarItem>
<Separator />
<StatusBarItem>
<TextBlock>
<Run Text="PLC: " />
<Run FontWeight="Bold" Text="{Binding PlcStatus, Mode=OneWay}" />
</TextBlock>
</StatusBarItem>
<Separator />
<StatusBarItem>
<TextBlock>
<Run Text="当前页面: " />
<Run Text="{Binding CurrentPageName, Mode=OneWay}" />
</TextBlock>
</StatusBarItem>
</StatusBar>
</Grid>
</hc:GlowWindow>
+33
View File
@@ -0,0 +1,33 @@
using System.Windows;
using System.Windows.Controls;
using HandyControl.Controls;
using Sharp.Demo.ViewModels;
namespace Sharp.Demo
{
/// <summary>
/// MainWindow.xaml 的交互逻辑
/// </summary>
public partial class MainWindow : GlowWindow
{
public MainWindow(MainViewModel viewModel)
{
InitializeComponent();
DataContext = viewModel;
}
/// <summary>
/// TreeView 选择变化事件处理
/// </summary>
private void TreeView_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
if (e.NewValue is TreeViewItem { Tag: string pageKey } && pageKey != "Category")
{
if (DataContext is MainViewModel vm)
{
vm.NavigateCommand.Execute(pageKey);
}
}
}
}
}
+50
View File
@@ -0,0 +1,50 @@
using System.Windows.Input;
namespace Sharp.Demo.Models
{
/// <summary>
/// 模块信息 - 描述一个功能模块
/// </summary>
public class ModuleInfo
{
/// <summary>
/// 模块唯一标识
/// </summary>
public string Key { get; set; } = string.Empty;
/// <summary>
/// 模块名称
/// </summary>
public string Name { get; set; } = string.Empty;
/// <summary>
/// 模块描述
/// </summary>
public string Description { get; set; } = string.Empty;
/// <summary>
/// 模块图标(Emoji 或字体图标)
/// </summary>
public string Icon { get; set; } = "📦";
/// <summary>
/// 模块分类
/// </summary>
public string Category { get; set; } = "其他";
/// <summary>
/// 是否已启用
/// </summary>
public bool IsEnabled { get; set; } = true;
/// <summary>
/// 当前状态描述
/// </summary>
public string Status { get; set; } = "就绪";
/// <summary>
/// 打开窗口的命令
/// </summary>
public ICommand? OpenCommand { get; set; }
}
}
@@ -0,0 +1,166 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Configuration;
using Sharp.NetServer;
using Sharp.NetServer.Server;
namespace Sharp.Demo.Services
{
/// <summary>
/// HTTP 服务器服务 - 管理服务器生命周期
/// </summary>
public class ServerService : IDisposable
{
private HttpServer? _httpServer;
private Task? _serverTask;
private CancellationTokenSource? _cts;
private readonly IConfiguration _configuration;
private readonly ILoggerFactory _loggerFactory;
private readonly ILogger<ServerService> _logger;
public bool IsRunning { get; private set; }
public string Url => $"http://{Host}:{Port}";
public string Host { get; private set; } = "localhost";
public int Port { get; private set; } = 8080;
public event EventHandler<string>? StatusChanged;
public ServerService(IConfiguration configuration, ILoggerFactory loggerFactory)
{
_configuration = configuration ?? throw new ArgumentNullException(nameof(configuration));
_loggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory));
_logger = loggerFactory.CreateLogger<ServerService>();
// 从配置读取服务器设置
Host = _configuration.GetValue<string>("Server:Host") ?? "localhost";
Port = _configuration.GetValue<int>("Server:Port", 8080);
}
/// <summary>
/// 启动服务器
/// </summary>
public void Start()
{
try
{
if (IsRunning)
{
_logger.LogWarning("服务器已经在运行");
return;
}
_logger.LogInformation("正在启动 HTTP 服务器: {Url}", Url);
OnStatusChanged($"正在启动服务器 {Url}...");
var builder = new HttpServerBuilder()
.UseUrls(Url + "/")
.ConfigureServices(services =>
{
// 注册配置和日志工厂
services.AddSingleton(_configuration);
services.AddSingleton(_loggerFactory);
})
// 注册控制器(从当前程序集)
.AddControllers<ServerService>();
_httpServer = builder.Build();
// 在后台启动服务器
_cts = new CancellationTokenSource();
_serverTask = Task.Run(async () =>
{
try
{
await _httpServer.StartAsync(_cts.Token);
}
catch (Exception ex)
{
_logger.LogError(ex, "服务器任务错误");
OnStatusChanged($"服务器运行错误: {ex.Message}");
IsRunning = false;
}
}, _cts.Token);
// 等待服务器启动
Thread.Sleep(500);
IsRunning = true;
_logger.LogInformation("HTTP 服务器启动成功: {Url}", Url);
OnStatusChanged($"服务器已启动: {Url}");
}
catch (Exception ex)
{
IsRunning = false;
_logger.LogError(ex, "HTTP 服务器启动失败");
OnStatusChanged($"启动失败: {ex.Message}");
throw;
}
}
/// <summary>
/// 停止服务器
/// </summary>
public void Stop()
{
try
{
if (!IsRunning)
{
_logger.LogWarning("服务器未运行");
return;
}
_logger.LogInformation("正在停止 HTTP 服务器");
OnStatusChanged("正在停止服务器...");
_cts?.Cancel();
_httpServer?.Stop();
_serverTask?.Wait(TimeSpan.FromSeconds(5));
IsRunning = false;
_logger.LogInformation("HTTP 服务器停止成功");
OnStatusChanged("服务器已停止");
}
catch (Exception ex)
{
_logger.LogError(ex, "HTTP 服务器停止失败");
OnStatusChanged($"停止失败: {ex.Message}");
throw;
}
}
/// <summary>
/// 重启服务器
/// </summary>
public void Restart()
{
_logger.LogInformation("正在重启 HTTP 服务器");
OnStatusChanged("正在重启服务器...");
Stop();
Thread.Sleep(500); // 等待端口释放
Start();
OnStatusChanged("服务器已重启");
}
private void OnStatusChanged(string status)
{
StatusChanged?.Invoke(this, status);
}
public void Dispose()
{
if (IsRunning)
{
Stop();
}
_cts?.Dispose();
_serverTask?.Dispose();
}
}
}
@@ -0,0 +1,145 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace Sharp.Demo.Services
{
/// <summary>
/// 窗口管理器 - 管理所有模块窗口的生命周期
/// </summary>
public class WindowManager
{
private readonly IServiceProvider _serviceProvider;
private readonly ILogger<WindowManager> _logger;
private readonly Dictionary<string, Window> _openWindows = new();
public WindowManager(IServiceProvider serviceProvider, ILogger<WindowManager> logger)
{
_serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_logger.LogInformation("窗口管理器已初始化");
}
/// <summary>
/// 打开或激活指定模块的窗口(单例模式)
/// </summary>
/// <typeparam name="TWindow">窗口类型</typeparam>
/// <param name="moduleKey">模块唯一标识</param>
/// <param name="ownerWindow">父窗口(可选)</param>
public void ShowWindow<TWindow>(string moduleKey, Window? ownerWindow = null) where TWindow : Window
{
try
{
// 如果窗口已打开,激活它
if (_openWindows.TryGetValue(moduleKey, out var existingWindow))
{
_logger.LogInformation("激活已打开的窗口: {ModuleKey} ({WindowType})", moduleKey, typeof(TWindow).Name);
ActivateWindow(existingWindow);
return;
}
_logger.LogInformation("正在创建新窗口: {ModuleKey} ({WindowType})", moduleKey, typeof(TWindow).Name);
// 创建新窗口
var window = _serviceProvider.GetRequiredService<TWindow>();
if (ownerWindow != null)
{
window.Owner = ownerWindow;
}
// 注册窗口关闭事件
window.Closed += (s, e) => OnWindowClosed(moduleKey);
// 保存窗口引用
_openWindows[moduleKey] = window;
// 显示窗口
window.Show();
_logger.LogInformation("窗口已成功打开: {ModuleKey}, 当前打开窗口数: {Count}", moduleKey, _openWindows.Count);
}
catch (Exception ex)
{
_logger.LogError(ex, "打开窗口失败: {ModuleKey} ({WindowType})", moduleKey, typeof(TWindow).Name);
throw;
}
}
/// <summary>
/// 关闭指定模块的窗口
/// </summary>
public void CloseWindow(string moduleKey)
{
if (_openWindows.TryGetValue(moduleKey, out var window))
{
_logger.LogInformation("正在关闭窗口: {ModuleKey}", moduleKey);
window.Close();
}
else
{
_logger.LogWarning("尝试关闭不存在的窗口: {ModuleKey}", moduleKey);
}
}
/// <summary>
/// 关闭所有窗口
/// </summary>
public void CloseAllWindows()
{
_logger.LogInformation("正在关闭所有窗口, 当前打开窗口数: {Count}", _openWindows.Count);
var windows = _openWindows.Values.ToList();
foreach (var window in windows)
{
window.Close();
}
_logger.LogInformation("所有窗口已关闭");
}
/// <summary>
/// 获取指定模块的窗口
/// </summary>
public Window? GetWindow(string moduleKey)
{
return _openWindows.TryGetValue(moduleKey, out var window) ? window : null;
}
/// <summary>
/// 检查窗口是否已打开
/// </summary>
public bool IsWindowOpen(string moduleKey)
{
return _openWindows.ContainsKey(moduleKey);
}
/// <summary>
/// 获取所有已打开的窗口数量
/// </summary>
public int OpenWindowCount => _openWindows.Count;
/// <summary>
/// 激活窗口
/// </summary>
private void ActivateWindow(Window window)
{
if (window.WindowState == WindowState.Minimized)
{
window.WindowState = WindowState.Normal;
}
window.Activate();
window.Focus();
}
/// <summary>
/// 窗口关闭时的处理
/// </summary>
private void OnWindowClosed(string moduleKey)
{
_openWindows.Remove(moduleKey);
_logger.LogInformation("窗口已关闭: {ModuleKey}, 剩余打开窗口数: {Count}", moduleKey, _openWindows.Count);
}
}
}
+43
View File
@@ -0,0 +1,43 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net8.0-windows</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<UseWPF>true</UseWPF>
<AssemblyName>Sharp.Demo</AssemblyName>
<RootNamespace>Sharp.Demo</RootNamespace>
</PropertyGroup>
<!-- NuGet 包 -->
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="9.0.0" />
</ItemGroup>
<!-- 项目引用 -->
<ItemGroup>
<ProjectReference Include="..\Common\Common.csproj" />
<ProjectReference Include="..\Config\Config.csproj" />
<ProjectReference Include="..\Wpf\Wpf.csproj" />
<ProjectReference Include="..\Wpf.HandyControl\Wpf.HandyControl.csproj" />
<ProjectReference Include="..\Wpf.DevTools\Wpf.DevTools.csproj" />
<ProjectReference Include="..\Log\Log.csproj" />
<ProjectReference Include="..\Data\Data.csproj" />
<ProjectReference Include="..\NetClient\NetClient.csproj" />
<ProjectReference Include="..\NetServer\NetServer.csproj" />
<ProjectReference Include="..\Plc.Siemens\Plc.Siemens.csproj" />
<ProjectReference Include="..\Ports\Ports.csproj" />
<ProjectReference Include="..\Process\Process.csproj" />
<ProjectReference Include="..\Python\Python.csproj" />
</ItemGroup>
<!-- 配置文件 -->
<ItemGroup>
<None Update="appsettings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>
+123
View File
@@ -0,0 +1,123 @@
# HTTP 服务器测试指南
## 问题修复总结
### 修复的问题
1. **端口不匹配**
- 之前:服务器监听 8080,但客户端请求 5000
- 现在:统一使用配置文件中的 8080 端口
2. **缺少控制器**
- 之前:没有任何 HTTP 控制器,导致所有请求返回 404
- 现在:添加了 `HealthController` 和 `DemoController`
3. **HandyControl 兼容性**
- 之前:HandyControl 3.5.0 与 .NET 8 不兼容
- 现在:升级到 HandyControl 3.5.1
## 测试步骤
### 1. 启动 Sharp.Demo 应用
```bash
cd D:\yuanll\sharp-basic\src\Sharp\Sharp.Demo
dotnet run
```
### 2. 打开 HTTP 服务器窗口
在启动器中点击 "HTTP 服务器" 按钮
### 3. 启动服务器
点击 "启动服务器" 按钮,应该看到:
- 状态变为 "运行中" (绿色)
- 日志显示 "✅ 服务器已启动: http://localhost:8080"
### 4. 测试可用的 API 端点
#### 使用浏览器测试
打开浏览器访问以下地址:
- **健康检查**: http://localhost:8080/api/health
- **详细健康检查**: http://localhost:8080/api/health/detailed
- **Hello World**: http://localhost:8080/api/demo/hello
- **服务器时间**: http://localhost:8080/api/demo/time
#### 使用 HTTP 客户端窗口测试
1. 打开 "HTTP 客户端" 窗口
2. 默认 URL 已经设置为 `http://localhost:8080/api/health`
3. 点击 "发送请求" 或 "测试本地服务器"
4. 应该看到成功的响应:
```json
{
"success": true,
"data": {
"status": "healthy",
"timestamp": "2025-12-24T19:20:00",
"version": "1.0.0",
"server": "Sharp.Demo"
}
}
```
#### 使用 curl 测试
```bash
# 健康检查
curl http://localhost:8080/api/health
# POST 请求测试
curl -X POST http://localhost:8080/api/demo/echo -H "Content-Type: application/json" -d "{\"test\": \"hello\"}"
```
## 已注册的路由
| 方法 | 路径 | 描述 |
|------|------|------|
| GET | /api/health | 基本健康检查 |
| GET | /api/health/detailed | 详细健康检查 |
| GET | /api/demo/hello | Hello World 测试 |
| GET | /api/demo/time | 获取服务器时间 |
| POST | /api/demo/echo | Echo 测试(返回请求体) |
## 配置说明
服务器配置在 `appsettings.json` 中:
```json
{
"Server": {
"Port": 8080,
"Host": "localhost",
"MaxConcurrentRequests": 100
}
}
```
可以修改端口和主机地址,重启服务器后生效。
## 故障排查
### 端口被占用
如果看到 "端口被占用" 错误:
1. 修改 `appsettings.json` 中的端口号
2. 重新启动应用
### 防火墙阻止
如果其他计算机无法访问:
1. 检查 Windows 防火墙设置
2. 确保允许 8080 端口的入站连接
### 客户端连接失败
确认:
1. 服务器确实已启动(状态为"运行中")
2. URL 端口号正确(8080)
3. 使用的是 `localhost` 或 `127.0.0.1`
@@ -0,0 +1,401 @@
using System;
using System.Collections.ObjectModel;
using System.Linq;
using System.Windows.Threading;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Common;
using Sharp.Wpf.Mvvm;
using Sharp.Wpf.Commands;
using Sharp.Demo.Services;
using Sharp.Demo.Models;
namespace Sharp.Demo.ViewModels
{
/// <summary>
/// 启动器 ViewModel - 管理所有模块的启动
/// </summary>
public class LauncherViewModel : ViewModelBase
{
private readonly WindowManager _windowManager;
private readonly ServerService _serverService;
private readonly ILogger<LauncherViewModel> _logger;
private readonly DispatcherTimer _timer;
private readonly DateTime _startTime;
private string _serverStatus = "未运行";
private string _plcStatus = "未连接";
private int _openWindowCount = 0;
private string _uptime = "00:00:00";
public LauncherViewModel(WindowManager windowManager, ServerService serverService, ILogger<LauncherViewModel> logger)
{
_windowManager = windowManager ?? throw new ArgumentNullException(nameof(windowManager));
_serverService = serverService ?? throw new ArgumentNullException(nameof(serverService));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_startTime = DateTime.Now;
_logger.LogInformation("启动器 ViewModel 已初始化");
// 初始化命令
OpenDevToolsCommand = new RelayCommand(OpenDevTools);
OpenLogViewerCommand = new RelayCommand(OpenLogViewer);
OpenPerformanceMonitorCommand = new RelayCommand(OpenPerformanceMonitor);
ShowAboutCommand = new RelayCommand(ShowAbout);
// 初始化模块列表
InitializeModules();
// 订阅服务器状态
_serverService.StatusChanged += OnServerStatusChanged;
// 启动定时器(更新运行时间和窗口计数)
_timer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(1) };
_timer.Tick += OnTimerTick;
_timer.Start();
}
#region 属性
public string ServerStatus
{
get => _serverStatus;
set => SetProperty(ref _serverStatus, value);
}
public string PlcStatus
{
get => _plcStatus;
set => SetProperty(ref _plcStatus, value);
}
public int OpenWindowCount
{
get => _openWindowCount;
set => SetProperty(ref _openWindowCount, value);
}
public string Uptime
{
get => _uptime;
set => SetProperty(ref _uptime, value);
}
public ObservableCollection<ModuleCategory> ModuleCategories { get; } = new();
#endregion
#region 命令
public RelayCommand OpenDevToolsCommand { get; }
public RelayCommand OpenLogViewerCommand { get; }
public RelayCommand OpenPerformanceMonitorCommand { get; }
public RelayCommand ShowAboutCommand { get; }
#endregion
#region 模块初始化
private void InitializeModules()
{
// 网络通信模块
var networkCategory = new ModuleCategory { CategoryName = "🌐 网络通信" };
networkCategory.Modules.Add(new ModuleInfo
{
Key = "HttpServer",
Name = "HTTP 服务器",
Description = "轻量级 HTTP 服务器,支持中间件和路由",
Icon = "🖥️",
Category = "网络通信",
OpenCommand = new RelayCommand(() => OpenModuleWindow("HttpServer"))
});
networkCategory.Modules.Add(new ModuleInfo
{
Key = "HttpClient",
Name = "HTTP 客户端",
Description = "集成 Polly 重试策略的 HTTP 客户端",
Icon = "📡",
Category = "网络通信",
OpenCommand = new RelayCommand(() => OpenModuleWindow("HttpClient"))
});
ModuleCategories.Add(networkCategory);
// 数据管理模块
var dataCategory = new ModuleCategory { CategoryName = "💾 数据管理" };
dataCategory.Modules.Add(new ModuleInfo
{
Key = "Database",
Name = "数据库管理",
Description = "EF Core + Repository 模式",
Icon = "🗄️",
Category = "数据管理",
OpenCommand = new RelayCommand(() => OpenModuleWindow("Database"))
});
ModuleCategories.Add(dataCategory);
// 工业控制模块
var industrialCategory = new ModuleCategory { CategoryName = "🏭 工业控制" };
industrialCategory.Modules.Add(new ModuleInfo
{
Key = "PlcDebug",
Name = "PLC 调试",
Description = "西门子 PLC 通信调试工具",
Icon = "🔧",
Category = "工业控制",
OpenCommand = new RelayCommand(() => OpenModuleWindow("PlcDebug"))
});
industrialCategory.Modules.Add(new ModuleInfo
{
Key = "SerialPort",
Name = "串口调试",
Description = "RS232/RS485 串口通信工具",
Icon = "🔌",
Category = "工业控制",
OpenCommand = new RelayCommand(() => OpenModuleWindow("SerialPort"))
});
ModuleCategories.Add(industrialCategory);
// 进程管理模块
var processCategory = new ModuleCategory { CategoryName = "⚙️ 进程管理" };
processCategory.Modules.Add(new ModuleInfo
{
Key = "ProcessTest",
Name = "Process 执行",
Description = "外部进程执行和管理",
Icon = "▶️",
Category = "进程管理",
OpenCommand = new RelayCommand(() => OpenModuleWindow("ProcessTest"))
});
processCategory.Modules.Add(new ModuleInfo
{
Key = "PythonTest",
Name = "Python 集成",
Description = "Python 脚本执行和集成",
Icon = "🐍",
Category = "进程管理",
Status = "开发中",
IsEnabled = false,
OpenCommand = new RelayCommand(() => { /* 待实现 */ })
});
ModuleCategories.Add(processCategory);
// 基础设施模块
var infrastructureCategory = new ModuleCategory { CategoryName = "🔨 基础设施" };
infrastructureCategory.Modules.Add(new ModuleInfo
{
Key = "LogDemo",
Name = "日志管理",
Description = "Serilog 结构化日志系统",
Icon = "📝",
Category = "基础设施",
OpenCommand = new RelayCommand(() => OpenModuleWindow("LogDemo"))
});
infrastructureCategory.Modules.Add(new ModuleInfo
{
Key = "ConfigDemo",
Name = "配置管理",
Description = "多格式配置文件管理",
Icon = "⚙️",
Category = "基础设施",
OpenCommand = new RelayCommand(() => OpenModuleWindow("ConfigDemo"))
});
infrastructureCategory.Modules.Add(new ModuleInfo
{
Key = "WpfServices",
Name = "WPF 服务",
Description = "对话框、文件选择、MVVM 基础",
Icon = "🎨",
Category = "基础设施",
OpenCommand = new RelayCommand(() => OpenModuleWindow("WpfServices"))
});
ModuleCategories.Add(infrastructureCategory);
}
/// <summary>
/// 打开模块窗口
/// </summary>
private void OpenModuleWindow(string moduleKey)
{
try
{
_logger.LogInformation("用户点击模块卡片: {ModuleKey}", moduleKey);
switch (moduleKey)
{
case "HttpServer":
_logger.LogInformation("正在打开 HTTP 服务器窗口...");
_windowManager.ShowWindow<Sharp.Demo.Views.Windows.HttpServerWindow>(moduleKey);
break;
case "HttpClient":
_logger.LogInformation("正在打开 HTTP 客户端窗口...");
_windowManager.ShowWindow<Sharp.Demo.Views.Windows.HttpClientWindow>(moduleKey);
break;
case "PlcDebug":
_logger.LogInformation("正在打开 PLC 调试窗口...");
_windowManager.ShowWindow<Sharp.Demo.Views.Windows.PlcDebugWindow>(moduleKey);
break;
// 其他窗口待实现
case "Database":
case "SerialPort":
case "ProcessTest":
case "LogDemo":
case "ConfigDemo":
case "WpfServices":
_logger.LogWarning("模块 {ModuleKey} 窗口尚未实现", moduleKey);
System.Windows.MessageBox.Show(
$"{moduleKey} 窗口开发中...\n\n请按照 HttpServerWindow 的模板创建对应的窗口。",
"提示",
System.Windows.MessageBoxButton.OK,
System.Windows.MessageBoxImage.Information
);
break;
default:
_logger.LogError("未知的模块: {ModuleKey}", moduleKey);
System.Windows.MessageBox.Show(
$"未知的模块: {moduleKey}",
"错误",
System.Windows.MessageBoxButton.OK,
System.Windows.MessageBoxImage.Warning
);
break;
}
}
catch (Exception ex)
{
_logger.LogError(ex, "打开模块失败: {ModuleKey}", moduleKey);
System.Windows.MessageBox.Show(
$"打开模块失败: {ex.Message}\n\n详细信息已记录到日志中。",
"错误",
System.Windows.MessageBoxButton.OK,
System.Windows.MessageBoxImage.Error
);
}
}
#endregion
#region 开发者工具
private void OpenDevTools()
{
try
{
_logger.LogInformation("用户点击了 F12 开发者工具");
var sp = Global.Get<IServiceProvider>();
var window = sp?.GetService<Sharp.Wpf.DevTools.Views.DevToolsWindow>();
window?.Show();
}
catch (Exception ex)
{
_logger.LogError(ex, "打开开发者工具失败");
System.Windows.MessageBox.Show($"打开开发者工具失败: {ex.Message}", "错误");
}
}
private void OpenLogViewer()
{
try
{
_logger.LogInformation("用户点击了 F11 日志查看器");
var sp = Global.Get<IServiceProvider>();
var window = sp?.GetService<Sharp.Wpf.DevTools.Views.LogViewerWindow>();
window?.Show();
}
catch (Exception ex)
{
_logger.LogError(ex, "打开日志查看器失败");
System.Windows.MessageBox.Show($"打开日志查看器失败: {ex.Message}", "错误");
}
}
private void OpenPerformanceMonitor()
{
try
{
_logger.LogInformation("用户点击了 F10 性能监控");
var sp = Global.Get<IServiceProvider>();
var window = sp?.GetService<Sharp.Wpf.DevTools.Views.PerformanceMonitorWindow>();
window?.Show();
}
catch (Exception ex)
{
_logger.LogError(ex, "打开性能监控失败");
System.Windows.MessageBox.Show($"打开性能监控失败: {ex.Message}", "错误");
}
}
private void ShowAbout()
{
_logger.LogInformation("用户查看了关于对话框");
System.Windows.MessageBox.Show(
"Sharp Framework v1.0.0\n\n" +
"模块化 .NET 8 开发框架\n" +
"功能演示和测试平台\n\n" +
"© 2024 Sharp Team",
"关于",
System.Windows.MessageBoxButton.OK,
System.Windows.MessageBoxImage.Information
);
}
#endregion
#region 定时器和事件
private void OnTimerTick(object? sender, EventArgs e)
{
// 更新运行时间
var elapsed = DateTime.Now - _startTime;
Uptime = $"{elapsed.Hours:D2}:{elapsed.Minutes:D2}:{elapsed.Seconds:D2}";
// 更新窗口计数
OpenWindowCount = _windowManager.OpenWindowCount;
}
private void OnServerStatusChanged(object? sender, string status)
{
ServerStatus = _serverService.IsRunning ? "运行中" : "未运行";
}
#endregion
#region 公共方法
/// <summary>
/// 关闭所有模块窗口
/// </summary>
public void CloseAllWindows()
{
_logger.LogInformation("用户请求关闭所有窗口");
_windowManager.CloseAllWindows();
}
#endregion
#region 清理
public override void Dispose()
{
_logger.LogInformation("启动器 ViewModel 正在释放资源");
_timer?.Stop();
_serverService.StatusChanged -= OnServerStatusChanged;
base.Dispose();
_logger.LogInformation("启动器 ViewModel 已释放");
}
#endregion
}
/// <summary>
/// 模块分类
/// </summary>
public class ModuleCategory
{
public string CategoryName { get; set; } = string.Empty;
public ObservableCollection<ModuleInfo> Modules { get; } = new();
}
}
@@ -0,0 +1,265 @@
using System;
using System.Threading.Tasks;
using System.Windows.Input;
using Microsoft.Extensions.DependencyInjection;
using Common;
using Sharp.Wpf.Mvvm;
using Sharp.Wpf.Abstractions;
using Sharp.Wpf.Commands;
using Sharp.Wpf.Navigation;
using Sharp.Demo.ViewModels.Pages;
using Sharp.Demo.Services;
namespace Sharp.Demo.ViewModels
{
/// <summary>
/// 主窗口 ViewModel - 负责导航管理和全局状态
/// </summary>
public class MainViewModel : ViewModelBase
{
private readonly IServiceProvider _serviceProvider;
private readonly ServerService _serverService;
private ViewModelBase? _currentPage;
private string _serverStatus = "未运行";
private string _plcStatus = "未连接";
private string _currentPageName = "主页";
public MainViewModel(IServiceProvider serviceProvider, ServerService serverService)
{
_serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider));
_serverService = serverService ?? throw new ArgumentNullException(nameof(serverService));
// 订阅服务器状态变化
_serverService.StatusChanged += OnServerStatusChanged;
// 初始化命令
NavigateCommand = new RelayCommand<string>(async pageKey => await NavigateToAsync(pageKey ?? "Home"));
OpenDevToolsCommand = new RelayCommand(OpenDevTools);
OpenLogViewerCommand = new RelayCommand(OpenLogViewer);
OpenPerformanceMonitorCommand = new RelayCommand(OpenPerformanceMonitor);
// 默认导航到首页
_ = NavigateToAsync("Home");
}
#region 属性
/// <summary>
/// 当前显示的页面 ViewModel
/// </summary>
public ViewModelBase? CurrentPage
{
get => _currentPage;
set => SetProperty(ref _currentPage, value);
}
/// <summary>
/// HTTP 服务器状态
/// </summary>
public string ServerStatus
{
get => _serverStatus;
set => SetProperty(ref _serverStatus, value);
}
/// <summary>
/// PLC 状态
/// </summary>
public string PlcStatus
{
get => _plcStatus;
set => SetProperty(ref _plcStatus, value);
}
/// <summary>
/// 当前页面名称
/// </summary>
public string CurrentPageName
{
get => _currentPageName;
set => SetProperty(ref _currentPageName, value);
}
#endregion
#region 命令
public ICommand NavigateCommand { get; }
public ICommand OpenDevToolsCommand { get; }
public ICommand OpenLogViewerCommand { get; }
public ICommand OpenPerformanceMonitorCommand { get; }
#endregion
#region 导航方法
/// <summary>
/// 导航到指定页面
/// </summary>
private async Task NavigateToAsync(string pageKey)
{
try
{
// 1. 离开当前页面
if (CurrentPage is INavigationAware currentAware)
{
await currentAware.OnNavigatedFromAsync();
}
// 2. 创建新页面 ViewModel
var newPage = CreatePageViewModel(pageKey);
if (newPage != null)
{
CurrentPage = newPage;
CurrentPageName = GetPageName(pageKey);
// 3. 进入新页面
if (newPage is INavigationAware newAware)
{
await newAware.OnNavigatedToAsync(null);
}
}
}
catch (Exception ex)
{
System.Windows.MessageBox.Show(
$"页面导航失败: {ex.Message}",
"错误",
System.Windows.MessageBoxButton.OK,
System.Windows.MessageBoxImage.Error
);
}
}
/// <summary>
/// 根据页面 Key 创建对应的 ViewModel
/// </summary>
private ViewModelBase? CreatePageViewModel(string pageKey)
{
return pageKey switch
{
"Home" => _serviceProvider.GetRequiredService<HomeViewModel>(),
// P0 页面(已实现)
"HttpServer" => _serviceProvider.GetRequiredService<HttpServerViewModel>(),
"Database" => _serviceProvider.GetRequiredService<DatabaseViewModel>(),
"PlcDebug" => _serviceProvider.GetRequiredService<PlcDebugViewModel>(),
// P1 页面(已实现)
"SerialPort" => _serviceProvider.GetRequiredService<SerialPortViewModel>(),
"ProcessTest" => _serviceProvider.GetRequiredService<ProcessTestViewModel>(),
// 新增页面(已实现)
"HttpClient" => _serviceProvider.GetRequiredService<HttpClientViewModel>(),
"LogDemo" => _serviceProvider.GetRequiredService<LogDemoViewModel>(),
"ConfigDemo" => _serviceProvider.GetRequiredService<ConfigDemoViewModel>(),
"WpfServices" => _serviceProvider.GetRequiredService<WpfServicesViewModel>(),
// 暂未实现的页面
// "PythonTest" => _serviceProvider.GetRequiredService<PythonTestViewModel>(),
_ => CurrentPage // 如果页面未实现,保持当前页面
};
}
/// <summary>
/// 获取页面显示名称
/// </summary>
private static string GetPageName(string pageKey)
{
return pageKey switch
{
"Home" => "主页",
"HttpServer" => "HTTP 服务器",
"HttpClient" => "HTTP 客户端",
"Database" => "数据库管理",
"PlcDebug" => "PLC 调试",
"SerialPort" => "串口调试",
"ProcessTest" => "Process 测试",
"PythonTest" => "Python 测试",
"LogDemo" => "日志管理",
"ConfigDemo" => "配置管理",
"WpfServices" => "WPF 服务",
_ => "未知页面"
};
}
#endregion
#region 开发者工具命令
private void OpenDevTools()
{
try
{
var sp = Global.Get<IServiceProvider>();
var window = sp?.GetService<Sharp.Wpf.DevTools.Views.DevToolsWindow>();
window?.Show();
}
catch (Exception ex)
{
System.Windows.MessageBox.Show($"打开开发者工具失败: {ex.Message}", "错误");
}
}
private void OpenLogViewer()
{
try
{
var sp = Global.Get<IServiceProvider>();
var window = sp?.GetService<Sharp.Wpf.DevTools.Views.LogViewerWindow>();
window?.Show();
}
catch (Exception ex)
{
System.Windows.MessageBox.Show($"打开日志查看器失败: {ex.Message}", "错误");
}
}
private void OpenPerformanceMonitor()
{
try
{
var sp = Global.Get<IServiceProvider>();
var window = sp?.GetService<Sharp.Wpf.DevTools.Views.PerformanceMonitorWindow>();
window?.Show();
}
catch (Exception ex)
{
System.Windows.MessageBox.Show($"打开性能监控失败: {ex.Message}", "错误");
}
}
#endregion
#region 事件处理
/// <summary>
/// 服务器状态变化事件处理
/// </summary>
private void OnServerStatusChanged(object? sender, string status)
{
ServerStatus = _serverService.IsRunning ? "运行中" : "未运行";
}
#endregion
#region 资源清理
public override void Dispose()
{
// 取消订阅事件
_serverService.StatusChanged -= OnServerStatusChanged;
// 清理当前页面
if (CurrentPage is INavigationAware aware)
{
aware.OnNavigatedFromAsync().Wait();
}
base.Dispose();
}
#endregion
}
}
@@ -0,0 +1,69 @@
using Sharp.Wpf.Mvvm;
using Sharp.Wpf.Commands;
namespace Sharp.Demo.ViewModels.Pages
{
/// <summary>
/// 配置管理演示 ViewModel
/// </summary>
public class ConfigDemoViewModel : ViewModelBase
{
private string _configFile = "appsettings.json";
private bool _isHotReloadEnabled = true;
private string _configKey = "Logging:LogLevel:Default";
private string _configValue = "";
private string _configPreview = "等待加载配置...";
public string ConfigFile
{
get => _configFile;
set => SetProperty(ref _configFile, value);
}
public bool IsHotReloadEnabled
{
get => _isHotReloadEnabled;
set => SetProperty(ref _isHotReloadEnabled, value);
}
public string ConfigKey
{
get => _configKey;
set => SetProperty(ref _configKey, value);
}
public string ConfigValue
{
get => _configValue;
set => SetProperty(ref _configValue, value);
}
public string ConfigPreview
{
get => _configPreview;
set => SetProperty(ref _configPreview, value);
}
// 命令
public RelayCommand ReadConfigCommand { get; }
public RelayCommand ViewAllConfigCommand { get; }
public RelayCommand ReadDbConfigCommand { get; }
public RelayCommand ReadLogConfigCommand { get; }
public RelayCommand ReadServerConfigCommand { get; }
public RelayCommand TestHotReloadCommand { get; }
public RelayCommand OpenConfigFileCommand { get; }
public RelayCommand ValidateConfigCommand { get; }
public ConfigDemoViewModel()
{
ReadConfigCommand = new RelayCommand(() => ConfigValue = "读取配置功能待实现...");
ViewAllConfigCommand = new RelayCommand(() => ConfigPreview = "查看所有配置功能待实现...");
ReadDbConfigCommand = new RelayCommand(() => ConfigPreview = "数据库配置:\n- ConnectionString\n- Provider\n功能待实现...");
ReadLogConfigCommand = new RelayCommand(() => ConfigPreview = "日志配置:\n- MinimumLevel\n- WriteTo\n功能待实现...");
ReadServerConfigCommand = new RelayCommand(() => ConfigPreview = "服务器配置:\n- Port\n- Host\n功能待实现...");
TestHotReloadCommand = new RelayCommand(() => ConfigPreview = "热重载测试功能待实现...\n请修改 appsettings.json 文件测试");
OpenConfigFileCommand = new RelayCommand(() => ConfigPreview = "打开配置文件功能待实现...");
ValidateConfigCommand = new RelayCommand(() => ConfigPreview = "配置验证功能待实现...");
}
}
}
@@ -0,0 +1,309 @@
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.Logging;
using Sharp.Data.Context;
using Sharp.Wpf.Mvvm;
using Sharp.Wpf.Commands;
using Sharp.Wpf.Abstractions;
using Sharp.Wpf.Services;
using Sharp.Wpf.Navigation;
using Sharp.Demo.Data;
using Sharp.Demo.Views.Dialogs;
namespace Sharp.Demo.ViewModels.Pages
{
/// <summary>
/// 数据库管理页面 ViewModel - EF Core + Repository 演示
/// </summary>
public class DatabaseViewModel : ViewModelBase, INavigationAware
{
private readonly IDataContext _dataContext;
private readonly ILogger<DatabaseViewModel> _logger;
private readonly IDialogService _dialogService;
private User? _selectedUser;
private string _searchText = string.Empty;
private string _statusText = string.Empty;
private string _recordCount = "记录数: 0";
public DatabaseViewModel(
IDataContext dataContext,
ILogger<DatabaseViewModel> logger,
IDialogService dialogService)
{
_dataContext = dataContext ?? throw new ArgumentNullException(nameof(dataContext));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_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(DeleteAsync, () => SelectedUser != null);
RefreshCommand = new AsyncCommand(async () => await LoadDataAsync(SearchText.Trim()));
}
#region 属性
/// <summary>
/// 用户列表
/// </summary>
public ObservableCollection<User> Users { get; }
/// <summary>
/// 选中的用户
/// </summary>
public User? SelectedUser
{
get => _selectedUser;
set
{
if (SetProperty(ref _selectedUser, value))
{
// 触发命令的 CanExecute 更新
OnPropertyChanged(nameof(EditCommand));
OnPropertyChanged(nameof(DeleteCommand));
}
}
}
/// <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);
}
#endregion
#region 命令
public ICommand LoadDataCommand { get; }
public ICommand SearchCommand { get; }
public ICommand ClearSearchCommand { get; }
public ICommand AddCommand { get; }
public ICommand EditCommand { get; }
public ICommand DeleteCommand { get; }
public ICommand RefreshCommand { get; }
#endregion
#region INavigationAware 实现
public async Task OnNavigatedToAsync(object? parameter)
{
// 进入页面时加载数据
await LoadDataAsync();
}
public Task OnNavigatedFromAsync()
{
// 离开页面时无需特殊处理
return Task.CompletedTask;
}
#endregion
#region 私有方法
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
{
Owner = Application.Current.MainWindow
};
if (dialog.ShowDialog() == true)
{
_ = SaveUserAsync(dialog.UserData);
}
}
private void Edit()
{
if (SelectedUser == null)
{
_ = _dialogService.ShowInfoAsync("请选择要编辑的用户", "提示");
return;
}
var dialog = new UserEditDialog(SelectedUser)
{
Owner = Application.Current.MainWindow
};
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}", "错误");
}
}
#endregion
}
}
@@ -0,0 +1,219 @@
using System;
using System.Collections.ObjectModel;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;
using Sharp.Wpf.Mvvm;
using Sharp.Wpf.Abstractions;
using Sharp.Wpf.Commands;
using Sharp.Wpf.Navigation;
using Sharp.Demo.Services;
namespace Sharp.Demo.ViewModels.Pages
{
/// <summary>
/// 首页 ViewModel - 展示框架概览和所有模块
/// </summary>
public class HomeViewModel : ViewModelBase, INavigationAware
{
private readonly ServerService _serverService;
private string _serverStatusText = "HTTP 服务器未运行";
private string _plcStatusText = "PLC 未连接";
public HomeViewModel(ServerService serverService)
{
_serverService = serverService ?? throw new ArgumentNullException(nameof(serverService));
// 初始化导航命令
NavigateToHttpServerCommand = new RelayCommand(() => NavigateTo("HttpServer"));
NavigateToDatabaseCommand = new RelayCommand(() => NavigateTo("Database"));
NavigateToPlcCommand = new RelayCommand(() => NavigateTo("PlcDebug"));
// 初始化模块列表
InitializeModules();
}
#region 属性
/// <summary>
/// 服务器状态文本
/// </summary>
public string ServerStatusText
{
get => _serverStatusText;
set => SetProperty(ref _serverStatusText, value);
}
/// <summary>
/// PLC 状态文本
/// </summary>
public string PlcStatusText
{
get => _plcStatusText;
set => SetProperty(ref _plcStatusText, value);
}
/// <summary>
/// 系统信息
/// </summary>
public string SystemInfo => $".NET {Environment.Version} | {Environment.OSVersion.VersionString} | {(Environment.Is64BitOperatingSystem ? "64位" : "32位")}";
/// <summary>
/// 模块列表
/// </summary>
public ObservableCollection<ModuleCardViewModel> Modules { get; } = new();
#endregion
#region 命令
public ICommand NavigateToHttpServerCommand { get; }
public ICommand NavigateToDatabaseCommand { get; }
public ICommand NavigateToPlcCommand { get; }
#endregion
#region INavigationAware 实现
public Task OnNavigatedToAsync(object? parameter)
{
// 进入页面时刷新状态
RefreshStatus();
return Task.CompletedTask;
}
public Task OnNavigatedFromAsync()
{
// 离开页面时无需特殊处理
return Task.CompletedTask;
}
#endregion
#region 私有方法
/// <summary>
/// 初始化模块列表
/// </summary>
private void InitializeModules()
{
Modules.Add(new ModuleCardViewModel
{
Icon = "🌐",
Name = "HTTP 服务器",
Description = "Sharp.NetServer - 轻量级 HTTP 服务器框架,支持中间件、路由和控制器",
NavigateCommand = new RelayCommand(() => NavigateTo("HttpServer"))
});
Modules.Add(new ModuleCardViewModel
{
Icon = "📡",
Name = "HTTP 客户端",
Description = "Sharp.NetClient - 带 Polly 重试策略的 HTTP 客户端",
NavigateCommand = new RelayCommand(() => NavigateTo("HttpClient"))
});
Modules.Add(new ModuleCardViewModel
{
Icon = "💾",
Name = "数据库管理",
Description = "Sharp.Data - EF Core 封装,支持仓储模式、审计追踪和软删除",
NavigateCommand = new RelayCommand(() => NavigateTo("Database"))
});
Modules.Add(new ModuleCardViewModel
{
Icon = "🏭",
Name = "PLC 调试",
Description = "Sharp.Plc.Siemens - 西门子 PLC 通信驱动 (S7-1200/1500/300/400)",
NavigateCommand = new RelayCommand(() => NavigateTo("PlcDebug"))
});
Modules.Add(new ModuleCardViewModel
{
Icon = "🔌",
Name = "串口调试",
Description = "Sharp.Ports - 串口通信抽象 (RS232/RS485)",
NavigateCommand = new RelayCommand(() => NavigateTo("SerialPort"))
});
Modules.Add(new ModuleCardViewModel
{
Icon = "⚙️",
Name = "Process 执行",
Description = "Sharp.Process - 进程执行和输出捕获",
NavigateCommand = new RelayCommand(() => NavigateTo("ProcessTest"))
});
Modules.Add(new ModuleCardViewModel
{
Icon = "🐍",
Name = "Python 集成",
Description = "Sharp.Python - Python 脚本执行集成",
NavigateCommand = new RelayCommand(() => NavigateTo("PythonTest"))
});
Modules.Add(new ModuleCardViewModel
{
Icon = "📝",
Name = "日志管理",
Description = "Sharp.Log - Serilog 集成,支持结构化日志和多种输出",
NavigateCommand = new RelayCommand(() => NavigateTo("LogDemo"))
});
Modules.Add(new ModuleCardViewModel
{
Icon = "🔧",
Name = "配置管理",
Description = "Sharp.Config - 多格式配置 (JSON/XML/INI/TOML) 和热重载",
NavigateCommand = new RelayCommand(() => NavigateTo("ConfigDemo"))
});
Modules.Add(new ModuleCardViewModel
{
Icon = "🎨",
Name = "WPF 服务",
Description = "Sharp.Wpf - MVVM 基础设施、对话框服务和主题管理",
NavigateCommand = new RelayCommand(() => NavigateTo("WpfDemo"))
});
}
/// <summary>
/// 刷新状态
/// </summary>
private void RefreshStatus()
{
ServerStatusText = _serverService.IsRunning
? "✅ HTTP 服务器运行中"
: "⚪ HTTP 服务器未运行";
// TODO: 获取 PLC 状态
PlcStatusText = "⚪ PLC 未连接";
}
/// <summary>
/// 导航到指定页面
/// </summary>
private void NavigateTo(string pageKey)
{
// 通过 Application.Current.MainWindow 获取 MainViewModel 并调用导航命令
if (Application.Current.MainWindow?.DataContext is MainViewModel mainViewModel)
{
mainViewModel.NavigateCommand.Execute(pageKey);
}
}
#endregion
}
/// <summary>
/// 模块卡片 ViewModel
/// </summary>
public class ModuleCardViewModel
{
public string Icon { get; set; } = "";
public string Name { get; set; } = "";
public string Description { get; set; } = "";
public ICommand? NavigateCommand { get; set; }
}
}
@@ -0,0 +1,74 @@
using System.Collections.ObjectModel;
using Sharp.Wpf.Mvvm;
using Sharp.Wpf.Commands;
namespace Sharp.Demo.ViewModels.Pages
{
/// <summary>
/// HTTP 客户端演示 ViewModel
/// </summary>
public class HttpClientViewModel : ViewModelBase
{
private string _requestUrl = "https://api.github.com/zen";
private string _selectedMethod = "GET";
private string _requestBody = "";
private string _responseText = "等待请求...";
private string _statusCode = "";
public string RequestUrl
{
get => _requestUrl;
set => SetProperty(ref _requestUrl, value);
}
public string SelectedMethod
{
get => _selectedMethod;
set => SetProperty(ref _selectedMethod, value);
}
public string RequestBody
{
get => _requestBody;
set => SetProperty(ref _requestBody, value);
}
public string ResponseText
{
get => _responseText;
set => SetProperty(ref _responseText, value);
}
public string StatusCode
{
get => _statusCode;
set => SetProperty(ref _statusCode, value);
}
public ObservableCollection<string> HttpMethods { get; } = new()
{
"GET", "POST", "PUT", "DELETE", "PATCH"
};
public RelayCommand SendRequestCommand { get; }
public RelayCommand GetTestCommand { get; }
public RelayCommand PostTestCommand { get; }
public RelayCommand RetryTestCommand { get; }
public RelayCommand TimeoutTestCommand { get; }
public RelayCommand ClearResultCommand { get; }
public HttpClientViewModel()
{
SendRequestCommand = new RelayCommand(() => ResponseText = "发送请求功能待实现...");
GetTestCommand = new RelayCommand(() => ResponseText = "GET 测试功能待实现...");
PostTestCommand = new RelayCommand(() => ResponseText = "POST 测试功能待实现...");
RetryTestCommand = new RelayCommand(() => ResponseText = "重试测试功能待实现...");
TimeoutTestCommand = new RelayCommand(() => ResponseText = "超时测试功能待实现...");
ClearResultCommand = new RelayCommand(() =>
{
ResponseText = "";
StatusCode = "";
});
}
}
}
@@ -0,0 +1,245 @@
using System;
using System.Windows;
using System.Windows.Media;
using System.Windows.Input;
using Microsoft.Extensions.Logging;
using Sharp.Wpf.Mvvm;
using Sharp.Wpf.Commands;
using Sharp.Wpf.Navigation;
using Sharp.Demo.Services;
namespace Sharp.Demo.ViewModels.Pages
{
/// <summary>
/// HTTP 服务器管理页面 ViewModel
/// </summary>
public class HttpServerViewModel : ViewModelBase, INavigationAware
{
private readonly ServerService _serverService;
private readonly ILogger<HttpServerViewModel> _logger;
private string _statusText = "未启动";
private Brush _statusColor = Brushes.Gray;
private string _logText = string.Empty;
public HttpServerViewModel(ServerService serverService, ILogger<HttpServerViewModel> logger)
{
_serverService = serverService ?? throw new ArgumentNullException(nameof(serverService));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
ServerUrl = _serverService.Url;
// 初始化命令
StartCommand = new RelayCommand(_ => Start(), _ => CanStart);
StopCommand = new RelayCommand(_ => Stop(), _ => IsRunning);
RestartCommand = new RelayCommand(_ => Restart(), _ => IsRunning);
ClearLogCommand = new RelayCommand(_ => ClearLog());
// 订阅服务器状态变化事件
_serverService.StatusChanged += OnServerStatusChanged;
AppendLog("HTTP 服务器管理页面已加载");
AppendLog($"服务器地址: {ServerUrl}");
// 更新初始状态
RefreshStatus();
}
#region 属性
/// <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;
#endregion
#region 命令
public ICommand StartCommand { get; }
public ICommand StopCommand { get; }
public ICommand RestartCommand { get; }
public ICommand ClearLogCommand { get; }
#endregion
#region 命令实现
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;
AppendLog("日志已清空");
}
#endregion
#region INavigationAware 实现
public System.Threading.Tasks.Task OnNavigatedToAsync(object? parameter)
{
// 进入页面时刷新状态
RefreshStatus();
return System.Threading.Tasks.Task.CompletedTask;
}
public System.Threading.Tasks.Task OnNavigatedFromAsync()
{
// 离开页面时无需特殊处理(服务器继续运行)
return System.Threading.Tasks.Task.CompletedTask;
}
#endregion
#region 私有方法
private void OnServerStatusChanged(object? sender, string status)
{
Application.Current.Dispatcher.Invoke(() =>
{
StatusText = status;
// 根据状态更新颜色
if (status.Contains("已启动") || 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";
}
private void RefreshStatus()
{
if (_serverService.IsRunning)
{
StatusText = "运行中";
StatusColor = Brushes.Green;
}
else
{
StatusText = "未启动";
StatusColor = Brushes.Gray;
}
UpdateButtonStates();
}
#endregion
#region 资源清理
public override void Dispose()
{
_serverService.StatusChanged -= OnServerStatusChanged;
base.Dispose();
}
#endregion
}
}
@@ -0,0 +1,75 @@
using System.Collections.ObjectModel;
using Sharp.Wpf.Mvvm;
using Sharp.Wpf.Commands;
namespace Sharp.Demo.ViewModels.Pages
{
/// <summary>
/// 日志管理演示 ViewModel
/// </summary>
public class LogDemoViewModel : ViewModelBase
{
private string _selectedLogLevel = "Information";
private string _logPreview = "日志预览区域...\n等待日志输出...";
public string SelectedLogLevel
{
get => _selectedLogLevel;
set => SetProperty(ref _selectedLogLevel, value);
}
public string LogPreview
{
get => _logPreview;
set => SetProperty(ref _logPreview, value);
}
public ObservableCollection<string> LogLevels { get; } = new()
{
"Trace", "Debug", "Information", "Warning", "Error", "Critical"
};
// 日志级别测试命令
public RelayCommand LogTraceCommand { get; }
public RelayCommand LogDebugCommand { get; }
public RelayCommand LogInformationCommand { get; }
public RelayCommand LogWarningCommand { get; }
public RelayCommand LogErrorCommand { get; }
public RelayCommand LogCriticalCommand { get; }
// 结构化日志命令
public RelayCommand LogWithParamsCommand { get; }
public RelayCommand LogWithExceptionCommand { get; }
public RelayCommand LogPerformanceCommand { get; }
public RelayCommand LogBusinessCommand { get; }
// 日志操作命令
public RelayCommand OpenLogFolderCommand { get; }
public RelayCommand ViewLatestLogCommand { get; }
public RelayCommand ClearLogsCommand { get; }
public LogDemoViewModel()
{
LogTraceCommand = new RelayCommand(() => AppendLog("[Trace] 追踪级别日志 - 功能待实现"));
LogDebugCommand = new RelayCommand(() => AppendLog("[Debug] 调试级别日志 - 功能待实现"));
LogInformationCommand = new RelayCommand(() => AppendLog("[Information] 信息级别日志 - 功能待实现"));
LogWarningCommand = new RelayCommand(() => AppendLog("[Warning] 警告级别日志 - 功能待实现"));
LogErrorCommand = new RelayCommand(() => AppendLog("[Error] 错误级别日志 - 功能待实现"));
LogCriticalCommand = new RelayCommand(() => AppendLog("[Critical] 严重错误日志 - 功能待实现"));
LogWithParamsCommand = new RelayCommand(() => AppendLog("带参数日志功能待实现..."));
LogWithExceptionCommand = new RelayCommand(() => AppendLog("带异常日志功能待实现..."));
LogPerformanceCommand = new RelayCommand(() => AppendLog("性能日志功能待实现..."));
LogBusinessCommand = new RelayCommand(() => AppendLog("业务日志功能待实现..."));
OpenLogFolderCommand = new RelayCommand(() => AppendLog("打开日志文件夹功能待实现..."));
ViewLatestLogCommand = new RelayCommand(() => AppendLog("查看最新日志功能待实现..."));
ClearLogsCommand = new RelayCommand(() => LogPreview = "");
}
private void AppendLog(string message)
{
LogPreview += $"\n[{DateTime.Now:HH:mm:ss}] {message}";
}
}
}
@@ -0,0 +1,221 @@
using System;
using System.Threading.Tasks;
using System.Windows.Input;
using Microsoft.Extensions.Logging;
using Sharp.Wpf.Mvvm;
using Sharp.Wpf.Commands;
using Sharp.Wpf.Navigation;
using Plc.Abstractions;
namespace Sharp.Demo.ViewModels.Pages
{
/// <summary>
/// PLC 调试页面 ViewModel - 简化版
/// </summary>
public class PlcDebugViewModel : ViewModelBase, INavigationAware
{
private readonly IPlcService _plcService;
private readonly ILogger<PlcDebugViewModel> _logger;
private string _ipAddress = "192.168.1.100";
private string _status = "未连接";
private string _readAddress = "DB1.DBW0";
private string _readValue = "-";
private string _writeAddress = "DB1.DBW0";
private string _writeValue = "0";
private string _logText = string.Empty;
public PlcDebugViewModel(IPlcService plcService, ILogger<PlcDebugViewModel> logger)
{
_plcService = plcService ?? throw new ArgumentNullException(nameof(plcService));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
// 初始化命令
ConnectCommand = new AsyncCommand(ConnectAsync);
DisconnectCommand = new AsyncCommand(DisconnectAsync);
ReadCommand = new AsyncCommand(ReadAsync);
WriteCommand = new AsyncCommand(WriteAsync);
ClearLogCommand = new RelayCommand(_ => ClearLog());
AppendLog("PLC 调试工具已加载");
AppendLog("提示:需要配置真实的 PLC 连接信息才能使用");
}
#region 属性
public string IpAddress
{
get => _ipAddress;
set => SetProperty(ref _ipAddress, value);
}
public string Status
{
get => _status;
set => SetProperty(ref _status, value);
}
public string ReadAddress
{
get => _readAddress;
set => SetProperty(ref _readAddress, value);
}
public string ReadValue
{
get => _readValue;
set => SetProperty(ref _readValue, value);
}
public string WriteAddress
{
get => _writeAddress;
set => SetProperty(ref _writeAddress, value);
}
public string WriteValue
{
get => _writeValue;
set => SetProperty(ref _writeValue, value);
}
public string LogText
{
get => _logText;
set => SetProperty(ref _logText, value);
}
#endregion
#region 命令
public ICommand ConnectCommand { get; }
public ICommand DisconnectCommand { get; }
public ICommand ReadCommand { get; }
public ICommand WriteCommand { get; }
public ICommand ClearLogCommand { get; }
#endregion
#region INavigationAware 实现
public Task OnNavigatedToAsync(object? parameter)
{
AppendLog("进入 PLC 调试页面");
return Task.CompletedTask;
}
public Task OnNavigatedFromAsync()
{
return Task.CompletedTask;
}
#endregion
#region 私有方法
private async Task ConnectAsync()
{
try
{
Status = "正在连接...";
AppendLog($"正在连接 PLC: {IpAddress}");
await _plcService.StartAsync();
Status = "已连接";
AppendLog("✅ PLC 连接成功");
}
catch (Exception ex)
{
Status = $"连接失败: {ex.Message}";
AppendLog($"❌ 连接失败: {ex.Message}");
_logger.LogError(ex, "PLC 连接失败");
}
}
private async Task DisconnectAsync()
{
try
{
await _plcService.StopAsync();
Status = "已断开";
AppendLog("PLC 已断开连接");
}
catch (Exception ex)
{
AppendLog($"❌ 断开失败: {ex.Message}");
_logger.LogError(ex, "PLC 断开失败");
}
}
private async Task ReadAsync()
{
try
{
AppendLog($"读取地址: {ReadAddress}");
var result = await _plcService.ReadAsync<short>(ReadAddress);
if (result.IsSuccess)
{
ReadValue = result.Value.ToString();
AppendLog($"✅ 读取成功: {ReadValue}");
}
else
{
ReadValue = "读取失败";
AppendLog($"❌ 读取失败: {result.ErrorMessage}");
}
}
catch (Exception ex)
{
ReadValue = "读取失败";
AppendLog($"❌ 读取异常: {ex.Message}");
_logger.LogError(ex, "PLC 读取失败");
}
}
private async Task WriteAsync()
{
try
{
if (!short.TryParse(WriteValue, out short value))
{
AppendLog($"❌ 写入失败: 值格式不正确");
return;
}
AppendLog($"写入地址: {WriteAddress} = {value}");
var result = await _plcService.WriteAsync(WriteAddress, value);
if (result.IsSuccess)
{
AppendLog($"✅ 写入成功");
}
else
{
AppendLog($"❌ 写入失败: {result.ErrorMessage}");
}
}
catch (Exception ex)
{
AppendLog($"❌ 写入异常: {ex.Message}");
_logger.LogError(ex, "PLC 写入失败");
}
}
private void ClearLog()
{
LogText = string.Empty;
AppendLog("日志已清空");
}
private void AppendLog(string message)
{
var timestamp = DateTime.Now.ToString("HH:mm:ss");
LogText += $"[{timestamp}] {message}\n";
}
#endregion
}
}
@@ -0,0 +1,149 @@
using System;
using System.Threading.Tasks;
using System.Windows.Input;
using Microsoft.Extensions.Logging;
using Sharp.Wpf.Mvvm;
using Sharp.Wpf.Commands;
using Sharp.Wpf.Navigation;
namespace Sharp.Demo.ViewModels.Pages
{
/// <summary>
/// 进程测试页面 ViewModel - 简化版
/// </summary>
public class ProcessTestViewModel : ViewModelBase, INavigationAware
{
private readonly ILogger<ProcessTestViewModel> _logger;
private string _command = "dotnet";
private string _arguments = "--version";
private int _timeoutSeconds = 30;
private string _status = "就绪";
private string _output = string.Empty;
public ProcessTestViewModel(ILogger<ProcessTestViewModel> logger)
{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
// 初始化命令
RunCommand = new AsyncCommand(RunProcessAsync);
TestDotnetCommand = new AsyncCommand(TestDotnetAsync);
TestGitCommand = new AsyncCommand(TestGitAsync);
ClearOutputCommand = new RelayCommand(_ => ClearOutput());
AppendOutput("进程测试工具已加载");
AppendOutput("提示:这是简化版本,展示基础界面布局");
}
#region 属性
public string Command
{
get => _command;
set => SetProperty(ref _command, value);
}
public string Arguments
{
get => _arguments;
set => SetProperty(ref _arguments, value);
}
public int TimeoutSeconds
{
get => _timeoutSeconds;
set => SetProperty(ref _timeoutSeconds, value);
}
public string Status
{
get => _status;
set => SetProperty(ref _status, value);
}
public string Output
{
get => _output;
set => SetProperty(ref _output, value);
}
#endregion
#region 命令
public ICommand RunCommand { get; }
public ICommand TestDotnetCommand { get; }
public ICommand TestGitCommand { get; }
public ICommand ClearOutputCommand { get; }
#endregion
#region INavigationAware 实现
public Task OnNavigatedToAsync(object? parameter)
{
AppendOutput("进入进程测试页面");
return Task.CompletedTask;
}
public Task OnNavigatedFromAsync()
{
return Task.CompletedTask;
}
#endregion
#region 私有方法
private async Task RunProcessAsync()
{
try
{
Status = "正在执行...";
AppendOutput($"\n执行命令: {Command} {Arguments}");
AppendOutput("超时时间: " + TimeoutSeconds + " 秒");
await Task.Delay(500); // 模拟执行
AppendOutput("✅ 执行完成");
AppendOutput("示例输出:命令执行成功");
Status = "完成";
}
catch (Exception ex)
{
AppendOutput($"❌ 执行失败: {ex.Message}");
Status = "失败";
_logger.LogError(ex, "进程执行失败");
}
}
private async Task TestDotnetAsync()
{
Command = "dotnet";
Arguments = "--version";
await RunProcessAsync();
}
private async Task TestGitAsync()
{
Command = "git";
Arguments = "--version";
await RunProcessAsync();
}
private void ClearOutput()
{
Output = string.Empty;
Status = "就绪";
AppendOutput("输出已清空");
}
private void AppendOutput(string message)
{
var timestamp = DateTime.Now.ToString("HH:mm:ss");
Output += $"[{timestamp}] {message}\n";
}
#endregion
}
}
@@ -0,0 +1,155 @@
using System;
using System.Threading.Tasks;
using System.Windows.Input;
using Microsoft.Extensions.Logging;
using Sharp.Wpf.Mvvm;
using Sharp.Wpf.Commands;
using Sharp.Wpf.Navigation;
namespace Sharp.Demo.ViewModels.Pages
{
/// <summary>
/// 串口调试页面 ViewModel - 简化版
/// </summary>
public class SerialPortViewModel : ViewModelBase, INavigationAware
{
private readonly ILogger<SerialPortViewModel> _logger;
private string _selectedPort = "COM1";
private int _baudRate = 9600;
private string _status = "未连接";
private string _sendData = "Hello Serial Port";
private string _receiveData = string.Empty;
private string _logText = string.Empty;
public SerialPortViewModel(ILogger<SerialPortViewModel> logger)
{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
// 初始化命令
ConnectCommand = new RelayCommand(_ => Connect());
DisconnectCommand = new RelayCommand(_ => Disconnect());
SendCommand = new RelayCommand(_ => Send());
ClearLogCommand = new RelayCommand(_ => ClearLog());
AppendLog("串口调试工具已加载");
AppendLog("提示:这是简化版本,展示基础界面布局");
}
#region 属性
public string SelectedPort
{
get => _selectedPort;
set => SetProperty(ref _selectedPort, value);
}
public int BaudRate
{
get => _baudRate;
set => SetProperty(ref _baudRate, value);
}
public string Status
{
get => _status;
set => SetProperty(ref _status, value);
}
public string SendData
{
get => _sendData;
set => SetProperty(ref _sendData, value);
}
public string ReceiveData
{
get => _receiveData;
set => SetProperty(ref _receiveData, value);
}
public string LogText
{
get => _logText;
set => SetProperty(ref _logText, value);
}
#endregion
#region 命令
public ICommand ConnectCommand { get; }
public ICommand DisconnectCommand { get; }
public ICommand SendCommand { get; }
public ICommand ClearLogCommand { get; }
#endregion
#region INavigationAware 实现
public Task OnNavigatedToAsync(object? parameter)
{
AppendLog("进入串口调试页面");
return Task.CompletedTask;
}
public Task OnNavigatedFromAsync()
{
return Task.CompletedTask;
}
#endregion
#region 私有方法
private void Connect()
{
try
{
Status = "已连接";
AppendLog($"✅ 已连接到 {SelectedPort} (波特率: {BaudRate})");
}
catch (Exception ex)
{
Status = "连接失败";
AppendLog($"❌ 连接失败: {ex.Message}");
_logger.LogError(ex, "串口连接失败");
}
}
private void Disconnect()
{
Status = "未连接";
AppendLog("串口已断开");
}
private void Send()
{
try
{
AppendLog($"发送数据: {SendData}");
ReceiveData += $"[{DateTime.Now:HH:mm:ss}] Echo: {SendData}\n";
}
catch (Exception ex)
{
AppendLog($"❌ 发送失败: {ex.Message}");
_logger.LogError(ex, "串口发送失败");
}
}
private void ClearLog()
{
LogText = string.Empty;
ReceiveData = string.Empty;
AppendLog("日志已清空");
}
private void AppendLog(string message)
{
var timestamp = DateTime.Now.ToString("HH:mm:ss");
LogText += $"[{timestamp}] {message}\n";
}
#endregion
}
}
@@ -0,0 +1,101 @@
using Sharp.Wpf.Mvvm;
using Sharp.Wpf.Commands;
namespace Sharp.Demo.ViewModels.Pages
{
/// <summary>
/// WPF 服务演示 ViewModel
/// </summary>
public class WpfServicesViewModel : ViewModelBase
{
private string _sampleProperty = "示例属性值";
private string _logText = "等待操作...\n";
public string SampleProperty
{
get => _sampleProperty;
set => SetProperty(ref _sampleProperty, value);
}
public string LogText
{
get => _logText;
set => SetProperty(ref _logText, value);
}
// 对话框命令
public RelayCommand ShowMessageCommand { get; }
public RelayCommand ShowConfirmCommand { get; }
public RelayCommand ShowWarningCommand { get; }
public RelayCommand ShowErrorCommand { get; }
public RelayCommand ShowInputCommand { get; }
public RelayCommand ShowCustomDialogCommand { get; }
// 文件对话框命令
public RelayCommand OpenFileCommand { get; }
public RelayCommand SaveFileCommand { get; }
public RelayCommand SelectFolderCommand { get; }
public RelayCommand OpenMultipleFilesCommand { get; }
// 线程分发器命令
public RelayCommand InvokeOnUIThreadCommand { get; }
public RelayCommand InvokeAsyncCommand { get; }
public RelayCommand BackgroundTaskCommand { get; }
// MVVM 基础命令
public RelayCommand TriggerPropertyChangeCommand { get; }
public RelayCommand SyncCommandDemo { get; }
public AsyncCommand AsyncCommandDemo { get; }
public RelayCommand<string> CommandWithParamDemo { get; }
public RelayCommand ClearLogCommand { get; }
public WpfServicesViewModel()
{
// 对话框
ShowMessageCommand = new RelayCommand(() => AppendLog("消息框功能待实现..."));
ShowConfirmCommand = new RelayCommand(() => AppendLog("确认对话框功能待实现..."));
ShowWarningCommand = new RelayCommand(() => AppendLog("警告对话框功能待实现..."));
ShowErrorCommand = new RelayCommand(() => AppendLog("错误对话框功能待实现..."));
ShowInputCommand = new RelayCommand(() => AppendLog("输入框功能待实现..."));
ShowCustomDialogCommand = new RelayCommand(() => AppendLog("自定义对话框功能待实现..."));
// 文件对话框
OpenFileCommand = new RelayCommand(() => AppendLog("打开文件对话框功能待实现..."));
SaveFileCommand = new RelayCommand(() => AppendLog("保存文件对话框功能待实现..."));
SelectFolderCommand = new RelayCommand(() => AppendLog("选择文件夹功能待实现..."));
OpenMultipleFilesCommand = new RelayCommand(() => AppendLog("多选文件功能待实现..."));
// 线程分发器
InvokeOnUIThreadCommand = new RelayCommand(() => AppendLog("UI 线程调用功能待实现..."));
InvokeAsyncCommand = new RelayCommand(() => AppendLog("异步 UI 调用功能待实现..."));
BackgroundTaskCommand = new RelayCommand(() => AppendLog("后台任务功能待实现..."));
// MVVM 基础
TriggerPropertyChangeCommand = new RelayCommand(() =>
{
SampleProperty = $"已变更 - {DateTime.Now:HH:mm:ss}";
AppendLog($"属性变更: {SampleProperty}");
});
SyncCommandDemo = new RelayCommand(() => AppendLog("同步命令执行"));
AsyncCommandDemo = new AsyncCommand(async () =>
{
AppendLog("异步命令开始...");
await Task.Delay(1000);
AppendLog("异步命令完成");
});
CommandWithParamDemo = new RelayCommand<string>(param =>
AppendLog($"带参数命令: {param}"));
ClearLogCommand = new RelayCommand(() => LogText = "");
}
private void AppendLog(string message)
{
LogText += $"[{DateTime.Now:HH:mm:ss}] {message}\n";
}
}
}
@@ -0,0 +1,410 @@
using System;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Media;
using Microsoft.Extensions.Logging;
using Sharp.Wpf.Mvvm;
using Sharp.Wpf.Commands;
namespace Sharp.Demo.ViewModels.Windows
{
/// <summary>
/// HTTP 客户端窗口 ViewModel
/// </summary>
public class HttpClientViewModel : ViewModelBase
{
private readonly HttpClient _httpClient;
private readonly ILogger<HttpClientViewModel> _logger;
private string _requestUrl = "http://localhost:8080/api/health";
private string _selectedMethod = "GET";
private string _requestHeaders = "Content-Type: application/json\nUser-Agent: Sharp-HttpClient/1.0";
private string _requestBody = "{\n \"message\": \"Hello from Sharp\"\n}";
private string _responseText = "等待发送请求...\n\n提示: 可以先启动 HTTP 服务器窗口,然后在这里测试请求。";
private string _responseHeaders = "";
private string _logText = "";
private string _statusCode = "-";
private Brush _statusColor = Brushes.Gray;
private string _contentType = "-";
private string _elapsedTime = "0 ms";
private string _responseSize = "0 bytes";
private int _totalRequests = 0;
private int _successCount = 0;
private int _failureCount = 0;
public HttpClientViewModel(HttpClient httpClient, ILogger<HttpClientViewModel> logger)
{
_httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_logger.LogInformation("HTTP 客户端窗口 ViewModel 已创建");
// 初始化命令
SendRequestCommand = new AsyncCommand(SendRequest);
ClearCommand = new RelayCommand(Clear);
CopyResponseCommand = new RelayCommand(CopyResponse);
SaveResponseCommand = new AsyncCommand(SaveResponse);
TestLocalServerCommand = new AsyncCommand(TestLocalServer);
TestRetryCommand = new AsyncCommand(TestRetry);
TestTimeoutCommand = new AsyncCommand(TestTimeout);
TestJsonApiCommand = new AsyncCommand(TestJsonApi);
AppendLog("HTTP 客户端已初始化");
AppendLog($"当前目标地址: {RequestUrl}");
}
#region 属性
public string RequestUrl
{
get => _requestUrl;
set => SetProperty(ref _requestUrl, value);
}
public string SelectedMethod
{
get => _selectedMethod;
set => SetProperty(ref _selectedMethod, value);
}
public string RequestHeaders
{
get => _requestHeaders;
set => SetProperty(ref _requestHeaders, value);
}
public string RequestBody
{
get => _requestBody;
set => SetProperty(ref _requestBody, value);
}
public string ResponseText
{
get => _responseText;
set => SetProperty(ref _responseText, value);
}
public string ResponseHeaders
{
get => _responseHeaders;
set => SetProperty(ref _responseHeaders, value);
}
public string LogText
{
get => _logText;
set => SetProperty(ref _logText, value);
}
public string StatusCode
{
get => _statusCode;
set => SetProperty(ref _statusCode, value);
}
public Brush StatusColor
{
get => _statusColor;
set => SetProperty(ref _statusColor, value);
}
public string ContentType
{
get => _contentType;
set => SetProperty(ref _contentType, value);
}
public string ElapsedTime
{
get => _elapsedTime;
set => SetProperty(ref _elapsedTime, value);
}
public string ResponseSize
{
get => _responseSize;
set => SetProperty(ref _responseSize, value);
}
public int TotalRequests
{
get => _totalRequests;
set => SetProperty(ref _totalRequests, value);
}
public int SuccessCount
{
get => _successCount;
set => SetProperty(ref _successCount, value);
}
public int FailureCount
{
get => _failureCount;
set => SetProperty(ref _failureCount, value);
}
public ObservableCollection<string> HttpMethods { get; } = new()
{
"GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"
};
#endregion
#region 命令
public AsyncCommand SendRequestCommand { get; }
public RelayCommand ClearCommand { get; }
public RelayCommand CopyResponseCommand { get; }
public AsyncCommand SaveResponseCommand { get; }
public AsyncCommand TestLocalServerCommand { get; }
public AsyncCommand TestRetryCommand { get; }
public AsyncCommand TestTimeoutCommand { get; }
public AsyncCommand TestJsonApiCommand { get; }
#endregion
#region 命令实现
private async Task SendRequest()
{
if (string.IsNullOrWhiteSpace(RequestUrl))
{
_logger.LogWarning("用户尝试发送空 URL 请求");
AppendLog("❌ 错误: 请求 URL 不能为空");
return;
}
_logger.LogInformation("用户点击发送请求: {Method} {Url}", SelectedMethod, RequestUrl);
var sw = Stopwatch.StartNew();
TotalRequests++;
try
{
AppendLog($"[{DateTime.Now:HH:mm:ss}] 正在发送 {SelectedMethod} 请求到: {RequestUrl}");
HttpResponseMessage response;
HttpMethod method = new HttpMethod(SelectedMethod);
HttpRequestMessage request = new HttpRequestMessage(method, RequestUrl);
// 如果有请求体且不是 GET/HEAD,添加内容
if (!string.IsNullOrWhiteSpace(RequestBody) &&
method != HttpMethod.Get &&
method != HttpMethod.Head)
{
request.Content = new StringContent(RequestBody, Encoding.UTF8, "application/json");
}
response = await _httpClient.SendAsync(request);
sw.Stop();
// 处理响应
await HandleResponse(response, sw.ElapsedMilliseconds);
SuccessCount++;
AppendLog($"[{DateTime.Now:HH:mm:ss}] ✅ 请求成功 - {StatusCode} ({sw.ElapsedMilliseconds} ms)");
}
catch (HttpRequestException ex)
{
sw.Stop();
FailureCount++;
StatusCode = "请求失败";
StatusColor = Brushes.Red;
ResponseText = $"HTTP 请求异常:\n{ex.Message}\n\n{ex.StackTrace}";
ElapsedTime = $"{sw.ElapsedMilliseconds} ms";
AppendLog($"[{DateTime.Now:HH:mm:ss}] ❌ 请求失败: {ex.Message}");
_logger.LogError(ex, "HTTP 请求失败");
}
catch (TaskCanceledException ex)
{
sw.Stop();
FailureCount++;
StatusCode = "超时";
StatusColor = Brushes.Orange;
ResponseText = $"请求超时:\n{ex.Message}";
ElapsedTime = $"{sw.ElapsedMilliseconds} ms";
AppendLog($"[{DateTime.Now:HH:mm:ss}] ⏱ 请求超时");
_logger.LogWarning("HTTP 请求超时");
}
catch (Exception ex)
{
sw.Stop();
FailureCount++;
StatusCode = "异常";
StatusColor = Brushes.Red;
ResponseText = $"发生异常:\n{ex.Message}\n\n{ex.StackTrace}";
ElapsedTime = $"{sw.ElapsedMilliseconds} ms";
AppendLog($"[{DateTime.Now:HH:mm:ss}] ❌ 异常: {ex.Message}");
_logger.LogError(ex, "HTTP 请求异常");
}
}
private async Task HandleResponse(HttpResponseMessage response, long elapsedMs)
{
// 状态码
StatusCode = $"{(int)response.StatusCode} {response.ReasonPhrase}";
StatusColor = response.IsSuccessStatusCode ? Brushes.Green : Brushes.Red;
// 响应头
var headersBuilder = new StringBuilder();
headersBuilder.AppendLine("=== 响应头 ===");
foreach (var header in response.Headers)
{
headersBuilder.AppendLine($"{header.Key}: {string.Join(", ", header.Value)}");
}
if (response.Content != null)
{
headersBuilder.AppendLine("\n=== 内容头 ===");
foreach (var header in response.Content.Headers)
{
headersBuilder.AppendLine($"{header.Key}: {string.Join(", ", header.Value)}");
}
}
ResponseHeaders = headersBuilder.ToString();
// 内容类型
ContentType = response.Content?.Headers.ContentType?.MediaType ?? "未知";
// 响应体
if (response.Content != null)
{
var content = await response.Content.ReadAsStringAsync();
ResponseText = string.IsNullOrWhiteSpace(content) ? "(空响应)" : content;
// 响应大小
var sizeBytes = Encoding.UTF8.GetByteCount(content);
ResponseSize = FormatSize(sizeBytes);
}
else
{
ResponseText = "(无内容)";
ResponseSize = "0 bytes";
}
// 耗时
ElapsedTime = $"{elapsedMs} ms";
}
private void Clear()
{
ResponseText = "";
ResponseHeaders = "";
StatusCode = "-";
StatusColor = Brushes.Gray;
ContentType = "-";
ElapsedTime = "0 ms";
ResponseSize = "0 bytes";
AppendLog($"[{DateTime.Now:HH:mm:ss}] 已清空响应内容");
}
private void CopyResponse()
{
try
{
System.Windows.Clipboard.SetText(ResponseText);
AppendLog($"[{DateTime.Now:HH:mm:ss}] ✅ 已复制响应内容到剪贴板");
}
catch (Exception ex)
{
AppendLog($"[{DateTime.Now:HH:mm:ss}] ❌ 复制失败: {ex.Message}");
}
}
private async Task SaveResponse()
{
try
{
var dialog = new Microsoft.Win32.SaveFileDialog
{
FileName = $"response_{DateTime.Now:yyyyMMdd_HHmmss}",
DefaultExt = ".txt",
Filter = "文本文件 (.txt)|*.txt|JSON 文件 (.json)|*.json|所有文件 (*.*)|*.*"
};
if (dialog.ShowDialog() == true)
{
await System.IO.File.WriteAllTextAsync(dialog.FileName, ResponseText);
AppendLog($"[{DateTime.Now:HH:mm:ss}] ✅ 响应已保存到: {dialog.FileName}");
}
}
catch (Exception ex)
{
AppendLog($"[{DateTime.Now:HH:mm:ss}] ❌ 保存失败: {ex.Message}");
}
}
private async Task TestLocalServer()
{
RequestUrl = "http://localhost:8080/api/health";
SelectedMethod = "GET";
RequestBody = "";
AppendLog($"[{DateTime.Now:HH:mm:ss}] 准备测试本地服务器健康检查");
await SendRequest();
}
private async Task TestRetry()
{
// 测试重试策略 - 使用一个不存在的端口
RequestUrl = "http://localhost:9999/api/test";
SelectedMethod = "GET";
AppendLog($"[{DateTime.Now:HH:mm:ss}] 测试重试策略(此请求会失败并重试)");
await SendRequest();
}
private async Task TestTimeout()
{
// 测试超时 - 使用一个超时的端点
RequestUrl = "http://httpstat.us/200?sleep=60000";
SelectedMethod = "GET";
AppendLog($"[{DateTime.Now:HH:mm:ss}] 测试超时处理(此请求会超时)");
await SendRequest();
}
private async Task TestJsonApi()
{
RequestUrl = "http://localhost:5000/api/data";
SelectedMethod = "POST";
RequestBody = "{\n \"name\": \"测试数据\",\n \"value\": 123,\n \"timestamp\": \"" + DateTime.Now.ToString("o") + "\"\n}";
AppendLog($"[{DateTime.Now:HH:mm:ss}] 准备测试 JSON API");
await SendRequest();
}
#endregion
#region 辅助方法
private void AppendLog(string message)
{
LogText += message + "\n";
}
private string FormatSize(long bytes)
{
if (bytes < 1024)
return $"{bytes} bytes";
if (bytes < 1024 * 1024)
return $"{bytes / 1024.0:F2} KB";
return $"{bytes / (1024.0 * 1024.0):F2} MB";
}
#endregion
#region 清理
public override void Dispose()
{
_logger.LogInformation("HTTP 客户端窗口 ViewModel 正在释放资源, 总请求数: {Total}, 成功: {Success}, 失败: {Failure}",
TotalRequests, SuccessCount, FailureCount);
base.Dispose();
_logger.LogInformation("HTTP 客户端窗口 ViewModel 已释放");
}
#endregion
}
}
@@ -0,0 +1,298 @@
using System;
using System.Windows.Media;
using Microsoft.Extensions.Logging;
using Sharp.Wpf.Mvvm;
using Sharp.Wpf.Commands;
using Sharp.Demo.Services;
namespace Sharp.Demo.ViewModels.Windows
{
/// <summary>
/// HTTP 服务器窗口 ViewModel
/// </summary>
public class HttpServerViewModel : ViewModelBase
{
private readonly ServerService _serverService;
private readonly ILogger<HttpServerViewModel> _logger;
private string _serverUrl = "";
private string _statusText = "未运行";
private Brush _statusColor = Brushes.Gray;
private string _logText = "等待启动服务器...\n";
private string _host = "";
private int _port = 0;
private bool _enableCors = true;
private bool _enableLogging = true;
private string _uptime = "00:00:00";
private int _totalRequests = 0;
private int _activeConnections = 0;
public HttpServerViewModel(ServerService serverService, ILogger<HttpServerViewModel> logger)
{
_serverService = serverService ?? throw new ArgumentNullException(nameof(serverService));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_logger.LogInformation("HTTP 服务器窗口 ViewModel 已创建");
// 从 ServerService 获取配置
_serverUrl = _serverService.Url;
_host = _serverService.Host;
_port = _serverService.Port;
// 订阅服务器事件
_serverService.StatusChanged += OnServerStatusChanged;
// 初始化命令
StartServerCommand = new RelayCommand(_ => StartServer(), _ => !_serverService.IsRunning);
StopServerCommand = new RelayCommand(_ => StopServer(), _ => _serverService.IsRunning);
RestartServerCommand = new RelayCommand(_ => RestartServer(), _ => _serverService.IsRunning);
OpenUserManagementCommand = new RelayCommand(OpenUserManagement);
ClearLogCommand = new RelayCommand(_ => LogText = "");
OpenInBrowserCommand = new RelayCommand(_ => OpenInBrowser(), _ => _serverService.IsRunning);
CopyUrlCommand = new RelayCommand(CopyUrl);
ShowMetricsCommand = new RelayCommand(_ => ShowMetrics(), _ => _serverService.IsRunning);
OpenAdvancedSettingsCommand = new RelayCommand(OpenAdvancedSettings);
}
#region 属性
public string ServerUrl
{
get => _serverUrl;
set => SetProperty(ref _serverUrl, value);
}
public string StatusText
{
get => _statusText;
set => SetProperty(ref _statusText, value);
}
public Brush StatusColor
{
get => _statusColor;
set => SetProperty(ref _statusColor, value);
}
public string LogText
{
get => _logText;
set => SetProperty(ref _logText, value);
}
public string Host
{
get => _host;
set => SetProperty(ref _host, value);
}
public int Port
{
get => _port;
set => SetProperty(ref _port, value);
}
public bool EnableCors
{
get => _enableCors;
set => SetProperty(ref _enableCors, value);
}
public bool EnableLogging
{
get => _enableLogging;
set => SetProperty(ref _enableLogging, value);
}
public string Uptime
{
get => _uptime;
set => SetProperty(ref _uptime, value);
}
public int TotalRequests
{
get => _totalRequests;
set => SetProperty(ref _totalRequests, value);
}
public int ActiveConnections
{
get => _activeConnections;
set => SetProperty(ref _activeConnections, value);
}
#endregion
#region 命令
public RelayCommand StartServerCommand { get; }
public RelayCommand StopServerCommand { get; }
public RelayCommand RestartServerCommand { get; }
public RelayCommand OpenUserManagementCommand { get; }
public RelayCommand ClearLogCommand { get; }
public RelayCommand OpenInBrowserCommand { get; }
public RelayCommand CopyUrlCommand { get; }
public RelayCommand ShowMetricsCommand { get; }
public RelayCommand OpenAdvancedSettingsCommand { get; }
#endregion
#region 命令实现
private void StartServer()
{
try
{
_logger.LogInformation("用户点击了启动服务器按钮");
AppendLog($"[{DateTime.Now:HH:mm:ss}] 正在启动服务器...");
_serverService.Start();
AppendLog($"[{DateTime.Now:HH:mm:ss}] ✅ 服务器已启动: {ServerUrl}");
StatusText = "运行中";
StatusColor = Brushes.Green;
// 刷新命令状态
StartServerCommand.RaiseCanExecuteChanged();
StopServerCommand.RaiseCanExecuteChanged();
RestartServerCommand.RaiseCanExecuteChanged();
OpenInBrowserCommand.RaiseCanExecuteChanged();
ShowMetricsCommand.RaiseCanExecuteChanged();
_logger.LogInformation("HTTP 服务器已成功启动");
}
catch (Exception ex)
{
_logger.LogError(ex, "启动 HTTP 服务器失败");
AppendLog($"[{DateTime.Now:HH:mm:ss}] ❌ 启动失败: {ex.Message}");
StatusText = "启动失败";
StatusColor = Brushes.Red;
}
}
private void StopServer()
{
try
{
_logger.LogInformation("用户点击了停止服务器按钮");
AppendLog($"[{DateTime.Now:HH:mm:ss}] 正在停止服务器...");
_serverService.Stop();
AppendLog($"[{DateTime.Now:HH:mm:ss}] ⏹ 服务器已停止");
StatusText = "已停止";
StatusColor = Brushes.Gray;
// 刷新命令状态
StartServerCommand.RaiseCanExecuteChanged();
StopServerCommand.RaiseCanExecuteChanged();
RestartServerCommand.RaiseCanExecuteChanged();
OpenInBrowserCommand.RaiseCanExecuteChanged();
ShowMetricsCommand.RaiseCanExecuteChanged();
_logger.LogInformation("HTTP 服务器已停止");
}
catch (Exception ex)
{
_logger.LogError(ex, "停止 HTTP 服务器失败");
AppendLog($"[{DateTime.Now:HH:mm:ss}] ❌ 停止失败: {ex.Message}");
}
}
private void RestartServer()
{
_logger.LogInformation("用户点击了重启服务器按钮");
AppendLog($"[{DateTime.Now:HH:mm:ss}] 重启服务器...");
StopServer();
System.Threading.Thread.Sleep(500);
StartServer();
}
private void OpenUserManagement()
{
AppendLog($"[{DateTime.Now:HH:mm:ss}] 打开用户管理功能(待实现)");
System.Windows.MessageBox.Show("用户管理功能开发中...", "提示");
}
private void OpenInBrowser()
{
try
{
System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo
{
FileName = ServerUrl,
UseShellExecute = true
});
AppendLog($"[{DateTime.Now:HH:mm:ss}] 已在浏览器中打开: {ServerUrl}");
}
catch (Exception ex)
{
AppendLog($"[{DateTime.Now:HH:mm:ss}] ❌ 打开浏览器失败: {ex.Message}");
}
}
private void CopyUrl()
{
try
{
System.Windows.Clipboard.SetText(ServerUrl);
AppendLog($"[{DateTime.Now:HH:mm:ss}] ✅ 已复制服务器地址到剪贴板");
}
catch (Exception ex)
{
AppendLog($"[{DateTime.Now:HH:mm:ss}] ❌ 复制失败: {ex.Message}");
}
}
private void ShowMetrics()
{
AppendLog($"[{DateTime.Now:HH:mm:ss}] 性能指标: 请求总数={TotalRequests}, 活跃连接={ActiveConnections}");
}
private void OpenAdvancedSettings()
{
AppendLog($"[{DateTime.Now:HH:mm:ss}] 高级设置功能(待实现)");
System.Windows.MessageBox.Show("高级设置功能开发中...", "提示");
}
#endregion
#region 事件处理
private void OnServerStatusChanged(object? sender, string status)
{
if (_serverService.IsRunning)
{
StatusText = "运行中";
StatusColor = Brushes.Green;
}
else
{
StatusText = "未运行";
StatusColor = Brushes.Gray;
}
}
#endregion
#region 辅助方法
private void AppendLog(string message)
{
LogText += message + "\n";
}
#endregion
#region 清理
public override void Dispose()
{
_logger.LogInformation("HTTP 服务器窗口 ViewModel 正在释放资源");
_serverService.StatusChanged -= OnServerStatusChanged;
base.Dispose();
_logger.LogInformation("HTTP 服务器窗口 ViewModel 已释放");
}
#endregion
}
}
@@ -0,0 +1,117 @@
using System;
using Sharp.Wpf.Mvvm;
using Sharp.Wpf.Commands;
namespace Sharp.Demo.ViewModels.Windows
{
public class PlcDebugViewModel : ViewModelBase
{
private string _ipAddress = "127.0.0.1";
private string _status = "未连接";
private string _readAddress = "DB1.DBW0";
private string _readValue = "";
private string _writeAddress = "DB1.DBW0";
private string _writeValue = "";
private string _logText = "等待连接PLC...\n";
public PlcDebugViewModel()
{
ConnectCommand = new RelayCommand(Connect);
DisconnectCommand = new RelayCommand(Disconnect);
ReadCommand = new RelayCommand(Read);
WriteCommand = new RelayCommand(Write);
ClearLogCommand = new RelayCommand(() => LogText = "");
}
#region 属性
public string IpAddress
{
get => _ipAddress;
set => SetProperty(ref _ipAddress, value);
}
public string Status
{
get => _status;
set => SetProperty(ref _status, value);
}
public string ReadAddress
{
get => _readAddress;
set => SetProperty(ref _readAddress, value);
}
public string ReadValue
{
get => _readValue;
set => SetProperty(ref _readValue, value);
}
public string WriteAddress
{
get => _writeAddress;
set => SetProperty(ref _writeAddress, value);
}
public string WriteValue
{
get => _writeValue;
set => SetProperty(ref _writeValue, value);
}
public string LogText
{
get => _logText;
set => SetProperty(ref _logText, value);
}
#endregion
#region 命令
public RelayCommand ConnectCommand { get; }
public RelayCommand DisconnectCommand { get; }
public RelayCommand ReadCommand { get; }
public RelayCommand WriteCommand { get; }
public RelayCommand ClearLogCommand { get; }
#endregion
#region 命令实现
private void Connect()
{
AppendLog($"[{DateTime.Now:HH:mm:ss}] 正在连接 PLC: {IpAddress}");
Status = "已连接";
AppendLog($"[{DateTime.Now:HH:mm:ss}] ✅ 连接成功");
}
private void Disconnect()
{
AppendLog($"[{DateTime.Now:HH:mm:ss}] 断开连接");
Status = "未连接";
}
private void Read()
{
AppendLog($"[{DateTime.Now:HH:mm:ss}] 读取 {ReadAddress}");
ReadValue = "42"; // 示例值
AppendLog($"[{DateTime.Now:HH:mm:ss}] ✅ 读取成功: {ReadValue}");
}
private void Write()
{
AppendLog($"[{DateTime.Now:HH:mm:ss}] 写入 {WriteAddress} = {WriteValue}");
AppendLog($"[{DateTime.Now:HH:mm:ss}] ✅ 写入成功");
}
#endregion
private void AppendLog(string message)
{
LogText += message + "\n";
}
}
}
@@ -0,0 +1,56 @@
<Window x:Class="Sharp.Demo.Views.Dialogs.UserEditDialog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:hc="https://handyorg.github.io/handycontrol"
Title="用户编辑" Height="450" Width="500"
WindowStartupLocation="CenterOwner"
ResizeMode="NoResize">
<Grid Margin="20">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<!-- 标题 -->
<TextBlock Grid.Row="0" Text="用户信息" FontSize="18" FontWeight="Bold" Margin="0,0,0,20"/>
<!-- 表单 -->
<ScrollViewer Grid.Row="1" VerticalScrollBarVisibility="Auto">
<StackPanel>
<!-- 用户名 -->
<TextBlock Text="用户名 *" FontWeight="Bold" Margin="0,0,0,5"/>
<TextBox x:Name="UserNameTextBox" Height="32" Padding="8,0" Margin="0,0,0,15"
BorderBrush="{DynamicResource BorderBrush}" BorderThickness="1"/>
<!-- 邮箱 -->
<TextBlock Text="邮箱 *" FontWeight="Bold" Margin="0,0,0,5"/>
<TextBox x:Name="EmailTextBox" Height="32" Padding="8,0" Margin="0,0,0,15"
BorderBrush="{DynamicResource BorderBrush}" BorderThickness="1"/>
<!-- 年龄 -->
<TextBlock Text="年龄 *" FontWeight="Bold" Margin="0,0,0,5"/>
<TextBox x:Name="AgeTextBox" Height="32" Padding="8,0" Margin="0,0,0,15"
BorderBrush="{DynamicResource BorderBrush}" BorderThickness="1"/>
<!-- 激活状态 -->
<CheckBox x:Name="IsActiveCheckBox" Content="激活" IsChecked="True" Margin="0,0,0,15"/>
<!-- 备注 -->
<TextBlock Text="备注" FontWeight="Bold" Margin="0,0,0,5"/>
<TextBox x:Name="RemarksTextBox" Height="80" Padding="8" Margin="0,0,0,15"
TextWrapping="Wrap" AcceptsReturn="True"
BorderBrush="{DynamicResource BorderBrush}" BorderThickness="1"
VerticalScrollBarVisibility="Auto"/>
</StackPanel>
</ScrollViewer>
<!-- 按钮 -->
<StackPanel Grid.Row="2" Orientation="Horizontal" HorizontalAlignment="Right" Margin="0,20,0,0">
<Button Content="保存" Width="100" Height="35" Margin="0,0,10,0" Click="SaveButton_Click"
Style="{StaticResource ButtonSuccess}" Cursor="Hand"/>
<Button Content="取消" Width="100" Height="35" Click="CancelButton_Click"
Style="{StaticResource ButtonDefault}" Cursor="Hand"/>
</StackPanel>
</Grid>
</Window>
@@ -0,0 +1,90 @@
using System.Windows;
using Sharp.Demo.Data;
namespace Sharp.Demo.Views.Dialogs
{
public partial class UserEditDialog : Window
{
public User UserData { get; private set; }
private readonly bool _isEditMode;
public UserEditDialog(User? existingUser = null)
{
InitializeComponent();
_isEditMode = existingUser != null;
Title = _isEditMode ? "编辑用户" : "新增用户";
if (existingUser != null)
{
UserData = existingUser;
LoadUserData(existingUser);
}
else
{
UserData = new User
{
IsActive = true
};
}
}
private void LoadUserData(User user)
{
UserNameTextBox.Text = user.UserName;
EmailTextBox.Text = user.Email;
AgeTextBox.Text = user.Age.ToString();
IsActiveCheckBox.IsChecked = user.IsActive;
RemarksTextBox.Text = user.Remarks;
}
private void SaveButton_Click(object sender, RoutedEventArgs e)
{
// 验证
if (string.IsNullOrWhiteSpace(UserNameTextBox.Text))
{
MessageBox.Show("请输入用户名", "验证失败", MessageBoxButton.OK, MessageBoxImage.Warning);
UserNameTextBox.Focus();
return;
}
if (string.IsNullOrWhiteSpace(EmailTextBox.Text))
{
MessageBox.Show("请输入邮箱", "验证失败", MessageBoxButton.OK, MessageBoxImage.Warning);
EmailTextBox.Focus();
return;
}
if (!int.TryParse(AgeTextBox.Text, out int age) || age < 0 || age > 150)
{
MessageBox.Show("请输入有效的年龄(0-150)", "验证失败", MessageBoxButton.OK, MessageBoxImage.Warning);
AgeTextBox.Focus();
return;
}
// 简单的邮箱格式验证
if (!EmailTextBox.Text.Contains("@"))
{
MessageBox.Show("请输入有效的邮箱地址", "验证失败", MessageBoxButton.OK, MessageBoxImage.Warning);
EmailTextBox.Focus();
return;
}
// 更新数据
UserData.UserName = UserNameTextBox.Text.Trim();
UserData.Email = EmailTextBox.Text.Trim();
UserData.Age = age;
UserData.IsActive = IsActiveCheckBox.IsChecked == true;
UserData.Remarks = RemarksTextBox.Text.Trim();
DialogResult = true;
Close();
}
private void CancelButton_Click(object sender, RoutedEventArgs e)
{
DialogResult = false;
Close();
}
}
}
@@ -0,0 +1,179 @@
<hc:GlowWindow x:Class="Sharp.Demo.Views.LauncherWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:hc="https://handyorg.github.io/handycontrol"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Title="Sharp Framework - 模块启动器"
Height="700"
Width="1100"
WindowStartupLocation="CenterScreen"
Background="{DynamicResource RegionBrush}">
<!-- 快捷键 -->
<hc:GlowWindow.InputBindings>
<KeyBinding Key="F12" Command="{Binding OpenDevToolsCommand}" />
<KeyBinding Key="F11" Command="{Binding OpenLogViewerCommand}" />
<KeyBinding Key="F10" Command="{Binding OpenPerformanceMonitorCommand}" />
</hc:GlowWindow.InputBindings>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<!-- 标题栏 -->
<RowDefinition Height="*"/>
<!-- 模块区域 -->
<RowDefinition Height="Auto"/>
<!-- 状态栏 -->
</Grid.RowDefinitions>
<!-- 标题栏 -->
<Border Grid.Row="0" Background="{DynamicResource PrimaryBrush}" Padding="30,20">
<Grid>
<StackPanel>
<TextBlock Text="Sharp Framework" FontSize="32" FontWeight="Bold" Foreground="White"/>
<TextBlock Text="模块化 .NET 8 开发框架 - 功能演示平台" FontSize="14" Foreground="White" Opacity="0.9" Margin="0,5,0,0"/>
</StackPanel>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right" VerticalAlignment="Center">
<Button Content="⚙️ 设置" Width="80" Height="32" Margin="0,0,10,0" Style="{StaticResource ButtonDefault}"/>
<Button Content="📖 文档" Width="80" Height="32" Margin="0,0,10,0" Style="{StaticResource ButtonDefault}"/>
<Button Content="❓ 关于" Width="80" Height="32" Command="{Binding ShowAboutCommand}" Style="{StaticResource ButtonDefault}"/>
</StackPanel>
</Grid>
</Border>
<!-- 主内容区 -->
<ScrollViewer Grid.Row="1" VerticalScrollBarVisibility="Auto" Padding="20">
<StackPanel>
<!-- 快速状态 -->
<Border Background="{DynamicResource SecondaryRegionBrush}" CornerRadius="8" Padding="20" Margin="0,0,0,20">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0" Margin="0,0,20,0">
<TextBlock Text="HTTP 服务器" FontWeight="SemiBold" Foreground="{DynamicResource SecondaryTextBrush}"/>
<TextBlock Text="{Binding ServerStatus}" FontSize="20" Margin="0,5,0,0"/>
</StackPanel>
<StackPanel Grid.Column="1" Margin="0,0,20,0">
<TextBlock Text="PLC 连接" FontWeight="SemiBold" Foreground="{DynamicResource SecondaryTextBrush}"/>
<TextBlock Text="{Binding PlcStatus}" FontSize="20" Margin="0,5,0,0"/>
</StackPanel>
<StackPanel Grid.Column="2" Margin="0,0,20,0">
<TextBlock Text="打开窗口" FontWeight="SemiBold" Foreground="{DynamicResource SecondaryTextBrush}"/>
<TextBlock Text="{Binding OpenWindowCount}" FontSize="20" Margin="0,5,0,0"/>
</StackPanel>
<StackPanel Grid.Column="3" Orientation="Horizontal" VerticalAlignment="Center">
<Button Content="F12 开发者工具" Width="130" Height="32" Margin="0,0,10,0"
Command="{Binding OpenDevToolsCommand}" Style="{StaticResource ButtonInfo}"/>
<Button Content="F11 日志查看" Width="120" Height="32" Margin="0,0,10,0"
Command="{Binding OpenLogViewerCommand}" Style="{StaticResource ButtonSuccess}"/>
<Button Content="F10 性能监控" Width="120" Height="32"
Command="{Binding OpenPerformanceMonitorCommand}" Style="{StaticResource ButtonWarning}"/>
</StackPanel>
</Grid>
</Border>
<!-- 模块分类展示 -->
<ItemsControl ItemsSource="{Binding ModuleCategories}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Margin="0,0,0,25">
<!-- 分类标题 -->
<TextBlock Text="{Binding CategoryName}" FontSize="20" FontWeight="Bold" Margin="0,0,0,15"/>
<!-- 模块卡片 -->
<ItemsControl ItemsSource="{Binding Modules}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="4" HorizontalAlignment="Left"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border Background="{DynamicResource RegionBrush}"
BorderBrush="{DynamicResource BorderBrush}"
BorderThickness="1"
CornerRadius="8"
Padding="20"
Margin="0,0,15,15"
Height="160"
Cursor="Hand">
<Border.Style>
<Style TargetType="Border">
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="{DynamicResource SecondaryRegionBrush}"/>
</Trigger>
</Style.Triggers>
</Style>
</Border.Style>
<Border.InputBindings>
<MouseBinding MouseAction="LeftClick" Command="{Binding OpenCommand}"/>
</Border.InputBindings>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<!-- 图标 -->
<TextBlock Grid.Row="0" Text="{Binding Icon}" FontSize="40" HorizontalAlignment="Left"/>
<!-- 名称和描述 -->
<StackPanel Grid.Row="1" VerticalAlignment="Center" Margin="0,10,0,0">
<TextBlock Text="{Binding Name}" FontSize="16" FontWeight="Bold"/>
<TextBlock Text="{Binding Description}" FontSize="12"
Foreground="{DynamicResource SecondaryTextBrush}"
TextWrapping="Wrap" Margin="0,5,0,0"/>
</StackPanel>
<!-- 状态 -->
<TextBlock Grid.Row="2" Text="{Binding Status}" FontSize="11"
Foreground="{DynamicResource SuccessBrush}"
HorizontalAlignment="Right"/>
</Grid>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</ScrollViewer>
<!-- 状态栏 -->
<StatusBar Grid.Row="2" Background="{DynamicResource RegionBrush}"
BorderBrush="{DynamicResource BorderBrush}" BorderThickness="0,1,0,0">
<StatusBarItem>
<TextBlock Text="Sharp Framework v1.0.0"/>
</StatusBarItem>
<Separator/>
<StatusBarItem>
<TextBlock>
<Run Text="运行时间: "/>
<Run Text="{Binding Uptime, Mode=OneWay}"/>
</TextBlock>
</StatusBarItem>
<Separator/>
<StatusBarItem>
<TextBlock>
<Run Text="已打开窗口: "/>
<Run Text="{Binding OpenWindowCount, Mode=OneWay}"/>
</TextBlock>
</StatusBarItem>
</StatusBar>
</Grid>
</hc:GlowWindow>
@@ -0,0 +1,43 @@
using System.Windows;
using HandyControl.Controls;
using Sharp.Demo.ViewModels;
namespace Sharp.Demo.Views
{
/// <summary>
/// LauncherWindow.xaml 的交互逻辑
/// </summary>
public partial class LauncherWindow : GlowWindow
{
public LauncherWindow(LauncherViewModel viewModel)
{
InitializeComponent();
DataContext = viewModel;
}
protected override void OnClosing(System.ComponentModel.CancelEventArgs e)
{
var result = System.Windows.MessageBox.Show(
"确定要退出 Sharp Framework 吗?\n这将关闭所有已打开的模块窗口。",
"确认退出",
MessageBoxButton.YesNo,
MessageBoxImage.Question
);
if (result == MessageBoxResult.No)
{
e.Cancel = true;
}
else
{
// 关闭所有模块窗口
if (DataContext is LauncherViewModel vm)
{
vm.CloseAllWindows();
}
}
base.OnClosing(e);
}
}
}
@@ -0,0 +1,169 @@
<UserControl x:Class="Sharp.Demo.Views.Pages.ConfigDemoPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:hc="https://handyorg.github.io/handycontrol"
mc:Ignorable="d"
d:DesignHeight="600"
d:DesignWidth="800">
<ScrollViewer VerticalScrollBarVisibility="Auto">
<StackPanel Margin="20">
<!-- 标题区域 -->
<TextBlock
FontSize="28"
FontWeight="Bold"
Text="配置管理" />
<TextBlock
Margin="0,10,0,30"
FontSize="14"
Foreground="{DynamicResource SecondaryTextBrush}"
Text="Sharp.Config - 多格式配置管理,支持 JSON/XML/INI/TOML,热重载" />
<!-- 配置信息 -->
<Border
Background="{DynamicResource RegionBrush}"
BorderBrush="{DynamicResource BorderBrush}"
BorderThickness="1"
CornerRadius="8"
Padding="20"
Margin="0,0,0,15">
<StackPanel>
<TextBlock Text="当前配置" FontSize="16" FontWeight="Bold" Margin="0,0,0,15"/>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock Text="配置文件:" VerticalAlignment="Center" Margin="0,0,0,10"/>
<TextBlock Grid.Column="1" Text="{Binding ConfigFile}" VerticalAlignment="Center"
Margin="0,0,0,10" Foreground="{DynamicResource SecondaryTextBrush}"/>
<TextBlock Grid.Row="1" Text="支持格式:" VerticalAlignment="Center" Margin="0,0,0,10"/>
<TextBlock Grid.Row="1" Grid.Column="1" Text="JSON, XML, INI, TOML" VerticalAlignment="Center"
Margin="0,0,0,10" Foreground="{DynamicResource SecondaryTextBrush}"/>
<TextBlock Grid.Row="2" Text="热重载:" VerticalAlignment="Center" Margin="0,0,0,10"/>
<StackPanel Grid.Row="2" Grid.Column="1" Orientation="Horizontal" Margin="0,0,0,10">
<CheckBox IsChecked="{Binding IsHotReloadEnabled}" Content="已启用"
IsEnabled="False" VerticalAlignment="Center"/>
</StackPanel>
<TextBlock Grid.Row="3" Text="环境变量支持:" VerticalAlignment="Center"/>
<StackPanel Grid.Row="3" Grid.Column="1" Orientation="Horizontal">
<CheckBox IsChecked="True" Content="已启用 (前缀: APP_)"
IsEnabled="False" VerticalAlignment="Center"/>
</StackPanel>
</Grid>
</StackPanel>
</Border>
<!-- 配置读取 -->
<Border
Background="{DynamicResource RegionBrush}"
BorderBrush="{DynamicResource BorderBrush}"
BorderThickness="1"
CornerRadius="8"
Padding="20"
Margin="0,0,0,15">
<StackPanel>
<TextBlock Text="配置读取测试" FontSize="16" FontWeight="Bold" Margin="0,0,0,15"/>
<Grid Margin="0,0,0,15">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Text="配置键:" VerticalAlignment="Center" Margin="0,0,10,0"/>
<TextBox Grid.Column="1" Text="{Binding ConfigKey}" Height="32" Padding="8,0"
Margin="0,0,10,0" BorderBrush="{DynamicResource BorderBrush}" BorderThickness="1"/>
<Button Grid.Column="2" Content="读取" Width="100" Height="32"
Command="{Binding ReadConfigCommand}"
Style="{StaticResource ButtonPrimary}"/>
</Grid>
<TextBlock Text="配置值:" Margin="0,0,0,5"/>
<TextBox Text="{Binding ConfigValue, Mode=OneWay}" Height="60" IsReadOnly="True"
TextWrapping="Wrap" Background="{DynamicResource SecondaryRegionBrush}"
BorderBrush="{DynamicResource BorderBrush}" BorderThickness="1"
FontFamily="Consolas" Padding="8"/>
</StackPanel>
</Border>
<!-- 快捷功能 -->
<Border
Background="{DynamicResource RegionBrush}"
BorderBrush="{DynamicResource BorderBrush}"
BorderThickness="1"
CornerRadius="8"
Padding="20"
Margin="0,0,0,15">
<StackPanel>
<TextBlock Text="快捷功能" FontSize="16" FontWeight="Bold" Margin="0,0,0,15"/>
<WrapPanel>
<Button Content="查看所有配置" Width="140" Height="35" Margin="0,0,10,10"
Command="{Binding ViewAllConfigCommand}"
Style="{StaticResource ButtonPrimary}"/>
<Button Content="读取数据库配置" Width="140" Height="35" Margin="0,0,10,10"
Command="{Binding ReadDbConfigCommand}"
Style="{StaticResource ButtonInfo}"/>
<Button Content="读取日志配置" Width="140" Height="35" Margin="0,0,10,10"
Command="{Binding ReadLogConfigCommand}"
Style="{StaticResource ButtonInfo}"/>
<Button Content="读取服务器配置" Width="140" Height="35" Margin="0,0,10,10"
Command="{Binding ReadServerConfigCommand}"
Style="{StaticResource ButtonInfo}"/>
<Button Content="测试热重载" Width="140" Height="35" Margin="0,0,10,10"
Command="{Binding TestHotReloadCommand}"
Style="{StaticResource ButtonSuccess}"/>
<Button Content="打开配置文件" Width="140" Height="35" Margin="0,0,10,10"
Command="{Binding OpenConfigFileCommand}"
Style="{StaticResource ButtonWarning}"/>
<Button Content="验证配置" Width="140" Height="35" Margin="0,0,10,10"
Command="{Binding ValidateConfigCommand}"
Style="{StaticResource ButtonDefault}"/>
</WrapPanel>
</StackPanel>
</Border>
<!-- 配置预览 -->
<Border
Background="{DynamicResource RegionBrush}"
BorderBrush="{DynamicResource BorderBrush}"
BorderThickness="1"
CornerRadius="8"
Padding="15">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="250"/>
</Grid.RowDefinitions>
<TextBlock Text="配置内容预览" FontSize="16" FontWeight="Bold" Margin="0,0,0,10"/>
<ScrollViewer Grid.Row="1" VerticalScrollBarVisibility="Auto">
<TextBox Text="{Binding ConfigPreview, Mode=OneWay}"
IsReadOnly="True"
TextWrapping="Wrap"
Background="{DynamicResource SecondaryRegionBrush}"
BorderThickness="0"
FontFamily="Consolas"
FontSize="11"
Padding="10"/>
</ScrollViewer>
</Grid>
</Border>
</StackPanel>
</ScrollViewer>
</UserControl>
@@ -0,0 +1,15 @@
using System.Windows.Controls;
namespace Sharp.Demo.Views.Pages
{
/// <summary>
/// ConfigDemoPage.xaml 的交互逻辑
/// </summary>
public partial class ConfigDemoPage : UserControl
{
public ConfigDemoPage()
{
InitializeComponent();
}
}
}
@@ -0,0 +1,164 @@
<UserControl x:Class="Sharp.Demo.Views.Pages.DatabasePage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:hc="https://handyorg.github.io/handycontrol"
mc:Ignorable="d"
d:DesignHeight="600"
d:DesignWidth="800">
<ScrollViewer VerticalScrollBarVisibility="Auto">
<Grid Margin="20">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<!-- 标题区域 -->
<TextBlock
Grid.Row="0"
FontSize="28"
FontWeight="Bold"
Text="数据库管理"
Margin="0,0,0,10" />
<TextBlock
Grid.Row="1"
FontSize="14"
Foreground="{DynamicResource SecondaryTextBrush}"
Text="Sharp.Data - EF Core + Repository + 审计追踪 + 软删除"
Margin="0,0,0,20" />
<!-- 搜索和操作栏 -->
<Border
Grid.Row="2"
Background="{DynamicResource RegionBrush}"
BorderBrush="{DynamicResource BorderBrush}"
BorderThickness="1"
CornerRadius="8"
Padding="15"
Margin="0,0,0,15">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<!-- 搜索框 -->
<StackPanel Grid.Column="0" Orientation="Horizontal" Margin="0,0,15,0">
<TextBlock Text="搜索:" VerticalAlignment="Center" Margin="0,0,10,0"/>
<TextBox Text="{Binding SearchText, UpdateSourceTrigger=PropertyChanged}"
Width="250" Height="32" VerticalContentAlignment="Center"
Padding="8,0" BorderBrush="{DynamicResource BorderBrush}" BorderThickness="1"/>
<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" Text="{Binding StatusText}" VerticalAlignment="Center"
Foreground="{DynamicResource SecondaryTextBrush}" Margin="15,0"/>
<!-- 操作按钮 -->
<StackPanel Grid.Column="2" Orientation="Horizontal">
<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="3"
Background="{DynamicResource RegionBrush}"
BorderBrush="{DynamicResource BorderBrush}"
BorderThickness="1"
CornerRadius="8"
Padding="10"
Margin="0,0,0,15">
<DataGrid ItemsSource="{Binding Users}"
SelectedItem="{Binding SelectedUser}"
AutoGenerateColumns="False"
IsReadOnly="True"
SelectionMode="Single"
GridLinesVisibility="None"
HeadersVisibility="Column"
AlternationCount="2"
RowHeight="40"
CanUserAddRows="False"
CanUserDeleteRows="False">
<DataGrid.Resources>
<Style TargetType="DataGridRow">
<Style.Triggers>
<Trigger Property="ItemsControl.AlternationIndex" Value="1">
<Setter Property="Background" Value="{DynamicResource SecondaryRegionBrush}" />
</Trigger>
</Style.Triggers>
</Style>
</DataGrid.Resources>
<DataGrid.Columns>
<DataGridTextColumn Header="ID" Binding="{Binding Id}" Width="80" Visibility="Collapsed"/>
<DataGridTextColumn Header="用户名" Binding="{Binding UserName}" Width="150"/>
<DataGridTextColumn Header="邮箱" Binding="{Binding Email}" Width="200"/>
<DataGridTextColumn Header="年龄" Binding="{Binding Age}" Width="80"/>
<DataGridCheckBoxColumn Header="激活" Binding="{Binding IsActive}" Width="80"/>
<DataGridTextColumn Header="备注" Binding="{Binding Remarks}" Width="*"/>
<DataGridTextColumn Header="创建时间" Binding="{Binding CreatedAt, StringFormat={}{0:yyyy-MM-dd HH:mm}}" Width="150"/>
<DataGridTextColumn Header="创建人" Binding="{Binding CreatedBy}" Width="100"/>
<DataGridTextColumn Header="修改时间" Binding="{Binding UpdatedAt, StringFormat={}{0:yyyy-MM-dd HH:mm}}" Width="150"/>
</DataGrid.Columns>
</DataGrid>
</Border>
<!-- 底部信息栏 -->
<Border
Grid.Row="4"
Background="{DynamicResource SecondaryRegionBrush}"
BorderBrush="{DynamicResource BorderBrush}"
BorderThickness="1"
CornerRadius="8"
Padding="15">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0">
<TextBlock FontWeight="Bold" Margin="0,0,0,5">
<Run Text="💡 功能说明"/>
</TextBlock>
<TextBlock FontSize="11" TextWrapping="Wrap">
<Run Text="• 本示例展示 Sharp.Data 模块的使用:SQLite 数据库、仓储模式、自动审计、软删除"/>
<LineBreak/>
<Run Text="• 删除操作使用软删除(IsDeleted=true),数据不会真正删除"/>
<LineBreak/>
<Run Text="• 创建/修改时间和操作人由 Data 模块自动记录"/>
</TextBlock>
</StackPanel>
<StackPanel Grid.Column="1" VerticalAlignment="Center">
<TextBlock Text="数据库: SQLite (demo.db)" FontSize="11" FontWeight="Bold"/>
<TextBlock Text="{Binding RecordCount}" FontSize="11"/>
</StackPanel>
</Grid>
</Border>
</Grid>
</ScrollViewer>
</UserControl>
@@ -0,0 +1,15 @@
using System.Windows.Controls;
namespace Sharp.Demo.Views.Pages
{
/// <summary>
/// DatabasePage.xaml 的交互逻辑
/// </summary>
public partial class DatabasePage : UserControl
{
public DatabasePage()
{
InitializeComponent();
}
}
}
@@ -0,0 +1,181 @@
<UserControl x:Class="Sharp.Demo.Views.Pages.HomePage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:hc="https://handyorg.github.io/handycontrol"
mc:Ignorable="d"
d:DesignHeight="600"
d:DesignWidth="800">
<ScrollViewer VerticalScrollBarVisibility="Auto">
<StackPanel Margin="20">
<!-- 标题区域 -->
<TextBlock
FontSize="32"
FontWeight="Bold"
Text="Sharp Framework Demo" />
<TextBlock
Margin="0,10,0,30"
FontSize="16"
Foreground="{DynamicResource SecondaryTextBrush}"
Text="模块化 C# .NET 8 开发框架 - 功能演示和测试平台" />
<!-- 快速开始卡片区 -->
<TextBlock
Margin="0,0,0,15"
FontSize="20"
FontWeight="Bold"
Text="快速开始" />
<UniformGrid Columns="3" Rows="1">
<!-- 网络通信卡片 -->
<Border
Margin="0,0,10,0"
Background="{DynamicResource RegionBrush}"
BorderBrush="{DynamicResource BorderBrush}"
BorderThickness="1"
CornerRadius="8"
Padding="20">
<StackPanel>
<TextBlock
FontSize="20"
FontWeight="Bold"
Text="🌐 网络通信" />
<TextBlock
Margin="0,10,0,0"
Text="{Binding ServerStatusText}"
TextWrapping="Wrap" />
<Button
Margin="0,15,0,0"
Command="{Binding NavigateToHttpServerCommand}"
Content="打开 HTTP 服务器"
Style="{StaticResource ButtonPrimary}" />
</StackPanel>
</Border>
<!-- 数据管理卡片 -->
<Border
Margin="5,0,5,0"
Background="{DynamicResource RegionBrush}"
BorderBrush="{DynamicResource BorderBrush}"
BorderThickness="1"
CornerRadius="8"
Padding="20">
<StackPanel>
<TextBlock
FontSize="20"
FontWeight="Bold"
Text="💾 数据管理" />
<TextBlock
Margin="0,10,0,0"
Text="SQLite 数据库 + EF Core"
TextWrapping="Wrap" />
<Button
Margin="0,15,0,0"
Command="{Binding NavigateToDatabaseCommand}"
Content="打开数据库管理"
Style="{StaticResource ButtonSuccess}" />
</StackPanel>
</Border>
<!-- 工业控制卡片 -->
<Border
Margin="10,0,0,0"
Background="{DynamicResource RegionBrush}"
BorderBrush="{DynamicResource BorderBrush}"
BorderThickness="1"
CornerRadius="8"
Padding="20">
<StackPanel>
<TextBlock
FontSize="20"
FontWeight="Bold"
Text="🏭 工业控制" />
<TextBlock
Margin="0,10,0,0"
Text="{Binding PlcStatusText}"
TextWrapping="Wrap" />
<Button
Margin="0,15,0,0"
Command="{Binding NavigateToPlcCommand}"
Content="打开 PLC 调试"
Style="{StaticResource ButtonWarning}" />
</StackPanel>
</Border>
</UniformGrid>
<!-- 所有模块列表 -->
<TextBlock
Margin="0,40,0,15"
FontSize="20"
FontWeight="Bold"
Text="所有模块" />
<ItemsControl ItemsSource="{Binding Modules}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border
Margin="0,0,0,10"
Background="{DynamicResource RegionBrush}"
BorderBrush="{DynamicResource BorderBrush}"
BorderThickness="1"
CornerRadius="8"
Padding="15">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock
Grid.Column="0"
Margin="0,0,15,0"
FontSize="32"
Text="{Binding Icon}"
VerticalAlignment="Center" />
<StackPanel Grid.Column="1" VerticalAlignment="Center">
<TextBlock
FontSize="16"
FontWeight="Bold"
Text="{Binding Name}" />
<TextBlock
Margin="0,5,0,0"
Foreground="{DynamicResource SecondaryTextBrush}"
Text="{Binding Description}"
TextWrapping="Wrap" />
</StackPanel>
<Button
Grid.Column="2"
Command="{Binding NavigateCommand}"
Content="打开"
VerticalAlignment="Center" />
</Grid>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<!-- 系统信息 -->
<Border
Margin="0,30,0,0"
Background="{DynamicResource SecondaryRegionBrush}"
BorderBrush="{DynamicResource BorderBrush}"
BorderThickness="1"
CornerRadius="8"
Padding="15">
<StackPanel>
<TextBlock
FontSize="18"
FontWeight="Bold"
Text="系统信息" />
<TextBlock
Margin="0,10,0,0"
Text="{Binding SystemInfo}" />
</StackPanel>
</Border>
</StackPanel>
</ScrollViewer>
</UserControl>
@@ -0,0 +1,15 @@
using System.Windows.Controls;
namespace Sharp.Demo.Views.Pages
{
/// <summary>
/// HomePage.xaml 的交互逻辑
/// </summary>
public partial class HomePage : UserControl
{
public HomePage()
{
InitializeComponent();
}
}
}
@@ -0,0 +1,127 @@
<UserControl x:Class="Sharp.Demo.Views.Pages.HttpClientPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:hc="https://handyorg.github.io/handycontrol"
mc:Ignorable="d"
d:DesignHeight="600"
d:DesignWidth="800">
<ScrollViewer VerticalScrollBarVisibility="Auto">
<StackPanel Margin="20">
<!-- 标题区域 -->
<TextBlock
FontSize="28"
FontWeight="Bold"
Text="HTTP 客户端" />
<TextBlock
Margin="0,10,0,30"
FontSize="14"
Foreground="{DynamicResource SecondaryTextBrush}"
Text="Sharp.NetClient - 基于 HttpClient 的网络请求库,集成 Polly 重试策略" />
<!-- 请求配置 -->
<Border
Background="{DynamicResource RegionBrush}"
BorderBrush="{DynamicResource BorderBrush}"
BorderThickness="1"
CornerRadius="8"
Padding="20"
Margin="0,0,0,15">
<StackPanel>
<TextBlock Text="请求配置" FontSize="16" FontWeight="Bold" Margin="0,0,0,15"/>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="80"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Text="URL:" VerticalAlignment="Center"/>
<TextBox Grid.Column="1" Text="{Binding RequestUrl}" Height="32" Padding="8,0"
Margin="0,0,0,10" BorderBrush="{DynamicResource BorderBrush}" BorderThickness="1"/>
<TextBlock Grid.Row="1" Text="Method:" VerticalAlignment="Center"/>
<ComboBox Grid.Row="1" Grid.Column="1" SelectedItem="{Binding SelectedMethod}" Height="32"
Margin="0,0,0,10" ItemsSource="{Binding HttpMethods}"/>
<TextBlock Grid.Row="2" Text="Body:" VerticalAlignment="Top" Margin="0,5,0,0"/>
<TextBox Grid.Row="2" Grid.Column="1" Text="{Binding RequestBody}" Height="80"
TextWrapping="Wrap" AcceptsReturn="True" FontFamily="Consolas"
BorderBrush="{DynamicResource BorderBrush}" BorderThickness="1"/>
</Grid>
</StackPanel>
</Border>
<!-- 功能按钮 -->
<Border
Background="{DynamicResource RegionBrush}"
BorderBrush="{DynamicResource BorderBrush}"
BorderThickness="1"
CornerRadius="8"
Padding="20"
Margin="0,0,0,15">
<StackPanel>
<TextBlock Text="快速功能" FontSize="16" FontWeight="Bold" Margin="0,0,0,15"/>
<WrapPanel>
<Button Content="发送请求" Width="120" Height="35" Margin="0,0,10,10"
Command="{Binding SendRequestCommand}"
Style="{StaticResource ButtonPrimary}"/>
<Button Content="GET 测试" Width="120" Height="35" Margin="0,0,10,10"
Command="{Binding GetTestCommand}"
Style="{StaticResource ButtonSuccess}"/>
<Button Content="POST 测试" Width="120" Height="35" Margin="0,0,10,10"
Command="{Binding PostTestCommand}"
Style="{StaticResource ButtonInfo}"/>
<Button Content="带重试" Width="120" Height="35" Margin="0,0,10,10"
Command="{Binding RetryTestCommand}"
Style="{StaticResource ButtonWarning}"/>
<Button Content="超时测试" Width="120" Height="35" Margin="0,0,10,10"
Command="{Binding TimeoutTestCommand}"/>
<Button Content="清空结果" Width="120" Height="35" Margin="0,0,10,10"
Command="{Binding ClearResultCommand}"
Style="{StaticResource ButtonDefault}"/>
</WrapPanel>
</StackPanel>
</Border>
<!-- 响应结果 -->
<Border
Background="{DynamicResource RegionBrush}"
BorderBrush="{DynamicResource BorderBrush}"
BorderThickness="1"
CornerRadius="8"
Padding="15">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="300"/>
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal" Margin="0,0,0,10">
<TextBlock Text="响应结果" FontSize="16" FontWeight="Bold" VerticalAlignment="Center"/>
<TextBlock Text="{Binding StatusCode}" Margin="15,0,0,0" VerticalAlignment="Center"
FontWeight="Bold" Foreground="{DynamicResource SuccessBrush}"/>
</StackPanel>
<ScrollViewer Grid.Row="1" VerticalScrollBarVisibility="Auto">
<TextBox Text="{Binding ResponseText, Mode=OneWay}"
IsReadOnly="True"
TextWrapping="Wrap"
Background="{DynamicResource SecondaryRegionBrush}"
BorderThickness="0"
FontFamily="Consolas"
FontSize="11"
Padding="10"/>
</ScrollViewer>
</Grid>
</Border>
</StackPanel>
</ScrollViewer>
</UserControl>
@@ -0,0 +1,15 @@
using System.Windows.Controls;
namespace Sharp.Demo.Views.Pages
{
/// <summary>
/// HttpClientPage.xaml 的交互逻辑
/// </summary>
public partial class HttpClientPage : UserControl
{
public HttpClientPage()
{
InitializeComponent();
}
}
}
@@ -0,0 +1,187 @@
<UserControl x:Class="Sharp.Demo.Views.Pages.HttpServerPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:hc="https://handyorg.github.io/handycontrol"
mc:Ignorable="d"
d:DesignHeight="600"
d:DesignWidth="800">
<ScrollViewer VerticalScrollBarVisibility="Auto">
<StackPanel Margin="20">
<!-- 标题区域 -->
<TextBlock
FontSize="28"
FontWeight="Bold"
Text="HTTP 服务器管理" />
<TextBlock
Margin="0,10,0,30"
FontSize="14"
Foreground="{DynamicResource SecondaryTextBrush}"
Text="Sharp.NetServer - 轻量级 HTTP 服务器框架" />
<!-- 服务器控制面板 -->
<Border
Background="{DynamicResource RegionBrush}"
BorderBrush="{DynamicResource BorderBrush}"
BorderThickness="1"
CornerRadius="8"
Padding="20"
Margin="0,0,0,15">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<!-- 服务器信息 -->
<TextBlock
Grid.Row="0"
Grid.Column="0"
Text="服务器地址:"
VerticalAlignment="Center"
Margin="0,0,15,10" />
<TextBlock
Grid.Row="0"
Grid.Column="1"
Text="{Binding ServerUrl}"
FontWeight="Bold"
VerticalAlignment="Center"
Margin="0,0,0,10" />
<TextBlock
Grid.Row="1"
Grid.Column="0"
Text="服务器状态:"
VerticalAlignment="Center"
Margin="0,0,15,10" />
<TextBlock
Grid.Row="1"
Grid.Column="1"
Text="{Binding StatusText}"
Foreground="{Binding StatusColor}"
FontWeight="Bold"
VerticalAlignment="Center"
Margin="0,0,0,10" />
<!-- 控制按钮 -->
<StackPanel
Grid.Row="2"
Grid.Column="0"
Grid.ColumnSpan="2"
Orientation="Horizontal"
Margin="0,10,0,0">
<Button
Content="启动服务器"
Width="120"
Height="35"
Margin="0,0,10,0"
Command="{Binding StartCommand}"
IsEnabled="{Binding CanStart}"
Style="{StaticResource ButtonSuccess}" />
<Button
Content="停止服务器"
Width="120"
Height="35"
Margin="0,0,10,0"
Command="{Binding StopCommand}"
IsEnabled="{Binding IsRunning}"
Style="{StaticResource ButtonDanger}" />
<Button
Content="重启服务器"
Width="120"
Height="35"
Margin="0,0,10,0"
Command="{Binding RestartCommand}"
IsEnabled="{Binding IsRunning}"
Style="{StaticResource ButtonWarning}" />
<Button
Content="清空日志"
Width="100"
Height="35"
Command="{Binding ClearLogCommand}"
Style="{StaticResource ButtonDefault}" />
</StackPanel>
</Grid>
</Border>
<!-- 日志显示区域 -->
<Border
Background="{DynamicResource RegionBrush}"
BorderBrush="{DynamicResource BorderBrush}"
BorderThickness="1"
CornerRadius="8"
Padding="15"
Margin="0,0,0,15">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="350" />
</Grid.RowDefinitions>
<TextBlock
Grid.Row="0"
Text="日志信息"
FontSize="16"
FontWeight="Bold"
Margin="0,0,0,10" />
<ScrollViewer
Grid.Row="1"
VerticalScrollBarVisibility="Auto"
HorizontalScrollBarVisibility="Auto">
<TextBox
Text="{Binding LogText, Mode=OneWay}"
IsReadOnly="True"
TextWrapping="Wrap"
Background="{DynamicResource SecondaryRegionBrush}"
BorderThickness="0"
FontFamily="Consolas"
FontSize="11"
Padding="10" />
</ScrollViewer>
</Grid>
</Border>
<!-- API 端点提示 -->
<Border
Background="{DynamicResource SecondaryRegionBrush}"
BorderBrush="{DynamicResource BorderBrush}"
BorderThickness="1"
CornerRadius="8"
Padding="15">
<StackPanel>
<TextBlock
Text="可用的 API 端点"
FontSize="16"
FontWeight="Bold"
Margin="0,0,0,10" />
<TextBlock FontSize="12" TextWrapping="Wrap">
<Run Text="• GET /api/health - 健康检查" />
<LineBreak />
<Run Text="• GET /api/health/detailed - 详细健康检查" />
<LineBreak />
<Run Text="• GET /api/system/info - 系统信息" />
<LineBreak />
<Run Text="• GET /api/system/time - 当前时间" />
<LineBreak />
<Run Text="• GET /api/data - 获取所有数据" />
<LineBreak />
<Run Text="• POST /api/data - 创建数据 (Body: {&quot;name&quot;:&quot;test&quot;,&quot;value&quot;:&quot;data&quot;})" />
<LineBreak />
<Run Text="• GET /api/data/{id} - 获取指定数据" />
<LineBreak />
<Run Text="• PUT /api/data/{id} - 更新数据" />
<LineBreak />
<Run Text="• DELETE /api/data/{id} - 删除数据" />
</TextBlock>
</StackPanel>
</Border>
</StackPanel>
</ScrollViewer>
</UserControl>
@@ -0,0 +1,15 @@
using System.Windows.Controls;
namespace Sharp.Demo.Views.Pages
{
/// <summary>
/// HttpServerPage.xaml 的交互逻辑
/// </summary>
public partial class HttpServerPage : UserControl
{
public HttpServerPage()
{
InitializeComponent();
}
}
}
@@ -0,0 +1,171 @@
<UserControl x:Class="Sharp.Demo.Views.Pages.LogDemoPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:hc="https://handyorg.github.io/handycontrol"
mc:Ignorable="d"
d:DesignHeight="600"
d:DesignWidth="800">
<ScrollViewer VerticalScrollBarVisibility="Auto">
<StackPanel Margin="20">
<!-- 标题区域 -->
<TextBlock
FontSize="28"
FontWeight="Bold"
Text="日志管理" />
<TextBlock
Margin="0,10,0,30"
FontSize="14"
Foreground="{DynamicResource SecondaryTextBrush}"
Text="Sharp.Log - 基于 Serilog 的结构化日志系统" />
<!-- 日志级别测试 -->
<Border
Background="{DynamicResource RegionBrush}"
BorderBrush="{DynamicResource BorderBrush}"
BorderThickness="1"
CornerRadius="8"
Padding="20"
Margin="0,0,0,15">
<StackPanel>
<TextBlock Text="日志级别测试" FontSize="16" FontWeight="Bold" Margin="0,0,0,15"/>
<WrapPanel>
<Button Content="Trace" Width="110" Height="35" Margin="0,0,10,10"
Command="{Binding LogTraceCommand}"
Style="{StaticResource ButtonDefault}"/>
<Button Content="Debug" Width="110" Height="35" Margin="0,0,10,10"
Command="{Binding LogDebugCommand}"
Style="{StaticResource ButtonInfo}"/>
<Button Content="Information" Width="110" Height="35" Margin="0,0,10,10"
Command="{Binding LogInformationCommand}"
Style="{StaticResource ButtonSuccess}"/>
<Button Content="Warning" Width="110" Height="35" Margin="0,0,10,10"
Command="{Binding LogWarningCommand}"
Style="{StaticResource ButtonWarning}"/>
<Button Content="Error" Width="110" Height="35" Margin="0,0,10,10"
Command="{Binding LogErrorCommand}"
Style="{StaticResource ButtonDanger}"/>
<Button Content="Critical" Width="110" Height="35" Margin="0,0,10,10"
Command="{Binding LogCriticalCommand}"
Style="{StaticResource ButtonDanger}"/>
</WrapPanel>
</StackPanel>
</Border>
<!-- 结构化日志 -->
<Border
Background="{DynamicResource RegionBrush}"
BorderBrush="{DynamicResource BorderBrush}"
BorderThickness="1"
CornerRadius="8"
Padding="20"
Margin="0,0,0,15">
<StackPanel>
<TextBlock Text="结构化日志" FontSize="16" FontWeight="Bold" Margin="0,0,0,15"/>
<WrapPanel>
<Button Content="带参数日志" Width="140" Height="35" Margin="0,0,10,10"
Command="{Binding LogWithParamsCommand}"
Style="{StaticResource ButtonPrimary}"/>
<Button Content="带异常日志" Width="140" Height="35" Margin="0,0,10,10"
Command="{Binding LogWithExceptionCommand}"
Style="{StaticResource ButtonDanger}"/>
<Button Content="性能日志" Width="140" Height="35" Margin="0,0,10,10"
Command="{Binding LogPerformanceCommand}"
Style="{StaticResource ButtonInfo}"/>
<Button Content="业务日志" Width="140" Height="35" Margin="0,0,10,10"
Command="{Binding LogBusinessCommand}"
Style="{StaticResource ButtonSuccess}"/>
</WrapPanel>
</StackPanel>
</Border>
<!-- 日志配置 -->
<Border
Background="{DynamicResource RegionBrush}"
BorderBrush="{DynamicResource BorderBrush}"
BorderThickness="1"
CornerRadius="8"
Padding="20"
Margin="0,0,0,15">
<StackPanel>
<TextBlock Text="日志配置" FontSize="16" FontWeight="Bold" Margin="0,0,0,15"/>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock Text="最小日志级别:" VerticalAlignment="Center" Margin="0,0,10,0"/>
<ComboBox Grid.Column="1" SelectedItem="{Binding SelectedLogLevel}" Height="32"
ItemsSource="{Binding LogLevels}" Margin="0,0,0,10"/>
<TextBlock Grid.Row="1" Text="日志输出位置:" VerticalAlignment="Center" Margin="0,0,10,0"/>
<TextBlock Grid.Row="1" Grid.Column="1" Text="logs/app.log | Console | Debug"
VerticalAlignment="Center" Foreground="{DynamicResource SecondaryTextBrush}"/>
</Grid>
</StackPanel>
</Border>
<!-- 功能按钮 -->
<Border
Background="{DynamicResource RegionBrush}"
BorderBrush="{DynamicResource BorderBrush}"
BorderThickness="1"
CornerRadius="8"
Padding="20"
Margin="0,0,0,15">
<StackPanel>
<TextBlock Text="日志操作" FontSize="16" FontWeight="Bold" Margin="0,0,0,15"/>
<WrapPanel>
<Button Content="打开日志文件夹" Width="140" Height="35" Margin="0,0,10,10"
Command="{Binding OpenLogFolderCommand}"
Style="{StaticResource ButtonPrimary}"/>
<Button Content="查看最新日志" Width="140" Height="35" Margin="0,0,10,10"
Command="{Binding ViewLatestLogCommand}"
Style="{StaticResource ButtonInfo}"/>
<Button Content="清空日志" Width="140" Height="35" Margin="0,0,10,10"
Command="{Binding ClearLogsCommand}"
Style="{StaticResource ButtonDanger}"/>
</WrapPanel>
</StackPanel>
</Border>
<!-- 日志预览 -->
<Border
Background="{DynamicResource RegionBrush}"
BorderBrush="{DynamicResource BorderBrush}"
BorderThickness="1"
CornerRadius="8"
Padding="15">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="250"/>
</Grid.RowDefinitions>
<TextBlock Text="日志预览" FontSize="16" FontWeight="Bold" Margin="0,0,0,10"/>
<ScrollViewer Grid.Row="1" VerticalScrollBarVisibility="Auto">
<TextBox Text="{Binding LogPreview, Mode=OneWay}"
IsReadOnly="True"
TextWrapping="Wrap"
Background="{DynamicResource SecondaryRegionBrush}"
BorderThickness="0"
FontFamily="Consolas"
FontSize="11"
Padding="10"/>
</ScrollViewer>
</Grid>
</Border>
</StackPanel>
</ScrollViewer>
</UserControl>
@@ -0,0 +1,15 @@
using System.Windows.Controls;
namespace Sharp.Demo.Views.Pages
{
/// <summary>
/// LogDemoPage.xaml 的交互逻辑
/// </summary>
public partial class LogDemoPage : UserControl
{
public LogDemoPage()
{
InitializeComponent();
}
}
}
@@ -0,0 +1,141 @@
<UserControl x:Class="Sharp.Demo.Views.Pages.PlcDebugPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:hc="https://handyorg.github.io/handycontrol"
mc:Ignorable="d"
d:DesignHeight="600"
d:DesignWidth="800">
<ScrollViewer VerticalScrollBarVisibility="Auto">
<StackPanel Margin="20">
<!-- 标题区域 -->
<TextBlock
FontSize="28"
FontWeight="Bold"
Text="PLC 调试工具" />
<TextBlock
Margin="0,10,0,30"
FontSize="14"
Foreground="{DynamicResource SecondaryTextBrush}"
Text="Sharp.Plc.Siemens - 西门子 PLC 通信驱动 (S7-1200/1500/300/400)" />
<!-- 连接控制 -->
<Border
Background="{DynamicResource RegionBrush}"
BorderBrush="{DynamicResource BorderBrush}"
BorderThickness="1"
CornerRadius="8"
Padding="20"
Margin="0,0,0,15">
<StackPanel>
<TextBlock Text="PLC 连接" FontSize="16" FontWeight="Bold" Margin="0,0,0,10"/>
<StackPanel Orientation="Horizontal" Margin="0,0,0,10">
<TextBlock Text="IP 地址:" VerticalAlignment="Center" Margin="0,0,10,0" Width="80"/>
<TextBox Text="{Binding IpAddress}" Width="200" Height="32" Padding="8,0"
BorderBrush="{DynamicResource BorderBrush}" BorderThickness="1"/>
</StackPanel>
<StackPanel Orientation="Horizontal" Margin="0,0,0,10">
<TextBlock Text="状态:" VerticalAlignment="Center" Margin="0,0,10,0" Width="80"/>
<TextBlock Text="{Binding Status}" FontWeight="Bold" VerticalAlignment="Center"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<Button Content="连接" Width="100" Height="35" Margin="0,0,10,0"
Command="{Binding ConnectCommand}"
Style="{StaticResource ButtonSuccess}"/>
<Button Content="断开" Width="100" Height="35"
Command="{Binding DisconnectCommand}"
Style="{StaticResource ButtonDanger}"/>
</StackPanel>
</StackPanel>
</Border>
<!-- 单次读写 -->
<Border
Background="{DynamicResource RegionBrush}"
BorderBrush="{DynamicResource BorderBrush}"
BorderThickness="1"
CornerRadius="8"
Padding="20"
Margin="0,0,0,15">
<StackPanel>
<TextBlock Text="单次读写测试 (数据类型: Short)" FontSize="16" FontWeight="Bold" Margin="0,0,0,15"/>
<!-- 读取 -->
<Grid Margin="0,0,0,15">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="80"/>
<ColumnDefinition Width="200"/>
<ColumnDefinition Width="100"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Text="读取地址:" VerticalAlignment="Center"/>
<TextBox Grid.Column="1" Text="{Binding ReadAddress}" Height="32" Padding="8,0"
Margin="0,0,10,0" BorderBrush="{DynamicResource BorderBrush}" BorderThickness="1"/>
<Button Grid.Column="2" Content="读取" Height="32" Margin="0,0,10,0"
Command="{Binding ReadCommand}"
Style="{StaticResource ButtonPrimary}"/>
<TextBlock Grid.Column="3" Text="{Binding ReadValue}" VerticalAlignment="Center"
FontWeight="Bold" FontSize="14"/>
</Grid>
<!-- 写入 -->
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="80"/>
<ColumnDefinition Width="200"/>
<ColumnDefinition Width="100"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Text="写入地址:" VerticalAlignment="Center"/>
<TextBox Grid.Column="1" Text="{Binding WriteAddress}" Height="32" Padding="8,0"
Margin="0,0,10,0" BorderBrush="{DynamicResource BorderBrush}" BorderThickness="1"/>
<TextBox Grid.Column="2" Text="{Binding WriteValue}" Height="32" Padding="8,0"
Margin="0,0,10,0" BorderBrush="{DynamicResource BorderBrush}" BorderThickness="1"/>
<Button Grid.Column="3" Content="写入" Width="100" Height="32" HorizontalAlignment="Left"
Command="{Binding WriteCommand}"
Style="{StaticResource ButtonWarning}"/>
</Grid>
</StackPanel>
</Border>
<!-- 日志区域 -->
<Border
Background="{DynamicResource RegionBrush}"
BorderBrush="{DynamicResource BorderBrush}"
BorderThickness="1"
CornerRadius="8"
Padding="15">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="300"/>
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal" Margin="0,0,0,10">
<TextBlock Text="操作日志" FontSize="16" FontWeight="Bold" VerticalAlignment="Center"/>
<Button Content="清空日志" Width="100" Height="30" Margin="15,0,0,0"
Command="{Binding ClearLogCommand}"
Style="{StaticResource ButtonDefault}"/>
</StackPanel>
<ScrollViewer Grid.Row="1" VerticalScrollBarVisibility="Auto">
<TextBox Text="{Binding LogText, Mode=OneWay}"
IsReadOnly="True"
TextWrapping="Wrap"
Background="{DynamicResource SecondaryRegionBrush}"
BorderThickness="0"
FontFamily="Consolas"
FontSize="11"
Padding="10"/>
</ScrollViewer>
</Grid>
</Border>
</StackPanel>
</ScrollViewer>
</UserControl>
@@ -0,0 +1,15 @@
using System.Windows.Controls;
namespace Sharp.Demo.Views.Pages
{
/// <summary>
/// PlcDebugPage.xaml 的交互逻辑
/// </summary>
public partial class PlcDebugPage : UserControl
{
public PlcDebugPage()
{
InitializeComponent();
}
}
}
@@ -0,0 +1,85 @@
<UserControl x:Class="Sharp.Demo.Views.Pages.ProcessTestPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:hc="https://handyorg.github.io/handycontrol"
mc:Ignorable="d"
d:DesignHeight="600"
d:DesignWidth="800">
<ScrollViewer VerticalScrollBarVisibility="Auto">
<StackPanel Margin="20">
<!-- 标题区域 -->
<TextBlock FontSize="28" FontWeight="Bold" Text="进程测试工具" />
<TextBlock Margin="0,10,0,30" FontSize="14"
Foreground="{DynamicResource SecondaryTextBrush}"
Text="Sharp.Process - 进程执行和输出捕获" />
<!-- 状态栏 -->
<Border Background="{DynamicResource PrimaryBrush}" Padding="15" CornerRadius="8" Margin="0,0,0,15">
<TextBlock Text="{Binding Status}" Foreground="White" FontWeight="Bold" FontSize="16"/>
</Border>
<!-- 命令配置 -->
<Border Background="{DynamicResource RegionBrush}"
BorderBrush="{DynamicResource BorderBrush}"
BorderThickness="1" CornerRadius="8" Padding="20" Margin="0,0,0,15">
<StackPanel>
<TextBlock Text="命令配置" FontSize="16" FontWeight="Bold" Margin="0,0,0,15"/>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock Text="命令:" VerticalAlignment="Center" Margin="0,0,10,10"/>
<TextBox Grid.Column="1" Text="{Binding Command}" Height="32" Padding="8,0" Margin="0,0,0,10"/>
<TextBlock Grid.Row="1" Text="参数:" VerticalAlignment="Center" Margin="0,0,10,10"/>
<TextBox Grid.Row="1" Grid.Column="1" Text="{Binding Arguments}" Height="32" Padding="8,0" Margin="0,0,0,10"/>
<TextBlock Grid.Row="2" Text="超时(秒):" VerticalAlignment="Center" Margin="0,0,10,0"/>
<TextBox Grid.Row="2" Grid.Column="1" Text="{Binding TimeoutSeconds}" Height="32" Padding="8,0"/>
</Grid>
</StackPanel>
</Border>
<!-- 快速测试按钮 -->
<Border Background="{DynamicResource RegionBrush}"
BorderBrush="{DynamicResource BorderBrush}"
BorderThickness="1" CornerRadius="8" Padding="20" Margin="0,0,0,15">
<StackPanel>
<TextBlock Text="快速测试" FontSize="16" FontWeight="Bold" Margin="0,0,0,15"/>
<WrapPanel>
<Button Content="执行命令" Width="120" Height="35" Margin="0,0,10,10"
Command="{Binding RunCommand}" Style="{StaticResource ButtonPrimary}"/>
<Button Content="测试 dotnet" Width="120" Height="35" Margin="0,0,10,10"
Command="{Binding TestDotnetCommand}" Style="{StaticResource ButtonInfo}"/>
<Button Content="测试 Git" Width="120" Height="35" Margin="0,0,10,10"
Command="{Binding TestGitCommand}" Style="{StaticResource ButtonInfo}"/>
<Button Content="清空输出" Width="120" Height="35" Margin="0,0,10,10"
Command="{Binding ClearOutputCommand}" Style="{StaticResource ButtonDefault}"/>
</WrapPanel>
</StackPanel>
</Border>
<!-- 执行输出 -->
<Border Background="{DynamicResource RegionBrush}"
BorderBrush="{DynamicResource BorderBrush}"
BorderThickness="1" CornerRadius="8" Padding="15">
<StackPanel>
<TextBlock Text="执行输出" FontSize="16" FontWeight="Bold" Margin="0,0,0,10"/>
<TextBox Text="{Binding Output, Mode=OneWay}" Height="350"
IsReadOnly="True" TextWrapping="Wrap" VerticalScrollBarVisibility="Auto"
FontFamily="Consolas" FontSize="11" Background="{DynamicResource SecondaryRegionBrush}"/>
</StackPanel>
</Border>
</StackPanel>
</ScrollViewer>
</UserControl>
@@ -0,0 +1,12 @@
using System.Windows.Controls;
namespace Sharp.Demo.Views.Pages
{
public partial class ProcessTestPage : UserControl
{
public ProcessTestPage()
{
InitializeComponent();
}
}
}
@@ -0,0 +1,98 @@
<UserControl x:Class="Sharp.Demo.Views.Pages.SerialPortPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:hc="https://handyorg.github.io/handycontrol"
mc:Ignorable="d"
d:DesignHeight="600"
d:DesignWidth="800">
<ScrollViewer VerticalScrollBarVisibility="Auto">
<StackPanel Margin="20">
<!-- 标题区域 -->
<TextBlock FontSize="28" FontWeight="Bold" Text="串口调试工具" />
<TextBlock Margin="0,10,0,30" FontSize="14"
Foreground="{DynamicResource SecondaryTextBrush}"
Text="Sharp.Ports - 串口通信抽象 (RS232/RS485)" />
<!-- 连接控制 -->
<Border Background="{DynamicResource RegionBrush}"
BorderBrush="{DynamicResource BorderBrush}"
BorderThickness="1" CornerRadius="8" Padding="20" Margin="0,0,0,15">
<StackPanel>
<TextBlock Text="连接配置" FontSize="16" FontWeight="Bold" Margin="0,0,0,15"/>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="200"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="200"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock Text="端口:" VerticalAlignment="Center" Margin="0,0,10,10"/>
<TextBox Grid.Column="1" Text="{Binding SelectedPort}" Height="32" Padding="8,0" Margin="0,0,15,10"/>
<TextBlock Grid.Column="2" Text="波特率:" VerticalAlignment="Center" Margin="0,0,10,10"/>
<TextBox Grid.Column="3" Text="{Binding BaudRate}" Height="32" Padding="8,0" Margin="0,0,0,10"/>
<TextBlock Grid.Row="1" Text="状态:" VerticalAlignment="Center" Margin="0,0,10,0"/>
<TextBlock Grid.Row="1" Grid.Column="1" Text="{Binding Status}" FontWeight="Bold" VerticalAlignment="Center"/>
</Grid>
<StackPanel Orientation="Horizontal" Margin="0,15,0,0">
<Button Content="连接" Width="100" Height="35" Margin="0,0,10,0"
Command="{Binding ConnectCommand}" Style="{StaticResource ButtonSuccess}"/>
<Button Content="断开" Width="100" Height="35"
Command="{Binding DisconnectCommand}" Style="{StaticResource ButtonDanger}"/>
</StackPanel>
</StackPanel>
</Border>
<!-- 数据发送 -->
<Border Background="{DynamicResource RegionBrush}"
BorderBrush="{DynamicResource BorderBrush}"
BorderThickness="1" CornerRadius="8" Padding="20" Margin="0,0,0,15">
<StackPanel>
<TextBlock Text="数据发送" FontSize="16" FontWeight="Bold" Margin="0,0,0,10"/>
<TextBox Text="{Binding SendData}" Height="80" Padding="8" TextWrapping="Wrap"
AcceptsReturn="True" VerticalScrollBarVisibility="Auto" Margin="0,0,0,10"/>
<Button Content="发送数据" Width="120" Height="35" HorizontalAlignment="Left"
Command="{Binding SendCommand}" Style="{StaticResource ButtonPrimary}"/>
</StackPanel>
</Border>
<!-- 接收数据 -->
<Border Background="{DynamicResource RegionBrush}"
BorderBrush="{DynamicResource BorderBrush}"
BorderThickness="1" CornerRadius="8" Padding="20" Margin="0,0,0,15">
<StackPanel>
<TextBlock Text="接收数据" FontSize="16" FontWeight="Bold" Margin="0,0,0,10"/>
<TextBox Text="{Binding ReceiveData, Mode=OneWay}" Height="150"
IsReadOnly="True" TextWrapping="Wrap" VerticalScrollBarVisibility="Auto"
FontFamily="Consolas" Background="{DynamicResource SecondaryRegionBrush}"/>
</StackPanel>
</Border>
<!-- 日志 -->
<Border Background="{DynamicResource RegionBrush}"
BorderBrush="{DynamicResource BorderBrush}"
BorderThickness="1" CornerRadius="8" Padding="15">
<StackPanel>
<StackPanel Orientation="Horizontal" Margin="0,0,0,10">
<TextBlock Text="操作日志" FontSize="16" FontWeight="Bold" VerticalAlignment="Center"/>
<Button Content="清空日志" Width="100" Height="30" Margin="15,0,0,0"
Command="{Binding ClearLogCommand}" Style="{StaticResource ButtonDefault}"/>
</StackPanel>
<TextBox Text="{Binding LogText, Mode=OneWay}" Height="150"
IsReadOnly="True" TextWrapping="Wrap" VerticalScrollBarVisibility="Auto"
FontFamily="Consolas" FontSize="11" Background="{DynamicResource SecondaryRegionBrush}"/>
</StackPanel>
</Border>
</StackPanel>
</ScrollViewer>
</UserControl>
@@ -0,0 +1,12 @@
using System.Windows.Controls;
namespace Sharp.Demo.Views.Pages
{
public partial class SerialPortPage : UserControl
{
public SerialPortPage()
{
InitializeComponent();
}
}
}
@@ -0,0 +1,200 @@
<UserControl x:Class="Sharp.Demo.Views.Pages.WpfServicesPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:hc="https://handyorg.github.io/handycontrol"
mc:Ignorable="d"
d:DesignHeight="600"
d:DesignWidth="800">
<ScrollViewer VerticalScrollBarVisibility="Auto">
<StackPanel Margin="20">
<!-- 标题区域 -->
<TextBlock
FontSize="28"
FontWeight="Bold"
Text="WPF 服务" />
<TextBlock
Margin="0,10,0,30"
FontSize="14"
Foreground="{DynamicResource SecondaryTextBrush}"
Text="Sharp.Wpf - MVVM 基础设施、对话框服务、文件对话框、线程分发器" />
<!-- 对话框服务 -->
<Border
Background="{DynamicResource RegionBrush}"
BorderBrush="{DynamicResource BorderBrush}"
BorderThickness="1"
CornerRadius="8"
Padding="20"
Margin="0,0,0,15">
<StackPanel>
<TextBlock Text="对话框服务 (IDialogService)" FontSize="16" FontWeight="Bold" Margin="0,0,0,15"/>
<WrapPanel>
<Button Content="消息框" Width="120" Height="35" Margin="0,0,10,10"
Command="{Binding ShowMessageCommand}"
Style="{StaticResource ButtonPrimary}"/>
<Button Content="确认对话框" Width="120" Height="35" Margin="0,0,10,10"
Command="{Binding ShowConfirmCommand}"
Style="{StaticResource ButtonInfo}"/>
<Button Content="警告对话框" Width="120" Height="35" Margin="0,0,10,10"
Command="{Binding ShowWarningCommand}"
Style="{StaticResource ButtonWarning}"/>
<Button Content="错误对话框" Width="120" Height="35" Margin="0,0,10,10"
Command="{Binding ShowErrorCommand}"
Style="{StaticResource ButtonDanger}"/>
<Button Content="输入框" Width="120" Height="35" Margin="0,0,10,10"
Command="{Binding ShowInputCommand}"
Style="{StaticResource ButtonSuccess}"/>
<Button Content="自定义对话框" Width="120" Height="35" Margin="0,0,10,10"
Command="{Binding ShowCustomDialogCommand}"
Style="{StaticResource ButtonDefault}"/>
</WrapPanel>
</StackPanel>
</Border>
<!-- 文件对话框服务 -->
<Border
Background="{DynamicResource RegionBrush}"
BorderBrush="{DynamicResource BorderBrush}"
BorderThickness="1"
CornerRadius="8"
Padding="20"
Margin="0,0,0,15">
<StackPanel>
<TextBlock Text="文件对话框 (IFileDialogService)" FontSize="16" FontWeight="Bold" Margin="0,0,0,15"/>
<WrapPanel>
<Button Content="打开文件" Width="130" Height="35" Margin="0,0,10,10"
Command="{Binding OpenFileCommand}"
Style="{StaticResource ButtonPrimary}"/>
<Button Content="保存文件" Width="130" Height="35" Margin="0,0,10,10"
Command="{Binding SaveFileCommand}"
Style="{StaticResource ButtonInfo}"/>
<Button Content="选择文件夹" Width="130" Height="35" Margin="0,0,10,10"
Command="{Binding SelectFolderCommand}"
Style="{StaticResource ButtonSuccess}"/>
<Button Content="多选文件" Width="130" Height="35" Margin="0,0,10,10"
Command="{Binding OpenMultipleFilesCommand}"
Style="{StaticResource ButtonWarning}"/>
</WrapPanel>
</StackPanel>
</Border>
<!-- 线程分发器 -->
<Border
Background="{DynamicResource RegionBrush}"
BorderBrush="{DynamicResource BorderBrush}"
BorderThickness="1"
CornerRadius="8"
Padding="20"
Margin="0,0,0,15">
<StackPanel>
<TextBlock Text="线程分发器 (IDispatcher)" FontSize="16" FontWeight="Bold" Margin="0,0,0,15"/>
<WrapPanel>
<Button Content="UI 线程调用" Width="140" Height="35" Margin="0,0,10,10"
Command="{Binding InvokeOnUIThreadCommand}"
Style="{StaticResource ButtonPrimary}"/>
<Button Content="异步 UI 调用" Width="140" Height="35" Margin="0,0,10,10"
Command="{Binding InvokeAsyncCommand}"
Style="{StaticResource ButtonInfo}"/>
<Button Content="后台任务" Width="140" Height="35" Margin="0,0,10,10"
Command="{Binding BackgroundTaskCommand}"
Style="{StaticResource ButtonSuccess}"/>
</WrapPanel>
</StackPanel>
</Border>
<!-- MVVM 基础 -->
<Border
Background="{DynamicResource RegionBrush}"
BorderBrush="{DynamicResource BorderBrush}"
BorderThickness="1"
CornerRadius="8"
Padding="20"
Margin="0,0,0,15">
<StackPanel>
<TextBlock Text="MVVM 基础设施" FontSize="16" FontWeight="Bold" Margin="0,0,0,15"/>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<!-- 左侧 -->
<StackPanel Margin="0,0,10,0">
<TextBlock Text="ViewModelBase 示例:" FontWeight="SemiBold" Margin="0,0,0,10"/>
<TextBlock Text="属性变更通知:" Margin="0,0,0,5"/>
<TextBox Text="{Binding SampleProperty}" Height="32" Padding="8,0" Margin="0,0,0,10"
BorderBrush="{DynamicResource BorderBrush}" BorderThickness="1"/>
<Button Content="触发属性变更" Height="35" Margin="0,0,0,10"
Command="{Binding TriggerPropertyChangeCommand}"
Style="{StaticResource ButtonPrimary}"/>
<StackPanel Orientation="Horizontal" Margin="0,10,0,0">
<TextBlock Text="IsBusy 状态: " VerticalAlignment="Center"/>
<CheckBox IsChecked="{Binding IsBusy}" IsEnabled="False" VerticalAlignment="Center"/>
</StackPanel>
</StackPanel>
<!-- 右侧 -->
<StackPanel Grid.Column="1" Margin="10,0,0,0">
<TextBlock Text="命令 (RelayCommand):" FontWeight="SemiBold" Margin="0,0,0,10"/>
<Button Content="同步命令" Height="35" Margin="0,0,0,10"
Command="{Binding SyncCommandDemo}"
Style="{StaticResource ButtonInfo}"/>
<Button Content="异步命令" Height="35" Margin="0,0,0,10"
Command="{Binding AsyncCommandDemo}"
Style="{StaticResource ButtonSuccess}"/>
<Button Content="带参数命令" Height="35" Margin="0,0,0,10"
Command="{Binding CommandWithParamDemo}"
CommandParameter="测试参数"
Style="{StaticResource ButtonWarning}"/>
</StackPanel>
</Grid>
</StackPanel>
</Border>
<!-- 日志输出 -->
<Border
Background="{DynamicResource RegionBrush}"
BorderBrush="{DynamicResource BorderBrush}"
BorderThickness="1"
CornerRadius="8"
Padding="15">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="200"/>
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal" Margin="0,0,0,10">
<TextBlock Text="操作日志" FontSize="16" FontWeight="Bold" VerticalAlignment="Center"/>
<Button Content="清空" Width="80" Height="30" Margin="15,0,0,0"
Command="{Binding ClearLogCommand}"
Style="{StaticResource ButtonDefault}"/>
</StackPanel>
<ScrollViewer Grid.Row="1" VerticalScrollBarVisibility="Auto">
<TextBox Text="{Binding LogText, Mode=OneWay}"
IsReadOnly="True"
TextWrapping="Wrap"
Background="{DynamicResource SecondaryRegionBrush}"
BorderThickness="0"
FontFamily="Consolas"
FontSize="11"
Padding="10"/>
</ScrollViewer>
</Grid>
</Border>
</StackPanel>
</ScrollViewer>
</UserControl>
@@ -0,0 +1,15 @@
using System.Windows.Controls;
namespace Sharp.Demo.Views.Pages
{
/// <summary>
/// WpfServicesPage.xaml 的交互逻辑
/// </summary>
public partial class WpfServicesPage : UserControl
{
public WpfServicesPage()
{
InitializeComponent();
}
}
}
@@ -0,0 +1,219 @@
<Window x:Class="Sharp.Demo.Views.Windows.HttpClientWindow"
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"
mc:Ignorable="d"
Title="HTTP 客户端"
Height="750"
Width="1100"
WindowStartupLocation="CenterScreen">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<!-- 工具栏 -->
<Border Grid.Row="0" Background="{DynamicResource RegionBrush}"
BorderBrush="{DynamicResource BorderBrush}" BorderThickness="0,0,0,1" Padding="15,10">
<Grid>
<StackPanel Orientation="Horizontal">
<Button Content="📤 发送请求" Width="120" Height="35" Margin="0,0,10,0"
Command="{Binding SendRequestCommand}"/>
<Button Content="🗑 清空" Width="100" Height="35" Margin="0,0,10,0"
Command="{Binding ClearCommand}"/>
<Separator Margin="10,0"/>
<Button Content="📋 复制响应" Width="110" Height="35" Margin="0,0,10,0"
Command="{Binding CopyResponseCommand}"/>
<Button Content="💾 保存响应" Width="110" Height="35"
Command="{Binding SaveResponseCommand}"/>
</StackPanel>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right">
<TextBlock Text="状态码: " VerticalAlignment="Center" Margin="0,0,5,0"/>
<TextBlock Text="{Binding StatusCode}" FontWeight="Bold" VerticalAlignment="Center"
Foreground="{Binding StatusColor}" Margin="0,0,15,0"/>
<TextBlock Text="耗时: " VerticalAlignment="Center" Margin="0,0,5,0"/>
<TextBlock Text="{Binding ElapsedTime}" FontWeight="Bold" VerticalAlignment="Center"/>
</StackPanel>
</Grid>
</Border>
<!-- 主内容区 -->
<Grid Grid.Row="1" Margin="15">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<!-- 左侧:请求配置 -->
<Border Grid.Column="0" Background="{DynamicResource RegionBrush}"
BorderBrush="{DynamicResource BorderBrush}" BorderThickness="1" CornerRadius="8">
<ScrollViewer VerticalScrollBarVisibility="Auto">
<StackPanel Margin="15">
<TextBlock Text="请求配置" FontSize="16" FontWeight="Bold" Margin="0,0,0,15"/>
<!-- HTTP 方法 -->
<TextBlock Text="HTTP 方法" Margin="0,0,0,5"/>
<ComboBox ItemsSource="{Binding HttpMethods}"
SelectedItem="{Binding SelectedMethod}"
Height="32" Margin="0,0,0,15"/>
<!-- URL -->
<TextBlock Text="请求 URL" Margin="0,0,0,5"/>
<TextBox Text="{Binding RequestUrl}" Height="32" Margin="0,0,0,15"/>
<!-- 请求头 -->
<TextBlock Text="请求头 (Headers)" Margin="0,0,0,5"/>
<TextBox Text="{Binding RequestHeaders}"
Height="100"
TextWrapping="Wrap"
AcceptsReturn="True"
VerticalScrollBarVisibility="Auto"
Margin="0,0,0,15"/>
<!-- 请求体 -->
<TextBlock Text="请求体 (Body)" Margin="0,0,0,5"/>
<TextBox Text="{Binding RequestBody}"
Height="200"
TextWrapping="Wrap"
AcceptsReturn="True"
VerticalScrollBarVisibility="Auto"
FontFamily="Consolas"
Margin="0,0,0,15"/>
<!-- 快速测试 -->
<Border Background="{DynamicResource SecondaryRegionBrush}"
CornerRadius="8" Padding="15" Margin="0,10,0,0">
<StackPanel>
<TextBlock Text="快速测试" FontSize="14" FontWeight="Bold" Margin="0,0,0,10"/>
<Button Content="🌐 测试本地服务器" Height="32" Margin="0,0,0,8"
Command="{Binding TestLocalServerCommand}"/>
<Button Content="🔄 测试重试策略" Height="32" Margin="0,0,0,8"
Command="{Binding TestRetryCommand}"/>
<Button Content="⏱ 测试超时" Height="32" Margin="0,0,0,8"
Command="{Binding TestTimeoutCommand}"/>
<Button Content="📊 测试 JSON API" Height="32"
Command="{Binding TestJsonApiCommand}"/>
</StackPanel>
</Border>
</StackPanel>
</ScrollViewer>
</Border>
<GridSplitter Grid.Column="1" Width="5" HorizontalAlignment="Center" Background="Transparent"/>
<!-- 右侧:响应显示 -->
<Border Grid.Column="2" Background="{DynamicResource RegionBrush}"
BorderBrush="{DynamicResource BorderBrush}" BorderThickness="1" CornerRadius="8">
<Grid Margin="15">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Text="响应内容" FontSize="16" FontWeight="Bold" Margin="0,0,0,10"/>
<!-- 响应头信息 -->
<Border Grid.Row="1" Background="{DynamicResource SecondaryRegionBrush}"
CornerRadius="4" Padding="10" Margin="0,0,0,10">
<StackPanel>
<TextBlock FontSize="11">
<Run Text="状态码: " FontWeight="SemiBold"/>
<Run Text="{Binding StatusCode}" Foreground="{Binding StatusColor}"/>
</TextBlock>
<TextBlock FontSize="11" Margin="0,3,0,0">
<Run Text="内容类型: " FontWeight="SemiBold"/>
<Run Text="{Binding ContentType}"/>
</TextBlock>
<TextBlock FontSize="11" Margin="0,3,0,0">
<Run Text="响应时间: " FontWeight="SemiBold"/>
<Run Text="{Binding ElapsedTime}"/>
</TextBlock>
<TextBlock FontSize="11" Margin="0,3,0,0">
<Run Text="响应大小: " FontWeight="SemiBold"/>
<Run Text="{Binding ResponseSize}"/>
</TextBlock>
</StackPanel>
</Border>
<!-- 响应体 -->
<TabControl Grid.Row="2">
<TabItem Header="响应体">
<ScrollViewer VerticalScrollBarVisibility="Auto">
<TextBox Text="{Binding ResponseText, Mode=OneWay}"
IsReadOnly="True"
TextWrapping="Wrap"
Background="{DynamicResource SecondaryRegionBrush}"
BorderThickness="0"
FontFamily="Consolas"
FontSize="11"
Padding="10"/>
</ScrollViewer>
</TabItem>
<TabItem Header="响应头">
<ScrollViewer VerticalScrollBarVisibility="Auto">
<TextBox Text="{Binding ResponseHeaders, Mode=OneWay}"
IsReadOnly="True"
TextWrapping="Wrap"
Background="{DynamicResource SecondaryRegionBrush}"
BorderThickness="0"
FontFamily="Consolas"
FontSize="11"
Padding="10"/>
</ScrollViewer>
</TabItem>
<TabItem Header="日志">
<ScrollViewer VerticalScrollBarVisibility="Auto">
<TextBox Text="{Binding LogText, Mode=OneWay}"
IsReadOnly="True"
TextWrapping="Wrap"
Background="{DynamicResource SecondaryRegionBrush}"
BorderThickness="0"
FontFamily="Consolas"
FontSize="11"
Padding="10"/>
</ScrollViewer>
</TabItem>
</TabControl>
</Grid>
</Border>
</Grid>
<!-- 状态栏 -->
<StatusBar Grid.Row="2" Background="{DynamicResource RegionBrush}"
BorderBrush="{DynamicResource BorderBrush}" BorderThickness="0,1,0,0">
<StatusBarItem>
<TextBlock>
<Run Text="请求总数: "/>
<Run Text="{Binding TotalRequests, Mode=OneWay}"/>
</TextBlock>
</StatusBarItem>
<Separator/>
<StatusBarItem>
<TextBlock>
<Run Text="成功: "/>
<Run Text="{Binding SuccessCount, Mode=OneWay}" Foreground="Green"/>
</TextBlock>
</StatusBarItem>
<Separator/>
<StatusBarItem>
<TextBlock>
<Run Text="失败: "/>
<Run Text="{Binding FailureCount, Mode=OneWay}" Foreground="Red"/>
</TextBlock>
</StatusBarItem>
</StatusBar>
</Grid>
</Window>
@@ -0,0 +1,17 @@
using System.Windows;
using Sharp.Demo.ViewModels.Windows;
namespace Sharp.Demo.Views.Windows
{
/// <summary>
/// HttpClientWindow.xaml 的交互逻辑
/// </summary>
public partial class HttpClientWindow : Window
{
public HttpClientWindow(HttpClientViewModel viewModel)
{
InitializeComponent();
DataContext = viewModel;
}
}
}
@@ -0,0 +1,190 @@
<Window x:Class="Sharp.Demo.Views.Windows.HttpServerWindow"
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"
mc:Ignorable="d"
Title="HTTP 服务器"
Height="700"
Width="1000"
WindowStartupLocation="CenterScreen">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<!-- 工具栏 -->
<Border Grid.Row="0" Background="{DynamicResource RegionBrush}"
BorderBrush="{DynamicResource BorderBrush}" BorderThickness="0,0,0,1" Padding="15,10">
<Grid>
<StackPanel Orientation="Horizontal">
<Button Content="▶️ 启动服务器" Width="120" Height="35" Margin="0,0,10,0"
Command="{Binding StartServerCommand}"/>
<Button Content="⏹ 停止服务器" Width="120" Height="35" Margin="0,0,10,0"
Command="{Binding StopServerCommand}"/>
<Button Content="🔄 重启服务器" Width="120" Height="35" Margin="0,0,10,0"
Command="{Binding RestartServerCommand}"/>
<Separator Margin="10,0"/>
<Button Content="📊 用户管理" Width="110" Height="35" Margin="0,0,10,0"
Command="{Binding OpenUserManagementCommand}"/>
<Button Content="🗑 清空日志" Width="100" Height="35"
Command="{Binding ClearLogCommand}"/>
</StackPanel>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right">
<TextBlock Text="服务器地址: " VerticalAlignment="Center" Margin="0,0,5,0"/>
<TextBlock Text="{Binding ServerUrl}" FontWeight="Bold" VerticalAlignment="Center" Margin="0,0,15,0"/>
<TextBlock Text="状态: " VerticalAlignment="Center" Margin="0,0,5,0"/>
<TextBlock Text="{Binding StatusText}" FontWeight="Bold" VerticalAlignment="Center"
Foreground="{Binding StatusColor}"/>
</StackPanel>
</Grid>
</Border>
<!-- 主内容区 -->
<Grid Grid.Row="1" Margin="15">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="2*"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<!-- 左侧:日志显示 -->
<Border Grid.Column="0" Background="{DynamicResource RegionBrush}"
BorderBrush="{DynamicResource BorderBrush}" BorderThickness="1" CornerRadius="8">
<Grid Margin="15">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<TextBlock Text="服务器日志" FontSize="16" FontWeight="Bold" Margin="0,0,0,10"/>
<ScrollViewer Grid.Row="1" VerticalScrollBarVisibility="Auto">
<TextBox Text="{Binding LogText, Mode=OneWay}"
IsReadOnly="True"
TextWrapping="Wrap"
Background="{DynamicResource SecondaryRegionBrush}"
BorderThickness="0"
FontFamily="Consolas"
FontSize="11"
Padding="10"/>
</ScrollViewer>
</Grid>
</Border>
<GridSplitter Grid.Column="1" Width="5" HorizontalAlignment="Center" Background="Transparent"/>
<!-- 右侧:API 端点和功能 -->
<ScrollViewer Grid.Column="2" VerticalScrollBarVisibility="Auto">
<StackPanel Margin="5,0,0,0">
<!-- 服务器配置 -->
<Border Background="{DynamicResource RegionBrush}"
BorderBrush="{DynamicResource BorderBrush}"
BorderThickness="1" CornerRadius="8" Padding="15" Margin="0,0,0,15">
<StackPanel>
<TextBlock Text="服务器配置" FontSize="14" FontWeight="Bold" Margin="0,0,0,10"/>
<StackPanel Orientation="Horizontal" Margin="0,0,0,8">
<TextBlock Text="端口:" Width="60" VerticalAlignment="Center"/>
<TextBox Text="{Binding Port}" Width="80" Height="28"/>
</StackPanel>
<StackPanel Orientation="Horizontal" Margin="0,0,0,8">
<TextBlock Text="主机:" Width="60" VerticalAlignment="Center"/>
<TextBox Text="{Binding Host}" Width="120" Height="28"/>
</StackPanel>
<CheckBox Content="启用CORS" IsChecked="{Binding EnableCors}" Margin="0,5"/>
<CheckBox Content="启用日志" IsChecked="{Binding EnableLogging}" Margin="0,5"/>
</StackPanel>
</Border>
<!-- API 端点列表 -->
<Border Background="{DynamicResource RegionBrush}"
BorderBrush="{DynamicResource BorderBrush}"
BorderThickness="1" CornerRadius="8" Padding="15" Margin="0,0,0,15">
<StackPanel>
<TextBlock Text="可用的 API 端点" FontSize="14" FontWeight="Bold" Margin="0,0,0,10"/>
<TextBlock FontSize="11" TextWrapping="Wrap">
<Run Text="• GET /api/health" FontFamily="Consolas"/>
<LineBreak/>
<Run Text=" 健康检查" Foreground="{DynamicResource SecondaryTextBrush}"/>
<LineBreak/>
<LineBreak/>
<Run Text="• GET /api/health/detailed" FontFamily="Consolas"/>
<LineBreak/>
<Run Text=" 详细健康检查" Foreground="{DynamicResource SecondaryTextBrush}"/>
<LineBreak/>
<LineBreak/>
<Run Text="• GET /api/system/info" FontFamily="Consolas"/>
<LineBreak/>
<Run Text=" 系统信息" Foreground="{DynamicResource SecondaryTextBrush}"/>
<LineBreak/>
<LineBreak/>
<Run Text="• GET /api/data" FontFamily="Consolas"/>
<LineBreak/>
<Run Text=" 获取所有数据" Foreground="{DynamicResource SecondaryTextBrush}"/>
<LineBreak/>
<LineBreak/>
<Run Text="• POST /api/data" FontFamily="Consolas"/>
<LineBreak/>
<Run Text=" 创建数据" Foreground="{DynamicResource SecondaryTextBrush}"/>
</TextBlock>
</StackPanel>
</Border>
<!-- 快速操作 -->
<Border Background="{DynamicResource RegionBrush}"
BorderBrush="{DynamicResource BorderBrush}"
BorderThickness="1" CornerRadius="8" Padding="15">
<StackPanel>
<TextBlock Text="快速操作" FontSize="14" FontWeight="Bold" Margin="0,0,0,10"/>
<Button Content="🌐 在浏览器中打开" Height="32" Margin="0,0,0,8"
Command="{Binding OpenInBrowserCommand}"/>
<Button Content="📋 复制服务器地址" Height="32" Margin="0,0,0,8"
Command="{Binding CopyUrlCommand}"/>
<Button Content="📊 查看性能指标" Height="32" Margin="0,0,0,8"
Command="{Binding ShowMetricsCommand}"/>
<Button Content="⚙️ 高级设置" Height="32"
Command="{Binding OpenAdvancedSettingsCommand}"/>
</StackPanel>
</Border>
</StackPanel>
</ScrollViewer>
</Grid>
<!-- 状态栏 -->
<StatusBar Grid.Row="2" Background="{DynamicResource RegionBrush}"
BorderBrush="{DynamicResource BorderBrush}" BorderThickness="0,1,0,0">
<StatusBarItem>
<TextBlock>
<Run Text="运行时间: "/>
<Run Text="{Binding Uptime, Mode=OneWay}"/>
</TextBlock>
</StatusBarItem>
<Separator/>
<StatusBarItem>
<TextBlock>
<Run Text="请求总数: "/>
<Run Text="{Binding TotalRequests, Mode=OneWay}"/>
</TextBlock>
</StatusBarItem>
<Separator/>
<StatusBarItem>
<TextBlock>
<Run Text="活跃连接: "/>
<Run Text="{Binding ActiveConnections, Mode=OneWay}"/>
</TextBlock>
</StatusBarItem>
</StatusBar>
</Grid>
</Window>
@@ -0,0 +1,17 @@
using System.Windows;
using Sharp.Demo.ViewModels.Windows;
namespace Sharp.Demo.Views.Windows
{
/// <summary>
/// HttpServerWindow.xaml 的交互逻辑
/// </summary>
public partial class HttpServerWindow : Window
{
public HttpServerWindow(HttpServerViewModel viewModel)
{
InitializeComponent();
DataContext = viewModel;
}
}
}
@@ -0,0 +1,130 @@
<Window x:Class="Sharp.Demo.Views.Windows.PlcDebugWindow"
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"
mc:Ignorable="d"
Title="PLC 调试工具"
Height="650"
Width="900"
WindowStartupLocation="CenterScreen">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!-- 工具栏 -->
<Border Grid.Row="0" Background="{DynamicResource RegionBrush}"
BorderBrush="{DynamicResource BorderBrush}" BorderThickness="0,0,0,1" Padding="15,10">
<StackPanel Orientation="Horizontal">
<TextBlock Text="IP 地址:" VerticalAlignment="Center" Margin="0,0,10,0"/>
<TextBox Text="{Binding IpAddress}" Width="150" Height="28" Margin="0,0,10,0"/>
<Button Content="🔗 连接" Width="90" Height="32" Margin="0,0,10,0"
Command="{Binding ConnectCommand}"/>
<Button Content="🔌 断开" Width="90" Height="32" Margin="0,0,10,0"
Command="{Binding DisconnectCommand}"/>
<Separator Margin="10,0"/>
<TextBlock Text="状态:" VerticalAlignment="Center" Margin="0,0,10,0"/>
<TextBlock Text="{Binding Status}" FontWeight="Bold" VerticalAlignment="Center"
Foreground="{DynamicResource SuccessBrush}"/>
</StackPanel>
</Border>
<!-- 主内容区 -->
<TabControl Grid.Row="1" Margin="15">
<!-- 单次读写 -->
<TabItem Header="📝 单次读写">
<ScrollViewer VerticalScrollBarVisibility="Auto">
<StackPanel Margin="15">
<Border Background="{DynamicResource RegionBrush}"
BorderBrush="{DynamicResource BorderBrush}"
BorderThickness="1" CornerRadius="8" Padding="20" Margin="0,0,0,15">
<StackPanel>
<TextBlock Text="读取数据" FontSize="16" FontWeight="Bold" Margin="0,0,0,15"/>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Text="地址:" VerticalAlignment="Center" Margin="0,0,10,0"/>
<TextBox Grid.Column="1" Text="{Binding ReadAddress}" Height="32" Margin="0,0,15,0"/>
<Button Grid.Column="2" Content="读取" Width="100" Height="32" Margin="0,0,15,0"
Command="{Binding ReadCommand}" Style="{StaticResource ButtonPrimary}"/>
<TextBlock Grid.Column="3" Text="{Binding ReadValue}" FontWeight="Bold" FontSize="14" VerticalAlignment="Center"/>
</Grid>
</StackPanel>
</Border>
<Border Background="{DynamicResource RegionBrush}"
BorderBrush="{DynamicResource BorderBrush}"
BorderThickness="1" CornerRadius="8" Padding="20">
<StackPanel>
<TextBlock Text="写入数据" FontSize="16" FontWeight="Bold" Margin="0,0,0,15"/>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Text="地址:" VerticalAlignment="Center" Margin="0,0,10,0"/>
<TextBox Grid.Column="1" Text="{Binding WriteAddress}" Height="32" Margin="0,0,0,10"/>
<TextBlock Grid.Row="1" Text="值:" VerticalAlignment="Center" Margin="0,0,10,0"/>
<StackPanel Grid.Row="1" Grid.Column="1" Orientation="Horizontal">
<TextBox Text="{Binding WriteValue}" Width="150" Height="32" Margin="0,0,10,0"/>
<Button Content="写入" Width="100" Height="32"
Command="{Binding WriteCommand}"/>
</StackPanel>
</Grid>
</StackPanel>
</Border>
</StackPanel>
</ScrollViewer>
</TabItem>
<!-- 日志 -->
<TabItem Header="📋 操作日志">
<Border Margin="15" Background="{DynamicResource RegionBrush}"
BorderBrush="{DynamicResource BorderBrush}" BorderThickness="1" CornerRadius="8">
<Grid Margin="15">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal" Margin="0,0,0,10">
<TextBlock Text="操作日志" FontSize="16" FontWeight="Bold" VerticalAlignment="Center"/>
<Button Content="清空" Width="80" Height="30" Margin="15,0,0,0"
Command="{Binding ClearLogCommand}"/>
</StackPanel>
<ScrollViewer Grid.Row="1" VerticalScrollBarVisibility="Auto">
<TextBox Text="{Binding LogText, Mode=OneWay}"
IsReadOnly="True"
TextWrapping="Wrap"
Background="{DynamicResource SecondaryRegionBrush}"
BorderThickness="0"
FontFamily="Consolas"
FontSize="11"
Padding="10"/>
</ScrollViewer>
</Grid>
</Border>
</TabItem>
</TabControl>
</Grid>
</Window>
@@ -0,0 +1,14 @@
using System.Windows;
using Sharp.Demo.ViewModels.Windows;
namespace Sharp.Demo.Views.Windows
{
public partial class PlcDebugWindow : Window
{
public PlcDebugWindow(PlcDebugViewModel viewModel)
{
InitializeComponent();
DataContext = viewModel;
}
}
}
+89
View File
@@ -0,0 +1,89 @@
{
// Sharp Framework Demo 配置文件
"System": {
"AppName": "Sharp Framework Demo",
"Version": "1.0.0",
"HTTPIp": "127.0.0.1",
"HttpPort": 8080,
"TimeoutSeconds": 30,
"Scheme": "http"
},
// HTTP 服务器配置
"Server": {
"Port": 8080,
"Host": "localhost",
"MaxConcurrentRequests": 100
},
// NetServer 配置
"NetServer": {
"UseLogging": true,
"UseExceptionHandling": true,
"UseCors": true,
"UseRateLimit": true,
"UseResponseCache": true
},
// NetClient 配置
"NetClient": {
"Timeout": "00:00:20",
"EnableLogging": true,
"IncludeCorrelationId": true,
"CorrelationIdHeaderName": "X-Correlation-Id",
"UseBearerToken": false,
"Retry": {
"Enabled": true,
"MaxRetryAttempts": 3,
"FirstDelay": "00:00:01",
"Handle429": true,
"HandleRequestTimeout": true
}
},
// PLC 配置(西门子)
"Plc": {
"Siemens": {
"IpAddress": "192.168.1.100",
"CpuType": "S71200",
"Rack": 0,
"Slot": 1,
"MonitorInterval": 200
}
},
// 串口配置
"SerialPort": {
"BaudRate": 9600,
"Parity": "None",
"DataBits": 8,
"StopBits": "One"
},
// 日志配置
"SharpLog": {
"UseSerilog": true,
"UseConsole": true,
"ConsoleJson": false,
"MinLevel": "Information",
"Overrides": {
"Microsoft": "Warning",
"System": "Warning"
},
"File": {
"Enabled": true,
"Path": "logs/demo-.log",
"Rolling": "Day",
"RetainedFileCountLimit": 30,
"Json": true
},
"Enrich": {
"Enabled": true,
"WithThreadId": true,
"WithProcessId": true,
"WithMachineName": true,
"WithEnvironmentName": true,
"WithActivityTrace": true
}
}
}
+14
View File
@@ -51,6 +51,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Python", "Python\Python.csp
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Wpf.DevTools", "Wpf.DevTools\Wpf.DevTools.csproj", "{3DFC9CBE-D9A3-43A8-80D9-D877000A0BAA}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Wpf.DevTools", "Wpf.DevTools\Wpf.DevTools.csproj", "{3DFC9CBE-D9A3-43A8-80D9-D877000A0BAA}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sharp.Demo", "Sharp.Demo\Sharp.Demo.csproj", "{1C5E7681-6B3E-47A6-B8D1-78E2A7EA705A}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
@@ -349,6 +351,18 @@ Global
{3DFC9CBE-D9A3-43A8-80D9-D877000A0BAA}.Release|x64.Build.0 = Release|Any CPU {3DFC9CBE-D9A3-43A8-80D9-D877000A0BAA}.Release|x64.Build.0 = Release|Any CPU
{3DFC9CBE-D9A3-43A8-80D9-D877000A0BAA}.Release|x86.ActiveCfg = Release|Any CPU {3DFC9CBE-D9A3-43A8-80D9-D877000A0BAA}.Release|x86.ActiveCfg = Release|Any CPU
{3DFC9CBE-D9A3-43A8-80D9-D877000A0BAA}.Release|x86.Build.0 = Release|Any CPU {3DFC9CBE-D9A3-43A8-80D9-D877000A0BAA}.Release|x86.Build.0 = Release|Any CPU
{1C5E7681-6B3E-47A6-B8D1-78E2A7EA705A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1C5E7681-6B3E-47A6-B8D1-78E2A7EA705A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1C5E7681-6B3E-47A6-B8D1-78E2A7EA705A}.Debug|x64.ActiveCfg = Debug|Any CPU
{1C5E7681-6B3E-47A6-B8D1-78E2A7EA705A}.Debug|x64.Build.0 = Debug|Any CPU
{1C5E7681-6B3E-47A6-B8D1-78E2A7EA705A}.Debug|x86.ActiveCfg = Debug|Any CPU
{1C5E7681-6B3E-47A6-B8D1-78E2A7EA705A}.Debug|x86.Build.0 = Debug|Any CPU
{1C5E7681-6B3E-47A6-B8D1-78E2A7EA705A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1C5E7681-6B3E-47A6-B8D1-78E2A7EA705A}.Release|Any CPU.Build.0 = Release|Any CPU
{1C5E7681-6B3E-47A6-B8D1-78E2A7EA705A}.Release|x64.ActiveCfg = Release|Any CPU
{1C5E7681-6B3E-47A6-B8D1-78E2A7EA705A}.Release|x64.Build.0 = Release|Any CPU
{1C5E7681-6B3E-47A6-B8D1-78E2A7EA705A}.Release|x86.ActiveCfg = Release|Any CPU
{1C5E7681-6B3E-47A6-B8D1-78E2A7EA705A}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
@@ -11,7 +11,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="HandyControl" Version="3.5.0" /> <PackageReference Include="HandyControl" Version="3.5.1" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>