| | |
| | | using Serilog; |
| | | using System.Diagnostics.CodeAnalysis; |
| | | using WIDESEA_Core; |
| | | using WIDESEAWCS_Common.HttpEnum; |
| | | using WIDESEAWCS_Common.TaskEnum; |
| | | using WIDESEAWCS_Core; |
| | | using WIDESEAWCS_Core.Enums; |
| | | using WIDESEAWCS_Core.Helper; |
| | | using WIDESEAWCS_DTO; |
| | | using WIDESEAWCS_DTO.TaskInfo; |
| | | using WIDESEAWCS_ITaskInfoRepository; |
| | | using WIDESEAWCS_ITaskInfoService; |
| | | using WIDESEAWCS_Model.Models; |
| | | using WIDESEAWCS_QuartzJob.Models; |
| | | using WIDESEAWCS_QuartzJob.Service; |
| | | using WIDESEAWCS_Tasks; |
| | | |
| | | namespace WIDESEAWCS_TaskInfoService.Flows |
| | | { |
| | | /// <summary> |
| | | /// å
¥åºä»»å¡æµç¨æå¡ã |
| | | /// è´è´£å
¥åºä»»å¡æ¥æ¶åå§åãç¶ææ¨è¿åå åæºå®æå¤çã |
| | | /// </summary> |
| | | public class InboundTaskFlowService : IInboundTaskFlowService |
| | | { |
| | | using Serilog;
|
| | | using System.Diagnostics.CodeAnalysis;
|
| | | using WIDESEA_Core;
|
| | | using WIDESEAWCS_Common.HttpEnum;
|
| | | using WIDESEAWCS_Common.TaskEnum;
|
| | | using WIDESEAWCS_Core;
|
| | | using WIDESEAWCS_Core.Enums;
|
| | | using WIDESEAWCS_Core.Helper;
|
| | | using WIDESEAWCS_DTO;
|
| | | using WIDESEAWCS_DTO.TaskInfo;
|
| | | using WIDESEAWCS_ITaskInfoRepository;
|
| | | using WIDESEAWCS_ITaskInfoService;
|
| | | using WIDESEAWCS_Model.Models;
|
| | | using WIDESEAWCS_QuartzJob.Models;
|
| | | using WIDESEAWCS_QuartzJob.Service;
|
| | | using WIDESEAWCS_Tasks;
|
| | |
|
| | | namespace WIDESEAWCS_TaskInfoService.Flows
|
| | | {
|
| | | /// <summary>
|
| | | /// å
¥åºä»»å¡æµç¨æå¡ã
|
| | | /// è´è´£å
¥åºä»»å¡æ¥æ¶åå§åãç¶ææ¨è¿åå åæºå®æå¤çã
|
| | | /// </summary>
|
| | | public class InboundTaskFlowService : IInboundTaskFlowService
|
| | | {
|
| | | private readonly IRouterService _routerService;
|
| | | private readonly ITaskRepository _taskRepository; |
| | | private readonly HttpClientHelper _httpClientHelper; |
| | | private readonly ILogger _logger; |
| | | |
| | | /// <summary> |
| | | /// åå§åå
¥åºä»»å¡æµç¨æå¡ã |
| | | /// </summary> |
| | | /// <param name="routerService">è·¯ç±æå¡ã</param> |
| | | /// <param name="httpClientHelper">WMSæ¥å£è°ç¨å¸®å©ç±»ã</param> |
| | | public InboundTaskFlowService(IRouterService routerService, ITaskRepository taskRepository, HttpClientHelper httpClientHelper, ILogger logger) |
| | | { |
| | | _routerService = routerService; |
| | | _taskRepository = taskRepository; |
| | | _httpClientHelper = httpClientHelper; |
| | | _logger = logger; |
| | | } |
| | | |
| | | /// <summary> |
| | | /// æ¥æ¶WMS任塿¶åå§åå
¥åºä»»å¡ã |
| | | /// </summary> |
| | | /// <param name="task">ä»»å¡å®ä½ã</param> |
| | | /// <param name="source">WMSä»»å¡åå§æ°æ®ã</param> |
| | | public WebResponseContent InitializeOnReceive([NotNull] Dt_Task task, [NotNull] WMSTaskDTO source) |
| | | { |
| | | WebResponseContent content = new WebResponseContent(); |
| | | Dt_Router routers = _routerService.QueryNextRoute(source.SourceAddress); |
| | | if (routers.IsNullOrEmpty()) |
| | | { |
| | | return content.Error("æªæ¾å°è·¯ç±ä¿¡æ¯"); |
| | | } |
| | | |
| | | task.TaskStatus = (int)TaskInStatusEnum.InNew; |
| | | task.CurrentAddress = source.SourceAddress; |
| | | task.NextAddress = routers.ChildPosi; |
| | | |
| | | return content.OK(); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// æ¨è¿å
¥åºä»»å¡ç¶æï¼å¹¶å¨å
³é®ç¶æè°ç¨WMSæ¥å£ã |
| | | /// </summary> |
| | | /// <param name="task">ä»»å¡å®ä½ã</param> |
| | | /// <returns>æ¨è¿ç»æã</returns> |
| | | public WebResponseContent MoveToNextStatus([NotNull] Dt_Task task) |
| | | { |
| | | if (task.TaskStatus >= (int)TaskInStatusEnum.InFinish) |
| | | return WebResponseContent.Instance.Error($"该任å¡ç¶æä¸å¯è·³è½¬å°ä¸ä¸æ¥,ä»»å¡å·:ã{task.TaskNum}ã,ä»»å¡ç¶æ:ã{task.TaskStatus}ã"); |
| | | |
| | | task.TaskStatus = task.TaskStatus.GetNextNotCompletedStatus<TaskInStatusEnum>(); |
| | | if (task.TaskStatus <= 0) |
| | | return WebResponseContent.Instance.Error($"该任å¡ç¶æä¸å¯è·³è½¬å°ä¸ä¸æ¥,ä»»å¡å·:ã{task.TaskNum}ã,ä»»å¡ç¶æ:ã{task.TaskStatus}ã"); |
| | | |
| | | if (task.TaskStatus == (int)TaskInStatusEnum.Line_InFinish) |
| | | { |
| | | return GetWMSInboundLocation(task); |
| | | } |
| | | |
| | | return UpdateWMSTaskStatus(task); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// å¤çå åæºå
¥åºå®æå¨ä½ã |
| | | /// </summary> |
| | | /// <param name="task">ä»»å¡å®ä½ã</param> |
| | | /// <returns>å¤çç»æã</returns> |
| | | public WebResponseContent CompleteStackerTask([NotNull] Dt_Task task) |
| | | { |
| | | WebResponseContent content = new WebResponseContent(); |
| | | if (task.TaskStatus != (int)TaskInStatusEnum.SC_InExecuting) |
| | | { |
| | | return WebResponseContent.Instance.OK(); |
| | | } |
| | | |
| | | int nextStatus = task.TaskStatus.GetNextNotCompletedStatus<TaskInStatusEnum>(); |
| | | task.TaskStatus = nextStatus; |
| | | task.ModifyDate = DateTime.Now; |
| | | task.Modifier = "System"; |
| | | |
| | | var result = _httpClientHelper.Post<WebResponseContent>( |
| | | nameof(ConfigKey.InboundFinishTaskAsync), |
| | | (new CreateTaskDto { PalletCode = task.PalletCode }).ToJson()); |
| | | |
| | | if (!result.IsSuccess || !result.Data.Status) |
| | | { |
| | | QuartzLogHelper.LogError(_logger, $"è°ç¨WMSæ¥å£å¤±è´¥,æ¥å£:ãInboundFinishTaskAsyncã,请æ±åæ°:ã{task.PalletCode}ã,é误信æ¯:ã{result.Data?.Message}ã", "InboundTaskFlowService"); |
| | | return content.Error($"éç¥WMSç³»ç»å åæºå
¥åºå®æå¤±è´¥,ä»»å¡å·:ã{task.TaskNum}ã,æçå·:ã{task.PalletCode}ã,é误信æ¯:ã{result.Data?.Message}ã"); |
| | | } |
| | | |
| | | QuartzLogHelper.LogInfo(_logger, $"è°ç¨WMSæ¥å£æå,æ¥å£:ãInboundFinishTaskAsyncã,ååºæ°æ®:ã{result.Data?.Data}ã,èæ¶:0ms", "InboundTaskFlowService"); |
| | | return content.OK($"éç¥WMSç³»ç»å åæºå
¥åºå®ææå,ä»»å¡å·:ã{task.TaskNum}ã,æçå·:ã{task.PalletCode}ã"); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// ä»WMSè·åå
¥åºç®æ å°åå¹¶ååä»»å¡ã |
| | | /// </summary> |
| | | /// <param name="task">ä»»å¡å®ä½ã</param> |
| | | /// <returns>è°ç¨ç»æã</returns> |
| | | private WebResponseContent GetWMSInboundLocation(Dt_Task task) |
| | | { |
| | | string configKey = nameof(ConfigKey.GetTasksLocation); |
| | | string requestParam = new CreateTaskDto { PalletCode = task.PalletCode }.ToJson(); |
| | | DateTime startTime = DateTime.Now; |
| | | |
| | | var result = _httpClientHelper.Post<WebResponseContent>( |
| | | configKey, |
| | | requestParam); |
| | | |
| | | if (!result.IsSuccess || !result.Data.Status) |
| | | { |
| | | QuartzLogHelper.LogError(_logger, $"è°ç¨WMSæ¥å£å¤±è´¥,æ¥å£:ã{configKey}ã,请æ±åæ°:ã{requestParam}ã,é误信æ¯:ã{result.Data?.Message}ã", "InboundTaskFlowService"); |
| | | return WebResponseContent.Instance.Error($"è°ç¨WMSæ¥å£è·åä»»å¡ç®æ å°å失败,ä»»å¡å·:ã{task.TaskNum}ã,é误信æ¯:ã{result.Data?.Message}ã"); |
| | | } |
| | | |
| | | QuartzLogHelper.LogInfo(_logger, $"è°ç¨WMSæ¥å£æå,æ¥å£:ã{configKey}ã,ååºæ°æ®:ã{result.Data?.Data}ã,èæ¶:{(DateTime.Now - startTime).TotalMilliseconds}ms", "InboundTaskFlowService"); |
| | | |
| | | string? nextAddress = result.Data.Data?.ToString(); |
| | | if (string.IsNullOrEmpty(nextAddress)) |
| | | return WebResponseContent.Instance.Error($"è°ç¨WMSæ¥å£è·åä»»å¡ç®æ å°å失败,ä»»å¡å·:ã{task.TaskNum}ã,é误信æ¯:ãæªè·åå°ææçç®æ å°åã"); |
| | | |
| | | task.CurrentAddress = task.NextAddress; |
| | | task.NextAddress = nextAddress; |
| | | task.TargetAddress = nextAddress; |
| | | |
| | | return WebResponseContent.Instance.OK(); |
| | | private readonly ITaskRepository _taskRepository;
|
| | | private readonly HttpClientHelper _httpClientHelper;
|
| | | private readonly ILogger _logger;
|
| | |
|
| | | /// <summary>
|
| | | /// åå§åå
¥åºä»»å¡æµç¨æå¡ã
|
| | | /// </summary>
|
| | | /// <param name="routerService">è·¯ç±æå¡ã</param>
|
| | | /// <param name="httpClientHelper">WMSæ¥å£è°ç¨å¸®å©ç±»ã</param>
|
| | | public InboundTaskFlowService(IRouterService routerService, ITaskRepository taskRepository, HttpClientHelper httpClientHelper, ILogger logger)
|
| | | {
|
| | | _routerService = routerService;
|
| | | _taskRepository = taskRepository;
|
| | | _httpClientHelper = httpClientHelper;
|
| | | _logger = logger;
|
| | | }
|
| | |
|
| | | /// <summary>
|
| | | /// æ¥æ¶WMS任塿¶åå§åå
¥åºä»»å¡ã
|
| | | /// </summary>
|
| | | /// <param name="task">ä»»å¡å®ä½ã</param>
|
| | | /// <param name="source">WMSä»»å¡åå§æ°æ®ã</param>
|
| | | public WebResponseContent InitializeOnReceive([NotNull] Dt_Task task, [NotNull] WMSTaskDTO source)
|
| | | {
|
| | | WebResponseContent content = new WebResponseContent();
|
| | | Dt_Router routers = _routerService.QueryNextRoute(source.SourceAddress);
|
| | | if (routers.IsNullOrEmpty())
|
| | | {
|
| | | return content.Error("æªæ¾å°è·¯ç±ä¿¡æ¯");
|
| | | }
|
| | |
|
| | | task.TaskStatus = (int)TaskInStatusEnum.InNew;
|
| | | task.CurrentAddress = source.SourceAddress;
|
| | | task.NextAddress = routers.ChildPosi;
|
| | |
|
| | | return content.OK();
|
| | | }
|
| | |
|
| | | /// <summary>
|
| | | /// æ¨è¿å
¥åºä»»å¡ç¶æï¼å¹¶å¨å
³é®ç¶æè°ç¨WMSæ¥å£ã
|
| | | /// </summary>
|
| | | /// <param name="task">ä»»å¡å®ä½ã</param>
|
| | | /// <returns>æ¨è¿ç»æã</returns>
|
| | | public WebResponseContent MoveToNextStatus([NotNull] Dt_Task task)
|
| | | {
|
| | | if (task.TaskStatus >= (int)TaskInStatusEnum.InFinish)
|
| | | return WebResponseContent.Instance.Error($"该任å¡ç¶æä¸å¯è·³è½¬å°ä¸ä¸æ¥,ä»»å¡å·:ã{task.TaskNum}ã,ä»»å¡ç¶æ:ã{task.TaskStatus}ã");
|
| | |
|
| | | task.TaskStatus = task.TaskStatus.GetNextNotCompletedStatus<TaskInStatusEnum>();
|
| | | if (task.TaskStatus <= 0)
|
| | | return WebResponseContent.Instance.Error($"该任å¡ç¶æä¸å¯è·³è½¬å°ä¸ä¸æ¥,ä»»å¡å·:ã{task.TaskNum}ã,ä»»å¡ç¶æ:ã{task.TaskStatus}ã");
|
| | |
|
| | | if (task.TaskStatus == (int)TaskInStatusEnum.Line_InFinish)
|
| | | {
|
| | | return GetWMSInboundLocation(task);
|
| | | }
|
| | |
|
| | | return UpdateWMSTaskStatus(task);
|
| | | }
|
| | |
|
| | | /// <summary>
|
| | | /// å¤çå åæºå
¥åºå®æå¨ä½ã
|
| | | /// </summary>
|
| | | /// <param name="task">ä»»å¡å®ä½ã</param>
|
| | | /// <returns>å¤çç»æã</returns>
|
| | | public WebResponseContent CompleteStackerTask([NotNull] Dt_Task task)
|
| | | {
|
| | | WebResponseContent content = new WebResponseContent();
|
| | | if (task.TaskStatus != (int)TaskInStatusEnum.SC_InExecuting)
|
| | | {
|
| | | return WebResponseContent.Instance.OK();
|
| | | }
|
| | |
|
| | | int nextStatus = task.TaskStatus.GetNextNotCompletedStatus<TaskInStatusEnum>();
|
| | | task.TaskStatus = nextStatus;
|
| | | task.ModifyDate = DateTime.Now;
|
| | | task.Modifier = "System";
|
| | |
|
| | | var result = _httpClientHelper.Post<WebResponseContent>(
|
| | | nameof(ConfigKey.InboundFinishTaskAsync),
|
| | | (new CreateTaskDto { PalletCode = task.PalletCode }).ToJson());
|
| | |
|
| | | if (!result.IsSuccess || !result.Data.Status)
|
| | | {
|
| | | QuartzLogHelper.LogError(_logger, $"è°ç¨WMSæ¥å£å¤±è´¥,æ¥å£:ãInboundFinishTaskAsyncã,请æ±åæ°:ã{task.PalletCode}ã,é误信æ¯:ã{result.Data?.Message}ã", "InboundTaskFlowService");
|
| | | return content.Error($"éç¥WMSç³»ç»å åæºå
¥åºå®æå¤±è´¥,ä»»å¡å·:ã{task.TaskNum}ã,æçå·:ã{task.PalletCode}ã,é误信æ¯:ã{result.Data?.Message}ã");
|
| | | }
|
| | |
|
| | | QuartzLogHelper.LogInfo(_logger, $"è°ç¨WMSæ¥å£æå,æ¥å£:ãInboundFinishTaskAsyncã,ååºæ°æ®:ã{result.Data?.Data}ã,èæ¶:0ms", "InboundTaskFlowService");
|
| | | return content.OK($"éç¥WMSç³»ç»å åæºå
¥åºå®ææå,ä»»å¡å·:ã{task.TaskNum}ã,æçå·:ã{task.PalletCode}ã");
|
| | | }
|
| | |
|
| | | /// <summary>
|
| | | /// ä»WMSè·åå
¥åºç®æ å°åå¹¶ååä»»å¡ã
|
| | | /// </summary>
|
| | | /// <param name="task">ä»»å¡å®ä½ã</param>
|
| | | /// <returns>è°ç¨ç»æã</returns>
|
| | | private WebResponseContent GetWMSInboundLocation(Dt_Task task)
|
| | | {
|
| | | string configKey = nameof(ConfigKey.GetTasksLocation);
|
| | | string requestParam = new CreateTaskDto { PalletCode = task.PalletCode }.ToJson();
|
| | | DateTime startTime = DateTime.Now;
|
| | |
|
| | | var result = _httpClientHelper.Post<WebResponseContent>(
|
| | | configKey,
|
| | | requestParam);
|
| | |
|
| | | if (!result.IsSuccess || !result.Data.Status)
|
| | | {
|
| | | QuartzLogHelper.LogError(_logger, $"è°ç¨WMSæ¥å£å¤±è´¥,æ¥å£:ã{configKey}ã,请æ±åæ°:ã{requestParam}ã,é误信æ¯:ã{result.Data?.Message}ã", "InboundTaskFlowService");
|
| | | return WebResponseContent.Instance.Error($"è°ç¨WMSæ¥å£è·åä»»å¡ç®æ å°å失败,ä»»å¡å·:ã{task.TaskNum}ã,é误信æ¯:ã{result.Data?.Message}ã");
|
| | | }
|
| | |
|
| | | QuartzLogHelper.LogInfo(_logger, $"è°ç¨WMSæ¥å£æå,æ¥å£:ã{configKey}ã,ååºæ°æ®:ã{result.Data?.Data}ã,èæ¶:{(DateTime.Now - startTime).TotalMilliseconds}ms", "InboundTaskFlowService");
|
| | |
|
| | | string? nextAddress = result.Data.Data?.ToString();
|
| | | if (string.IsNullOrEmpty(nextAddress))
|
| | | return WebResponseContent.Instance.Error($"è°ç¨WMSæ¥å£è·åä»»å¡ç®æ å°å失败,ä»»å¡å·:ã{task.TaskNum}ã,é误信æ¯:ãæªè·åå°ææçç®æ å°åã");
|
| | |
|
| | | task.CurrentAddress = task.NextAddress;
|
| | | task.NextAddress = nextAddress;
|
| | | task.TargetAddress = nextAddress;
|
| | |
|
| | | return WebResponseContent.Instance.OK();
|
| | | }
|
| | |
|
| | | /// <summary>
|
| | |
| | |
|
| | | QuartzLogHelper.LogInfo(_logger, $"è°ç¨WMSæ¥å£æå,æ¥å£:ã{configKey}ã,ååºæ°æ®:ã{result.Data?.Data}ã,èæ¶:{(DateTime.Now - startTime).TotalMilliseconds}ms", "InboundTaskFlowService");
|
| | | return WebResponseContent.Instance.OK();
|
| | | } |
| | | } |
| | | } |
| | | }
|
| | | }
|
| | | }
|
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | //æ¤jsæä»¶æ¯ç¨æ¥èªå®ä¹æ©å±ä¸å¡ä»£ç ï¼å¯ä»¥æ©å±ä¸äºèªå®ä¹é¡µé¢æè
éæ°é
ç½®çæç代ç
|
| | |
|
| | | let extension = {
|
| | | components: {
|
| | | //æ¥è¯¢ç颿©å±ç»ä»¶
|
| | | gridHeader: "",
|
| | | gridBody: "",
|
| | | gridFooter: "",
|
| | | //æ°å»ºãç¼è¾å¼¹åºæ¡æ©å±ç»ä»¶
|
| | | modelHeader: "",
|
| | | modelBody: "",
|
| | | modelFooter: "",
|
| | | },
|
| | | tableAction: "",
|
| | | buttons: { view: [], box: [], detail: [] },
|
| | | methods: {
|
| | | onInit() {
|
| | | // æ·»å MESæä½å
|
| | | this.columns.push({
|
| | | title: "æä½",
|
| | | field: "æä½",
|
| | | align: "center",
|
| | | width: 200,
|
| | | fixed: "right",
|
| | | render: (h, { row, column, index }) => {
|
| | | return (
|
| | | <div>
|
| | | <el-button
|
| | | type="primary"
|
| | | size="small"
|
| | | onClick={($e) => {
|
| | | this.handleInbound(row);
|
| | | }}
|
| | | >
|
| | | è¿ç«
|
| | | </el-button>
|
| | | <el-button
|
| | | type="success"
|
| | | size="small"
|
| | | style="margin-left: 8px"
|
| | | onClick={($e) => {
|
| | | this.handleOutbound(row);
|
| | | }}
|
| | | >
|
| | | åºç«
|
| | | </el-button>
|
| | | </div>
|
| | | );
|
| | | },
|
| | | });
|
| | | },
|
| | |
|
| | | // æçè¿ç«æä½
|
| | | async handleInbound(row) {
|
| | | try {
|
| | | await this.$confirm(
|
| | | `确认æ§è¡æçè¿ç«æä½ï¼\næçç¼å·ï¼${row.palletCode}`,
|
| | | "è¿ç«ç¡®è®¤",
|
| | | {
|
| | | confirmButtonText: "确认",
|
| | | cancelButtonText: "åæ¶",
|
| | | type: "warning",
|
| | | },
|
| | | );
|
| | |
|
| | | const result = await this.http.post(
|
| | | "/api/StockInfo/inboundInContainer",
|
| | | {
|
| | | palletCode: row.palletCode,
|
| | | stockId: row.id,
|
| | | },
|
| | | "æ£å¨è°ç¨MESæ¥å£...",
|
| | | );
|
| | |
|
| | | if (result.status) {
|
| | | this.$Message.success(result.message || "æçè¿ç«æå");
|
| | | this.$refs.table.load();
|
| | | } else {
|
| | | this.$error(result.message || "æçè¿ç«å¤±è´¥");
|
| | | }
|
| | | } catch (error) {
|
| | | if (error !== "cancel") {
|
| | | this.$error(error.message || "ç½ç»é误ï¼è¯·ç¨åéè¯");
|
| | | }
|
| | | }
|
| | | },
|
| | |
|
| | | // æçåºç«æä½
|
| | | async handleOutbound(row) {
|
| | | try {
|
| | | await this.$confirm(
|
| | | `确认æ§è¡æçåºç«æä½ï¼\næçç¼å·ï¼${row.palletCode}`,
|
| | | "åºç«ç¡®è®¤",
|
| | | {
|
| | | confirmButtonText: "确认",
|
| | | cancelButtonText: "åæ¶",
|
| | | type: "warning",
|
| | | },
|
| | | );
|
| | |
|
| | | const result = await this.http.post(
|
| | | "/api/StockInfo/outboundInContainer",
|
| | | {
|
| | | palletCode: row.palletCode,
|
| | | stockId: row.id,
|
| | | },
|
| | | "æ£å¨è°ç¨MESæ¥å£...",
|
| | | );
|
| | |
|
| | | if (result.status) {
|
| | | this.$Message.success(result.message || "æçåºç«æå");
|
| | | this.$refs.table.load();
|
| | | } else {
|
| | | this.$error(result.message || "æçåºç«å¤±è´¥");
|
| | | }
|
| | | } catch (error) {
|
| | | if (error !== "cancel") {
|
| | | this.$error(error.message || "ç½ç»é误ï¼è¯·ç¨åéè¯");
|
| | | }
|
| | | }
|
| | | },
|
| | |
|
| | | onInited() {
|
| | | // æ¡æ¶åå§åé
ç½®å
|
| | | },
|
| | | searchBefore(param) {
|
| | | const stockStatusFilter1 = {
|
| | | name: "stockStatus",
|
| | | value: "6",
|
| | | displayType: "int"
|
| | | };
|
| | | const warehouseIdFilter1 = {
|
| | | name: "warehouseId",
|
| | | value: "3",
|
| | | displayType: "int"
|
| | | };
|
| | | if (!param.wheres) {
|
| | | param.wheres = [];
|
| | | }
|
| | | // å°è¿æ»¤æ¡ä»¶æ·»å å°æ¥è¯¢åæ°ä¸
|
| | | param.wheres.push(stockStatusFilter1);
|
| | | param.wheres.push(warehouseIdFilter1);
|
| | | return true;
|
| | | },
|
| | | searchAfter(result) {
|
| | | return result;
|
| | | },
|
| | | addBefore(formData) {
|
| | | return true;
|
| | | },
|
| | | updateBefore(formData) {
|
| | | return true;
|
| | | },
|
| | | rowClick({ row, column, event }) {
|
| | | this.$refs.table.$refs.table.toggleRowSelection(row);
|
| | | },
|
| | | modelOpenAfter(row) {
|
| | | // ç¹å»ç¼è¾ãæ°å»ºæé®å¼¹åºæ¡å
|
| | | },
|
| | | },
|
| | | };
|
| | |
|
| | | export default extension;
|
| | |
| | | path: '/outboundOrderDetail', |
| | | name: 'outboundOrderDetail', |
| | | component: () => import('@/views/outbound/outboundOrderDetail.vue') |
| | | }, { |
| | | }, |
| | | { |
| | | path: '/stockInfo', |
| | | name: 'stockInfo', |
| | | component: () => import('@/views/stock/stockInfo.vue') |
| | | }, { |
| | | }, |
| | | { |
| | | path: '/hcstockInfo', |
| | | name: 'hcstockInfo', |
| | | component: () => import('@/views/stock/hcstockInfo.vue') |
| | | }, |
| | | { |
| | | path: '/stockInfoDetail', |
| | | name: 'stockInfoDetail', |
| | | component: () => import('@/views/stock/stockInfoDetail.vue') |
| | |
| | | <template> |
| | | <div class="dashboard-container"> |
| | | <!-- åä»åºæåº¦åºå
¥åºå¯¹æ¯å¾ --> |
| | | <!-- é¡¶é¨KPIå¡çï¼æ¾ç¤ºä»åºæ»æ°åæ»åºåé --> |
| | | <div class="kpi-cards"> |
| | | <div class="kpi-card"> |
| | | <div class="kpi-icon">ðï¸</div> |
| | | <div class="kpi-info"> |
| | | <div class="kpi-label">ä»åºæ»æ°</div> |
| | | <div class="kpi-value">{{ totalWarehouses }}</div> |
| | | </div> |
| | | </div> |
| | | <div class="kpi-card"> |
| | | <div class="kpi-icon">ð¦</div> |
| | | <div class="kpi-info"> |
| | | <div class="kpi-label">æ»åºåé</div> |
| | | <div class="kpi-value">{{ totalStock.toLocaleString() }}</div> |
| | | </div> |
| | | </div> |
| | | <div class="kpi-card"> |
| | | <div class="kpi-icon">ð</div> |
| | | <div class="kpi-info"> |
| | | <div class="kpi-label">æ¬ææ»å
¥åº</div> |
| | | <div class="kpi-value">{{ monthlyInboundTotal.toLocaleString() }}</div> |
| | | </div> |
| | | </div> |
| | | <div class="kpi-card"> |
| | | <div class="kpi-icon">ð¤</div> |
| | | <div class="kpi-info"> |
| | | <div class="kpi-label">æ¬ææ»åºåº</div> |
| | | <div class="kpi-value">{{ monthlyOutboundTotal.toLocaleString() }}</div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- é¡¶é¨ï¼æ¬æåºå
¥åºè¶å¿ - ä¸3ä¸2å¸å±ï¼æ¯ä¸ªå¡çç´æ¥æ¾ç¤ºä»åºæ°å --> |
| | | <div class="chart-row top-three"> |
| | | <div v-for="warehouse in topWarehouses" :key="warehouse.code" class="chart-card"> |
| | | <div class="card-title">{{ warehouse.name }}</div> |
| | | <!-- ä»åºæ°åæ¾ç¤ºåºå --> |
| | | <div class="warehouse-numbers"> |
| | | <div class="number-item inbound"> |
| | | <span class="number-label">å
¥åº</span> |
| | | <span class="number-value">{{ getMonthlyInbound(warehouse.code) }}</span> |
| | | </div> |
| | | <div class="number-item outbound"> |
| | | <span class="number-label">åºåº</span> |
| | | <span class="number-value">{{ getMonthlyOutbound(warehouse.code) }}</span> |
| | | </div> |
| | | <div class="number-item stock"> |
| | | <span class="number-label">åºå</span> |
| | | <span class="number-value">{{ getWarehouseStock(warehouse.code) }}</span> |
| | | </div> |
| | | </div> |
| | | <div :id="`chart-${warehouse.code}`" class="chart-content"></div> |
| | | </div> |
| | | </div> |
| | | |
| | | <div class="chart-row bottom-two"> |
| | | <div v-for="warehouse in bottomWarehouses" :key="warehouse.code" class="chart-card"> |
| | | <div class="card-title">{{ warehouse.name }}</div> |
| | | <!-- ä»åºæ°åæ¾ç¤ºåºå --> |
| | | <div class="warehouse-numbers"> |
| | | <div class="number-item inbound"> |
| | | <span class="number-label">å
¥åº</span> |
| | | <span class="number-value">{{ getMonthlyInbound(warehouse.code) }}</span> |
| | | </div> |
| | | <div class="number-item outbound"> |
| | | <span class="number-label">åºåº</span> |
| | | <span class="number-value">{{ getMonthlyOutbound(warehouse.code) }}</span> |
| | | </div> |
| | | <div class="number-item stock"> |
| | | <span class="number-label">åºå</span> |
| | | <span class="number-value">{{ getWarehouseStock(warehouse.code) }}</span> |
| | | </div> |
| | | </div> |
| | | <div :id="`chart-${warehouse.code}`" class="chart-content"></div> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- æ¯æ¥åºå
¥åºè¶å¿ (å
¨å®½) --> |
| | | <div class="chart-row full-width"> |
| | | <div class="chart-card"> |
| | | <div class="card-title">åä»åºæåº¦åºå
¥åºå¯¹æ¯</div> |
| | | <div id="chart-warehouse-monthly" class="chart-content"></div> |
| | | <div class="card-title">æ¯æ¥åºå
¥åºè¶å¿</div> |
| | | <div id="chart-daily" class="chart-content"></div> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- ä»åºåå¸ --> |
| | | <div class="chart-row"> |
| | | <div class="chart-card"> |
| | | <div class="card-title">åä»åºåºååå¸</div> |
| | | <div id="chart-warehouse" class="chart-content"></div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | |
| | | data() { |
| | | return { |
| | | charts: {}, |
| | | monthlyData: [], |
| | | warehouseNames: ['FJSC1', 'ZJSC1', 'GWSC1', 'CWSC1', 'HCSC1'] |
| | | // äºä¸ªä»åºå®ä¹ - ä¸3个 |
| | | topWarehouses: [ |
| | | { code: "GWSC1", name: "髿¸©1å·ä»åº" }, |
| | | { code: "CWSC1", name: "常温1å·ä»åº" }, |
| | | { code: "HCSC1", name: "å容1å·ä»åº" } |
| | | ], |
| | | // ä¸2个 |
| | | bottomWarehouses: [ |
| | | { code: "FJSC1", name: "è´æå·1å·ä»åº" }, |
| | | { code: "ZJSC1", name: "æ£æå·1å·ä»åº" } |
| | | ], |
| | | dailyData: [], |
| | | // å卿¯ä¸ªä»åºçæåº¦æ°æ® |
| | | monthlyData: { |
| | | GWSC1: [], |
| | | CWSC1: [], |
| | | HCSC1: [], |
| | | FJSC1: [], |
| | | ZJSC1: [] |
| | | }, |
| | | // å卿¯ä¸ªä»åºçå½ååºå |
| | | warehouseStocks: { |
| | | GWSC1: 0, |
| | | CWSC1: 0, |
| | | HCSC1: 0, |
| | | FJSC1: 0, |
| | | ZJSC1: 0 |
| | | }, |
| | | warehouseData: [], |
| | | // KPI æ±æ»æ°æ® |
| | | totalWarehouses: 5, |
| | | totalStock: 0, |
| | | monthlyInboundTotal: 0, |
| | | monthlyOutboundTotal: 0 |
| | | }; |
| | | }, |
| | | mounted() { |
| | |
| | | }, |
| | | beforeUnmount() { |
| | | window.removeEventListener("resize", this.handleResize); |
| | | Object.values(this.charts).forEach(chart => chart.dispose()); |
| | | Object.values(this.charts).forEach(chart => chart && chart.dispose()); |
| | | }, |
| | | methods: { |
| | | handleResize() { |
| | | Object.values(this.charts).forEach(chart => chart.resize()); |
| | | Object.values(this.charts).forEach(chart => chart && chart.resize()); |
| | | }, |
| | | |
| | | initCharts() { |
| | | this.charts.warehouseMonthly = echarts.init(document.getElementById("chart-warehouse-monthly")); |
| | | // åå§åææä»åºå¾è¡¨ |
| | | const allWarehouses = [...this.topWarehouses, ...this.bottomWarehouses]; |
| | | allWarehouses.forEach(warehouse => { |
| | | const chartId = `chart-${warehouse.code}`; |
| | | const el = document.getElementById(chartId); |
| | | if (el) { |
| | | this.charts[warehouse.code] = echarts.init(el); |
| | | } |
| | | }); |
| | | // åå§åæ¯æ¥å¾è¡¨åä»åºåå¸å¾è¡¨ |
| | | this.charts.daily = echarts.init(document.getElementById("chart-daily")); |
| | | this.charts.warehouse = echarts.init(document.getElementById("chart-warehouse")); |
| | | }, |
| | | |
| | | async loadData() { |
| | | await this.loadMonthlyStats(); |
| | | // å¹¶è¡å è½½ææä»åºçæåº¦æ°æ®ï¼åå«ä¼ å
¥ä¸åçRoadwayåæ°ï¼ |
| | | const allWarehouses = [...this.topWarehouses, ...this.bottomWarehouses]; |
| | | const monthlyPromises = allWarehouses.map(warehouse => |
| | | this.loadMonthlyStatsForWarehouse(warehouse.code) |
| | | ); |
| | | await Promise.all(monthlyPromises); |
| | | // æ´æ°ææä»åºçæåº¦å¾è¡¨ |
| | | this.updateAllMonthlyTrendCharts(); |
| | | |
| | | await this.loadDailyStats(); |
| | | await this.loadStockByWarehouse(); |
| | | await this.loadWarehouseStocks(); |
| | | this.calculateKPIs(); |
| | | }, |
| | | |
| | | async loadMonthlyStats() { |
| | | async loadMonthlyStatsForWarehouse(roadway) { |
| | | console.log(`æ£å¨å è½½${roadway}çæ¯æç»è®¡æ°æ®...`); |
| | | try { |
| | | const promises = this.warehouseNames.map(warehouse => |
| | | this.http.get("/api/Dashboard/MonthlyStats", { |
| | | months: 6, |
| | | Roadway: warehouse |
| | | }) |
| | | ); |
| | | |
| | | const results = await Promise.all(promises); |
| | | |
| | | this.monthlyData = results.map((res, index) => ({ |
| | | warehouse: this.warehouseNames[index], |
| | | warehouseName: this.getWarehouseName(this.warehouseNames[index]), |
| | | data: res.data || [] |
| | | })); |
| | | |
| | | this.updateWarehouseMonthlyChart(); |
| | | // å
³é®ä¿®å¤ï¼åå«ä¼ å
¥ä¸åçRoadwayåæ° |
| | | const res = await this.http.get("/api/Dashboard/MonthlyStats?monthly=12&roadway=" + roadway); |
| | | if (res.status && res.data) { |
| | | console.log(`${roadway} æ¯æç»è®¡æ°æ®:`, res.data); |
| | | this.monthlyData[roadway] = res.data; |
| | | } else { |
| | | this.monthlyData[roadway] = []; |
| | | } |
| | | } catch (e) { |
| | | console.error("å è½½æ¯æç»è®¡å¤±è´¥", e); |
| | | console.error(`å è½½${roadway}æ¯æç»è®¡å¤±è´¥`, e); |
| | | this.monthlyData[roadway] = []; |
| | | } |
| | | }, |
| | | |
| | | getWarehouseName(code) { |
| | | const nameMap = { |
| | | 'FJSC1': 'è´æå·1å·ä»åº', |
| | | 'ZJSC1': 'æ£æå·1å·ä»åº', |
| | | 'GWSC1': '髿¸©1å·ä»åº', |
| | | 'CWSC1': '常温1å·ä»åº', |
| | | 'HCSC1': 'å容1å·ä»åº' |
| | | }; |
| | | return nameMap[code] || code; |
| | | async loadDailyStats() { |
| | | try { |
| | | const res = await this.http.get("/api/Dashboard/DailyStats", { days: 30 }); |
| | | if (res.status && res.data) { |
| | | console.log("æ¯æ¥ç»è®¡æ°æ®:", res.data); |
| | | this.dailyData = res.data; |
| | | this.updateDailyChart(); |
| | | } |
| | | } catch (e) { |
| | | console.error("å è½½æ¯æ¥ç»è®¡å¤±è´¥", e); |
| | | } |
| | | }, |
| | | |
| | | updateWarehouseMonthlyChart() { |
| | | // è·åæææä»½ |
| | | const months = this.monthlyData[0]?.data.map(d => `${d.month}æ`) || []; |
| | | |
| | | // 为æ¯ä¸ªä»åºçæç³»åæ°æ® |
| | | const series = []; |
| | | |
| | | this.monthlyData.forEach((warehouseData, index) => { |
| | | const data = warehouseData.data; |
| | | |
| | | series.push({ |
| | | name: warehouseData.warehouseName, |
| | | type: 'bar', |
| | | data: data.map(d => ({ |
| | | value: (d.inbound || 0) + (d.outbound || 0), |
| | | inbound: d.inbound || 0, |
| | | outbound: d.outbound || 0, |
| | | label: { |
| | | show: true, |
| | | position: 'top', |
| | | formatter: function(params) { |
| | | return `å
¥:${params.data.inbound}\nåº:${params.data.outbound}`; |
| | | }, |
| | | fontSize: 10, |
| | | color: '#fff', |
| | | lineHeight: 15 |
| | | async loadStockByWarehouse() { |
| | | try { |
| | | const res = await this.http.get("/api/Dashboard/StockByWarehouse"); |
| | | if (res.status && res.data) { |
| | | console.log("ä»åºå叿°æ®:", res.data); |
| | | this.warehouseData = res.data.data || res.data; |
| | | this.updateWarehouseChart(); |
| | | } |
| | | } catch (e) { |
| | | console.error("å è½½ä»åºåå¸å¤±è´¥", e); |
| | | } |
| | | }, |
| | | |
| | | async loadWarehouseStocks() { |
| | | // 模æå è½½æ¯ä¸ªä»åºçå½ååºåé |
| | | // 妿åç«¯ææ¥å£ï¼å¯ä»¥æ¿æ¢ä¸ºçå®APIè°ç¨ |
| | | try { |
| | | // å°è¯å è½½åºåæ°æ®ï¼å¦ææ¥å£ä¸åå¨åä½¿ç¨æ¨¡ææ°æ® |
| | | const allWarehouses = [...this.topWarehouses, ...this.bottomWarehouses]; |
| | | for (const warehouse of allWarehouses) { |
| | | try { |
| | | const res = await this.http.get(`/api/Dashboard/WarehouseStock?warehouse=${warehouse.code}`); |
| | | if (res.status && res.data) { |
| | | this.warehouseStocks[warehouse.code] = res.data.stock || 0; |
| | | } else { |
| | | // ä»æåº¦æ°æ®ä¸è®¡ç®æ¨¡æåºåï¼æè¿æä»½ç´¯è®¡å
¥åº-åºåºï¼ |
| | | const monthlyData = this.monthlyData[warehouse.code] || []; |
| | | let totalInbound = 0; |
| | | let totalOutbound = 0; |
| | | monthlyData.forEach(m => { |
| | | totalInbound += (m.inbound ?? m.Inbound) || 0; |
| | | totalOutbound += (m.outbound ?? m.Outbound) || 0; |
| | | }); |
| | | this.warehouseStocks[warehouse.code] = Math.max(0, totalInbound - totalOutbound); |
| | | } |
| | | })), |
| | | barWidth: '15%', |
| | | barGap: '10%', |
| | | itemStyle: { |
| | | color: this.getBarColor(index), |
| | | borderRadius: [3, 3, 0, 0] |
| | | } catch (e) { |
| | | // ä½¿ç¨æ¨¡ææ°æ® |
| | | const mockStocks = { |
| | | GWSC1: 12580, |
| | | CWSC1: 8920, |
| | | HCSC1: 15600, |
| | | FJSC1: 4300, |
| | | ZJSC1: 7200 |
| | | }; |
| | | this.warehouseStocks[warehouse.code] = mockStocks[warehouse.code] || 0; |
| | | } |
| | | }); |
| | | } |
| | | } catch (e) { |
| | | console.error("å è½½ä»åºåºå失败", e); |
| | | } |
| | | }, |
| | | |
| | | getMonthlyInbound(warehouseCode) { |
| | | const data = this.monthlyData[warehouseCode] || []; |
| | | if (data.length === 0) return 0; |
| | | // è·åæè¿ä¸ä¸ªæï¼æå䏿¡ï¼çå
¥åºæ° |
| | | const latest = data[data.length - 1]; |
| | | return (latest.inbound ?? latest.Inbound) || 0; |
| | | }, |
| | | |
| | | getMonthlyOutbound(warehouseCode) { |
| | | const data = this.monthlyData[warehouseCode] || []; |
| | | if (data.length === 0) return 0; |
| | | // è·åæè¿ä¸ä¸ªæï¼æå䏿¡ï¼çåºåºæ° |
| | | const latest = data[data.length - 1]; |
| | | return (latest.outbound ?? latest.Outbound) || 0; |
| | | }, |
| | | |
| | | getWarehouseStock(warehouseCode) { |
| | | return this.warehouseStocks[warehouseCode] || 0; |
| | | }, |
| | | |
| | | calculateKPIs() { |
| | | // è®¡ç®æ»åºå |
| | | let totalStock = 0; |
| | | for (const code in this.warehouseStocks) { |
| | | totalStock += this.warehouseStocks[code]; |
| | | } |
| | | this.totalStock = totalStock; |
| | | |
| | | // è®¡ç®æ¬ææ»å
¥åºåæ»åºåºï¼ææä»åºæè¿ä¸ä¸ªæçåè®¡ï¼ |
| | | let totalInbound = 0; |
| | | let totalOutbound = 0; |
| | | const allWarehouses = [...this.topWarehouses, ...this.bottomWarehouses]; |
| | | allWarehouses.forEach(warehouse => { |
| | | totalInbound += this.getMonthlyInbound(warehouse.code); |
| | | totalOutbound += this.getMonthlyOutbound(warehouse.code); |
| | | }); |
| | | this.monthlyInboundTotal = totalInbound; |
| | | this.monthlyOutboundTotal = totalOutbound; |
| | | }, |
| | | |
| | | // æ´æ°ææä»åºçæåº¦è¶å¿å¾è¡¨ |
| | | updateAllMonthlyTrendCharts() { |
| | | const allWarehouses = [...this.topWarehouses, ...this.bottomWarehouses]; |
| | | allWarehouses.forEach(warehouse => { |
| | | this.updateMonthlyTrendChartForWarehouse(warehouse.code); |
| | | }); |
| | | }, |
| | | |
| | | updateMonthlyTrendChartForWarehouse(roadway) { |
| | | const chart = this.charts[roadway]; |
| | | if (!chart) return; |
| | | |
| | | const data = this.monthlyData[roadway] || []; |
| | | // å
¼å®¹å¤§å°ååæ®µå |
| | | const monthLabels = data.map(m => m.month || m.Month || ""); |
| | | const inboundData = data.map(m => { |
| | | const val = m.inbound ?? m.Inbound; |
| | | return val !== undefined && val !== null ? Number(val) : 0; |
| | | }); |
| | | const outboundData = data.map(m => { |
| | | const val = m.outbound ?? m.Outbound; |
| | | return val !== undefined && val !== null ? Number(val) : 0; |
| | | }); |
| | | |
| | | const option = { |
| | | title: { |
| | | text: 'åä»åºæåº¦åºå
¥åºå¯¹æ¯', |
| | | textStyle: { |
| | | color: '#00ffff', |
| | | fontSize: 16 |
| | | }, |
| | | left: 'center', |
| | | top: 10 |
| | | tooltip: { |
| | | trigger: "axis", |
| | | formatter: function(params) { |
| | | let result = params[0].axisValue + "<br/>"; |
| | | params.forEach(p => { |
| | | result += `${p.marker}${p.seriesName}: ${p.value}<br/>`; |
| | | }); |
| | | return result; |
| | | } |
| | | }, |
| | | legend: { |
| | | data: ["å
¥åº", "åºåº"], |
| | | textStyle: { color: "#fff" }, |
| | | top: 0, |
| | | right: 10, |
| | | itemWidth: 20, |
| | | itemHeight: 12 |
| | | }, |
| | | grid: { |
| | | left: "8%", |
| | | right: "8%", |
| | | top: "18%", |
| | | bottom: "12%", |
| | | containLabel: true |
| | | }, |
| | | xAxis: { |
| | | type: "category", |
| | | data: monthLabels, |
| | | axisLabel: { |
| | | color: "#ccc", |
| | | rotate: 45, |
| | | fontSize: 10, |
| | | interval: 0, |
| | | margin: 8 |
| | | }, |
| | | axisLine: { lineStyle: { color: "#4a5b6e" } } |
| | | }, |
| | | yAxis: { |
| | | type: "value", |
| | | name: "任塿°é", |
| | | nameTextStyle: { color: "#ccc", fontSize: 11 }, |
| | | axisLabel: { color: "#ccc" }, |
| | | splitLine: { lineStyle: { color: "#2a3a4a", type: "dashed" } } |
| | | }, |
| | | series: [ |
| | | { |
| | | name: "å
¥åº", |
| | | type: "bar", |
| | | data: inboundData, |
| | | itemStyle: { |
| | | color: "#5470c6", |
| | | borderRadius: [4, 4, 0, 0] |
| | | }, |
| | | barWidth: "35%", |
| | | label: { |
| | | show: inboundData.length <= 8, |
| | | position: "top", |
| | | color: "#5470c6", |
| | | fontSize: 10 |
| | | } |
| | | }, |
| | | { |
| | | name: "åºåº", |
| | | type: "line", |
| | | data: outboundData, |
| | | symbol: "circle", |
| | | symbolSize: 6, |
| | | itemStyle: { color: "#91cc75" }, |
| | | lineStyle: { width: 2, type: "solid" }, |
| | | smooth: false, |
| | | label: { |
| | | show: outboundData.length <= 8, |
| | | position: "top", |
| | | color: "#91cc75", |
| | | fontSize: 10 |
| | | } |
| | | } |
| | | ] |
| | | }; |
| | | chart.setOption(option, true); |
| | | }, |
| | | |
| | | updateDailyChart() { |
| | | if (!this.charts.daily) return; |
| | | const option = { |
| | | tooltip: { trigger: "axis" }, |
| | | legend: { data: ["å
¥åº", "åºåº"], textStyle: { color: "#fff" } }, |
| | | xAxis: { |
| | | type: "category", |
| | | data: this.dailyData.map(d => d.date), |
| | | axisLabel: { |
| | | color: "#fff", |
| | | interval: 0, |
| | | rotate: 45, |
| | | fontSize: 12, |
| | | margin: 10 |
| | | }, |
| | | axisTick: { |
| | | alignWithLabel: true |
| | | } |
| | | }, |
| | | yAxis: { |
| | | type: "value", |
| | | axisLabel: { color: "#fff" } |
| | | }, |
| | | grid: { |
| | | left: "3%", |
| | | right: "4%", |
| | | bottom: "15%", |
| | | top: "10%", |
| | | containLabel: true |
| | | }, |
| | | series: [ |
| | | { |
| | | name: "å
¥åº", |
| | | type: "bar", |
| | | data: this.dailyData.map(d => d.inbound), |
| | | itemStyle: { color: "#5470c6" } |
| | | }, |
| | | { |
| | | name: "åºåº", |
| | | type: "bar", |
| | | data: this.dailyData.map(d => d.outbound), |
| | | itemStyle: { color: "#91cc75" } |
| | | } |
| | | ] |
| | | }; |
| | | this.charts.daily.setOption(option, true); |
| | | }, |
| | | |
| | | updateWarehouseChart() { |
| | | if (!this.charts.warehouse) return; |
| | | const warehouseNames = this.warehouseData.map(w => w.warehouse); |
| | | const hasStocks = this.warehouseData.map(w => w.hasStock); |
| | | const noStocks = this.warehouseData.map(w => w.noStock); |
| | | const hasStockPercentages = this.warehouseData.map(w => w.hasStockPercentage); |
| | | const noStockPercentages = this.warehouseData.map(w => w.noStockPercentage); |
| | | |
| | | const option = { |
| | | tooltip: { |
| | | trigger: 'axis', |
| | | trigger: "axis", |
| | | axisPointer: { |
| | | type: 'shadow' |
| | | type: "shadow" |
| | | }, |
| | | formatter: function(params) { |
| | | let tip = `<strong>${params[0].axisValue}</strong><br/>`; |
| | | let tip = params[0].name + "<br/>"; |
| | | params.forEach(param => { |
| | | tip += `<span style="display:inline-block;width:10px;height:10px;border-radius:50%;background:${param.color};margin-right:5px;"></span>`; |
| | | tip += `${param.seriesName}: `; |
| | | tip += `å
¥åº:${param.data.inbound} | åºåº:${param.data.outbound} | æ»è®¡:${param.value}<br/>`; |
| | | const dataIndex = param.dataIndex; |
| | | const warehouse = window.homeComponent?.warehouseData[dataIndex]; |
| | | if (warehouse) { |
| | | if (param.seriesName === "å·²ç¨å®¹é") { |
| | | tip += `${param.marker}${param.seriesName}: ${param.value} (${warehouse.hasStockPercentage})<br/>`; |
| | | tip += `æåºå: ${warehouse.hasStock}<br/>`; |
| | | tip += `æ åºå: ${warehouse.noStock}<br/>`; |
| | | tip += `æ»å®¹é: ${warehouse.total}`; |
| | | } else if (param.seriesName === "å©ä½å®¹é") { |
| | | tip += `${param.marker}${param.seriesName}: ${param.value} (${warehouse.noStockPercentage})<br/>`; |
| | | } |
| | | } else { |
| | | tip += `${param.marker}${param.seriesName}: ${param.value}<br/>`; |
| | | } |
| | | }); |
| | | return tip; |
| | | } |
| | | }, |
| | | legend: { |
| | | data: this.monthlyData.map(d => d.warehouseName), |
| | | textStyle: { color: '#fff', fontSize: 11 }, |
| | | top: 45, |
| | | left: 'center', |
| | | type: 'scroll' |
| | | }, |
| | | grid: { |
| | | left: '3%', |
| | | right: '4%', |
| | | bottom: '10%', |
| | | top: '20%', |
| | | containLabel: true |
| | | data: ["å·²ç¨å®¹é", "å©ä½å®¹é"], |
| | | textStyle: { color: "#fff" } |
| | | }, |
| | | xAxis: { |
| | | type: 'category', |
| | | data: months, |
| | | axisLabel: { |
| | | color: '#fff', |
| | | fontSize: 11 |
| | | }, |
| | | axisLine: { |
| | | lineStyle: { color: 'rgba(255,255,255,0.3)' } |
| | | }, |
| | | splitLine: { |
| | | show: true, |
| | | lineStyle: { |
| | | color: 'rgba(255,255,255,0.1)', |
| | | type: 'dashed' |
| | | } |
| | | } |
| | | type: "category", |
| | | data: warehouseNames, |
| | | axisLabel: { color: "#fff", rotate: 30, interval: 0 } |
| | | }, |
| | | yAxis: { |
| | | type: 'value', |
| | | name: 'æ°é', |
| | | nameTextStyle: { color: '#fff' }, |
| | | axisLabel: { color: '#fff' }, |
| | | splitLine: { |
| | | lineStyle: { |
| | | color: 'rgba(255,255,255,0.1)', |
| | | type: 'dashed' |
| | | } |
| | | } |
| | | type: "value", |
| | | name: "容é", |
| | | axisLabel: { color: "#fff" } |
| | | }, |
| | | dataZoom: [ |
| | | series: [ |
| | | { |
| | | type: 'inside', |
| | | start: 0, |
| | | end: 100 |
| | | name: "å·²ç¨å®¹é", |
| | | type: "bar", |
| | | data: hasStocks.map((value, index) => ({ |
| | | value: value, |
| | | label: { |
| | | show: true, |
| | | position: "top", |
| | | formatter: (params) => { |
| | | const pct = hasStockPercentages[params.dataIndex]; |
| | | return `${params.value} (${pct})`; |
| | | }, |
| | | color: "#91cc75", |
| | | fontSize: 11 |
| | | } |
| | | })), |
| | | itemStyle: { color: "#91cc75" } |
| | | }, |
| | | { |
| | | start: 0, |
| | | end: 100, |
| | | height: 20, |
| | | bottom: 0, |
| | | borderColor: 'rgba(255,255,255,0.3)', |
| | | fillerColor: 'rgba(0,255,255,0.1)', |
| | | handleStyle: { |
| | | color: '#00ffff', |
| | | borderColor: '#00ffff' |
| | | }, |
| | | textStyle: { |
| | | color: '#fff' |
| | | } |
| | | name: "å©ä½å®¹é", |
| | | type: "bar", |
| | | data: noStocks.map((value, index) => ({ |
| | | value: value, |
| | | label: { |
| | | show: true, |
| | | position: "top", |
| | | formatter: (params) => { |
| | | const pct = noStockPercentages[params.dataIndex]; |
| | | return `${params.value} (${pct})`; |
| | | }, |
| | | color: "#fac858", |
| | | fontSize: 11 |
| | | } |
| | | })), |
| | | itemStyle: { color: "#fac858" } |
| | | } |
| | | ], |
| | | series: series |
| | | ] |
| | | }; |
| | | |
| | | this.charts.warehouseMonthly.setOption(option, true); |
| | | }, |
| | | |
| | | getBarColor(index) { |
| | | const colors = [ |
| | | '#5470c6', // è |
| | | '#fac858', // é» |
| | | '#73c0de', // 天è |
| | | '#fc8452', // æ© |
| | | '#ea7ccc' // ç² |
| | | ]; |
| | | return colors[index] || '#5470c6'; |
| | | window.homeComponent = this; |
| | | this.charts.warehouse.setOption(option, true); |
| | | } |
| | | } |
| | | }; |
| | |
| | | background-attachment: fixed; |
| | | } |
| | | |
| | | .chart-row { |
| | | /* KPI å¡çæ ·å¼ */ |
| | | .kpi-cards { |
| | | display: grid; |
| | | grid-template-columns: repeat(4, 1fr); |
| | | gap: 20px; |
| | | margin-bottom: 24px; |
| | | } |
| | | |
| | | .kpi-card { |
| | | background: rgba(10, 16, 35, 0.7); |
| | | backdrop-filter: blur(10px); |
| | | border: 1px solid rgba(64, 224, 208, 0.3); |
| | | border-radius: 16px; |
| | | padding: 16px 20px; |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 16px; |
| | | transition: all 0.3s ease; |
| | | box-shadow: 0 0 15px rgba(0, 255, 255, 0.1); |
| | | } |
| | | |
| | | .kpi-card:hover { |
| | | transform: translateY(-3px); |
| | | border-color: rgba(64, 224, 208, 0.6); |
| | | box-shadow: 0 0 25px rgba(0, 255, 255, 0.2); |
| | | } |
| | | |
| | | .kpi-icon { |
| | | font-size: 32px; |
| | | opacity: 0.9; |
| | | } |
| | | |
| | | .kpi-info { |
| | | flex: 1; |
| | | } |
| | | |
| | | .kpi-label { |
| | | font-size: 13px; |
| | | color: #8ba0b5; |
| | | margin-bottom: 6px; |
| | | letter-spacing: 1px; |
| | | } |
| | | |
| | | .kpi-value { |
| | | font-size: 28px; |
| | | font-weight: 700; |
| | | color: #00ffff; |
| | | text-shadow: 0 0 10px rgba(0, 255, 255, 0.5); |
| | | line-height: 1.2; |
| | | } |
| | | |
| | | /* ä¸3个å¾è¡¨å¸å± */ |
| | | .chart-row.top-three { |
| | | display: grid; |
| | | grid-template-columns: repeat(3, 1fr); |
| | | gap: 20px; |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | /* ä¸2个å¾è¡¨å¸å± */ |
| | | .chart-row.bottom-two { |
| | | display: grid; |
| | | grid-template-columns: repeat(2, 1fr); |
| | | gap: 20px; |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .chart-row.full-width { |
| | | width: 100%; |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .chart-card { |
| | | flex: 1; |
| | | background: rgba(10, 16, 35, 0.6); |
| | | backdrop-filter: blur(10px); |
| | | border: 1px solid rgba(64, 224, 208, 0.3); |
| | | border-radius: 12px; |
| | | padding: 15px; |
| | | position: relative; |
| | | box-shadow: |
| | | 0 0 15px rgba(0, 255, 255, 0.1), |
| | | inset 0 0 10px rgba(64, 224, 208, 0.1); |
| | | box-shadow: 0 0 15px rgba(0, 255, 255, 0.1), inset 0 0 10px rgba(64, 224, 208, 0.1); |
| | | transition: all 0.3s ease; |
| | | overflow: hidden; |
| | | } |
| | | |
| | | .chart-card:hover { |
| | | transform: translateY(-5px); |
| | | box-shadow: |
| | | 0 0 25px rgba(0, 255, 255, 0.3), |
| | | inset 0 0 15px rgba(64, 224, 208, 0.2); |
| | | box-shadow: 0 0 25px rgba(0, 255, 255, 0.3), inset 0 0 15px rgba(64, 224, 208, 0.2); |
| | | border: 1px solid rgba(64, 224, 208, 0.6); |
| | | } |
| | | |
| | |
| | | box-shadow: 2px -2px 10px #00ffff, 0 0 10px rgba(0, 255, 255, 0.7); |
| | | } |
| | | |
| | | .chart-card::before, |
| | | .chart-card::after { |
| | | animation: neon-flicker 2s infinite alternate; |
| | | } |
| | | |
| | | @keyframes neon-flicker { |
| | | 0%, |
| | | 100% { |
| | | opacity: 1; |
| | | box-shadow: -2px -2px 10px #00ffff, 0 0 10px rgba(0, 255, 255, 0.7); |
| | | } |
| | | 50% { |
| | | opacity: 0.8; |
| | | box-shadow: -2px -2px 5px #00ffff, 0 0 5px rgba(0, 255, 255, 0.5); |
| | | } |
| | | } |
| | | |
| | | .card-title { |
| | | color: #00ffff; |
| | | font-size: 16px; |
| | | font-size: 15px; |
| | | text-align: center; |
| | | margin-bottom: 10px; |
| | | margin-bottom: 12px; |
| | | text-shadow: 0 0 10px rgba(0, 255, 255, 0.7); |
| | | font-weight: 500; |
| | | white-space: nowrap; |
| | | overflow: hidden; |
| | | text-overflow: ellipsis; |
| | | } |
| | | |
| | | /* ä»åºæ°åæ¾ç¤ºåºå */ |
| | | .warehouse-numbers { |
| | | display: flex; |
| | | justify-content: space-around; |
| | | margin-bottom: 12px; |
| | | padding: 8px 0; |
| | | background: rgba(0, 0, 0, 0.3); |
| | | border-radius: 8px; |
| | | } |
| | | |
| | | .number-item { |
| | | text-align: center; |
| | | flex: 1; |
| | | } |
| | | |
| | | .number-label { |
| | | display: block; |
| | | font-size: 11px; |
| | | color: #8ba0b5; |
| | | margin-bottom: 4px; |
| | | } |
| | | |
| | | .number-value { |
| | | display: block; |
| | | font-size: 20px; |
| | | font-weight: 700; |
| | | letter-spacing: 1px; |
| | | } |
| | | |
| | | .number-item.inbound .number-value { |
| | | color: #5470c6; |
| | | } |
| | | |
| | | .number-item.outbound .number-value { |
| | | color: #91cc75; |
| | | } |
| | | |
| | | .number-item.stock .number-value { |
| | | color: #fac858; |
| | | } |
| | | |
| | | .chart-content { |
| | | height: 500px; |
| | | height: 240px; |
| | | width: 100%; |
| | | } |
| | | |
| | | /* å
¨å®½å¾è¡¨ */ |
| | | .full-width .chart-card { |
| | | flex: none; |
| | | width: 100%; |
| | | } |
| | | |
| | | .full-width .chart-content { |
| | | height: 500px; |
| | | height: 350px; |
| | | } |
| | | |
| | | /* ååºå¼è°æ´ */ |
| | | @media (max-width: 1024px) { |
| | | .kpi-cards { |
| | | grid-template-columns: repeat(2, 1fr); |
| | | } |
| | | .chart-row.top-three { |
| | | grid-template-columns: repeat(2, 1fr); |
| | | } |
| | | .chart-row.bottom-two { |
| | | grid-template-columns: repeat(2, 1fr); |
| | | } |
| | | } |
| | | |
| | | @media (max-width: 768px) { |
| | | .kpi-cards { |
| | | grid-template-columns: 1fr; |
| | | } |
| | | .chart-row.top-three { |
| | | grid-template-columns: 1fr; |
| | | } |
| | | .chart-row.bottom-two { |
| | | grid-template-columns: 1fr; |
| | | } |
| | | .chart-content { |
| | | height: 220px; |
| | | } |
| | | .full-width .chart-content { |
| | | height: 280px; |
| | | } |
| | | .card-title { |
| | | font-size: 13px; |
| | | white-space: normal; |
| | | } |
| | | .number-value { |
| | | font-size: 16px; |
| | | } |
| | | } |
| | | |
| | | /* æ·»å ç½æ ¼çº¿ææ */ |
| | | .dashboard-container::before { |
| | | content: ""; |
| | | position: fixed; |
| | |
| | | left: 0; |
| | | right: 0; |
| | | bottom: 0; |
| | | background-image: |
| | | linear-gradient(rgba(64, 224, 208, 0.05) 1px, transparent 1px), |
| | | background-image: linear-gradient(rgba(64, 224, 208, 0.05) 1px, transparent 1px), |
| | | linear-gradient(90deg, rgba(64, 224, 208, 0.05) 1px, transparent 1px); |
| | | background-size: 30px 30px; |
| | | pointer-events: none; |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template>
|
| | | <view-grid ref="grid" :columns="columns" :detail="detail" :editFormFields="editFormFields"
|
| | | :editFormOptions="editFormOptions" :searchFormFields="searchFormFields" :searchFormOptions="searchFormOptions"
|
| | | :table="table" :tableExpand="tableExpand" :extend="extend">
|
| | | </view-grid>
|
| | | </template>
|
| | |
|
| | | <script>
|
| | | import extend from "@/extension/stock/hcstockInfo.jsx";
|
| | | import {
|
| | | defineComponent,
|
| | | getCurrentInstance,
|
| | | h,
|
| | | reactive,
|
| | | ref,
|
| | | resolveComponent,
|
| | | } from "vue";
|
| | |
|
| | | const TEXT = {
|
| | | pageName: "åºåä¿¡æ¯",
|
| | | palletCode: "æçç¼å·",
|
| | | stockStatus: "åºåç¶æ",
|
| | | locationCode: "è´§ä½ç¼å·",
|
| | | outboundDate: "åºåºæ¶é´",
|
| | | warehouse: "ä»åº",
|
| | | creator: "å建人",
|
| | | createDate: "å建æ¶é´",
|
| | | modifier: "ä¿®æ¹äºº",
|
| | | modifyDate: "ä¿®æ¹æ¶é´",
|
| | | detailName: "åºåæç»",
|
| | | materielName: "ç©æåç§°",
|
| | | serialNumber: "çµè¯ç ",
|
| | | stockQuantity: "åºåæ°é",
|
| | | status: "ç¶æ",
|
| | | inboundOrderRowNo: "ééå·",
|
| | | detailLoading: "åºåæç»å è½½ä¸...",
|
| | | detailLoadFailed: "åºåæç»å 载失败",
|
| | | detailEmpty: "å½ååºåå¤´ææ æç»æ°æ®",
|
| | | expandPrefix: "æçï¼",
|
| | | expandMiddle: " / ",
|
| | | expandLocation: "è´§ä½ï¼",
|
| | | };
|
| | |
|
| | | export default defineComponent({
|
| | | setup() {
|
| | | const { proxy } = getCurrentInstance();
|
| | | const ElTable = resolveComponent("el-table");
|
| | | const ElTableColumn = resolveComponent("el-table-column");
|
| | |
|
| | | const table = ref({
|
| | | key: "id",
|
| | | footer: "Foots",
|
| | | cnName: TEXT.pageName,
|
| | | name: "stockInfo",
|
| | | url: "/StockInfo/",
|
| | | sortName: "id",
|
| | | });
|
| | |
|
| | | const editFormFields = ref({
|
| | | palletCode: "",
|
| | | palletType: 0,
|
| | | warehouseId: 0,
|
| | | mesUploadStatus: "",
|
| | | stockStatus: "",
|
| | | locationCode: "",
|
| | | locationDetails: ""
|
| | | });
|
| | |
|
| | |
|
| | | const editFormOptions = ref([
|
| | | [
|
| | | { field: "palletCode", title: TEXT.palletCode, type: "string" },
|
| | | { field: "stockStatus", title: TEXT.stockStatus, type: "select", dataKey: "stockStatusEmun", data: [] },
|
| | | { field: "locationCode", title: TEXT.locationCode, type: "string" },
|
| | | ],
|
| | | ]);
|
| | |
|
| | | const searchFormFields = ref({
|
| | | palletCode: "",
|
| | | warehouseId: "",
|
| | | stockStatus: "",
|
| | | locationCode: "",
|
| | | });
|
| | |
|
| | | const searchFormOptions = ref([
|
| | | [
|
| | | { title: TEXT.palletCode, field: "palletCode", type: "like" },
|
| | | { title: TEXT.warehouse, field: "warehouseId", type: "selectList", dataKey: "warehouseEnum", data: [] },
|
| | | { title: TEXT.stockStatus, field: "stockStatus", type: "selectList", dataKey: "stockStatusEmun", data: [] },
|
| | | { title: TEXT.locationCode, field: "locationCode", type: "like" },
|
| | | ],
|
| | | ]);
|
| | |
|
| | | const columns = ref([
|
| | | {
|
| | | field: "id",
|
| | | title: "Id",
|
| | | type: "int",
|
| | | width: 90,
|
| | | hidden: true,
|
| | | readonly: true,
|
| | | require: true,
|
| | | align: "left",
|
| | | },
|
| | | {
|
| | | field: "palletCode",
|
| | | title: TEXT.palletCode,
|
| | | type: "string",
|
| | | width: 150,
|
| | | align: "left",
|
| | | },
|
| | | {
|
| | | field: "stockStatus",
|
| | | title: TEXT.stockStatus,
|
| | | type: "int",
|
| | | width: 120,
|
| | | align: "left",
|
| | | bind: { key: "stockStatusEmun", data: [] },
|
| | | },
|
| | | {
|
| | | field: "mesUploadStatus",
|
| | | title: "MESç¶æ",
|
| | | type: "int",
|
| | | width: 120,
|
| | | align: "left",
|
| | | bind: { key: "mesUploadStatusEnum", data: [] },
|
| | | },
|
| | | {
|
| | | field: "outboundDate",
|
| | | title: TEXT.outboundDate,
|
| | | type: "string",
|
| | | width: 150,
|
| | | align: "left",
|
| | | }, |
| | | {
|
| | | field: "locationCode",
|
| | | title: TEXT.locationCode,
|
| | | type: "string",
|
| | | width: 120,
|
| | | align: "left",
|
| | | },
|
| | | {
|
| | | field: "warehouseId",
|
| | | title: TEXT.warehouse,
|
| | | type: "select",
|
| | | width: 100,
|
| | | align: "left",
|
| | | bind: { key: "warehouseEnum", data: [] },
|
| | | },
|
| | | {
|
| | | field: "creater",
|
| | | title: TEXT.creator,
|
| | | type: "string",
|
| | | width: 90,
|
| | | align: "left",
|
| | | },
|
| | | {
|
| | | field: "createDate",
|
| | | title: TEXT.createDate,
|
| | | type: "datetime",
|
| | | width: 160,
|
| | | align: "left",
|
| | | },
|
| | | {
|
| | | field: "modifier",
|
| | | title: TEXT.modifier,
|
| | | type: "string",
|
| | | width: 100,
|
| | | align: "left",
|
| | | hidden: true,
|
| | | },
|
| | | {
|
| | | field: "modifyDate",
|
| | | title: TEXT.modifyDate,
|
| | | type: "datetime",
|
| | | width: 160,
|
| | | align: "left",
|
| | | hidden: true,
|
| | | },
|
| | | ]);
|
| | |
|
| | | const detail = ref({
|
| | | cnName: "#detailCnName",
|
| | | table: "",
|
| | | columns: [],
|
| | | sortName: "",
|
| | | });
|
| | |
|
| | | const detailState = reactive({
|
| | | rowsMap: {},
|
| | | loadingMap: {},
|
| | | errorMap: {},
|
| | | });
|
| | |
|
| | | const stockStatusOptions = ref([]);
|
| | |
|
| | | const detailColumns = [
|
| | | { field: "materielName", title: TEXT.materielName, minWidth: 160 },
|
| | | { field: "serialNumber", title: TEXT.serialNumber, minWidth: 160 },
|
| | | { field: "stockQuantity", title: TEXT.stockQuantity, minWidth: 120 },
|
| | | { field: "status", title: TEXT.status, minWidth: 120 },
|
| | | { field: "inboundOrderRowNo", title: TEXT.inboundOrderRowNo, minWidth: 120 },
|
| | | ];
|
| | |
|
| | | const normalizeValue = (value) => {
|
| | | return value === null || value === undefined || value === "" ? "--" : value;
|
| | | };
|
| | |
|
| | | const formatStatusText = (value) => {
|
| | | const matched = stockStatusOptions.value.find((item) => `${item.key}` === `${value}`);
|
| | | return matched ? matched.value || matched.label : normalizeValue(value);
|
| | | };
|
| | |
|
| | | const getDetailRows = (stockId) => {
|
| | | return detailState.rowsMap[stockId] || [];
|
| | | };
|
| | |
|
| | | const loadDetailRows = async (row) => {
|
| | | if (!row || !row.id || detailState.loadingMap[row.id]) {
|
| | | return;
|
| | | }
|
| | | if (detailState.rowsMap[row.id]) {
|
| | | return;
|
| | | }
|
| | |
|
| | | detailState.loadingMap[row.id] = true;
|
| | | detailState.errorMap[row.id] = "";
|
| | | try {
|
| | | const result = await proxy.http.post("/api/StockInfoDetail/getPageData", {
|
| | | page: 1,
|
| | | rows: 200,
|
| | | sort: "id",
|
| | | order: "asc",
|
| | | wheres: JSON.stringify([
|
| | | {
|
| | | name: "stockId",
|
| | | value: String(row.id),
|
| | | displayType: "int",
|
| | | },
|
| | | ]),
|
| | | });
|
| | | detailState.rowsMap[row.id] = (result && result.rows) || [];
|
| | | } catch (error) {
|
| | | detailState.rowsMap[row.id] = null;
|
| | | detailState.errorMap[row.id] = error?.message || TEXT.detailLoadFailed;
|
| | | } finally {
|
| | | detailState.loadingMap[row.id] = false;
|
| | | }
|
| | | };
|
| | |
|
| | | const loadStockStatusOptions = async () => {
|
| | | try {
|
| | | const result = await proxy.http.post("/api/Sys_Dictionary/GetVueDictionary", ["stockStatusEmun", "mesUploadStatusEnum"]);
|
| | | const matched = (result || []).find((item) => item.dicNo === "stockStatusEmun");
|
| | | stockStatusOptions.value = matched ? matched.data || [] : [];
|
| | | } catch (error) {
|
| | | stockStatusOptions.value = [];
|
| | | }
|
| | | };
|
| | |
|
| | | loadStockStatusOptions();
|
| | |
|
| | | const renderStatus = (row) => {
|
| | | if (detailState.loadingMap[row.id]) {
|
| | | return h("div", { class: "stock-detail-status" }, TEXT.detailLoading);
|
| | | }
|
| | | if (detailState.errorMap[row.id]) {
|
| | | return h(
|
| | | "div",
|
| | | { class: "stock-detail-status stock-detail-status--error" },
|
| | | detailState.errorMap[row.id]
|
| | | );
|
| | | }
|
| | | return null;
|
| | | };
|
| | |
|
| | | const renderDetailTable = (row) => {
|
| | | const statusNode = renderStatus(row);
|
| | | if (statusNode) {
|
| | | return statusNode;
|
| | | }
|
| | |
|
| | | const rows = getDetailRows(row.id);
|
| | | if (!rows.length) {
|
| | | return h("div", { class: "stock-detail-status" }, TEXT.detailEmpty);
|
| | | }
|
| | |
|
| | | return h("div", { class: "stock-detail-table-wrapper" }, [
|
| | | h("div", { class: "stock-detail-toolbar" }, [
|
| | | h("div", { class: "stock-detail-toolbar__left" }, TEXT.detailName),
|
| | | h("div", { class: "stock-detail-toolbar__right" }, [
|
| | | h("span", { class: "stock-detail-count" }, `${rows.length} æ¡`),
|
| | | ]),
|
| | | ]),
|
| | | h(
|
| | | ElTable,
|
| | | {
|
| | | data: rows,
|
| | | border: true,
|
| | | stripe: true,
|
| | | size: "small",
|
| | | class: "stock-detail-el-table",
|
| | | maxHeight: 420,
|
| | | emptyText: TEXT.detailEmpty,
|
| | | },
|
| | | () =>
|
| | | detailColumns.map((column) =>
|
| | | h(ElTableColumn, {
|
| | | key: column.field,
|
| | | prop: column.field,
|
| | | label: column.title,
|
| | | minWidth: column.minWidth,
|
| | | showOverflowTooltip: true,
|
| | | formatter: (detailRow) =>
|
| | | column.field === "status"
|
| | | ? formatStatusText(detailRow[column.field])
|
| | | : normalizeValue(detailRow[column.field]),
|
| | | })
|
| | | )
|
| | | ),
|
| | | ]);
|
| | | };
|
| | |
|
| | | const tableExpand = ref({
|
| | | width: 55,
|
| | | onChange(row, expandedRows) {
|
| | | const isExpanded = expandedRows.some((item) => item.id === row.id);
|
| | | if (isExpanded) {
|
| | | loadDetailRows(row);
|
| | | }
|
| | | },
|
| | | render(render, { row }) {
|
| | | return render("div", { class: "stock-detail-panel" }, [
|
| | | render("div", { class: "stock-detail-header" }, [
|
| | | render("div", { class: "stock-detail-header__main" }, [
|
| | | render("div", { class: "stock-detail-title" }, TEXT.detailName),
|
| | | render(
|
| | | "div",
|
| | | { class: "stock-detail-subtitle" },
|
| | | `${TEXT.expandPrefix}${normalizeValue(row.palletCode)}${TEXT.expandMiddle}${TEXT.expandLocation}${normalizeValue(row.locationCode)}`
|
| | | ),
|
| | | ]),
|
| | | // render("div", { class: "stock-detail-tags" }, [
|
| | | // render("span", { class: "stock-detail-tag" }, normalizeValue(row.palletCode)),
|
| | | // render(
|
| | | // "span",
|
| | | // { class: "stock-detail-tag stock-detail-tag--muted" },
|
| | | // normalizeValue(row.locationCode)
|
| | | // ),
|
| | | // ]),
|
| | | ]),
|
| | | renderDetailTable(row),
|
| | | ]);
|
| | | },
|
| | | });
|
| | |
|
| | | return {
|
| | | table,
|
| | | extend,
|
| | | editFormFields,
|
| | | editFormOptions,
|
| | | searchFormFields,
|
| | | searchFormOptions,
|
| | | columns,
|
| | | detail,
|
| | | tableExpand,
|
| | | };
|
| | | },
|
| | | });
|
| | | </script>
|
| | |
|
| | | <style scoped>
|
| | | .stock-detail-panel {
|
| | | margin: 4px 8px 12px;
|
| | | padding: 14px 16px 16px;
|
| | | background: linear-gradient(180deg, #ffffff 0%, #fafbfc 100%);
|
| | | border: 1px solid #e8edf3;
|
| | | border-radius: 10px;
|
| | | box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.7);
|
| | | }
|
| | |
|
| | | .stock-detail-header {
|
| | | display: flex;
|
| | | align-items: flex-start;
|
| | | justify-content: space-between;
|
| | | gap: 12px;
|
| | | margin-bottom: 12px;
|
| | | padding-bottom: 12px;
|
| | | border-bottom: 1px solid #edf1f5;
|
| | | }
|
| | |
|
| | | .stock-detail-header__main {
|
| | | min-width: 0;
|
| | | }
|
| | |
|
| | | .stock-detail-title {
|
| | | margin-bottom: 4px;
|
| | | font-size: 15px;
|
| | | font-weight: 700;
|
| | | color: #303133;
|
| | | }
|
| | |
|
| | | .stock-detail-subtitle {
|
| | | font-size: 13px;
|
| | | color: #606266;
|
| | | line-height: 1.6;
|
| | | }
|
| | |
|
| | | .stock-detail-tags {
|
| | | display: flex;
|
| | | flex-wrap: wrap;
|
| | | justify-content: flex-end;
|
| | | gap: 8px;
|
| | | }
|
| | |
|
| | | .stock-detail-tag {
|
| | | display: inline-flex;
|
| | | align-items: center;
|
| | | height: 28px;
|
| | | padding: 0 10px;
|
| | | color: #1f5eff;
|
| | | background: #edf4ff;
|
| | | border: 1px solid #d8e6ff;
|
| | | border-radius: 999px;
|
| | | font-size: 12px;
|
| | | font-weight: 600;
|
| | | }
|
| | |
|
| | | .stock-detail-tag--muted {
|
| | | color: #4e5969;
|
| | | background: #f4f6f8;
|
| | | border-color: #e5e9ef;
|
| | | }
|
| | |
|
| | | .stock-detail-status {
|
| | | padding: 14px 12px;
|
| | | color: #606266;
|
| | | background: #f8fafc;
|
| | | border: 1px dashed #d9e2ec;
|
| | | border-radius: 8px;
|
| | | }
|
| | |
|
| | | .stock-detail-status--error {
|
| | | color: #f56c6c;
|
| | | background: #fef0f0;
|
| | | border-color: #fde2e2;
|
| | | }
|
| | |
|
| | | .stock-detail-table-wrapper {
|
| | | overflow-x: auto;
|
| | | border: 1px solid #ebeef5;
|
| | | border-radius: 8px;
|
| | | background: #fff;
|
| | | }
|
| | |
|
| | | .stock-detail-toolbar {
|
| | | display: flex;
|
| | | align-items: center;
|
| | | justify-content: space-between;
|
| | | gap: 12px;
|
| | | padding: 12px 14px;
|
| | | background: #f8fafc;
|
| | | border-bottom: 1px solid #edf1f5;
|
| | | }
|
| | |
|
| | | .stock-detail-toolbar__left {
|
| | | font-size: 13px;
|
| | | font-weight: 600;
|
| | | color: #303133;
|
| | | }
|
| | |
|
| | | .stock-detail-count {
|
| | | display: inline-flex;
|
| | | align-items: center;
|
| | | height: 24px;
|
| | | padding: 0 10px;
|
| | | color: #606266;
|
| | | background: #fff;
|
| | | border: 1px solid #e5e9ef;
|
| | | border-radius: 999px;
|
| | | font-size: 12px;
|
| | | }
|
| | |
|
| | | :deep(.stock-detail-el-table) {
|
| | | border-top: none;
|
| | | }
|
| | |
|
| | | :deep(.stock-detail-el-table .el-table__inner-wrapper::before) {
|
| | | display: none;
|
| | | }
|
| | |
|
| | | :deep(.stock-detail-el-table th.el-table__cell) {
|
| | | background: #f5f7fa;
|
| | | color: #303133;
|
| | | font-weight: 600;
|
| | | }
|
| | |
|
| | | :deep(.stock-detail-el-table td.el-table__cell) {
|
| | | color: #606266;
|
| | | }
|
| | |
|
| | | :deep(.stock-detail-el-table .el-table__body tr:hover > td.el-table__cell) {
|
| | | background: #f0f7ff;
|
| | | }
|
| | | </style>
|
| | |
| | | |
| | | const searchFormFields = ref({ |
| | | palletCode: "", |
| | | warehouseId: "", |
| | | stockStatus: "", |
| | | locationCode: "", |
| | | }); |
| | |
| | | const searchFormOptions = ref([ |
| | | [ |
| | | { title: TEXT.palletCode, field: "palletCode", type: "like" }, |
| | | { title: TEXT.warehouse, field: "warehouseId", type: "selectList", dataKey: "warehouseEnum", data: [] }, |
| | | { title: TEXT.stockStatus, field: "stockStatus", type: "selectList", dataKey: "stockStatusEmun", data: [] }, |
| | | { title: TEXT.locationCode, field: "locationCode", type: "like" }, |
| | | ], |
| | |
| | | /// æå¹´æç»è®¡å
¥ç«ååºç«ä»»å¡æ°é
|
| | | /// </remarks>
|
| | | [HttpGet("MonthlyStats"), AllowAnonymous]
|
| | | public async Task<WebResponseContent> MonthlyStats([FromQuery] int months = 12, string Roadway = null)
|
| | | public async Task<WebResponseContent> MonthlyStats(int months, string roadway)
|
| | | {
|
| | | try
|
| | | {
|
| | |
| | | // ä»åºåç§°æ å°
|
| | | var roadwayNames = new Dictionary<string, string>
|
| | | {
|
| | | { "FJSC1", "è´æå·1å·ä»åº" },
|
| | | { "ZJSC1", "æ£æå·1å·ä»åº" },
|
| | |
|
| | | { "GWSC1", "髿¸©1å·ä»åº" },
|
| | | { "CWSC1", "常温1å·ä»åº" },
|
| | | { "HCSC1", "å容1å·ä»åº" }
|
| | | { "HCSC1", "å容1å·ä»åº" },
|
| | | { "FJSC1", "è´æå·1å·ä»åº" },
|
| | | { "ZJSC1", "æ£æå·1å·ä»åº" },
|
| | | };
|
| | |
|
| | | // æå»ºæ¥è¯¢
|
| | |
| | | .Where(t => t.InsertTime >= startDate);
|
| | |
|
| | | // 妿æå®äºéè·¯ï¼æ·»å éè·¯è¿æ»¤æ¡ä»¶
|
| | | if (!string.IsNullOrEmpty(Roadway))
|
| | | if (!string.IsNullOrEmpty(roadway))
|
| | | {
|
| | | query = query.Where(t => t.Roadway == Roadway);
|
| | | query = query.Where(t => t.Roadway == roadway);
|
| | | }
|
| | |
|
| | | var monthlyStats = await query
|
| | |
| | | Month = monthKey,
|
| | | Inbound = stat.Inbound,
|
| | | Outbound = stat.Outbound,
|
| | | Roadway = Roadway,
|
| | | RoadwayName = !string.IsNullOrEmpty(Roadway) && roadwayNames.ContainsKey(Roadway)
|
| | | ? roadwayNames[Roadway]
|
| | | Roadway = roadway,
|
| | | RoadwayName = !string.IsNullOrEmpty(roadway) && roadwayNames.ContainsKey(roadway)
|
| | | ? roadwayNames[roadway]
|
| | | : null
|
| | | });
|
| | | }
|
| | |
| | | Month = monthKey,
|
| | | Inbound = 0,
|
| | | Outbound = 0,
|
| | | Roadway = Roadway,
|
| | | RoadwayName = !string.IsNullOrEmpty(Roadway) && roadwayNames.ContainsKey(Roadway)
|
| | | ? roadwayNames[Roadway]
|
| | | Roadway = roadway,
|
| | | RoadwayName = !string.IsNullOrEmpty(roadway) && roadwayNames.ContainsKey(roadway)
|
| | | ? roadwayNames[roadway]
|
| | | : null
|
| | | });
|
| | | }
|