From c493779a8504fe1eb548c865ff268a7f7436ec01 Mon Sep 17 00:00:00 2001
From: wanshenmean <cathay_xy@163.com>
Date: 星期四, 19 三月 2026 11:43:36 +0800
Subject: [PATCH] feat: 集成机械手客户端并重构模拟器前端工作台
---
Code/WCS/WIDESEAWCS_S7Simulator/WIDESEAWCS_S7Simulator.Server/Controllers/RobotClientsController.cs | 126 ++
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_TaskInfoService/TaskService.cs | 4
Code/WCS/WIDESEAWCS_Server/.vs/WIDESEAWCS_Server/v18/DocumentLayout.json | 146 --
Code/WCS/WIDESEAWCS_S7Simulator/WIDESEAWCS_S7Simulator.Web/src/router/index.ts | 5
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Model/Models/TaskInfo/Dt_RobotTask.cs | 2
Code/WMS/WIDESEA_WMSServer/.vs/WIDESEA_WMSServer/v18/DocumentLayout.json | 24
Code/WCS/WIDESEAWCS_S7Simulator/WIDESEAWCS_S7Simulator.Web/src/views/CreateView.vue | 25
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/Workflow/RobotWorkflowOrchestrator.cs | 2
Code/WCS/WIDESEAWCS_S7Simulator/WIDESEAWCS_S7Simulator.Web/src/views/DetailsView.vue | 251 +++--
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Server/appsettings.json | 10
Code/WCS/WIDESEAWCS_S7Simulator/WIDESEAWCS_S7Simulator.Web/src/views/HomeView.vue | 380 ++++++--
项目资料/设备协议/机械手协议/~$交互流程表(1).xlsx | 0
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_TaskInfoService/RobotTaskService.cs | 5
Code/WCS/WIDESEAWCS_S7Simulator/WIDESEAWCS_S7Simulator.Server/Program.cs | 1
Code/WCS/WIDESEAWCS_S7Simulator/WIDESEAWCS_S7Simulator.Web/src/api/index.ts | 42
Code/WCS/WIDESEAWCS_S7Simulator/WIDESEAWCS_S7Simulator.Web/src/views/ProtocolTemplatesView.vue | 20
Code/WMS/WIDESEA_WMSServer/WIDESEA_StockService/StockSerivce.cs | 11
Code/WCS/WIDESEAWCS_S7Simulator/WIDESEAWCS_S7Simulator.Web/src/types/index.ts | 55 +
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_DTO/TaskInfo/WMSTaskDTO.cs | 2
Code/WMS/WIDESEA_WMSServer/.vs/WIDESEA_WMSServer/v18/DocumentLayout.backup.json | 21
Code/WCS/WIDESEAWCS_S7Simulator/WIDESEAWCS_S7Simulator.Web/src/views/RobotClientsView.vue | 452 ++++++++++
Code/WCS/WIDESEAWCS_S7Simulator/WIDESEAWCS_S7Simulator.Web/src/views/EditView.vue | 44
Code/WCS/WIDESEAWCS_S7Simulator/WIDESEAWCS_S7Simulator.Server/Services/RobotClientManager.cs | 588 +++++++++++++
Code/WCS/WIDESEAWCS_S7Simulator/WIDESEAWCS_S7Simulator.Web/src/App.vue | 201 +++-
Code/WCS/WIDESEAWCS_S7Simulator/WIDESEAWCS_S7Simulator.Web/src/style.css | 145 ++
25 files changed, 2,012 insertions(+), 550 deletions(-)
diff --git a/Code/WCS/WIDESEAWCS_S7Simulator/WIDESEAWCS_S7Simulator.Server/Controllers/RobotClientsController.cs b/Code/WCS/WIDESEAWCS_S7Simulator/WIDESEAWCS_S7Simulator.Server/Controllers/RobotClientsController.cs
new file mode 100644
index 0000000..8192d03
--- /dev/null
+++ b/Code/WCS/WIDESEAWCS_S7Simulator/WIDESEAWCS_S7Simulator.Server/Controllers/RobotClientsController.cs
@@ -0,0 +1,126 @@
+锘縰sing Microsoft.AspNetCore.Mvc;
+using WIDESEAWCS_S7Simulator.Server.Services;
+
+namespace WIDESEAWCS_S7Simulator.Server.Controllers;
+
+/// <summary>
+/// 鏈烘鎵嬪鎴风绠$悊鎺ュ彛锛堜富鍔ㄨ繛鎺ユā寮忥級銆�
+/// </summary>
+[ApiController]
+[Route("api/[controller]")]
+public class RobotClientsController : ControllerBase
+{
+ private readonly IRobotClientManager _robotClientManager;
+ private readonly ILogger<RobotClientsController> _logger;
+
+ public RobotClientsController(IRobotClientManager robotClientManager, ILogger<RobotClientsController> logger)
+ {
+ _robotClientManager = robotClientManager;
+ _logger = logger;
+ }
+
+ [HttpGet("status")]
+ [ProducesResponseType(typeof(RobotServerCollectionStatusResponse), StatusCodes.Status200OK)]
+ public async Task<ActionResult<RobotServerCollectionStatusResponse>> GetStatus()
+ {
+ var status = await _robotClientManager.GetStatusAsync();
+ return Ok(status);
+ }
+
+ [HttpPost("start")]
+ [ProducesResponseType(typeof(RobotServerCollectionStatusResponse), StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status400BadRequest)]
+ public async Task<ActionResult<RobotServerCollectionStatusResponse>> Start([FromBody] RobotServerStartRequest request)
+ {
+ try
+ {
+ var status = await _robotClientManager.StartAsync(request, HttpContext.RequestAborted);
+ return Ok(status);
+ }
+ catch (ArgumentException ex)
+ {
+ return BadRequest(new { error = ex.Message });
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "鍚姩鏈烘鎵嬪鎴风瀹炰緥澶辫触");
+ return StatusCode(StatusCodes.Status500InternalServerError, new { error = "鍚姩鏈烘鎵嬪鎴风瀹炰緥澶辫触" });
+ }
+ }
+
+ [HttpPost("stop")]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ public async Task<ActionResult> Stop([FromQuery] string? serverId = null)
+ {
+ await _robotClientManager.StopAsync(serverId);
+ return Ok(new { message = string.IsNullOrWhiteSpace(serverId) ? "鏈烘鎵嬪鎴风宸插叏閮ㄥ仠姝�" : $"鏈烘鎵嬪鎴风 {serverId} 宸插仠姝�" });
+ }
+
+ [HttpPost("send")]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status400BadRequest)]
+ public async Task<ActionResult> Send([FromBody] RobotServerSendRequest request)
+ {
+ if (string.IsNullOrWhiteSpace(request.Message))
+ {
+ return BadRequest(new { error = "鍙戦�佸唴瀹逛笉鑳戒负绌�" });
+ }
+
+ try
+ {
+ if (string.IsNullOrWhiteSpace(request.ServerId))
+ {
+ return BadRequest(new { error = "ServerId 涓嶈兘涓虹┖" });
+ }
+
+ if (request.ClientId.HasValue)
+ {
+ await _robotClientManager.SendToClientAsync(request.ServerId, request.ClientId.Value, request.Message);
+ }
+ else
+ {
+ await _robotClientManager.SendToAllAsync(request.ServerId, request.Message);
+ }
+
+ return Ok(new { message = "鍙戦�佹垚鍔�" });
+ }
+ catch (InvalidOperationException ex)
+ {
+ return BadRequest(new { error = ex.Message });
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "鍙戦�佹満姊版墜瀹㈡埛绔秷鎭け璐�");
+ return StatusCode(StatusCodes.Status500InternalServerError, new { error = "鍙戦�佹満姊版墜瀹㈡埛绔秷鎭け璐�" });
+ }
+ }
+
+ /// <summary>
+ /// 娓呯┖鎸囧畾瀹㈡埛绔疄渚嬬殑娑堟伅鏃ュ織銆�
+ /// </summary>
+ [HttpPost("clear-received")]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status400BadRequest)]
+ public async Task<ActionResult> ClearReceived([FromQuery] string serverId)
+ {
+ if (string.IsNullOrWhiteSpace(serverId))
+ {
+ return BadRequest(new { error = "ServerId 涓嶈兘涓虹┖" });
+ }
+
+ try
+ {
+ await _robotClientManager.ClearReceivedMessagesAsync(serverId);
+ return Ok(new { message = $"瀹炰緥 {serverId} 鐨勬帴鏀舵秷鎭凡娓呯┖" });
+ }
+ catch (InvalidOperationException ex)
+ {
+ return BadRequest(new { error = ex.Message });
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "娓呯┖鎺ユ敹娑堟伅澶辫触");
+ return StatusCode(StatusCodes.Status500InternalServerError, new { error = "娓呯┖鎺ユ敹娑堟伅澶辫触" });
+ }
+ }
+}
diff --git a/Code/WCS/WIDESEAWCS_S7Simulator/WIDESEAWCS_S7Simulator.Server/Program.cs b/Code/WCS/WIDESEAWCS_S7Simulator/WIDESEAWCS_S7Simulator.Server/Program.cs
index 7ca0d53..18b602a 100644
--- a/Code/WCS/WIDESEAWCS_S7Simulator/WIDESEAWCS_S7Simulator.Server/Program.cs
+++ b/Code/WCS/WIDESEAWCS_S7Simulator/WIDESEAWCS_S7Simulator.Server/Program.cs
@@ -46,6 +46,7 @@
builder.Services.AddSingleton<IPersistenceService>(sp => new FilePersistenceService(dataPath));
builder.Services.AddSingleton<IProtocolTemplateService>(sp => new FileProtocolTemplateService(dataPath));
+builder.Services.AddSingleton<IRobotClientManager, RobotClientManager>();
builder.Services.Configure<ProtocolMonitoringOptions>(builder.Configuration.GetSection("ProtocolMonitoring"));
builder.Services.AddSingleton<MirrorAckProtocolHandler>();
builder.Services.AddSingleton<IDeviceProtocolHandler, WcsLineProtocolHandler>();
diff --git a/Code/WCS/WIDESEAWCS_S7Simulator/WIDESEAWCS_S7Simulator.Server/Services/RobotClientManager.cs b/Code/WCS/WIDESEAWCS_S7Simulator/WIDESEAWCS_S7Simulator.Server/Services/RobotClientManager.cs
new file mode 100644
index 0000000..1fd7887
--- /dev/null
+++ b/Code/WCS/WIDESEAWCS_S7Simulator/WIDESEAWCS_S7Simulator.Server/Services/RobotClientManager.cs
@@ -0,0 +1,588 @@
+锘縰sing System.Collections.Concurrent;
+using System.Net;
+using System.Net.Sockets;
+using System.Text;
+
+namespace WIDESEAWCS_S7Simulator.Server.Services;
+
+public interface IRobotClientManager
+{
+ Task<RobotServerCollectionStatusResponse> StartAsync(RobotServerStartRequest request, CancellationToken cancellationToken = default);
+ Task StopAsync(string? serverId = null);
+ Task<RobotServerCollectionStatusResponse> GetStatusAsync();
+ Task SendToClientAsync(string serverId, int clientId, string message);
+ Task SendToAllAsync(string serverId, string message);
+ Task ClearReceivedMessagesAsync(string serverId);
+}
+
+/// <summary>
+/// 鏈烘鎵� TCP 瀹㈡埛绔瀹炰緥绠$悊鍣ㄣ��
+/// 涓�涓� ServerId 瀵瑰簲涓�涓� TcpClient锛屼富鍔ㄨ繛鎺ョ洰鏍囨湇鍔$銆�
+/// </summary>
+public sealed class RobotClientManager : IRobotClientManager, IDisposable
+{
+ private readonly ILogger<RobotClientManager> _logger;
+ private readonly ConcurrentDictionary<string, RobotClientRuntime> _clients = new(StringComparer.OrdinalIgnoreCase);
+ private bool _disposed;
+
+ public RobotClientManager(ILogger<RobotClientManager> logger)
+ {
+ _logger = logger;
+ }
+
+ public async Task<RobotServerCollectionStatusResponse> StartAsync(RobotServerStartRequest request, CancellationToken cancellationToken = default)
+ {
+ ValidateStartRequest(request);
+ EnsureLocalPortAvailable(request.LocalPort);
+
+ var key = request.ServerId.Trim();
+ if (_clients.ContainsKey(key))
+ {
+ throw new InvalidOperationException($"瀹㈡埛绔疄渚� '{key}' 宸插瓨鍦�");
+ }
+
+ var runtime = new RobotClientRuntime
+ {
+ ServerId = key,
+ RemoteIp = request.ListenIp,
+ RemotePort = request.ListenPort,
+ LocalPort = request.LocalPort,
+ Cancellation = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken),
+ Connected = false
+ };
+
+ runtime.ConnectionLoopTask = Task.Run(() => ConnectionLoopAsync(runtime), runtime.Cancellation.Token);
+
+ if (!_clients.TryAdd(key, runtime))
+ {
+ await StopRuntimeAsync(runtime);
+ throw new InvalidOperationException($"瀹㈡埛绔疄渚� '{key}' 鍒涘缓澶辫触");
+ }
+
+ _logger.LogInformation("鏈烘鎵嬪鎴风宸插惎鍔�: {ServerId} 鏈湴:{LocalPort} -> 杩滅:{Ip}:{Port}",
+ key,
+ request.LocalPort,
+ request.ListenIp,
+ request.ListenPort);
+
+ return await GetStatusAsync();
+ }
+
+ public async Task StopAsync(string? serverId = null)
+ {
+ if (string.IsNullOrWhiteSpace(serverId))
+ {
+ var all = _clients.Values.ToArray();
+ _clients.Clear();
+ await Task.WhenAll(all.Select(StopRuntimeAsync));
+ return;
+ }
+
+ if (_clients.TryRemove(serverId.Trim(), out var runtime))
+ {
+ await StopRuntimeAsync(runtime);
+ }
+ }
+
+ public Task<RobotServerCollectionStatusResponse> GetStatusAsync()
+ {
+ var servers = _clients.Values
+ .OrderBy(x => x.ServerId, StringComparer.OrdinalIgnoreCase)
+ .Select(ToStatusItem)
+ .ToArray();
+
+ return Task.FromResult(new RobotServerCollectionStatusResponse
+ {
+ RunningServerCount = servers.Count(x => x.Running),
+ Servers = servers
+ });
+ }
+
+ public async Task SendToClientAsync(string serverId, int clientId, string message)
+ {
+ var runtime = GetClientOrThrow(serverId);
+ if (clientId != 1)
+ {
+ throw new InvalidOperationException($"瀹㈡埛绔疄渚� '{runtime.ServerId}' 浠呮敮鎸� ClientId=1");
+ }
+
+ await SendFrameAsync(runtime, message, CancellationToken.None);
+ }
+
+ public async Task SendToAllAsync(string serverId, string message)
+ {
+ var runtime = GetClientOrThrow(serverId);
+ await SendFrameAsync(runtime, message, CancellationToken.None);
+ }
+
+ /// <summary>
+ /// 娓呯┖鎸囧畾瀹㈡埛绔疄渚嬬殑鏀跺彂娑堟伅鏃ュ織銆�
+ /// </summary>
+ public Task ClearReceivedMessagesAsync(string serverId)
+ {
+ var runtime = GetClientOrThrow(serverId);
+
+ while (runtime.ReceivedMessages.TryDequeue(out _))
+ {
+ }
+
+ while (runtime.SentMessages.TryDequeue(out _))
+ {
+ }
+
+ return Task.CompletedTask;
+ }
+
+ private RobotClientRuntime GetClientOrThrow(string serverId)
+ {
+ if (string.IsNullOrWhiteSpace(serverId))
+ {
+ throw new ArgumentException("ServerId 涓嶈兘涓虹┖");
+ }
+
+ if (!_clients.TryGetValue(serverId.Trim(), out var runtime))
+ {
+ throw new InvalidOperationException($"瀹㈡埛绔疄渚� '{serverId}' 涓嶅瓨鍦�");
+ }
+
+ return runtime;
+ }
+
+ /// <summary>
+ /// 瀹㈡埛绔繛鎺ュ畧鎶ゅ惊鐜細鏂紑鍚庤嚜鍔ㄩ噸杩炪��
+ /// </summary>
+ private async Task ConnectionLoopAsync(RobotClientRuntime runtime)
+ {
+ var token = runtime.Cancellation.Token;
+
+ while (!token.IsCancellationRequested)
+ {
+ if (!runtime.Connected)
+ {
+ try
+ {
+ await ConnectOnceAsync(runtime, token);
+ }
+ catch (OperationCanceledException)
+ {
+ break;
+ }
+ catch (Exception ex)
+ {
+ runtime.LastError = ex.Message;
+ _logger.LogWarning(ex, "[{ServerId}] 杩炴帴澶辫触锛屽皢鍦� 2 绉掑悗閲嶈瘯", runtime.ServerId);
+ await DelayForReconnect(token);
+ continue;
+ }
+ }
+
+ var tcpClient = runtime.TcpClient;
+ if (tcpClient == null)
+ {
+ await DelayForReconnect(token);
+ continue;
+ }
+
+ await ReceiveLoopAsync(runtime, tcpClient);
+ await DelayForReconnect(token);
+ }
+ }
+
+ /// <summary>
+ /// 鍗曟杩炴帴鍔ㄤ綔銆傛垚鍔熷悗鏇存柊杩愯鏃惰繛鎺ョ姸鎬併��
+ /// </summary>
+ private async Task ConnectOnceAsync(RobotClientRuntime runtime, CancellationToken token)
+ {
+ var tcpClient = new TcpClient(AddressFamily.InterNetwork);
+ try
+ {
+ // 姣忔閲嶈繛閮藉浐瀹氱粦瀹氬悓涓�鏈湴绔彛銆�
+ tcpClient.Client.Bind(new IPEndPoint(IPAddress.Any, runtime.LocalPort));
+ await tcpClient.ConnectAsync(runtime.RemoteIp, runtime.RemotePort, token);
+ }
+ catch
+ {
+ tcpClient.Dispose();
+ throw;
+ }
+
+ runtime.TcpClient = tcpClient;
+ runtime.Connected = true;
+ runtime.ConnectedAt = DateTimeOffset.Now;
+ runtime.LastError = null;
+
+ _logger.LogInformation("[{ServerId}] 宸茶繛鎺� 鏈湴:{LocalPort} -> 杩滅:{Ip}:{Port}",
+ runtime.ServerId,
+ runtime.LocalPort,
+ runtime.RemoteIp,
+ runtime.RemotePort);
+ }
+
+ /// <summary>
+ /// 鍗曚釜瀹㈡埛绔敹鍖呭惊鐜紝鏀寔甯у崗璁拰鏅�氭枃鏈袱绉嶈緭鍏ャ��
+ /// </summary>
+ private async Task ReceiveLoopAsync(RobotClientRuntime runtime, TcpClient tcpClient)
+ {
+ var token = runtime.Cancellation.Token;
+ var buffer = new byte[2048];
+ var cache = new StringBuilder();
+
+ try
+ {
+ var stream = tcpClient.GetStream();
+ while (!token.IsCancellationRequested)
+ {
+ var length = await stream.ReadAsync(buffer, 0, buffer.Length, token);
+ if (length <= 0)
+ {
+ break;
+ }
+
+ runtime.LastReceivedAt = DateTimeOffset.Now;
+ cache.Append(Encoding.UTF8.GetString(buffer, 0, length));
+ TryReadMessages(runtime, cache);
+ }
+ }
+ catch (OperationCanceledException)
+ {
+ // 姝e父鍋滄銆�
+ }
+ catch (Exception ex)
+ {
+ runtime.LastError = ex.Message;
+ _logger.LogWarning(ex, "[{ServerId}] 瀹㈡埛绔敹鍖呭紓甯�", runtime.ServerId);
+ }
+ finally
+ {
+ runtime.Connected = false;
+ await CloseRuntimeSocketAsync(runtime);
+ _logger.LogWarning("[{ServerId}] 瀹㈡埛绔繛鎺ュ凡鏂紑锛屽噯澶囪嚜鍔ㄩ噸杩�", runtime.ServerId);
+ }
+ }
+
+ private void TryReadMessages(RobotClientRuntime runtime, StringBuilder cache)
+ {
+ const string start = "<START>";
+ const string end = "<END>";
+
+ while (true)
+ {
+ var text = cache.ToString();
+ var startIndex = text.IndexOf(start, StringComparison.Ordinal);
+ var endIndex = text.IndexOf(end, StringComparison.Ordinal);
+
+ // 鏈懡涓抚鍗忚鏃讹紝鎸夋暣娈垫枃鏈褰曪紝閬垮厤娑堟伅涓㈠け銆�
+ if (startIndex < 0 || endIndex < 0 || endIndex <= startIndex)
+ {
+ if (cache.Length > 0 && cache.Length < 2048 && !text.Contains("<START>", StringComparison.Ordinal))
+ {
+ AppendReceived(runtime, text.Trim());
+ cache.Clear();
+ }
+
+ if (cache.Length > 10240)
+ {
+ cache.Clear();
+ }
+
+ return;
+ }
+
+ var contentStart = startIndex + start.Length;
+ var contentLength = endIndex - contentStart;
+ var content = text.Substring(contentStart, contentLength);
+ cache.Remove(0, endIndex + end.Length);
+ AppendReceived(runtime, content);
+ }
+ }
+
+ private void AppendReceived(RobotClientRuntime runtime, string message)
+ {
+ if (string.IsNullOrWhiteSpace(message))
+ {
+ return;
+ }
+
+ runtime.LastReceivedMessage = message;
+ runtime.ReceivedMessages.Enqueue(new RobotServerReceivedMessageItem
+ {
+ ReceivedAt = DateTimeOffset.Now,
+ ClientId = 1,
+ RemoteEndPoint = $"{runtime.RemoteIp}:{runtime.RemotePort}",
+ Message = message
+ });
+
+ while (runtime.ReceivedMessages.Count > 500)
+ {
+ runtime.ReceivedMessages.TryDequeue(out _);
+ }
+
+ _logger.LogInformation("[{ServerId}] 鏀跺埌: {Message}", runtime.ServerId, message);
+ }
+
+ private async Task SendFrameAsync(RobotClientRuntime runtime, string message, CancellationToken token)
+ {
+ if (!runtime.Connected)
+ {
+ throw new InvalidOperationException($"瀹㈡埛绔疄渚� '{runtime.ServerId}' 鏈繛鎺�");
+ }
+
+ var frame = $"<START>{message}<END>";
+ var bytes = Encoding.UTF8.GetBytes(frame);
+
+ await runtime.SendLock.WaitAsync(token);
+ try
+ {
+ if (runtime.TcpClient == null)
+ {
+ throw new InvalidOperationException($"瀹㈡埛绔疄渚� '{runtime.ServerId}' 鏈繛鎺�");
+ }
+
+ var stream = runtime.TcpClient.GetStream();
+ await stream.WriteAsync(bytes, 0, bytes.Length, token);
+ await stream.FlushAsync(token);
+ runtime.LastSentAt = DateTimeOffset.Now;
+
+ runtime.SentMessages.Enqueue(new RobotServerSentMessageItem
+ {
+ SentAt = DateTimeOffset.Now,
+ ClientId = 1,
+ RemoteEndPoint = $"{runtime.RemoteIp}:{runtime.RemotePort}",
+ Message = message
+ });
+
+ while (runtime.SentMessages.Count > 500)
+ {
+ runtime.SentMessages.TryDequeue(out _);
+ }
+
+ _logger.LogInformation("[{ServerId}] 鍙戦��: {Frame}", runtime.ServerId, frame);
+ }
+ finally
+ {
+ runtime.SendLock.Release();
+ }
+ }
+
+ private static async Task CloseRuntimeSocketAsync(RobotClientRuntime runtime)
+ {
+ await runtime.SendLock.WaitAsync();
+ try
+ {
+ if (runtime.TcpClient != null)
+ {
+ try { runtime.TcpClient.Close(); } catch { }
+ runtime.TcpClient.Dispose();
+ runtime.TcpClient = null;
+ }
+ runtime.Connected = false;
+ }
+ finally
+ {
+ runtime.SendLock.Release();
+ }
+ }
+
+ private async Task StopRuntimeAsync(RobotClientRuntime runtime)
+ {
+ try { runtime.Cancellation.Cancel(); } catch { }
+
+ if (runtime.ConnectionLoopTask != null)
+ {
+ try { await runtime.ConnectionLoopTask; } catch { }
+ }
+
+ await CloseRuntimeSocketAsync(runtime);
+ runtime.Cancellation.Dispose();
+ _logger.LogInformation("鏈烘鎵嬪鎴风宸插仠姝�: {ServerId}", runtime.ServerId);
+ }
+
+ private static RobotServerStatusItem ToStatusItem(RobotClientRuntime runtime)
+ {
+ var clients = new[]
+ {
+ new RobotServerClientStatusItem
+ {
+ ClientId = 1,
+ RemoteEndPoint = $"{runtime.RemoteIp}:{runtime.RemotePort}",
+ Connected = runtime.Connected,
+ ConnectedAt = runtime.ConnectedAt,
+ LastReceivedAt = runtime.LastReceivedAt,
+ LastSentAt = runtime.LastSentAt,
+ LastReceivedMessage = runtime.LastReceivedMessage,
+ LastError = runtime.LastError
+ }
+ };
+
+ var receivedMessages = runtime.ReceivedMessages
+ .Reverse()
+ .Take(200)
+ .ToArray();
+
+ var sentMessages = runtime.SentMessages
+ .Reverse()
+ .Take(200)
+ .ToArray();
+
+ return new RobotServerStatusItem
+ {
+ ServerId = runtime.ServerId,
+ Running = !runtime.Cancellation.IsCancellationRequested,
+ ListenIp = runtime.RemoteIp,
+ ListenPort = runtime.RemotePort,
+ LocalPort = runtime.LocalPort,
+ ConnectedCount = runtime.Connected ? 1 : 0,
+ Clients = clients,
+ ReceivedMessages = receivedMessages,
+ SentMessages = sentMessages
+ };
+ }
+
+ private static void ValidateStartRequest(RobotServerStartRequest request)
+ {
+ if (string.IsNullOrWhiteSpace(request.ServerId))
+ {
+ throw new ArgumentException("ServerId 涓嶈兘涓虹┖");
+ }
+
+ if (string.IsNullOrWhiteSpace(request.ListenIp))
+ {
+ throw new ArgumentException("鐩爣鏈嶅姟绔湴鍧�涓嶈兘涓虹┖");
+ }
+
+ if (request.ListenPort <= 0 || request.ListenPort > 65535)
+ {
+ throw new ArgumentException("鐩爣鏈嶅姟绔鍙e繀椤诲湪 1-65535 鑼冨洿鍐�");
+ }
+
+ if (request.LocalPort <= 0 || request.LocalPort > 65535)
+ {
+ throw new ArgumentException("瀹㈡埛绔湰鍦扮鍙e繀椤诲湪 1-65535 鑼冨洿鍐�");
+ }
+ }
+
+ /// <summary>
+ /// 鍚姩鍓嶆鏌ユ湰鍦扮鍙f槸鍚﹀彲鐢紝閬垮厤瀹炰緥杩涘叆鏃犳晥閲嶈繛鐘舵�併��
+ /// </summary>
+ private static void EnsureLocalPortAvailable(int localPort)
+ {
+ Socket? probe = null;
+ try
+ {
+ probe = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
+ probe.Bind(new IPEndPoint(IPAddress.Any, localPort));
+ }
+ catch (SocketException ex)
+ {
+ throw new InvalidOperationException($"鏈湴绔彛 {localPort} 宸茶鍗犵敤鎴栦笉鍙敤", ex);
+ }
+ finally
+ {
+ try { probe?.Dispose(); } catch { }
+ }
+ }
+
+ private static async Task DelayForReconnect(CancellationToken token)
+ {
+ try
+ {
+ await Task.Delay(TimeSpan.FromSeconds(2), token);
+ }
+ catch (OperationCanceledException)
+ {
+ }
+ }
+
+ public void Dispose()
+ {
+ if (_disposed)
+ {
+ return;
+ }
+
+ _disposed = true;
+ StopAsync().GetAwaiter().GetResult();
+ }
+
+ private sealed class RobotClientRuntime
+ {
+ public string ServerId { get; set; } = string.Empty;
+ public string RemoteIp { get; set; } = string.Empty;
+ public int RemotePort { get; set; }
+ public int LocalPort { get; set; }
+ public TcpClient? TcpClient { get; set; }
+ public CancellationTokenSource Cancellation { get; set; } = default!;
+ public Task? ConnectionLoopTask { get; set; }
+ public SemaphoreSlim SendLock { get; } = new(1, 1);
+ public bool Connected { get; set; }
+ public DateTimeOffset? ConnectedAt { get; set; }
+ public DateTimeOffset? LastReceivedAt { get; set; }
+ public DateTimeOffset? LastSentAt { get; set; }
+ public string? LastReceivedMessage { get; set; }
+ public string? LastError { get; set; }
+ public ConcurrentQueue<RobotServerReceivedMessageItem> ReceivedMessages { get; } = new();
+ public ConcurrentQueue<RobotServerSentMessageItem> SentMessages { get; } = new();
+ }
+}
+
+public sealed class RobotServerStartRequest
+{
+ public string ServerId { get; set; } = "default";
+ public string ListenIp { get; set; } = "127.0.0.1";
+ public int ListenPort { get; set; } = 2000;
+ public int LocalPort { get; set; } = 2001;
+}
+
+public sealed class RobotServerSendRequest
+{
+ public string ServerId { get; set; } = string.Empty;
+ public int? ClientId { get; set; }
+ public string Message { get; set; } = string.Empty;
+}
+
+public sealed class RobotServerCollectionStatusResponse
+{
+ public int RunningServerCount { get; set; }
+ public IReadOnlyList<RobotServerStatusItem> Servers { get; set; } = Array.Empty<RobotServerStatusItem>();
+}
+
+public sealed class RobotServerStatusItem
+{
+ public string ServerId { get; set; } = string.Empty;
+ public bool Running { get; set; }
+ public string ListenIp { get; set; } = string.Empty;
+ public int ListenPort { get; set; }
+ public int LocalPort { get; set; }
+ public int ConnectedCount { get; set; }
+ public IReadOnlyList<RobotServerClientStatusItem> Clients { get; set; } = Array.Empty<RobotServerClientStatusItem>();
+ public IReadOnlyList<RobotServerReceivedMessageItem> ReceivedMessages { get; set; } = Array.Empty<RobotServerReceivedMessageItem>();
+ public IReadOnlyList<RobotServerSentMessageItem> SentMessages { get; set; } = Array.Empty<RobotServerSentMessageItem>();
+}
+
+public sealed class RobotServerClientStatusItem
+{
+ public int ClientId { get; set; }
+ public string? RemoteEndPoint { get; set; }
+ public bool Connected { get; set; }
+ public DateTimeOffset? ConnectedAt { get; set; }
+ public DateTimeOffset? LastReceivedAt { get; set; }
+ public DateTimeOffset? LastSentAt { get; set; }
+ public string? LastReceivedMessage { get; set; }
+ public string? LastError { get; set; }
+}
+
+public sealed class RobotServerReceivedMessageItem
+{
+ public DateTimeOffset ReceivedAt { get; set; }
+ public int ClientId { get; set; }
+ public string? RemoteEndPoint { get; set; }
+ public string Message { get; set; } = string.Empty;
+}
+
+public sealed class RobotServerSentMessageItem
+{
+ public DateTimeOffset SentAt { get; set; }
+ public int ClientId { get; set; }
+ public string? RemoteEndPoint { get; set; }
+ public string Message { get; set; } = string.Empty;
+}
diff --git a/Code/WCS/WIDESEAWCS_S7Simulator/WIDESEAWCS_S7Simulator.Web/src/App.vue b/Code/WCS/WIDESEAWCS_S7Simulator/WIDESEAWCS_S7Simulator.Web/src/App.vue
index 567aee1..10a7a1a 100644
--- a/Code/WCS/WIDESEAWCS_S7Simulator/WIDESEAWCS_S7Simulator.Web/src/App.vue
+++ b/Code/WCS/WIDESEAWCS_S7Simulator/WIDESEAWCS_S7Simulator.Web/src/App.vue
@@ -1,94 +1,169 @@
-<template>
- <el-container class="app-container">
- <el-header class="app-header">
- <div class="header-content">
- <router-link to="/" class="logo">
- <el-icon :size="24"><Cpu /></el-icon>
- <span>S7 PLC Simulator</span>
- </router-link>
- <div class="header-nav">
- <router-link to="/protocol-templates" class="nav-link">鍗忚妯℃澘</router-link>
+锘�<template>
+ <el-container class="admin-layout">
+ <el-aside class="admin-aside" width="220px">
+ <div class="brand-area">
+ <el-icon :size="24" class="brand-icon"><Cpu /></el-icon>
+ <div class="brand-text">
+ <div class="brand-title">WCS 妯℃嫙骞冲彴</div>
+ <div class="brand-subtitle">S7 Simulator</div>
</div>
</div>
- </el-header>
- <el-main class="app-main">
- <router-view />
- </el-main>
+ <el-menu
+ class="aside-menu"
+ :default-active="activeMenu"
+ background-color="#1f2a37"
+ text-color="#c7d2fe"
+ active-text-color="#ffffff"
+ router
+ >
+ <el-menu-item index="/">
+ <el-icon><House /></el-icon>
+ <span>瀹炰緥绠$悊</span>
+ </el-menu-item>
+ <el-menu-item index="/protocol-templates">
+ <el-icon><Files /></el-icon>
+ <span>鍗忚妯℃澘</span>
+ </el-menu-item>
+ <el-menu-item index="/robot-clients">
+ <el-icon><Connection /></el-icon>
+ <span>鏈烘鎵嬪鎴风</span>
+ </el-menu-item>
+ </el-menu>
+ </el-aside>
- <el-footer class="app-footer">
- <span>© 2026 - S7 PLC Simulator Management UI</span>
- </el-footer>
+ <el-container>
+ <el-header class="admin-header">
+ <div class="header-left">
+ <div class="page-title">{{ pageTitle }}</div>
+ <div class="page-path">{{ route.path }}</div>
+ </div>
+ </el-header>
+
+ <el-main class="admin-main">
+ <div class="content-shell">
+ <router-view />
+ </div>
+ </el-main>
+ </el-container>
</el-container>
</template>
<script setup lang="ts">
-import { Cpu } from '@element-plus/icons-vue'
+import { computed } from 'vue'
+import { useRoute } from 'vue-router'
+import { Connection, Cpu, Files, House } from '@element-plus/icons-vue'
+
+const route = useRoute()
+
+const activeMenu = computed(() => {
+ if (route.path.startsWith('/protocol-templates')) return '/protocol-templates'
+ if (route.path.startsWith('/robot-clients')) return '/robot-clients'
+ return '/'
+})
+
+const pageTitle = computed(() => {
+ if (route.path.startsWith('/create')) return '鍒涘缓瀹炰緥'
+ if (route.path.startsWith('/edit')) return '缂栬緫瀹炰緥'
+ if (route.path.startsWith('/details')) return '瀹炰緥璇︽儏'
+ if (route.path.startsWith('/protocol-templates')) return '鍗忚妯℃澘'
+ if (route.path.startsWith('/robot-clients')) return '鏈烘鎵嬪鎴风'
+ return '瀹炰緥绠$悊'
+})
</script>
<style scoped>
-.app-container {
- min-height: 100vh;
+.admin-layout {
+ height: 100vh;
}
-.app-header {
- background: #409eff;
- color: white;
- padding: 0 20px;
+.admin-aside {
+ background: linear-gradient(180deg, #1f2a37 0%, #111827 100%);
+ border-right: 1px solid #263445;
+ display: flex;
+ flex-direction: column;
+}
+
+.brand-area {
+ height: 64px;
+ padding: 0 14px;
display: flex;
align-items: center;
- box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+ border-bottom: 1px solid #2b3a4f;
+ color: #e5e7eb;
}
-.header-content {
- width: 100%;
- max-width: 1400px;
- margin: 0 auto;
- display: flex;
- justify-content: space-between;
- align-items: center;
+.brand-icon {
+ margin-right: 10px;
}
-.logo {
- display: flex;
- align-items: center;
- gap: 8px;
- color: white;
- text-decoration: none;
- font-size: 18px;
- font-weight: 500;
+.brand-title {
+ font-size: 15px;
+ font-weight: 700;
+ line-height: 1.2;
}
-.logo:hover {
- color: white;
+.brand-subtitle {
+ font-size: 12px;
+ color: #94a3b8;
+ line-height: 1.2;
}
-.header-nav {
- display: flex;
- gap: 12px;
-}
-
-.nav-link {
- color: #fff;
- text-decoration: none;
-}
-
-.app-main {
+.aside-menu {
+ border-right: none;
flex: 1;
- padding: 20px;
- max-width: 1400px;
+}
+
+.admin-header {
+ height: 64px;
+ background: #ffffff;
+ border-bottom: 1px solid #e5e7eb;
+ display: flex;
+ align-items: center;
+ padding: 0 20px;
+}
+
+.header-left {
+ display: flex;
+ flex-direction: column;
+ gap: 2px;
+}
+
+.page-title {
+ font-size: 18px;
+ font-weight: 600;
+ color: #0f172a;
+}
+
+.page-path {
+ font-size: 12px;
+ color: #64748b;
+}
+
+.admin-main {
+ background: #f1f5f9;
+ padding: 14px;
+}
+
+.content-shell {
+ min-height: calc(100vh - 64px - 28px);
width: 100%;
+ max-width: 1680px;
margin: 0 auto;
+ padding: 2px;
}
-.app-footer {
- text-align: center;
- color: #909399;
- border-top: 1px solid #dcdfe6;
- padding: 20px;
-}
+@media (max-width: 960px) {
+ .admin-aside {
+ width: 64px !important;
+ }
-a.router-link-active {
- font-weight: bold;
+ .brand-text {
+ display: none;
+ }
+
+ .admin-header {
+ padding: 0 12px;
+ }
}
</style>
diff --git a/Code/WCS/WIDESEAWCS_S7Simulator/WIDESEAWCS_S7Simulator.Web/src/api/index.ts b/Code/WCS/WIDESEAWCS_S7Simulator/WIDESEAWCS_S7Simulator.Web/src/api/index.ts
index e6c9850..c45b917 100644
--- a/Code/WCS/WIDESEAWCS_S7Simulator/WIDESEAWCS_S7Simulator.Web/src/api/index.ts
+++ b/Code/WCS/WIDESEAWCS_S7Simulator/WIDESEAWCS_S7Simulator.Web/src/api/index.ts
@@ -1,9 +1,12 @@
-import axios from 'axios'
+锘縤mport axios from 'axios'
import type {
InstanceListItem,
InstanceState,
InstanceConfig,
- ProtocolTemplate
+ ProtocolTemplate,
+ RobotClientStartRequest,
+ RobotClientSendRequest,
+ RobotClientStatusResponse
} from '../types'
const api = axios.create({
@@ -19,7 +22,7 @@
return response.data
}
-// 鑾峰彇鎸囧畾瀹炰緥鐘舵��
+// 鑾峰彇瀹炰緥鐘舵��
export async function getInstance(id: string): Promise<InstanceState | null> {
try {
const response = await api.get<InstanceState>('/SimulatorInstances/GetInstance', {
@@ -52,7 +55,7 @@
// 鍒涘缓瀹炰緥
export async function createInstance(config: InstanceConfig): Promise<InstanceState | null> {
try {
- const response = await api.post<InstanceState>('/SimulatorInstances/Create', config )
+ const response = await api.post<InstanceState>('/SimulatorInstances/Create', config)
return response.data
} catch (error) {
console.error('鍒涘缓瀹炰緥澶辫触:', error)
@@ -175,4 +178,35 @@
}
}
+// 鑾峰彇鏈烘鎵嬫湇鍔$杩愯鐘舵�侊紙鍖呭惈澶氬疄渚嬪拰鎺ユ敹娑堟伅鏃ュ織锛�
+export async function getRobotClientStatus(): Promise<RobotClientStatusResponse> {
+ const response = await api.get<RobotClientStatusResponse>('/RobotClients/status')
+ return response.data
+}
+
+// 鍚姩涓�涓満姊版墜鏈嶅姟绔疄渚�
+export async function startRobotClients(request: RobotClientStartRequest): Promise<RobotClientStatusResponse> {
+ const response = await api.post<RobotClientStatusResponse>('/RobotClients/start', request)
+ return response.data
+}
+
+// 鍋滄鏈烘鎵嬫湇鍔$锛宻erverId 涓虹┖鏃跺仠姝㈠叏閮�
+export async function stopRobotClients(serverId?: string): Promise<void> {
+ await api.post('/RobotClients/stop', null, {
+ params: { serverId }
+ })
+}
+
+// 鍙戦�佹満姊版墜娑堟伅锛堟寜鏈嶅姟绔疄渚嬪箍鎾垨鍗曞彂锛�
+export async function sendRobotClientMessage(request: RobotClientSendRequest): Promise<void> {
+ await api.post('/RobotClients/send', request)
+}
+
+// 娓呯┖鎸囧畾鏈嶅姟绔疄渚嬬殑鎺ユ敹娑堟伅鏃ュ織
+export async function clearRobotClientReceivedMessages(serverId: string): Promise<void> {
+ await api.post('/RobotClients/clear-received', null, {
+ params: { serverId }
+ })
+}
+
export default api
diff --git a/Code/WCS/WIDESEAWCS_S7Simulator/WIDESEAWCS_S7Simulator.Web/src/router/index.ts b/Code/WCS/WIDESEAWCS_S7Simulator/WIDESEAWCS_S7Simulator.Web/src/router/index.ts
index 766810e..cc0caaa 100644
--- a/Code/WCS/WIDESEAWCS_S7Simulator/WIDESEAWCS_S7Simulator.Web/src/router/index.ts
+++ b/Code/WCS/WIDESEAWCS_S7Simulator/WIDESEAWCS_S7Simulator.Web/src/router/index.ts
@@ -28,6 +28,11 @@
path: '/protocol-templates',
name: 'protocolTemplates',
component: () => import('../views/ProtocolTemplatesView.vue')
+ },
+ {
+ path: '/robot-clients',
+ name: 'robotClients',
+ component: () => import('../views/RobotClientsView.vue')
}
]
diff --git a/Code/WCS/WIDESEAWCS_S7Simulator/WIDESEAWCS_S7Simulator.Web/src/style.css b/Code/WCS/WIDESEAWCS_S7Simulator/WIDESEAWCS_S7Simulator.Web/src/style.css
index 6c3eee6..13f7281 100644
--- a/Code/WCS/WIDESEAWCS_S7Simulator/WIDESEAWCS_S7Simulator.Web/src/style.css
+++ b/Code/WCS/WIDESEAWCS_S7Simulator/WIDESEAWCS_S7Simulator.Web/src/style.css
@@ -1,50 +1,135 @@
-/* Global Styles */
:root {
- font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;
+ --bg-page: #edf2f7;
+ --bg-panel: #ffffff;
+ --bg-soft: #f8fafc;
+ --text-main: #0f172a;
+ --text-sub: #64748b;
+ --border-main: #dbe2ea;
+ --brand-main: #0b5cab;
+ --brand-soft: #e8f2ff;
+ --ok: #16a34a;
+ --warn: #d97706;
+ --danger: #dc2626;
+ font-family: 'Noto Sans SC', 'Microsoft YaHei', 'PingFang SC', 'Helvetica Neue', sans-serif;
+}
+
+* {
+ box-sizing: border-box;
}
body {
margin: 0;
min-height: 100vh;
- background: #f5f7fa;
+ color: var(--text-main);
+ background:
+ radial-gradient(circle at 0% 0%, #f6faff 0%, transparent 35%),
+ radial-gradient(circle at 100% 0%, #eef6ff 0%, transparent 38%),
+ var(--bg-page);
}
-/* Status Colors - Element Plus */
+#app {
+ min-height: 100vh;
+}
+
+.admin-page {
+ display: flex;
+ flex-direction: column;
+ gap: 14px;
+}
+
+.page-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: flex-start;
+ flex-wrap: wrap;
+ gap: 12px;
+ padding: 16px 18px;
+ border: 1px solid var(--border-main);
+ border-radius: 12px;
+ background: linear-gradient(180deg, #ffffff 0%, #f8fbff 100%);
+}
+
+.page-header h2 {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ margin: 0;
+ font-size: 20px;
+}
+
+.text-muted {
+ margin: 4px 0 0 0;
+ color: var(--text-sub);
+ font-size: 13px;
+}
+
+.panel-card {
+ border-radius: 12px;
+ border: 1px solid var(--border-main);
+ overflow: hidden;
+}
+
+.section-block {
+ border: 1px solid var(--border-main);
+ border-radius: 12px;
+ background: var(--bg-panel);
+ overflow: hidden;
+}
+
+.section-head {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ gap: 10px;
+ padding: 12px 16px;
+ border-bottom: 1px solid #e6edf4;
+ background: linear-gradient(180deg, #ffffff 0%, #f9fbfe 100%);
+}
+
+.section-title {
+ margin: 0;
+ font-size: 15px;
+ font-weight: 600;
+ color: #0f172a;
+}
+
+.section-desc {
+ margin: 2px 0 0;
+ font-size: 12px;
+ color: var(--text-sub);
+}
+
+.section-body {
+ padding: 12px 14px;
+}
+
.status-stopped {
- border-left: 4px solid #909399;
+ border-left: 4px solid #94a3b8;
}
.status-starting {
- border-left: 4px solid #409eff;
+ border-left: 4px solid var(--brand-main);
}
.status-running {
- border-left: 4px solid #67c23a;
+ border-left: 4px solid var(--ok);
}
.status-stopping {
- border-left: 4px solid #e6a23c;
+ border-left: 4px solid var(--warn);
}
.status-error {
- border-left: 4px solid #f56c6c;
+ border-left: 4px solid var(--danger);
}
-/* Loading Spinner Overlay */
-.spinner-overlay {
- position: fixed;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- background-color: rgba(0, 0, 0, 0.5);
- display: flex;
- align-items: center;
- justify-content: center;
- z-index: 9999;
+.loading-container {
+ text-align: center;
+ padding: 60px 0;
+ color: var(--text-sub);
}
-/* Spin icon animation */
+.loading-icon,
.spin-icon {
animation: spin 1s linear infinite;
}
@@ -58,30 +143,32 @@
}
}
-/* Card hover effect */
.el-card {
- transition: all 0.3s ease;
+ transition: all 0.2s ease;
}
.el-card:hover {
- transform: translateY(-2px);
+ transform: translateY(-1px);
}
-/* Custom scrollbar */
+.el-table {
+ --el-table-header-bg-color: #f3f7fb;
+}
+
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
::-webkit-scrollbar-track {
- background: #f1f1f1;
+ background: #eef2f6;
}
::-webkit-scrollbar-thumb {
- background: #c0c4cc;
+ background: #b4beca;
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
- background: #909399;
+ background: #8f9cab;
}
diff --git a/Code/WCS/WIDESEAWCS_S7Simulator/WIDESEAWCS_S7Simulator.Web/src/types/index.ts b/Code/WCS/WIDESEAWCS_S7Simulator/WIDESEAWCS_S7Simulator.Web/src/types/index.ts
index 63e0c17..85d6119 100644
--- a/Code/WCS/WIDESEAWCS_S7Simulator/WIDESEAWCS_S7Simulator.Web/src/types/index.ts
+++ b/Code/WCS/WIDESEAWCS_S7Simulator/WIDESEAWCS_S7Simulator.Web/src/types/index.ts
@@ -112,3 +112,58 @@
version: string
fields: ProtocolFieldMapping[]
}
+
+export interface RobotClientStartRequest {
+ serverId: string
+ listenIp: string
+ listenPort: number
+ localPort: number
+}
+
+export interface RobotClientSendRequest {
+ serverId: string
+ clientId?: number | null
+ message: string
+}
+
+export interface RobotClientStatusItem {
+ clientId: number
+ remoteEndPoint: string | null
+ connected: boolean
+ connectedAt: string | null
+ lastReceivedMessage: string | null
+ lastError: string | null
+ lastReceivedAt: string | null
+ lastSentAt: string | null
+}
+
+export interface RobotServerReceivedMessageItem {
+ receivedAt: string
+ clientId: number
+ remoteEndPoint: string | null
+ message: string
+}
+
+export interface RobotServerSentMessageItem {
+ sentAt: string
+ clientId: number
+ remoteEndPoint: string | null
+ message: string
+}
+
+export interface RobotServerStatusItem {
+ serverId: string
+ running: boolean
+ listenIp: string
+ listenPort: number
+ localPort: number
+ connectedCount: number
+ clients: RobotClientStatusItem[]
+ receivedMessages: RobotServerReceivedMessageItem[]
+ sentMessages: RobotServerSentMessageItem[]
+}
+
+export interface RobotClientStatusResponse {
+ runningServerCount: number
+ servers: RobotServerStatusItem[]
+}
diff --git a/Code/WCS/WIDESEAWCS_S7Simulator/WIDESEAWCS_S7Simulator.Web/src/views/CreateView.vue b/Code/WCS/WIDESEAWCS_S7Simulator/WIDESEAWCS_S7Simulator.Web/src/views/CreateView.vue
index 88b6545..78e1e50 100644
--- a/Code/WCS/WIDESEAWCS_S7Simulator/WIDESEAWCS_S7Simulator.Web/src/views/CreateView.vue
+++ b/Code/WCS/WIDESEAWCS_S7Simulator/WIDESEAWCS_S7Simulator.Web/src/views/CreateView.vue
@@ -1,5 +1,5 @@
锘�<template>
- <div>
+ <div class="admin-page">
<div class="page-header">
<div class="header-left">
<h2>
@@ -16,7 +16,7 @@
<el-row justify="center">
<el-col :lg="24">
- <el-card shadow="never">
+ <el-card shadow="never" class="panel-card">
<el-form :model="form" :rules="rules" ref="formRef" label-width="120px">
<!-- 鍩烘湰淇℃伅 -->
<el-divider content-position="left">
@@ -315,27 +315,6 @@
</script>
<style scoped>
-.page-header {
- display: flex;
- justify-content: space-between;
- align-items: flex-start;
- margin-bottom: 20px;
- flex-wrap: wrap;
- gap: 16px;
-}
-
-.header-left h2 {
- display: flex;
- align-items: center;
- gap: 8px;
- margin: 0 0 8px 0;
-}
-
-.text-muted {
- color: #909399;
- margin: 0;
-}
-
.el-divider h3 {
margin: 0;
font-size: 16px;
diff --git a/Code/WCS/WIDESEAWCS_S7Simulator/WIDESEAWCS_S7Simulator.Web/src/views/DetailsView.vue b/Code/WCS/WIDESEAWCS_S7Simulator/WIDESEAWCS_S7Simulator.Web/src/views/DetailsView.vue
index c336b5a..83ea82c 100644
--- a/Code/WCS/WIDESEAWCS_S7Simulator/WIDESEAWCS_S7Simulator.Web/src/views/DetailsView.vue
+++ b/Code/WCS/WIDESEAWCS_S7Simulator/WIDESEAWCS_S7Simulator.Web/src/views/DetailsView.vue
@@ -1,5 +1,6 @@
锘�<template>
<div
+ class="admin-page"
v-loading.fullscreen.lock="startActionLoading"
element-loading-text="姝e湪鍚姩瀹炰緥锛岃绋嶅��..."
>
@@ -31,44 +32,64 @@
</el-button>
</div>
- <el-row :gutter="20" class="status-cards">
- <el-col :xs="12" :sm="6">
- <el-card shadow="hover" class="status-card">
- <el-statistic title="鐘舵��">
- <template #default>
- <el-tag :type="getStatusTagType(instance.status)" size="large">
- {{ getStatusText(instance.status) }}
- </el-tag>
- </template>
- </el-statistic>
- </el-card>
- </el-col>
- <el-col :xs="12" :sm="6">
- <el-card shadow="hover" class="status-card">
- <el-statistic title="杩炴帴瀹㈡埛绔�" :value="instance.clientCount">
- <template #suffix>
- <el-icon><User /></el-icon>
- </template>
- </el-statistic>
- </el-card>
- </el-col>
- <el-col :xs="12" :sm="6">
- <el-card shadow="hover" class="status-card">
- <el-statistic title="鎬昏姹傛暟" :value="instance.totalRequests" />
- </el-card>
- </el-col>
- <el-col :xs="12" :sm="6">
- <el-card shadow="hover" class="status-card">
- <el-statistic title="绔彛" :value="instance.port" />
- </el-card>
- </el-col>
- </el-row>
+ <section class="section-block">
+ <div class="section-head">
+ <div>
+ <h3 class="section-title">淇℃伅鍖�</h3>
+ <p class="section-desc">瀹炰緥杩愯鐘舵�佷笌杩炴帴姒傝</p>
+ </div>
+ </div>
+ <div class="section-body">
+ <el-row :gutter="12" class="status-cards">
+ <el-col :xs="12" :sm="6">
+ <el-card shadow="hover" class="status-card panel-card">
+ <el-statistic title="鐘舵��">
+ <template #default>
+ <el-tag :type="getStatusTagType(instance.status)" size="large">
+ {{ getStatusText(instance.status) }}
+ </el-tag>
+ </template>
+ </el-statistic>
+ </el-card>
+ </el-col>
+ <el-col :xs="12" :sm="6">
+ <el-card shadow="hover" class="status-card panel-card">
+ <el-statistic title="杩炴帴瀹㈡埛绔�" :value="instance.clientCount">
+ <template #suffix>
+ <el-icon><User /></el-icon>
+ </template>
+ </el-statistic>
+ </el-card>
+ </el-col>
+ <el-col :xs="12" :sm="6">
+ <el-card shadow="hover" class="status-card panel-card">
+ <el-statistic title="鎬昏姹傛暟" :value="instance.totalRequests" />
+ </el-card>
+ </el-col>
+ <el-col :xs="12" :sm="6">
+ <el-card shadow="hover" class="status-card panel-card">
+ <el-statistic title="绔彛" :value="instance.port" />
+ </el-card>
+ </el-col>
+ </el-row>
+ </div>
+ </section>
- <el-card class="mt-4" shadow="never">
+ <section class="section-block detail-section">
+ <div class="section-head">
+ <div>
+ <h3 class="section-title">鎿嶄綔鍖�</h3>
+ <p class="section-desc">宸︿晶瀹炰緥淇℃伅涓庢搷浣滐紝鍙充晶瀹炴椂 DB 鏁版嵁</p>
+ </div>
+ </div>
+ <div class="section-body">
+ <el-row :gutter="16" class="detail-main">
+ <el-col :xs="24" :lg="8">
+ <el-card class="panel-card" shadow="never">
<template #header>
<span class="card-header-title">鍩烘湰淇℃伅</span>
</template>
- <el-descriptions :column="2" border>
+ <el-descriptions :column="1" border>
<el-descriptions-item label="瀹炰緥ID">{{ instance.instanceId }}</el-descriptions-item>
<el-descriptions-item label="瀹炰緥鍚嶇О">{{ instance.name }}</el-descriptions-item>
<el-descriptions-item label="PLC鍨嬪彿">{{ getPlcTypeText(instance.plcType) }}</el-descriptions-item>
@@ -79,17 +100,18 @@
<el-descriptions-item v-if="instance.lastActivityTime" label="鏈�鍚庢椿鍔ㄦ椂闂�">
{{ formatDate(instance.lastActivityTime) }}
</el-descriptions-item>
- <el-descriptions-item v-if="instance.errorMessage" label="閿欒淇℃伅" :span="2">
+ <el-descriptions-item v-if="instance.errorMessage" label="閿欒淇℃伅">
<el-text type="danger">{{ instance.errorMessage }}</el-text>
</el-descriptions-item>
</el-descriptions>
</el-card>
- <el-card class="mt-4" shadow="never">
+ <el-card class="panel-card left-actions" shadow="never">
<div class="action-buttons">
<el-button
v-if="instance.status === 'Stopped' || instance.status === 'Error'"
type="success"
+
@click="handleStart"
>
<el-icon><VideoPlay /></el-icon>
@@ -98,6 +120,7 @@
<el-button
v-if="instance.status === 'Running'"
type="warning"
+
@click="handleStop"
>
<el-icon><VideoPause /></el-icon>
@@ -114,13 +137,15 @@
</div>
</el-card>
- <el-card class="mt-4" shadow="never">
+ </el-col>
+ <el-col :xs="24" :lg="16">
+ <el-card class="panel-card" shadow="never">
<template #header>
<div class="db-header">
<span class="card-header-title">DB鍧楀疄鏃舵暟鎹�</span>
<div class="db-toolbar">
<el-switch v-model="autoRefreshDb" active-text="鑷姩鍒锋柊" />
- <el-button size="small" @click="loadMemoryData(true)">鎵嬪姩鍒锋柊</el-button>
+ <el-button @click="loadMemoryData(true)">鎵嬪姩鍒锋柊</el-button>
</div>
</div>
</template>
@@ -131,7 +156,7 @@
<template #default>
<div v-if="deviceDbViews.length === 0" class="text-muted">褰撳墠璁惧妯℃澘鏈尮閰嶅埌鍙樉绀虹殑DB鍧�</div>
<div v-else>
- <el-tabs type="border-card" class="db-tabs">
+ <el-tabs type="border-card" class="db-tabs">
<el-tab-pane
v-for="view in deviceDbViews"
:key="view.templateDbNumber"
@@ -149,25 +174,27 @@
:key="`${view.templateDbNumber}-${group.key}`"
>
<template #label>
- <el-tag :type="getFieldGroupTagType(group.key)" size="small">{{ group.key }}</el-tag>
+ <el-tag :type="getFieldGroupTagType(group.key)">{{ group.key }}</el-tag>
</template>
+ <div class="field-table-wrap">
<el-table
:data="group.fields"
border
- size="small"
+ class="field-table"
+ table-layout="auto"
empty-text="褰撳墠鍒嗙粍鏃犲瓧娈垫槧灏�"
>
- <el-table-column prop="fieldKey" label="瀛楁" min-width="140" />
- <el-table-column prop="address" label="鍦板潃" width="130" />
- <el-table-column prop="mappedDb" label="鏄犲皠鍧�" width="120">
+ <el-table-column prop="fieldKey" label="瀛楁" />
+ <el-table-column prop="address" label="鍦板潃" />
+ <el-table-column prop="mappedDb" label="鏄犲皠鍧�">
<template #default="{ row }">
- <el-tag :type="getDbTagType(row.mappedDb)" size="small">{{ row.mappedDb }}</el-tag>
+ <el-tag :type="getDbTagType(row.mappedDb)">{{ row.mappedDb }}</el-tag>
</template>
</el-table-column>
- <el-table-column prop="dataType" label="绫诲瀷" width="90" />
- <el-table-column prop="direction" label="鏂瑰悜" width="130" />
- <el-table-column prop="value" label="褰撳墠鍊�" min-width="220" />
- <el-table-column label="淇敼鍊�" min-width="220">
+ <el-table-column prop="dataType" label="绫诲瀷" />
+ <el-table-column prop="direction" label="鏂瑰悜" />
+ <el-table-column prop="value" label="褰撳墠鍊�" />
+ <el-table-column label="淇敼鍊�">
<template #default="{ row }">
<el-switch
v-if="row.dataType === 'Bool'"
@@ -181,17 +208,18 @@
@change="markFieldDirty(row)"
:disabled="!isFieldWritable(row)"
:controls="false"
- style="width: 100%"
+ class="editable-control"
/>
<el-input
v-else
v-model="fieldEditValues[getFieldEditKey(row)]"
@input="markFieldDirty(row)"
:disabled="!isFieldWritable(row)"
+ class="editable-control"
/>
</template>
</el-table-column>
- <el-table-column label="鎿嶄綔" width="90" fixed="right">
+ <el-table-column label="鎿嶄綔" width="88" fixed="right">
<template #default="{ row }">
<el-button
type="primary"
@@ -205,26 +233,29 @@
</template>
</el-table-column>
</el-table>
+ </div>
</el-tab-pane>
</el-tabs>
+ <template v-else-if="view.fields.length > 0">
+ <div class="field-table-wrap">
<el-table
- v-else-if="view.fields.length > 0"
:data="view.fields"
border
- size="small"
+ class="field-table"
+ table-layout="auto"
empty-text="褰撳墠DB鍧楁棤瀛楁鏄犲皠"
>
- <el-table-column prop="fieldKey" label="瀛楁" min-width="140" />
- <el-table-column prop="address" label="鍦板潃" width="130" />
- <el-table-column prop="mappedDb" label="鏄犲皠鍧�" width="120">
+ <el-table-column prop="fieldKey" label="瀛楁" />
+ <el-table-column prop="address" label="鍦板潃" />
+ <el-table-column prop="mappedDb" label="鏄犲皠鍧�">
<template #default="{ row }">
- <el-tag :type="getDbTagType(row.mappedDb)" size="small">{{ row.mappedDb }}</el-tag>
+ <el-tag :type="getDbTagType(row.mappedDb)">{{ row.mappedDb }}</el-tag>
</template>
</el-table-column>
- <el-table-column prop="dataType" label="绫诲瀷" width="90" />
- <el-table-column prop="direction" label="鏂瑰悜" width="130" />
- <el-table-column prop="value" label="褰撳墠鍊�" min-width="220" />
- <el-table-column label="淇敼鍊�" min-width="220">
+ <el-table-column prop="dataType" label="绫诲瀷" />
+ <el-table-column prop="direction" label="鏂瑰悜" />
+ <el-table-column prop="value" label="褰撳墠鍊�" />
+ <el-table-column label="淇敼鍊�">
<template #default="{ row }">
<el-switch
v-if="row.dataType === 'Bool'"
@@ -238,17 +269,18 @@
@change="markFieldDirty(row)"
:disabled="!isFieldWritable(row)"
:controls="false"
- style="width: 100%"
+ class="editable-control"
/>
<el-input
v-else
v-model="fieldEditValues[getFieldEditKey(row)]"
@input="markFieldDirty(row)"
:disabled="!isFieldWritable(row)"
+ class="editable-control"
/>
</template>
</el-table-column>
- <el-table-column label="鎿嶄綔" width="90" fixed="right">
+ <el-table-column label="鎿嶄綔" width="88" fixed="right">
<template #default="{ row }">
<el-button
type="primary"
@@ -262,6 +294,8 @@
</template>
</el-table-column>
</el-table>
+ </div>
+ </template>
<div v-else class="text-muted">褰撳墠DB鍧楁棤瀛楁鏄犲皠</div>
<div class="card-header-title field-title">鍘熷鏁版嵁</div>
@@ -300,6 +334,10 @@
/>
</div>
</el-card>
+ </el-col>
+ </el-row>
+ </div>
+ </section>
</div>
</div>
</template>
@@ -1065,48 +1103,8 @@
</script>
<style scoped>
-.page-header {
- display: flex;
- justify-content: space-between;
- align-items: flex-start;
- margin-bottom: 20px;
- flex-wrap: wrap;
- gap: 16px;
-}
-
-.header-left h2 {
- display: flex;
- align-items: center;
- gap: 8px;
- margin: 0 0 8px 0;
-}
-
-.text-muted {
- color: #909399;
- margin: 0;
-}
-
-.loading-container {
- text-align: center;
- padding: 60px 0;
- color: #909399;
-}
-
-.loading-icon {
- animation: spin 1s linear infinite;
-}
-
-@keyframes spin {
- from {
- transform: rotate(0deg);
- }
- to {
- transform: rotate(360deg);
- }
-}
-
.status-cards {
- margin-bottom: 20px;
+ margin-bottom: 8px;
}
.status-card {
@@ -1119,13 +1117,17 @@
}
.mt-4 {
- margin-top: 16px;
+ margin-top: 14px;
}
.action-buttons {
display: flex;
- gap: 12px;
+ gap: 10px;
flex-wrap: wrap;
+}
+
+.left-actions {
+ margin-top: 14px;
}
.db-header {
@@ -1142,7 +1144,7 @@
.db-content {
margin: 0;
- max-height: 180px;
+ max-height: 168px;
overflow: auto;
white-space: pre-wrap;
font-family: Consolas, Monaco, 'Courier New', monospace;
@@ -1168,11 +1170,11 @@
}
.db-tabs {
- margin-top: 8px;
+ margin-top: 10px;
}
.data-view-tabs {
- margin-top: 8px;
+ margin-top: 10px;
}
.db-raw-toolbar {
@@ -1189,4 +1191,35 @@
margin-top: 12px;
margin-bottom: 8px;
}
+
+.field-table-wrap {
+ width: 100%;
+ overflow-x: auto;
+}
+
+:deep(.field-table) {
+ width: max-content;
+ min-width: 100%;
+}
+
+:deep(.editable-control) {
+ width: 100%;
+}
+
+:deep(.status-card .el-card__body) {
+ padding: 14px 16px;
+}
+
+:deep(.action-buttons .el-button) {
+ min-width: 82px;
+}
+
+:deep(.panel-card > .el-card__header) {
+ padding: 14px 18px;
+}
+
+:deep(.panel-card > .el-card__body) {
+ padding: 14px 18px;
+}
</style>
+
diff --git a/Code/WCS/WIDESEAWCS_S7Simulator/WIDESEAWCS_S7Simulator.Web/src/views/EditView.vue b/Code/WCS/WIDESEAWCS_S7Simulator/WIDESEAWCS_S7Simulator.Web/src/views/EditView.vue
index cf81d0f..ecc26b1 100644
--- a/Code/WCS/WIDESEAWCS_S7Simulator/WIDESEAWCS_S7Simulator.Web/src/views/EditView.vue
+++ b/Code/WCS/WIDESEAWCS_S7Simulator/WIDESEAWCS_S7Simulator.Web/src/views/EditView.vue
@@ -1,5 +1,5 @@
锘�<template>
- <div>
+ <div class="admin-page">
<div v-if="loading" class="loading-container">
<el-icon class="loading-icon" :size="40"><Loading /></el-icon>
<p>鍔犺浇涓�...</p>
@@ -40,7 +40,7 @@
<el-row justify="center">
<el-col :lg="24">
- <el-card shadow="never">
+ <el-card shadow="never" class="panel-card">
<el-form :model="form" :rules="rules" ref="formRef" label-width="120px">
<!-- 鍩烘湰淇℃伅 -->
<el-divider content-position="left">
@@ -385,46 +385,6 @@
</script>
<style scoped>
-.page-header {
- display: flex;
- justify-content: space-between;
- align-items: flex-start;
- margin-bottom: 20px;
- flex-wrap: wrap;
- gap: 16px;
-}
-
-.header-left h2 {
- display: flex;
- align-items: center;
- gap: 8px;
- margin: 0 0 8px 0;
-}
-
-.text-muted {
- color: #909399;
- margin: 0;
-}
-
-.loading-container {
- text-align: center;
- padding: 60px 0;
- color: #909399;
-}
-
-.loading-icon {
- animation: spin 1s linear infinite;
-}
-
-@keyframes spin {
- from {
- transform: rotate(0deg);
- }
- to {
- transform: rotate(360deg);
- }
-}
-
.form-tip {
font-size: 12px;
color: #909399;
diff --git a/Code/WCS/WIDESEAWCS_S7Simulator/WIDESEAWCS_S7Simulator.Web/src/views/HomeView.vue b/Code/WCS/WIDESEAWCS_S7Simulator/WIDESEAWCS_S7Simulator.Web/src/views/HomeView.vue
index e16d086..06cb505 100644
--- a/Code/WCS/WIDESEAWCS_S7Simulator/WIDESEAWCS_S7Simulator.Web/src/views/HomeView.vue
+++ b/Code/WCS/WIDESEAWCS_S7Simulator/WIDESEAWCS_S7Simulator.Web/src/views/HomeView.vue
@@ -1,5 +1,6 @@
锘�<template>
<div
+ class="admin-page"
v-loading.fullscreen.lock="startActionLoading"
element-loading-text="姝e湪鍚姩瀹炰緥锛岃绋嶅��..."
>
@@ -12,16 +13,43 @@
<p class="text-muted">绠$悊鍜岀洃鎺� S7 PLC 妯℃嫙鍣ㄥ疄渚�</p>
</div>
<div class="header-right">
- <div class="stats">
- <span>杩愯涓�: {{ runningCount }} | 宸插仠姝�: {{ stoppedCount }}</span>
- <span v-if="errorCount > 0" class="error-text">| 閿欒: {{ errorCount }}</span>
- </div>
- <el-button type="primary" @click="$router.push('/create')">
+ <el-button type="primary" class="create-btn" @click="$router.push('/create')">
<el-icon><Plus /></el-icon>
鍒涘缓瀹炰緥
</el-button>
</div>
</div>
+
+ <section class="section-block">
+ <div class="section-head">
+ <div>
+ <h3 class="section-title">淇℃伅鍖�</h3>
+ <p class="section-desc">瀹炰緥杩愯鐘舵�佹�昏</p>
+ </div>
+ </div>
+ <div class="section-body">
+ <el-row :gutter="12" class="summary-row">
+ <el-col :xs="24" :sm="8">
+ <el-card shadow="hover" class="summary-card running-card">
+ <div class="summary-title">杩愯涓�</div>
+ <div class="summary-value">{{ runningCount }}</div>
+ </el-card>
+ </el-col>
+ <el-col :xs="24" :sm="8">
+ <el-card shadow="hover" class="summary-card stopped-card">
+ <div class="summary-title">宸插仠姝�</div>
+ <div class="summary-value">{{ stoppedCount }}</div>
+ </el-card>
+ </el-col>
+ <el-col :xs="24" :sm="8">
+ <el-card shadow="hover" class="summary-card error-card">
+ <div class="summary-title">閿欒瀹炰緥</div>
+ <div class="summary-value">{{ errorCount }}</div>
+ </el-card>
+ </el-col>
+ </el-row>
+ </div>
+ </section>
<!-- Loading state -->
<div v-if="loading && instances.length === 0" class="loading-container">
@@ -35,45 +63,75 @@
</el-empty>
<!-- Instances grid -->
- <el-row v-else :gutter="20">
- <el-col v-for="instance in instances" :key="instance.instanceId" :xs="24" :sm="12" :xl="8">
- <el-card class="instance-card" :class="getStatusClass(instance.status)" shadow="hover">
- <template #header>
- <div class="card-header">
- <span class="instance-id">{{ instance.instanceId }}</span>
- <el-tag :type="getStatusTagType(instance.status)">
- {{ getStatusText(instance.status) }}
- </el-tag>
+ <section v-else class="section-block">
+ <div class="section-head">
+ <div>
+ <h3 class="section-title">鎿嶄綔鍖�</h3>
+ <p class="section-desc">瀹炰緥鍚姩銆佸仠姝€�佺紪杈戜笌璇︽儏鍏ュ彛</p>
+ </div>
+ </div>
+ <div class="section-body">
+ <el-row :gutter="14" class="instances-grid">
+ <el-col
+ v-for="instance in instances"
+ :key="instance.instanceId"
+ :xs="24"
+ :sm="12"
+ :md="12"
+ :lg="8"
+ :xl="6"
+ >
+ <el-card class="instance-card panel-card" :class="getStatusClass(instance.status)" shadow="hover">
+ <div class="card-glow"></div>
+
+ <div class="card-top">
+ <div class="card-title">
+ <div class="instance-id-line">
+ <span class="instance-id">{{ instance.instanceId }}</span>
+ <span class="instance-name">{{ instance.name || '鏈懡鍚嶅疄渚�' }}</span>
+ <span class="instance-sub">PLC</span>
+ </div>
</div>
- </template>
-
- <div class="instance-info">
- <el-descriptions :column="2" size="small">
- <el-descriptions-item label="鍚嶇О">{{ instance.name || '-' }}</el-descriptions-item>
- <el-descriptions-item label="PLC鍨嬪彿">{{ getPlcTypeText(instance.plcType) }}</el-descriptions-item>
- <el-descriptions-item label="绔彛">{{ instance.port || '-' }}</el-descriptions-item>
- <el-descriptions-item label="瀹㈡埛绔�">
- <el-icon><User /></el-icon>
- {{ instance.clientCount || 0 }}
- </el-descriptions-item>
- <el-descriptions-item v-if="instance.startTime" label="鍚姩鏃堕棿" :span="2">
- {{ formatDate(instance.startTime) }}
- </el-descriptions-item>
- </el-descriptions>
-
- <el-alert
- v-if="instance.errorMessage"
- type="error"
- :closable="false"
- class="mt-2"
- show-icon
- >
- {{ instance.errorMessage }}
- </el-alert>
+ <el-tag :type="getStatusTagType(instance.status)" effect="dark" round>
+ {{ getStatusText(instance.status) }}
+ </el-tag>
</div>
- <template #footer>
- <div class="card-footer">
+ <div class="meta-row">
+ <div class="meta-chip">
+ <span class="chip-label">PLC</span>
+ <span class="chip-value">{{ getPlcTypeText(instance.plcType) }}</span>
+ </div>
+ <div class="meta-chip">
+ <span class="chip-label">绔彛</span>
+ <span class="chip-value">{{ instance.port || '-' }}</span>
+ </div>
+ <div class="meta-chip">
+ <span class="chip-label">瀹㈡埛绔�</span>
+ <span class="chip-value">
+ <el-icon><User /></el-icon>
+ {{ instance.clientCount || 0 }}
+ </span>
+ </div>
+ </div>
+
+ <div class="time-row">
+ <span class="time-label">鍚姩鏃堕棿</span>
+ <span class="time-value">{{ instance.startTime ? formatDate(instance.startTime) : '-' }}</span>
+ </div>
+
+ <el-alert
+ v-if="instance.errorMessage"
+ type="error"
+ :closable="false"
+ class="mt-2"
+ show-icon
+ >
+ {{ instance.errorMessage }}
+ </el-alert>
+
+ <div class="card-footer">
+ <div class="main-actions">
<el-button
v-if="instance.status === 'Running'"
type="warning"
@@ -98,14 +156,16 @@
<el-icon><Edit /></el-icon>
缂栬緫
</el-button>
- <el-button type="danger" @click="handleDelete(instance.instanceId)">
- <el-icon><Delete /></el-icon>
- </el-button>
</div>
- </template>
- </el-card>
- </el-col>
- </el-row>
+ <el-button class="delete-btn" type="danger" @click="handleDelete(instance.instanceId)">
+ <el-icon><Delete /></el-icon>
+ </el-button>
+ </div>
+ </el-card>
+ </el-col>
+ </el-row>
+ </div>
+ </section>
</div>
</template>
@@ -246,106 +306,220 @@
</script>
<style scoped>
-.page-header {
- display: flex;
- justify-content: space-between;
- align-items: flex-start;
- margin-bottom: 20px;
- flex-wrap: wrap;
- gap: 16px;
-}
-
.header-left h2 {
- display: flex;
- align-items: center;
- gap: 8px;
- margin: 0 0 8px 0;
-}
-
-.text-muted {
- color: #909399;
margin: 0;
}
.header-right {
display: flex;
align-items: center;
- gap: 16px;
+ gap: 10px;
flex-wrap: wrap;
}
-.stats {
- color: #606266;
- font-size: 14px;
+.create-btn {
+ padding-inline: 18px;
}
-.error-text {
- color: #f56c6c;
+.summary-row {
+ margin-top: -2px;
+ margin-bottom: 2px;
}
-.loading-container {
- text-align: center;
- padding: 60px 0;
- color: #909399;
+.instances-grid {
+ max-width: 1500px;
}
-.loading-icon {
- animation: spin 1s linear infinite;
+.summary-card {
+ border-radius: 12px;
+ border: 1px solid #dbe2ea;
+ overflow: hidden;
}
-@keyframes spin {
- from {
- transform: rotate(0deg);
- }
- to {
- transform: rotate(360deg);
- }
+.summary-title {
+ color: #64748b;
+ font-size: 13px;
+ margin-bottom: 6px;
+}
+
+.summary-value {
+ font-size: 28px;
+ font-weight: 700;
+ line-height: 1;
+}
+
+.running-card {
+ background: linear-gradient(180deg, #f0fdf4 0%, #ffffff 100%);
+}
+
+.running-card .summary-value {
+ color: #15803d;
+}
+
+.stopped-card {
+ background: linear-gradient(180deg, #f8fafc 0%, #ffffff 100%);
+}
+
+.stopped-card .summary-value {
+ color: #334155;
+}
+
+.error-card {
+ background: linear-gradient(180deg, #fef2f2 0%, #ffffff 100%);
+}
+
+.error-card .summary-value {
+ color: #b91c1c;
}
.instance-card {
- margin-bottom: 20px;
- transition: all 0.3s;
+ margin-bottom: 10px;
+ position: relative;
+ border-radius: 12px;
+ border: 1px solid #dbe2ea;
+ transition: all 0.25s;
+ overflow: hidden;
}
.instance-card:hover {
- transform: translateY(-4px);
+ transform: translateY(-3px);
+ box-shadow: 0 14px 26px rgba(15, 23, 42, 0.1);
}
-.card-header {
+.card-glow {
+ position: absolute;
+ top: -56px;
+ right: -56px;
+ width: 100px;
+ height: 100px;
+ border-radius: 50%;
+ background: radial-gradient(circle, rgba(14, 116, 244, 0.16), transparent 70%);
+ pointer-events: none;
+}
+
+.card-title {
display: flex;
- justify-content: space-between;
- align-items: center;
+ flex-direction: column;
+ gap: 1px;
+ min-width: 0;
}
.instance-id {
font-weight: 600;
- font-size: 16px;
+ font-size: 15px;
+ color: #0f172a;
+ white-space: nowrap;
}
-.instance-info {
- margin-bottom: 16px;
+.instance-name {
+ color: #64748b;
+ font-size: 12px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
}
-.card-footer {
+.instance-id-line {
display: flex;
+ align-items: baseline;
+ gap: 6px;
+ min-width: 0;
+}
+
+.instance-sub {
+ color: #94a3b8;
+ font-size: 10px;
+}
+
+.card-top {
+ margin-top: 2px;
+ display: flex;
+ justify-content: space-between;
gap: 8px;
+ align-items: flex-start;
+}
+
+.meta-row {
+ margin-top: 8px;
+ display: flex;
+ gap: 6px;
flex-wrap: wrap;
}
-.card-footer .el-button {
- flex: 1;
- min-width: 60px;
+.meta-chip {
+ border: 1px solid #e2e8f0;
+ border-radius: 999px;
+ padding: 4px 8px;
+ background: #f8fafc;
+ display: inline-flex;
+ align-items: center;
+ gap: 6px;
}
-.status-stopped { border-left: 4px solid #909399; }
-.status-starting { border-left: 4px solid #409eff; }
-.status-running { border-left: 4px solid #67c23a; }
-.status-stopping { border-left: 4px solid #e6a23c; }
-.status-error { border-left: 4px solid #f56c6c; }
+.chip-label {
+ font-size: 10px;
+ line-height: 1;
+ color: #64748b;
+ padding: 1px 5px;
+ border-radius: 999px;
+ background: #e2e8f0;
+}
+
+.chip-value {
+ font-size: 12px;
+ line-height: 1;
+ color: #0f172a;
+ display: inline-flex;
+ align-items: center;
+ gap: 4px;
+}
+
+.time-row {
+ margin-top: 7px;
+ margin-bottom: 8px;
+ display: flex;
+ justify-content: space-between;
+ gap: 8px;
+ font-size: 11px;
+}
+
+.time-label {
+ color: #64748b;
+}
+
+.time-value {
+ color: #0f172a;
+}
+
+.card-footer {
+ border-top: 1px dashed #dbe2ea;
+ padding-top: 8px;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ gap: 10px;
+}
+
+.main-actions {
+ display: flex;
+ gap: 6px;
+ flex-wrap: wrap;
+}
+
+.main-actions .el-button {
+ border-radius: 8px;
+ height: 28px;
+ padding: 0 10px;
+}
+
+.delete-btn {
+ border-radius: 8px;
+ height: 28px;
+ padding: 0 10px;
+}
.mt-2 {
margin-top: 8px;
}
</style>
-
diff --git a/Code/WCS/WIDESEAWCS_S7Simulator/WIDESEAWCS_S7Simulator.Web/src/views/ProtocolTemplatesView.vue b/Code/WCS/WIDESEAWCS_S7Simulator/WIDESEAWCS_S7Simulator.Web/src/views/ProtocolTemplatesView.vue
index 75f038f..be2a12f 100644
--- a/Code/WCS/WIDESEAWCS_S7Simulator/WIDESEAWCS_S7Simulator.Web/src/views/ProtocolTemplatesView.vue
+++ b/Code/WCS/WIDESEAWCS_S7Simulator/WIDESEAWCS_S7Simulator.Web/src/views/ProtocolTemplatesView.vue
@@ -1,5 +1,5 @@
锘�<template>
- <div>
+ <div class="admin-page">
<div class="page-header">
<div>
<h2>鍗忚妯℃澘绠$悊</h2>
@@ -8,7 +8,8 @@
<el-button type="primary" @click="openNewTemplate">鏂板缓妯℃澘</el-button>
</div>
- <el-table :data="templates" border>
+ <el-card shadow="never" class="panel-card">
+ <el-table :data="templates" border>
<el-table-column prop="id" label="妯℃澘ID" width="240" />
<el-table-column prop="name" label="鍚嶇О" min-width="240" />
<el-table-column prop="version" label="鐗堟湰" width="140" />
@@ -21,7 +22,8 @@
<el-button size="small" type="danger" @click="removeTemplate(row.id)">鍒犻櫎</el-button>
</template>
</el-table-column>
- </el-table>
+ </el-table>
+ </el-card>
<el-dialog v-model="dialogVisible" title="鍗忚妯℃澘" width="96vw" top="2vh" class="protocol-dialog">
<el-form :model="editing" label-width="100px">
@@ -187,18 +189,6 @@
</script>
<style scoped>
-.page-header {
- display: flex;
- justify-content: space-between;
- align-items: flex-start;
- margin-bottom: 16px;
-}
-
-.text-muted {
- color: #909399;
- margin: 4px 0 0 0;
-}
-
.mt-2 {
margin-top: 12px;
}
diff --git a/Code/WCS/WIDESEAWCS_S7Simulator/WIDESEAWCS_S7Simulator.Web/src/views/RobotClientsView.vue b/Code/WCS/WIDESEAWCS_S7Simulator/WIDESEAWCS_S7Simulator.Web/src/views/RobotClientsView.vue
new file mode 100644
index 0000000..693aa25
--- /dev/null
+++ b/Code/WCS/WIDESEAWCS_S7Simulator/WIDESEAWCS_S7Simulator.Web/src/views/RobotClientsView.vue
@@ -0,0 +1,452 @@
+锘�<template>
+ <div class="admin-page robot-admin-page">
+ <div class="page-header">
+ <div>
+ <h2>鏈烘鎵嬪鎴风绠$悊鍙�</h2>
+ <p class="text-muted">澶氬疄渚嬭繛鎺ョ洰鏍囨湇鍔$銆佹秷鎭敹鍙戠洃鎺�</p>
+ </div>
+ <div class="toolbar-actions">
+ <el-button :loading="refreshing" @click="loadStatus">鍒锋柊</el-button>
+ <el-button type="danger" :loading="stoppingAll" @click="handleStopAll">鍋滄鍏ㄩ儴</el-button>
+ </div>
+ </div>
+
+ <el-row :gutter="12" class="stats-row">
+ <el-col :xs="24" :sm="8">
+ <el-card shadow="hover" class="stat-card">
+ <el-statistic title="杩愯瀹炰緥" :value="status?.runningServerCount ?? 0" />
+ </el-card>
+ </el-col>
+ <el-col :xs="24" :sm="8">
+ <el-card shadow="hover" class="stat-card">
+ <el-statistic title="瀹炰緥鎬绘暟" :value="status?.servers.length ?? 0" />
+ </el-card>
+ </el-col>
+ <el-col :xs="24" :sm="8">
+ <el-card shadow="hover" class="stat-card">
+ <el-statistic title="鍦ㄧ嚎杩炴帴鎬绘暟" :value="totalConnectedCount" />
+ </el-card>
+ </el-col>
+ </el-row>
+
+ <el-card shadow="never" class="create-card">
+ <template #header>
+ <span>鏂板瀹㈡埛绔疄渚�</span>
+ </template>
+ <el-form :inline="true" :model="startForm" class="create-form">
+ <el-form-item label="瀹炰緥ID">
+ <el-input v-model="startForm.serverId" placeholder="robot-client-1" style="width: 180px" />
+ </el-form-item>
+ <el-form-item label="鏈嶅姟绔湴鍧�">
+ <el-input v-model="startForm.listenIp" placeholder="127.0.0.1" style="width: 160px" />
+ </el-form-item>
+ <el-form-item label="鏈嶅姟绔鍙�">
+ <el-input-number v-model="startForm.listenPort" :min="1" :max="65535" />
+ </el-form-item>
+ <el-form-item label="鏈湴绔彛">
+ <el-input-number v-model="startForm.localPort" :min="1" :max="65535" />
+ </el-form-item>
+ <el-form-item>
+ <el-button type="primary" :loading="starting" @click="handleStart">杩炴帴瀹炰緥</el-button>
+ </el-form-item>
+ </el-form>
+ </el-card>
+
+ <el-row :gutter="12" class="main-row">
+ <el-col :xs="24" :lg="8">
+ <el-card shadow="never" class="panel-card">
+ <template #header>
+ <span>瀹㈡埛绔疄渚嬪垪琛�</span>
+ </template>
+ <el-table
+ :data="status?.servers || []"
+ border
+ size="small"
+ highlight-current-row
+ :row-class-name="serverRowClassName"
+ @row-click="onServerRowClick"
+ >
+ <el-table-column prop="serverId" label="瀹炰緥ID" min-width="120" />
+ <el-table-column label="鐩爣鏈嶅姟绔�" min-width="130">
+ <template #default="{ row }">{{ row.listenIp }}:{{ row.listenPort }}</template>
+ </el-table-column>
+ <el-table-column prop="localPort" label="鏈湴绔彛" width="100" />
+ <el-table-column prop="connectedCount" label="杩炴帴" width="70" />
+ <el-table-column label="鎿嶄綔" width="90">
+ <template #default="{ row }">
+ <el-button link type="danger" @click.stop="handleStopOne(row.serverId)">鍋滄</el-button>
+ </template>
+ </el-table-column>
+ </el-table>
+ </el-card>
+ </el-col>
+
+ <el-col :xs="24" :lg="16">
+ <el-card v-if="selectedServer" shadow="never" class="panel-card">
+ <template #header>
+ <div class="panel-header">
+ <span>瀹炰緥璇︽儏锛歿{ selectedServer.serverId }}</span>
+ <el-tag :type="selectedServer.running ? 'success' : 'info'">
+ {{ selectedServer.running ? '杩愯涓�' : '宸插仠姝�' }}
+ </el-tag>
+ </div>
+ </template>
+
+ <el-descriptions :column="4" border class="desc-block">
+ <el-descriptions-item label="鏈嶅姟绔湴鍧�">{{ selectedServer.listenIp }}</el-descriptions-item>
+ <el-descriptions-item label="鏈嶅姟绔鍙�">{{ selectedServer.listenPort }}</el-descriptions-item>
+ <el-descriptions-item label="鏈湴绔彛">{{ selectedServer.localPort }}</el-descriptions-item>
+ <el-descriptions-item label="杩炴帴鐘舵��">{{ selectedServer.connectedCount > 0 ? '宸茶繛鎺�' : '鏈繛鎺�' }}</el-descriptions-item>
+ </el-descriptions>
+
+ <div class="message-actions">
+ <el-button type="primary" @click="openMessageCenter">
+ 娑堟伅涓績
+ </el-button>
+ </div>
+
+ <el-card shadow="never" class="connection-card">
+ <template #header>
+ <span>杩炴帴鍒楄〃</span>
+ </template>
+ <el-table :data="selectedServer.clients || []" border size="small">
+ <el-table-column prop="clientId" label="杩炴帴ID" width="100" />
+ <el-table-column prop="remoteEndPoint" label="杩滅鍦板潃" min-width="170" />
+ <el-table-column label="鐘舵��" width="100">
+ <template #default="{ row }">
+ <el-tag :type="row.connected ? 'success' : 'danger'">{{ row.connected ? '鍦ㄧ嚎' : '绂荤嚎' }}</el-tag>
+ </template>
+ </el-table-column>
+ <el-table-column prop="connectedAt" label="杩炴帴鏃堕棿" min-width="170" />
+ <el-table-column prop="lastReceivedAt" label="鏈�杩戞帴鏀�" min-width="170" />
+ <el-table-column prop="lastSentAt" label="鏈�杩戝彂閫�" min-width="170" />
+ <el-table-column prop="lastError" label="鏈�鍚庨敊璇�" min-width="190" />
+ </el-table>
+ </el-card>
+ </el-card>
+
+ <el-empty v-else description="璇烽�夋嫨宸︿晶瀹㈡埛绔疄渚嬫煡鐪嬭鎯�" />
+ </el-col>
+ </el-row>
+
+ <el-dialog
+ v-model="messageCenterVisible"
+ title="娑堟伅涓績"
+ width="78vw"
+ destroy-on-close
+ >
+ <el-card shadow="never" class="message-send-card">
+ <template #header>
+ <span>鍙戦�佹寚浠�</span>
+ </template>
+ <div class="send-row">
+ <el-input v-model="sendMessage" placeholder="渚嬪 Pickbattery,1" />
+ <el-button type="primary" :loading="sending" @click="handleSend">鍙戦��</el-button>
+ </div>
+ </el-card>
+
+ <el-tabs v-model="messageTab">
+ <el-tab-pane
+ :label="`鎺ユ敹娑堟伅 (${selectedServer?.receivedMessages?.length || 0})`"
+ name="received"
+ >
+ <div class="message-toolbar">
+ <el-button
+ link
+ type="danger"
+ :loading="clearingMessages"
+ @click="handleClearMessages"
+ >
+ 娓呯┖娑堟伅
+ </el-button>
+ </div>
+ <el-table :data="selectedServer?.receivedMessages || []" border size="small" max-height="430">
+ <el-table-column prop="receivedAt" label="鎺ユ敹鏃堕棿" min-width="170" />
+ <el-table-column prop="clientId" label="杩炴帴ID" width="100" />
+ <el-table-column prop="remoteEndPoint" label="杩滅鍦板潃" min-width="170" />
+ <el-table-column prop="message" label="娑堟伅鍐呭" min-width="250" />
+ </el-table>
+ </el-tab-pane>
+ <el-tab-pane
+ :label="`鍙戦�佹秷鎭� (${selectedServer?.sentMessages?.length || 0})`"
+ name="sent"
+ >
+ <el-table :data="selectedServer?.sentMessages || []" border size="small" max-height="430">
+ <el-table-column prop="sentAt" label="鍙戦�佹椂闂�" min-width="170" />
+ <el-table-column prop="clientId" label="杩炴帴ID" width="100" />
+ <el-table-column prop="remoteEndPoint" label="杩滅鍦板潃" min-width="170" />
+ <el-table-column prop="message" label="娑堟伅鍐呭" min-width="250" />
+ </el-table>
+ </el-tab-pane>
+ </el-tabs>
+ </el-dialog>
+ </div>
+</template>
+
+<script setup lang="ts">
+import { computed, onMounted, onUnmounted, ref } from 'vue'
+import { ElMessage } from 'element-plus'
+import {
+ clearRobotClientReceivedMessages,
+ getRobotClientStatus,
+ sendRobotClientMessage,
+ startRobotClients,
+ stopRobotClients
+} from '../api'
+import type {
+ RobotClientStartRequest,
+ RobotClientStatusResponse,
+ RobotServerStatusItem
+} from '../types'
+
+const startForm = ref<RobotClientStartRequest>({
+ serverId: 'robot-client-1',
+ listenIp: '127.0.0.1',
+ listenPort: 2000,
+ localPort: 2001
+})
+
+const status = ref<RobotClientStatusResponse | null>(null)
+const selectedServerId = ref('')
+
+const refreshing = ref(false)
+const starting = ref(false)
+const stoppingAll = ref(false)
+const sending = ref(false)
+const clearingMessages = ref(false)
+const messageCenterVisible = ref(false)
+const messageTab = ref<'received' | 'sent'>('received')
+
+const sendMessage = ref('')
+
+let timer: number | null = null
+
+const totalConnectedCount = computed(() =>
+ (status.value?.servers || []).reduce((sum, x) => sum + (x.connectedCount || 0), 0)
+)
+
+const selectedServer = computed<RobotServerStatusItem | null>(() => {
+ if (!status.value) return null
+ return status.value.servers.find(x => x.serverId === selectedServerId.value) || null
+})
+
+async function loadStatus() {
+ refreshing.value = true
+ try {
+ status.value = await getRobotClientStatus()
+ if ((status.value.servers || []).length > 0) {
+ const exists = status.value.servers.some(x => x.serverId === selectedServerId.value)
+ if (!exists) {
+ selectedServerId.value = status.value.servers[0].serverId
+ }
+ } else {
+ selectedServerId.value = ''
+ }
+ } catch (error) {
+ console.error(error)
+ } finally {
+ refreshing.value = false
+ }
+}
+
+function onServerRowClick(row: RobotServerStatusItem) {
+ selectedServerId.value = row.serverId
+}
+
+function serverRowClassName(args: { row: RobotServerStatusItem }) {
+ return args.row.serverId === selectedServerId.value ? 'is-selected-row' : ''
+}
+
+function openMessageCenter() {
+ if (!selectedServer.value) {
+ ElMessage.warning('璇峰厛閫夋嫨涓�涓鎴风瀹炰緥')
+ return
+ }
+
+ messageTab.value = 'received'
+ messageCenterVisible.value = true
+}
+
+async function handleStart() {
+ if (!startForm.value.serverId.trim()) {
+ ElMessage.warning('璇疯緭鍏ュ疄渚婭D')
+ return
+ }
+
+ starting.value = true
+ try {
+ status.value = await startRobotClients({
+ ...startForm.value,
+ serverId: startForm.value.serverId.trim()
+ })
+ selectedServerId.value = startForm.value.serverId.trim()
+ ElMessage.success('瀹㈡埛绔疄渚嬭繛鎺ユ垚鍔�')
+ } catch (error) {
+ console.error(error)
+ ElMessage.error('杩炴帴澶辫触锛岃妫�鏌ユ湇鍔$鍦板潃鍜岀鍙�')
+ } finally {
+ starting.value = false
+ }
+}
+
+async function handleStopAll() {
+ stoppingAll.value = true
+ try {
+ await stopRobotClients()
+ await loadStatus()
+ ElMessage.success('鍏ㄩ儴瀹㈡埛绔疄渚嬪凡鍋滄')
+ } catch (error) {
+ console.error(error)
+ ElMessage.error('鍋滄澶辫触锛岃鏌ョ湅鏃ュ織')
+ } finally {
+ stoppingAll.value = false
+ }
+}
+
+async function handleStopOne(serverId: string) {
+ try {
+ await stopRobotClients(serverId)
+ await loadStatus()
+ ElMessage.success(`瀹炰緥 ${serverId} 宸插仠姝)
+ } catch (error) {
+ console.error(error)
+ ElMessage.error('鍋滄瀹炰緥澶辫触')
+ }
+}
+
+async function handleSend() {
+ if (!selectedServer.value) {
+ ElMessage.warning('璇峰厛閫夋嫨涓�涓鎴风瀹炰緥')
+ return
+ }
+
+ if (!sendMessage.value.trim()) {
+ ElMessage.warning('璇疯緭鍏ュ彂閫佸唴瀹�')
+ return
+ }
+
+ sending.value = true
+ try {
+ await sendRobotClientMessage({
+ serverId: selectedServer.value.serverId,
+ clientId: null,
+ message: sendMessage.value.trim()
+ })
+ ElMessage.success('鍙戦�佹垚鍔�')
+ } catch (error) {
+ console.error(error)
+ ElMessage.error('鍙戦�佸け璐ワ紝璇锋鏌ヨ繛鎺ョ姸鎬�')
+ } finally {
+ sending.value = false
+ }
+}
+
+async function handleClearMessages() {
+ if (!selectedServer.value) {
+ ElMessage.warning('璇峰厛閫夋嫨涓�涓鎴风瀹炰緥')
+ return
+ }
+
+ clearingMessages.value = true
+ try {
+ await clearRobotClientReceivedMessages(selectedServer.value.serverId)
+ await loadStatus()
+ ElMessage.success('鎺ユ敹娑堟伅宸叉竻绌�')
+ } catch (error) {
+ console.error(error)
+ ElMessage.error('娓呯┖澶辫触锛岃鏌ョ湅鏃ュ織')
+ } finally {
+ clearingMessages.value = false
+ }
+}
+
+onMounted(async () => {
+ await loadStatus()
+ timer = window.setInterval(() => {
+ loadStatus()
+ }, 2000)
+})
+
+onUnmounted(() => {
+ if (timer !== null) {
+ clearInterval(timer)
+ }
+})
+</script>
+
+<style scoped>
+.robot-admin-page {
+ display: flex;
+ flex-direction: column;
+ gap: 14px;
+}
+
+.toolbar-actions {
+ display: flex;
+ gap: 8px;
+}
+
+.stats-row {
+ margin-bottom: 0;
+}
+
+.stat-card {
+ min-height: 92px;
+}
+
+.create-card,
+.panel-card {
+ border-radius: 8px;
+}
+
+.create-form {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 8px 12px;
+}
+
+.main-row {
+ margin-top: 0;
+}
+
+.panel-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.desc-block {
+ margin-bottom: 12px;
+}
+
+.connection-card {
+ margin-top: 8px;
+}
+
+.message-send-card {
+ margin-bottom: 8px;
+}
+
+.send-row {
+ display: flex;
+ gap: 8px;
+ align-items: center;
+ flex-wrap: wrap;
+}
+
+.message-actions {
+ display: flex;
+ gap: 10px;
+ margin-bottom: 8px;
+}
+
+.message-toolbar {
+ display: flex;
+ justify-content: flex-end;
+ margin-bottom: 6px;
+}
+
+:deep(.is-selected-row td) {
+ background-color: #ecf5ff !important;
+}
+</style>
diff --git a/Code/WCS/WIDESEAWCS_Server/.vs/WIDESEAWCS_Server/v18/DocumentLayout.json b/Code/WCS/WIDESEAWCS_Server/.vs/WIDESEAWCS_Server/v18/DocumentLayout.json
index 3c0868f..cee9aa1 100644
--- a/Code/WCS/WIDESEAWCS_Server/.vs/WIDESEAWCS_Server/v18/DocumentLayout.json
+++ b/Code/WCS/WIDESEAWCS_Server/.vs/WIDESEAWCS_Server/v18/DocumentLayout.json
@@ -3,40 +3,16 @@
"WorkspaceRootPath": "D:\\Git\\ShanMeiXinNengYuan\\Code\\WCS\\WIDESEAWCS_Server\\",
"Documents": [
{
- "AbsoluteMoniker": "D:0:0:{83F18A31-5983-4587-A0B2-414BF70E50B5}|WIDESEAWCS_TaskInfoService\\WIDESEAWCS_TaskInfoService.csproj|d:\\git\\shanmeixinnengyuan\\code\\wcs\\wideseawcs_server\\wideseawcs_taskinfoservice\\taskservice.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
- "RelativeMoniker": "D:0:0:{83F18A31-5983-4587-A0B2-414BF70E50B5}|WIDESEAWCS_TaskInfoService\\WIDESEAWCS_TaskInfoService.csproj|solutionrelative:wideseawcs_taskinfoservice\\taskservice.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
- },
- {
- "AbsoluteMoniker": "D:0:0:{294E4915-0241-4C8C-BA99-7588B945863A}|WIDESEAWCS_Tasks\\WIDESEAWCS_Tasks.csproj|d:\\git\\shanmeixinnengyuan\\code\\wcs\\wideseawcs_server\\wideseawcs_tasks\\conveyorlinenewjob\\commonconveyorlinenewjob.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
- "RelativeMoniker": "D:0:0:{294E4915-0241-4C8C-BA99-7588B945863A}|WIDESEAWCS_Tasks\\WIDESEAWCS_Tasks.csproj|solutionrelative:wideseawcs_tasks\\conveyorlinenewjob\\commonconveyorlinenewjob.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
- },
- {
- "AbsoluteMoniker": "D:0:0:{861C4D0B-A478-48DB-A0FA-AE70F5BA210A}|WIDESEAWCS_Communicator\\WIDESEAWCS_Communicator.csproj|d:\\git\\shanmeixinnengyuan\\code\\wcs\\wideseawcs_server\\wideseawcs_communicator\\siemens\\siemenss7communicator.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
- "RelativeMoniker": "D:0:0:{861C4D0B-A478-48DB-A0FA-AE70F5BA210A}|WIDESEAWCS_Communicator\\WIDESEAWCS_Communicator.csproj|solutionrelative:wideseawcs_communicator\\siemens\\siemenss7communicator.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
- },
- {
- "AbsoluteMoniker": "D:0:0:{294E4915-0241-4C8C-BA99-7588B945863A}|WIDESEAWCS_Tasks\\WIDESEAWCS_Tasks.csproj|d:\\git\\shanmeixinnengyuan\\code\\wcs\\wideseawcs_server\\wideseawcs_tasks\\conveyorlinenewjob\\conveyorlinedispatchhandler.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
- "RelativeMoniker": "D:0:0:{294E4915-0241-4C8C-BA99-7588B945863A}|WIDESEAWCS_Tasks\\WIDESEAWCS_Tasks.csproj|solutionrelative:wideseawcs_tasks\\conveyorlinenewjob\\conveyorlinedispatchhandler.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
- },
- {
- "AbsoluteMoniker": "D:0:0:{6236BFFF-173D-44A8-9FC3-7C001EA30347}|WIDESEAWCS_QuartzJob\\WIDESEAWCS_QuartzJob.csproj|d:\\git\\shanmeixinnengyuan\\code\\wcs\\wideseawcs_server\\wideseawcs_quartzjob\\service\\routerservice.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
- "RelativeMoniker": "D:0:0:{6236BFFF-173D-44A8-9FC3-7C001EA30347}|WIDESEAWCS_QuartzJob\\WIDESEAWCS_QuartzJob.csproj|solutionrelative:wideseawcs_quartzjob\\service\\routerservice.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
- },
- {
- "AbsoluteMoniker": "D:0:0:{9FBC654C-51DE-422D-9E1E-6A38268DE1E2}|WIDESEAWCS_Common\\WIDESEAWCS_Common.csproj|d:\\git\\shanmeixinnengyuan\\code\\wcs\\wideseawcs_server\\wideseawcs_common\\httpenum\\configkey.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
- "RelativeMoniker": "D:0:0:{9FBC654C-51DE-422D-9E1E-6A38268DE1E2}|WIDESEAWCS_Common\\WIDESEAWCS_Common.csproj|solutionrelative:wideseawcs_common\\httpenum\\configkey.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
- },
- {
- "AbsoluteMoniker": "D:0:0:{BFFDD936-2E61-4D3A-ABFE-7CF77FE0B184}|WIDESEAWCS_Core\\WIDESEAWCS_Core.csproj|d:\\git\\shanmeixinnengyuan\\code\\wcs\\wideseawcs_server\\wideseawcs_core\\http\\http\\httpclienthelper.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
- "RelativeMoniker": "D:0:0:{BFFDD936-2E61-4D3A-ABFE-7CF77FE0B184}|WIDESEAWCS_Core\\WIDESEAWCS_Core.csproj|solutionrelative:wideseawcs_core\\http\\http\\httpclienthelper.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
+ "AbsoluteMoniker": "D:0:0:{294E4915-0241-4C8C-BA99-7588B945863A}|WIDESEAWCS_Tasks\\WIDESEAWCS_Tasks.csproj|d:\\git\\shanmeixinnengyuan\\code\\wcs\\wideseawcs_server\\wideseawcs_tasks\\robotjob\\workflow\\robotworkfloworchestrator.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
+ "RelativeMoniker": "D:0:0:{294E4915-0241-4C8C-BA99-7588B945863A}|WIDESEAWCS_Tasks\\WIDESEAWCS_Tasks.csproj|solutionrelative:wideseawcs_tasks\\robotjob\\workflow\\robotworkfloworchestrator.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
},
{
"AbsoluteMoniker": "D:0:0:{294E4915-0241-4C8C-BA99-7588B945863A}|WIDESEAWCS_Tasks\\WIDESEAWCS_Tasks.csproj|d:\\git\\shanmeixinnengyuan\\code\\wcs\\wideseawcs_server\\wideseawcs_tasks\\robotjob\\robotjob.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
"RelativeMoniker": "D:0:0:{294E4915-0241-4C8C-BA99-7588B945863A}|WIDESEAWCS_Tasks\\WIDESEAWCS_Tasks.csproj|solutionrelative:wideseawcs_tasks\\robotjob\\robotjob.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
},
{
- "AbsoluteMoniker": "D:0:0:{7F200FE8-CAF6-4131-BD25-8D438FE0ABAC}|WIDESEAWCS_Model\\WIDESEAWCS_Model.csproj|d:\\git\\shanmeixinnengyuan\\code\\wcs\\wideseawcs_server\\wideseawcs_model\\models\\taskinfo\\dt_task_hty.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
- "RelativeMoniker": "D:0:0:{7F200FE8-CAF6-4131-BD25-8D438FE0ABAC}|WIDESEAWCS_Model\\WIDESEAWCS_Model.csproj|solutionrelative:wideseawcs_model\\models\\taskinfo\\dt_task_hty.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
+ "AbsoluteMoniker": "D:0:0:{294E4915-0241-4C8C-BA99-7588B945863A}|WIDESEAWCS_Tasks\\WIDESEAWCS_Tasks.csproj|d:\\git\\shanmeixinnengyuan\\code\\wcs\\wideseawcs_server\\wideseawcs_tasks\\robotjob\\robotmessagehandler.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
+ "RelativeMoniker": "D:0:0:{294E4915-0241-4C8C-BA99-7588B945863A}|WIDESEAWCS_Tasks\\WIDESEAWCS_Tasks.csproj|solutionrelative:wideseawcs_tasks\\robotjob\\robotmessagehandler.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
}
],
"DocumentGroupContainers": [
@@ -46,7 +22,7 @@
"DocumentGroups": [
{
"DockedWidth": 200,
- "SelectedChildIndex": 11,
+ "SelectedChildIndex": 3,
"Children": [
{
"$type": "Bookmark",
@@ -62,116 +38,40 @@
},
{
"$type": "Document",
- "DocumentIndex": 3,
- "Title": "ConveyorLineDispatchHandler.cs",
- "DocumentMoniker": "D:\\Git\\ShanMeiXinNengYuan\\Code\\WCS\\WIDESEAWCS_Server\\WIDESEAWCS_Tasks\\ConveyorLineNewJob\\ConveyorLineDispatchHandler.cs",
- "RelativeDocumentMoniker": "WIDESEAWCS_Tasks\\ConveyorLineNewJob\\ConveyorLineDispatchHandler.cs",
- "ToolTip": "D:\\Git\\ShanMeiXinNengYuan\\Code\\WCS\\WIDESEAWCS_Server\\WIDESEAWCS_Tasks\\ConveyorLineNewJob\\ConveyorLineDispatchHandler.cs",
- "RelativeToolTip": "WIDESEAWCS_Tasks\\ConveyorLineNewJob\\ConveyorLineDispatchHandler.cs",
- "ViewState": "AgIAAIgAAAAAAAAAAAAlwKAAAAAQAAAAAAAAAA==",
+ "DocumentIndex": 0,
+ "Title": "RobotWorkflowOrchestrator.cs",
+ "DocumentMoniker": "D:\\Git\\ShanMeiXinNengYuan\\Code\\WCS\\WIDESEAWCS_Server\\WIDESEAWCS_Tasks\\RobotJob\\Workflow\\RobotWorkflowOrchestrator.cs",
+ "RelativeDocumentMoniker": "WIDESEAWCS_Tasks\\RobotJob\\Workflow\\RobotWorkflowOrchestrator.cs",
+ "ToolTip": "D:\\Git\\ShanMeiXinNengYuan\\Code\\WCS\\WIDESEAWCS_Server\\WIDESEAWCS_Tasks\\RobotJob\\Workflow\\RobotWorkflowOrchestrator.cs*",
+ "RelativeToolTip": "WIDESEAWCS_Tasks\\RobotJob\\Workflow\\RobotWorkflowOrchestrator.cs*",
+ "ViewState": "AgIAAAAAAAAAAAAAAAAAACMAAABzAAAAAAAAAA==",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
- "WhenOpened": "2026-03-18T03:31:16.145Z",
+ "WhenOpened": "2026-03-18T09:21:38.852Z",
"EditorCaption": ""
},
{
"$type": "Document",
"DocumentIndex": 2,
- "Title": "SiemensS7Communicator.cs",
- "DocumentMoniker": "D:\\Git\\ShanMeiXinNengYuan\\Code\\WCS\\WIDESEAWCS_Server\\WIDESEAWCS_Communicator\\Siemens\\SiemensS7Communicator.cs",
- "RelativeDocumentMoniker": "WIDESEAWCS_Communicator\\Siemens\\SiemensS7Communicator.cs",
- "ToolTip": "D:\\Git\\ShanMeiXinNengYuan\\Code\\WCS\\WIDESEAWCS_Server\\WIDESEAWCS_Communicator\\Siemens\\SiemensS7Communicator.cs",
- "RelativeToolTip": "WIDESEAWCS_Communicator\\Siemens\\SiemensS7Communicator.cs",
- "ViewState": "AgIAANkBAAAAAAAAAAAewOsBAAAQAAAAAAAAAA==",
+ "Title": "RobotMessageHandler.cs",
+ "DocumentMoniker": "D:\\Git\\ShanMeiXinNengYuan\\Code\\WCS\\WIDESEAWCS_Server\\WIDESEAWCS_Tasks\\RobotJob\\RobotMessageHandler.cs",
+ "RelativeDocumentMoniker": "WIDESEAWCS_Tasks\\RobotJob\\RobotMessageHandler.cs",
+ "ToolTip": "D:\\Git\\ShanMeiXinNengYuan\\Code\\WCS\\WIDESEAWCS_Server\\WIDESEAWCS_Tasks\\RobotJob\\RobotMessageHandler.cs",
+ "RelativeToolTip": "WIDESEAWCS_Tasks\\RobotJob\\RobotMessageHandler.cs",
+ "ViewState": "AgIAABMAAAAAAAAAAAAAwDIAAAAQAAAAAAAAAA==",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
- "WhenOpened": "2026-03-18T03:25:37.718Z",
- "EditorCaption": ""
+ "WhenOpened": "2026-03-18T09:10:02.533Z"
},
{
"$type": "Document",
- "DocumentIndex": 4,
- "Title": "RouterService.cs",
- "DocumentMoniker": "D:\\Git\\ShanMeiXinNengYuan\\Code\\WCS\\WIDESEAWCS_Server\\WIDESEAWCS_QuartzJob\\Service\\RouterService.cs",
- "RelativeDocumentMoniker": "WIDESEAWCS_QuartzJob\\Service\\RouterService.cs",
- "ToolTip": "D:\\Git\\ShanMeiXinNengYuan\\Code\\WCS\\WIDESEAWCS_Server\\WIDESEAWCS_QuartzJob\\Service\\RouterService.cs",
- "RelativeToolTip": "WIDESEAWCS_QuartzJob\\Service\\RouterService.cs",
- "ViewState": "AgIAACYBAAAAAAAAAAAlwDkBAAAIAAAAAAAAAA==",
- "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
- "WhenOpened": "2026-03-17T07:55:50.917Z",
- "EditorCaption": ""
- },
- {
- "$type": "Document",
- "DocumentIndex": 7,
+ "DocumentIndex": 1,
"Title": "RobotJob.cs",
"DocumentMoniker": "D:\\Git\\ShanMeiXinNengYuan\\Code\\WCS\\WIDESEAWCS_Server\\WIDESEAWCS_Tasks\\RobotJob\\RobotJob.cs",
"RelativeDocumentMoniker": "WIDESEAWCS_Tasks\\RobotJob\\RobotJob.cs",
"ToolTip": "D:\\Git\\ShanMeiXinNengYuan\\Code\\WCS\\WIDESEAWCS_Server\\WIDESEAWCS_Tasks\\RobotJob\\RobotJob.cs",
"RelativeToolTip": "WIDESEAWCS_Tasks\\RobotJob\\RobotJob.cs",
- "ViewState": "AgIAADwAAAAAAAAAAADwv1UAAAAxAAAAAAAAAA==",
+ "ViewState": "AgIAAEoAAAAAAAAAAAAAwGAAAAA7AAAAAAAAAA==",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
- "WhenOpened": "2026-03-17T02:11:28.683Z"
- },
- {
- "$type": "Document",
- "DocumentIndex": 8,
- "Title": "Dt_Task_Hty.cs",
- "DocumentMoniker": "D:\\Git\\ShanMeiXinNengYuan\\Code\\WCS\\WIDESEAWCS_Server\\WIDESEAWCS_Model\\Models\\TaskInfo\\Dt_Task_Hty.cs",
- "RelativeDocumentMoniker": "WIDESEAWCS_Model\\Models\\TaskInfo\\Dt_Task_Hty.cs",
- "ToolTip": "D:\\Git\\ShanMeiXinNengYuan\\Code\\WCS\\WIDESEAWCS_Server\\WIDESEAWCS_Model\\Models\\TaskInfo\\Dt_Task_Hty.cs",
- "RelativeToolTip": "WIDESEAWCS_Model\\Models\\TaskInfo\\Dt_Task_Hty.cs",
- "ViewState": "AgIAAAsAAAAAAAAAAAAwwAsAAAAmAAAAAAAAAA==",
- "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
- "WhenOpened": "2026-03-13T02:59:26.007Z"
- },
- {
- "$type": "Document",
- "DocumentIndex": 5,
- "Title": "ConfigKey.cs",
- "DocumentMoniker": "D:\\Git\\ShanMeiXinNengYuan\\Code\\WCS\\WIDESEAWCS_Server\\WIDESEAWCS_Common\\HttpEnum\\ConfigKey.cs",
- "RelativeDocumentMoniker": "WIDESEAWCS_Common\\HttpEnum\\ConfigKey.cs",
- "ToolTip": "D:\\Git\\ShanMeiXinNengYuan\\Code\\WCS\\WIDESEAWCS_Server\\WIDESEAWCS_Common\\HttpEnum\\ConfigKey.cs",
- "RelativeToolTip": "WIDESEAWCS_Common\\HttpEnum\\ConfigKey.cs",
- "ViewState": "AgIAABkAAAAAAAAAAAAawCwAAAAIAAAAAAAAAA==",
- "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
- "WhenOpened": "2026-03-12T01:55:15.084Z"
- },
- {
- "$type": "Document",
- "DocumentIndex": 6,
- "Title": "HttpClientHelper.cs",
- "DocumentMoniker": "D:\\Git\\ShanMeiXinNengYuan\\Code\\WCS\\WIDESEAWCS_Server\\WIDESEAWCS_Core\\Http\\HTTP\\HttpClientHelper.cs",
- "RelativeDocumentMoniker": "WIDESEAWCS_Core\\Http\\HTTP\\HttpClientHelper.cs",
- "ToolTip": "D:\\Git\\ShanMeiXinNengYuan\\Code\\WCS\\WIDESEAWCS_Server\\WIDESEAWCS_Core\\Http\\HTTP\\HttpClientHelper.cs",
- "RelativeToolTip": "WIDESEAWCS_Core\\Http\\HTTP\\HttpClientHelper.cs",
- "ViewState": "AgIAABAAAAAAAAAAAAAAAB8AAAASAAAAAAAAAA==",
- "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
- "WhenOpened": "2026-03-12T01:54:05.934Z"
- },
- {
- "$type": "Document",
- "DocumentIndex": 1,
- "Title": "CommonConveyorLineNewJob.cs",
- "DocumentMoniker": "D:\\Git\\ShanMeiXinNengYuan\\Code\\WCS\\WIDESEAWCS_Server\\WIDESEAWCS_Tasks\\ConveyorLineNewJob\\CommonConveyorLineNewJob.cs",
- "RelativeDocumentMoniker": "WIDESEAWCS_Tasks\\ConveyorLineNewJob\\CommonConveyorLineNewJob.cs",
- "ToolTip": "D:\\Git\\ShanMeiXinNengYuan\\Code\\WCS\\WIDESEAWCS_Server\\WIDESEAWCS_Tasks\\ConveyorLineNewJob\\CommonConveyorLineNewJob.cs",
- "RelativeToolTip": "WIDESEAWCS_Tasks\\ConveyorLineNewJob\\CommonConveyorLineNewJob.cs",
- "ViewState": "AgIAADwAAAAAAAAAAAApwFEAAAAcAAAAAAAAAA==",
- "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
- "WhenOpened": "2026-03-11T09:29:57.419Z",
- "EditorCaption": ""
- },
- {
- "$type": "Document",
- "DocumentIndex": 0,
- "Title": "TaskService.cs",
- "DocumentMoniker": "D:\\Git\\ShanMeiXinNengYuan\\Code\\WCS\\WIDESEAWCS_Server\\WIDESEAWCS_TaskInfoService\\TaskService.cs",
- "RelativeDocumentMoniker": "WIDESEAWCS_TaskInfoService\\TaskService.cs",
- "ToolTip": "D:\\Git\\ShanMeiXinNengYuan\\Code\\WCS\\WIDESEAWCS_Server\\WIDESEAWCS_TaskInfoService\\TaskService.cs",
- "RelativeToolTip": "WIDESEAWCS_TaskInfoService\\TaskService.cs",
- "ViewState": "AgIAAG4CAAAAAAAAAAAAAIICAAB9AAAAAAAAAA==",
- "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
- "WhenOpened": "2026-03-11T09:01:01.549Z",
- "EditorCaption": ""
+ "WhenOpened": "2026-03-18T08:56:41.452Z"
}
]
}
diff --git a/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_DTO/TaskInfo/WMSTaskDTO.cs b/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_DTO/TaskInfo/WMSTaskDTO.cs
index cf1f52c..fbd91e7 100644
--- a/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_DTO/TaskInfo/WMSTaskDTO.cs
+++ b/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_DTO/TaskInfo/WMSTaskDTO.cs
@@ -36,7 +36,7 @@
/// <summary>
/// 浠诲姟鐘舵��
/// </summary>
- public int TaskState { get; set; }
+ public int TaskStatus { get; set; }
/// <summary>
/// 璧风偣
diff --git a/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Model/Models/TaskInfo/Dt_RobotTask.cs b/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Model/Models/TaskInfo/Dt_RobotTask.cs
index 7cf765d..cb2cc60 100644
--- a/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Model/Models/TaskInfo/Dt_RobotTask.cs
+++ b/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Model/Models/TaskInfo/Dt_RobotTask.cs
@@ -18,7 +18,7 @@
/// <summary>
/// 鏈哄櫒浜轰换鍔D
/// </summary>
- [SugarColumn(IsPrimaryKey = true, ColumnDescription = "鏈哄櫒浜轰换鍔D")]
+ [SugarColumn(IsPrimaryKey = true, IsIdentity = true, ColumnDescription = "鏈哄櫒浜轰换鍔D")]
public int RobotTaskId { get; set; }
/// <summary>
diff --git a/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Server/appsettings.json b/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Server/appsettings.json
index 3887e19..d5d6da7 100644
--- a/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Server/appsettings.json
+++ b/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Server/appsettings.json
@@ -72,11 +72,11 @@
"110681": "Split"
},
"AddressRoadwayMap": { // 瀵瑰簲璁惧鍦板潃鏄犲皠
- "11001": "HPRB1",
- "11010": "HPRB1",
- "11068": "ZYRB1",
- "10010": "HPRB1",
- "10030": "HPRB1"
+ "11001": "鎹㈢洏鏈烘鎵�",
+ "11010": "鎹㈢洏鏈烘鎵�",
+ "11068": "娉ㄦ恫缁勭洏鏈烘鎵�",
+ "10010": "鎹㈢洏鏈烘鎵�",
+ "10030": "鎹㈢洏鏈烘鎵�"
},
"AddressSourceLineNoMap": { // 瀵瑰簲杈撻�佺嚎缂栧彿鍦板潃鏄犲皠
"11001": "10010",
diff --git a/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_TaskInfoService/RobotTaskService.cs b/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_TaskInfoService/RobotTaskService.cs
index 465fcb5..f88a4e6 100644
--- a/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_TaskInfoService/RobotTaskService.cs
+++ b/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_TaskInfoService/RobotTaskService.cs
@@ -87,9 +87,10 @@
RobotSourceAddressPalletCode = stockDTO.SourcePalletNo,
RobotTargetAddressPalletCode = stockDTO.TargetPalletNo,
RobotTaskType = taskDTO.TaskType,
- RobotTaskState = taskDTO.TaskState,
+ RobotTaskState = taskDTO.TaskStatus,
RobotGrade = taskDTO.Grade,
- Creater = "WMS"
+ Creater = "WMS",
+ RobotTaskTotalNum = 0,
};
BaseDal.AddData(task);
diff --git a/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_TaskInfoService/TaskService.cs b/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_TaskInfoService/TaskService.cs
index 8622839..3dd7db2 100644
--- a/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_TaskInfoService/TaskService.cs
+++ b/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_TaskInfoService/TaskService.cs
@@ -144,7 +144,7 @@
Roadway = "SC01",
SourceAddress = sourceAddress,
TargetAddress = "SC01",
- TaskState = (int)TaskInStatusEnum.InNew,
+ TaskStatus = (int)TaskInStatusEnum.InNew,
Id = 0,
TaskType = (int)TaskInboundTypeEnum.Inbound
};
@@ -399,7 +399,7 @@
task.Modifier = "System";
if (task.TaskStatus == (int)TaskOutStatusEnum.Line_OutFinish)
{
- BaseDal.DeleteAndMoveIntoHty(task, OperateTypeEnum.鑷姩鍒犻櫎);
+ BaseDal.DeleteAndMoveIntoHty(task, OperateTypeEnum.鑷姩瀹屾垚);
}
else
{
diff --git a/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/Workflow/RobotWorkflowOrchestrator.cs b/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/Workflow/RobotWorkflowOrchestrator.cs
index b344986..190a8d6 100644
--- a/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/Workflow/RobotWorkflowOrchestrator.cs
+++ b/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/Workflow/RobotWorkflowOrchestrator.cs
@@ -39,7 +39,7 @@
{
await HandlePickFinishedStateAsync(task, ipAddress);
}
- else if ((latestState.CurrentAction == "PutFinished" || latestState.CurrentAction == "AllPutFinished")
+ else if ((latestState.CurrentAction == "PutFinished" || latestState.CurrentAction == "AllPutFinished" || latestState.CurrentAction == null)
&& latestState.OperStatus == "Homed"
&& latestState.RobotArmObject == 0
&& (task.RobotTaskState == TaskRobotStatusEnum.RobotPutFinish.GetHashCode()
diff --git a/Code/WMS/WIDESEA_WMSServer/.vs/WIDESEA_WMSServer/v18/DocumentLayout.backup.json b/Code/WMS/WIDESEA_WMSServer/.vs/WIDESEA_WMSServer/v18/DocumentLayout.backup.json
index b82eb6a..09fa9fe 100644
--- a/Code/WMS/WIDESEA_WMSServer/.vs/WIDESEA_WMSServer/v18/DocumentLayout.backup.json
+++ b/Code/WMS/WIDESEA_WMSServer/.vs/WIDESEA_WMSServer/v18/DocumentLayout.backup.json
@@ -7,12 +7,12 @@
"RelativeMoniker": "D:0:0:{7D7534D4-51D9-46DC-A6B7-6430042F4E12}|WIDESEA_TaskInfoService\\WIDESEA_TaskInfoService.csproj|solutionrelative:widesea_taskinfoservice\\taskservice.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
},
{
- "AbsoluteMoniker": "D:0:0:{D81A65B5-47D1-40C1-8FDE-7D24FF003F51}|WIDESEA_WMSServer\\WIDESEA_WMSServer.csproj|d:\\git\\shanmeixinnengyuan\\code\\wms\\widesea_wmsserver\\widesea_wmsserver\\controllers\\taskinfo\\taskcontroller.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
- "RelativeMoniker": "D:0:0:{D81A65B5-47D1-40C1-8FDE-7D24FF003F51}|WIDESEA_WMSServer\\WIDESEA_WMSServer.csproj|solutionrelative:widesea_wmsserver\\controllers\\taskinfo\\taskcontroller.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
- },
- {
"AbsoluteMoniker": "D:0:0:{929DF936-042C-4EEC-8722-A831FC2F0AEA}|WIDESEA_DTO\\WIDESEA_DTO.csproj|d:\\git\\shanmeixinnengyuan\\code\\wms\\widesea_wmsserver\\widesea_dto\\task\\wmstaskdto.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
"RelativeMoniker": "D:0:0:{929DF936-042C-4EEC-8722-A831FC2F0AEA}|WIDESEA_DTO\\WIDESEA_DTO.csproj|solutionrelative:widesea_dto\\task\\wmstaskdto.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
+ },
+ {
+ "AbsoluteMoniker": "D:0:0:{D81A65B5-47D1-40C1-8FDE-7D24FF003F51}|WIDESEA_WMSServer\\WIDESEA_WMSServer.csproj|d:\\git\\shanmeixinnengyuan\\code\\wms\\widesea_wmsserver\\widesea_wmsserver\\controllers\\taskinfo\\taskcontroller.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
+ "RelativeMoniker": "D:0:0:{D81A65B5-47D1-40C1-8FDE-7D24FF003F51}|WIDESEA_WMSServer\\WIDESEA_WMSServer.csproj|solutionrelative:widesea_wmsserver\\controllers\\taskinfo\\taskcontroller.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
},
{
"AbsoluteMoniker": "D:0:0:{AF8F3D65-1D75-4B8F-AFD9-4150E591C44D}|WIDESEA_Common\\WIDESEA_Common.csproj|d:\\git\\shanmeixinnengyuan\\code\\wms\\widesea_wmsserver\\widesea_common\\taskenum\\tasktypeenum.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
@@ -86,15 +86,16 @@
},
{
"$type": "Document",
- "DocumentIndex": 2,
+ "DocumentIndex": 1,
"Title": "WMSTaskDTO.cs",
"DocumentMoniker": "D:\\Git\\ShanMeiXinNengYuan\\Code\\WMS\\WIDESEA_WMSServer\\WIDESEA_DTO\\Task\\WMSTaskDTO.cs",
"RelativeDocumentMoniker": "WIDESEA_DTO\\Task\\WMSTaskDTO.cs",
"ToolTip": "D:\\Git\\ShanMeiXinNengYuan\\Code\\WMS\\WIDESEA_WMSServer\\WIDESEA_DTO\\Task\\WMSTaskDTO.cs",
"RelativeToolTip": "WIDESEA_DTO\\Task\\WMSTaskDTO.cs",
- "ViewState": "AgIAAAYAAAAAAAAAAAAuwB8AAAArAAAAAAAAAA==",
+ "ViewState": "AgIAABgAAAAAAAAAAAAIwCkAAAAdAAAAAAAAAA==",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
- "WhenOpened": "2026-03-17T07:42:09.48Z"
+ "WhenOpened": "2026-03-17T07:42:09.48Z",
+ "EditorCaption": ""
},
{
"$type": "Document",
@@ -134,13 +135,13 @@
},
{
"$type": "Document",
- "DocumentIndex": 1,
+ "DocumentIndex": 2,
"Title": "TaskController.cs",
"DocumentMoniker": "D:\\Git\\ShanMeiXinNengYuan\\Code\\WMS\\WIDESEA_WMSServer\\WIDESEA_WMSServer\\Controllers\\TaskInfo\\TaskController.cs",
"RelativeDocumentMoniker": "WIDESEA_WMSServer\\Controllers\\TaskInfo\\TaskController.cs",
"ToolTip": "D:\\Git\\ShanMeiXinNengYuan\\Code\\WMS\\WIDESEA_WMSServer\\WIDESEA_WMSServer\\Controllers\\TaskInfo\\TaskController.cs",
"RelativeToolTip": "WIDESEA_WMSServer\\Controllers\\TaskInfo\\TaskController.cs",
- "ViewState": "AgIAAHgAAAAAAAAAAADgv5cAAAAjAAAAAAAAAA==",
+ "ViewState": "AgIAAIoAAAAAAAAAAAAhwJsAAAAIAAAAAAAAAA==",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
"WhenOpened": "2026-03-13T02:00:31.089Z",
"EditorCaption": ""
@@ -262,7 +263,7 @@
"RelativeDocumentMoniker": "WIDESEA_TaskInfoService\\TaskService.cs",
"ToolTip": "D:\\Git\\ShanMeiXinNengYuan\\Code\\WMS\\WIDESEA_WMSServer\\WIDESEA_TaskInfoService\\TaskService.cs",
"RelativeToolTip": "WIDESEA_TaskInfoService\\TaskService.cs",
- "ViewState": "AgIAAGkCAAAAAAAAAAAkwHoCAAAIAAAAAAAAAA==",
+ "ViewState": "AgIAAGkCAAAAAAAAAAAkwHgCAAAWAAAAAAAAAA==",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
"WhenOpened": "2026-02-06T06:34:59.734Z",
"EditorCaption": ""
diff --git a/Code/WMS/WIDESEA_WMSServer/.vs/WIDESEA_WMSServer/v18/DocumentLayout.json b/Code/WMS/WIDESEA_WMSServer/.vs/WIDESEA_WMSServer/v18/DocumentLayout.json
index d7f56f6..d24497d 100644
--- a/Code/WMS/WIDESEA_WMSServer/.vs/WIDESEA_WMSServer/v18/DocumentLayout.json
+++ b/Code/WMS/WIDESEA_WMSServer/.vs/WIDESEA_WMSServer/v18/DocumentLayout.json
@@ -7,12 +7,12 @@
"RelativeMoniker": "D:0:0:{7D7534D4-51D9-46DC-A6B7-6430042F4E12}|WIDESEA_TaskInfoService\\WIDESEA_TaskInfoService.csproj|solutionrelative:widesea_taskinfoservice\\taskservice.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
},
{
- "AbsoluteMoniker": "D:0:0:{D81A65B5-47D1-40C1-8FDE-7D24FF003F51}|WIDESEA_WMSServer\\WIDESEA_WMSServer.csproj|d:\\git\\shanmeixinnengyuan\\code\\wms\\widesea_wmsserver\\widesea_wmsserver\\controllers\\taskinfo\\taskcontroller.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
- "RelativeMoniker": "D:0:0:{D81A65B5-47D1-40C1-8FDE-7D24FF003F51}|WIDESEA_WMSServer\\WIDESEA_WMSServer.csproj|solutionrelative:widesea_wmsserver\\controllers\\taskinfo\\taskcontroller.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
- },
- {
"AbsoluteMoniker": "D:0:0:{929DF936-042C-4EEC-8722-A831FC2F0AEA}|WIDESEA_DTO\\WIDESEA_DTO.csproj|d:\\git\\shanmeixinnengyuan\\code\\wms\\widesea_wmsserver\\widesea_dto\\task\\wmstaskdto.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
"RelativeMoniker": "D:0:0:{929DF936-042C-4EEC-8722-A831FC2F0AEA}|WIDESEA_DTO\\WIDESEA_DTO.csproj|solutionrelative:widesea_dto\\task\\wmstaskdto.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
+ },
+ {
+ "AbsoluteMoniker": "D:0:0:{D81A65B5-47D1-40C1-8FDE-7D24FF003F51}|WIDESEA_WMSServer\\WIDESEA_WMSServer.csproj|d:\\git\\shanmeixinnengyuan\\code\\wms\\widesea_wmsserver\\widesea_wmsserver\\controllers\\taskinfo\\taskcontroller.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
+ "RelativeMoniker": "D:0:0:{D81A65B5-47D1-40C1-8FDE-7D24FF003F51}|WIDESEA_WMSServer\\WIDESEA_WMSServer.csproj|solutionrelative:widesea_wmsserver\\controllers\\taskinfo\\taskcontroller.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
},
{
"AbsoluteMoniker": "D:0:0:{AF8F3D65-1D75-4B8F-AFD9-4150E591C44D}|WIDESEA_Common\\WIDESEA_Common.csproj|d:\\git\\shanmeixinnengyuan\\code\\wms\\widesea_wmsserver\\widesea_common\\taskenum\\tasktypeenum.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
@@ -86,13 +86,13 @@
},
{
"$type": "Document",
- "DocumentIndex": 2,
+ "DocumentIndex": 1,
"Title": "WMSTaskDTO.cs",
"DocumentMoniker": "D:\\Git\\ShanMeiXinNengYuan\\Code\\WMS\\WIDESEA_WMSServer\\WIDESEA_DTO\\Task\\WMSTaskDTO.cs",
"RelativeDocumentMoniker": "WIDESEA_DTO\\Task\\WMSTaskDTO.cs",
"ToolTip": "D:\\Git\\ShanMeiXinNengYuan\\Code\\WMS\\WIDESEA_WMSServer\\WIDESEA_DTO\\Task\\WMSTaskDTO.cs",
"RelativeToolTip": "WIDESEA_DTO\\Task\\WMSTaskDTO.cs",
- "ViewState": "AgIAAAYAAAAAAAAAAAAuwB8AAAArAAAAAAAAAA==",
+ "ViewState": "AgIAABgAAAAAAAAAAAAIwCkAAAAdAAAAAAAAAA==",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
"WhenOpened": "2026-03-17T07:42:09.48Z"
},
@@ -134,16 +134,15 @@
},
{
"$type": "Document",
- "DocumentIndex": 1,
+ "DocumentIndex": 2,
"Title": "TaskController.cs",
"DocumentMoniker": "D:\\Git\\ShanMeiXinNengYuan\\Code\\WMS\\WIDESEA_WMSServer\\WIDESEA_WMSServer\\Controllers\\TaskInfo\\TaskController.cs",
"RelativeDocumentMoniker": "WIDESEA_WMSServer\\Controllers\\TaskInfo\\TaskController.cs",
"ToolTip": "D:\\Git\\ShanMeiXinNengYuan\\Code\\WMS\\WIDESEA_WMSServer\\WIDESEA_WMSServer\\Controllers\\TaskInfo\\TaskController.cs",
"RelativeToolTip": "WIDESEA_WMSServer\\Controllers\\TaskInfo\\TaskController.cs",
- "ViewState": "AgIAAHgAAAAAAAAAAADgv5cAAAAjAAAAAAAAAA==",
+ "ViewState": "AgIAAIoAAAAAAAAAAAAhwJsAAAAIAAAAAAAAAA==",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
- "WhenOpened": "2026-03-13T02:00:31.089Z",
- "EditorCaption": ""
+ "WhenOpened": "2026-03-13T02:00:31.089Z"
},
{
"$type": "Document",
@@ -215,8 +214,7 @@
"RelativeToolTip": "WIDESEA_BasicService\\LocationInfoService.cs",
"ViewState": "AgIAAFcAAAAAAAAAAAAAwGgAAAARAAAAAAAAAA==",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
- "WhenOpened": "2026-03-12T02:05:14.224Z",
- "EditorCaption": ""
+ "WhenOpened": "2026-03-12T02:05:14.224Z"
},
{
"$type": "Document",
@@ -262,7 +260,7 @@
"RelativeDocumentMoniker": "WIDESEA_TaskInfoService\\TaskService.cs",
"ToolTip": "D:\\Git\\ShanMeiXinNengYuan\\Code\\WMS\\WIDESEA_WMSServer\\WIDESEA_TaskInfoService\\TaskService.cs",
"RelativeToolTip": "WIDESEA_TaskInfoService\\TaskService.cs",
- "ViewState": "AgIAAKwCAAAAAAAAAAAgwL8CAAAAAAAAAAAAAA==",
+ "ViewState": "AgIAAAAAAAAAAAAAAAAAAHgCAAAWAAAAAAAAAA==",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
"WhenOpened": "2026-02-06T06:34:59.734Z",
"EditorCaption": ""
diff --git a/Code/WMS/WIDESEA_WMSServer/WIDESEA_StockService/StockSerivce.cs b/Code/WMS/WIDESEA_WMSServer/WIDESEA_StockService/StockSerivce.cs
index 1271ec3..a4c4868 100644
--- a/Code/WMS/WIDESEA_WMSServer/WIDESEA_StockService/StockSerivce.cs
+++ b/Code/WMS/WIDESEA_WMSServer/WIDESEA_StockService/StockSerivce.cs
@@ -101,6 +101,7 @@
/// </summary>
public async Task<WebResponseContent> ChangePalletAsync(StockDTO stock)
{
+
WebResponseContent content = new WebResponseContent();
if (stock == null ||
string.IsNullOrWhiteSpace(stock.TargetPalletNo) ||
@@ -110,7 +111,7 @@
return content.Error("婧愭墭鐩樺彿涓庣洰鏍囨墭鐩樺彿鐩稿悓");
}
- var sourceStock = StockInfoService.Repository.QueryFirst(s => s.PalletCode == stock.SourcePalletNo);
+ var sourceStock = await StockInfoService.Repository.QueryDataNavFirstAsync(s => s.PalletCode == stock.SourcePalletNo);
if (sourceStock == null) return content.Error("婧愭墭鐩樹笉瀛樺湪");
var targetStock = StockInfoService.Repository.QueryFirst(s => s.PalletCode == stock.TargetPalletNo);
@@ -131,11 +132,11 @@
targetStock.Id = newId;
}
- var serialNumbers = stock.Details.Select(d => d.CellBarcode).Distinct().ToList();
+ var serialNumbers = stock.Details.Select(d => d.Channel).Distinct().ToList();
if (!serialNumbers.Any()) return content.Error("鏈壘鍒版湁鏁堢殑搴忓垪鍙�");
var detailEntities = StockInfoDetailService.Repository.QueryData(
- d => d.StockId == sourceStock.Id && serialNumbers.Contains(d.SerialNumber));
+ d => d.StockId == sourceStock.Id && serialNumbers.Contains(d.InboundOrderRowNo));
if (!detailEntities.Any()) return content.Error("鏈壘鍒版湁鏁堢殑搴撳瓨鏄庣粏");
if (await StockInfoDetail_HtyService.Repository.AddDataAsync(CreateDetailHistory(detailEntities, "鎹㈢洏")) <= 0)
@@ -253,7 +254,9 @@
Creater = s.Creater,
CreateDate = s.CreateDate,
Modifier = s.Modifier,
- ModifyDate = s.ModifyDate
+ ModifyDate = s.ModifyDate,
+ LocationId = s.LocationId,
+ OutboundDate = s.OutboundDate
}).ToList();
}
}
diff --git "a/\351\241\271\347\233\256\350\265\204\346\226\231/\350\256\276\345\244\207\345\215\217\350\256\256/\346\234\272\346\242\260\346\211\213\345\215\217\350\256\256/~$\344\272\244\344\272\222\346\265\201\347\250\213\350\241\250\0501\051.xlsx" "b/\351\241\271\347\233\256\350\265\204\346\226\231/\350\256\276\345\244\207\345\215\217\350\256\256/\346\234\272\346\242\260\346\211\213\345\215\217\350\256\256/~$\344\272\244\344\272\222\346\265\201\347\250\213\350\241\250\0501\051.xlsx"
new file mode 100644
index 0000000..236d63d
--- /dev/null
+++ "b/\351\241\271\347\233\256\350\265\204\346\226\231/\350\256\276\345\244\207\345\215\217\350\256\256/\346\234\272\346\242\260\346\211\213\345\215\217\350\256\256/~$\344\272\244\344\272\222\346\265\201\347\250\213\350\241\250\0501\051.xlsx"
Binary files differ
--
Gitblit v1.9.3