| ¶Ô±ÈÐÂÎļþ |
| | |
| | | # é¦é¡µä»ªè¡¨çå¾è¡¨åè½å®ç°è®¡å |
| | | |
| | | > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. |
| | | |
| | | **Goal:** å¨ WMS å端é¦é¡µæ·»å 仪表çå¾è¡¨ï¼å±ç¤ºåºå
¥åºç»è®¡ååºåæ°æ® |
| | | |
| | | **Architecture:** |
| | | - åç«¯ï¼æ°å»º DashboardControllerï¼æä¾6个ç»è®¡æ¥å£ï¼ä½¿ç¨ SqlSugar ç´æ¥æ¥è¯¢ Dt_Task_Htyï¼å·²å®æä»»å¡åå²è¡¨ï¼å Dt_StockInfo 表 |
| | | - å端ï¼éå Home.vueï¼ä½¿ç¨ ECharts 5.0.2 å®ç°ä»ªè¡¨çå¸å±ï¼å¤ç¨ bigdata.vue ä¸ç ECharts ä½¿ç¨æ¨¡å¼ |
| | | - æ°æ®æ¥æºï¼Dt_Task_Hty.InsertTimeï¼ä»»å¡å®ææ¶é´ï¼ï¼TaskType åºåå
¥åº(500-599)/åºåº(100-199) |
| | | |
| | | **Tech Stack:** ASP.NET Core 6.0, Vue 3, ECharts 5.0.2, SqlSugar |
| | | |
| | | --- |
| | | |
| | | ## æä»¶ç»æ |
| | | |
| | | ``` |
| | | å端æ°å¢: |
| | | - WIDESEA_WMSServer/Controllers/Dashboard/DashboardController.cs (ä»ªè¡¨çæ§å¶å¨) |
| | | |
| | | å端修æ¹: |
| | | - WIDESEA_WMSClient/src/views/Home.vue (éå为仪表ç页é¢) |
| | | ``` |
| | | |
| | | --- |
| | | |
| | | ## å®ç°ä»»å¡ |
| | | |
| | | ### Task 1: å建å端 DashboardController |
| | | |
| | | **æä»¶:** |
| | | - Create: `WIDESEA_WMSServer/Controllers/Dashboard/DashboardController.cs` |
| | | |
| | | **说æ:** å建 DashboardControllerï¼å
å«6个 API æ¥å£ |
| | | |
| | | - [ ] **Step 1: å建æ§å¶å¨æä»¶** |
| | | |
| | | ```csharp |
| | | using Microsoft.AspNetCore.Mvc; |
| | | using SqlSugar; |
| | | using WIDESEA_Core; |
| | | using WIDESEA_Model.Models; |
| | | |
| | | namespace WIDESEA_WMSServer.Controllers.Dashboard |
| | | { |
| | | /// <summary> |
| | | /// 仪表ç |
| | | /// </summary> |
| | | [Route("api/Dashboard")] |
| | | [ApiController] |
| | | public class DashboardController : ControllerBase |
| | | { |
| | | private readonly ISqlSugarClient _db; |
| | | |
| | | public DashboardController(ISqlSugarClient db) |
| | | { |
| | | _db = db; |
| | | } |
| | | |
| | | /// <summary> |
| | | /// æ»è§æ°æ® |
| | | /// </summary> |
| | | [HttpGet("Overview")] |
| | | public async Task<WebResponseContent> Overview() |
| | | { |
| | | // å®ç°è§ Step 2 |
| | | } |
| | | |
| | | /// <summary> |
| | | /// æ¯æ¥ç»è®¡ |
| | | /// </summary> |
| | | [HttpGet("DailyStats")] |
| | | public async Task<WebResponseContent> DailyStats([FromQuery] int days = 30) |
| | | { |
| | | // å®ç°è§ Step 3 |
| | | } |
| | | |
| | | /// <summary> |
| | | /// æ¯å¨ç»è®¡ |
| | | /// </summary> |
| | | [HttpGet("WeeklyStats")] |
| | | public async Task<WebResponseContent> WeeklyStats([FromQuery] int weeks = 12) |
| | | { |
| | | // å®ç°è§ Step 4 |
| | | } |
| | | |
| | | /// <summary> |
| | | /// æ¯æç»è®¡ |
| | | /// </summary> |
| | | [HttpGet("MonthlyStats")] |
| | | public async Task<WebResponseContent> MonthlyStats([FromQuery] int months = 12) |
| | | { |
| | | // å®ç°è§ Step 5 |
| | | } |
| | | |
| | | /// <summary> |
| | | /// åºååºé¾åå¸ |
| | | /// </summary> |
| | | [HttpGet("StockAgeDistribution")] |
| | | public async Task<WebResponseContent> StockAgeDistribution() |
| | | { |
| | | // å®ç°è§ Step 6 |
| | | } |
| | | |
| | | /// <summary> |
| | | /// åä»åºåºååå¸ |
| | | /// </summary> |
| | | [HttpGet("StockByWarehouse")] |
| | | public async Task<WebResponseContent> StockByWarehouse() |
| | | { |
| | | // å®ç°è§ Step 7 |
| | | } |
| | | } |
| | | } |
| | | ``` |
| | | |
| | | - [ ] **Step 2: å®ç° Overview æ¥å£** |
| | | |
| | | å¨ Overview æ¹æ³ä¸å®ç°ï¼ |
| | | |
| | | ```csharp |
| | | public async Task<WebResponseContent> Overview() |
| | | { |
| | | var today = DateTime.Today; |
| | | var firstDayOfMonth = new DateTime(today.Year, today.Month, 1); |
| | | |
| | | // 仿¥å
¥åºæ° |
| | | var todayInbound = await _db.Queryable<Dt_Task_Hty>() |
| | | .Where(t => t.InsertTime >= today && t.TaskType >= 500 && t.TaskType < 600) |
| | | .CountAsync(); |
| | | |
| | | // 仿¥åºåºæ° |
| | | var todayOutbound = await _db.Queryable<Dt_Task_Hty>() |
| | | .Where(t => t.InsertTime >= today && t.TaskType >= 100 && t.TaskType < 200) |
| | | .CountAsync(); |
| | | |
| | | // æ¬æå
¥åºæ° |
| | | var monthInbound = await _db.Queryable<Dt_Task_Hty>() |
| | | .Where(t => t.InsertTime >= firstDayOfMonth && t.TaskType >= 500 && t.TaskType < 600) |
| | | .CountAsync(); |
| | | |
| | | // æ¬æåºåºæ° |
| | | var monthOutbound = await _db.Queryable<Dt_Task_Hty>() |
| | | .Where(t => t.InsertTime >= firstDayOfMonth && t.TaskType >= 100 && t.TaskType < 200) |
| | | .CountAsync(); |
| | | |
| | | // å½åæ»åºå |
| | | var totalStock = await _db.Queryable<Dt_StockInfo>().CountAsync(); |
| | | |
| | | return WebResponseContent.Instance.OK(null, new |
| | | { |
| | | TodayInbound = todayInbound, |
| | | TodayOutbound = todayOutbound, |
| | | MonthInbound = monthInbound, |
| | | MonthOutbound = monthOutbound, |
| | | TotalStock = totalStock |
| | | }); |
| | | } |
| | | ``` |
| | | |
| | | - [ ] **Step 3: å®ç° DailyStats æ¥å£** |
| | | |
| | | ```csharp |
| | | public async Task<WebResponseContent> DailyStats([FromQuery] int days = 30) |
| | | { |
| | | if (days <= 0) days = 30; |
| | | if (days > 365) days = 365; |
| | | |
| | | var startDate = DateTime.Today.AddDays(-days + 1); |
| | | |
| | | var query = await _db.Queryable<Dt_Task_Hty>() |
| | | .Where(t => t.InsertTime >= startDate) |
| | | .Select(t => new { t.InsertTime, t.TaskType }) |
| | | .ToListAsync(); |
| | | |
| | | var result = query |
| | | .GroupBy(t => t.InsertTime.Date) |
| | | .Select(g => new |
| | | { |
| | | Date = g.Key.ToString("yyyy-MM-dd"), |
| | | Inbound = g.Count(t => t.TaskType >= 500 && t.TaskType < 600), |
| | | Outbound = g.Count(t => t.TaskType >= 100 && t.TaskType < 200) |
| | | }) |
| | | .OrderBy(x => x.Date) |
| | | .ToList(); |
| | | |
| | | return WebResponseContent.Instance.OK(null, result); |
| | | } |
| | | ``` |
| | | |
| | | - [ ] **Step 4: å®ç° WeeklyStats æ¥å£** |
| | | |
| | | ```csharp |
| | | public async Task<WebResponseContent> WeeklyStats([FromQuery] int weeks = 12) |
| | | { |
| | | if (weeks <= 0) weeks = 12; |
| | | |
| | | var startDate = DateTime.Today.AddDays(-weeks * 7); |
| | | |
| | | var query = await _db.Queryable<Dt_Task_Hty>() |
| | | .Where(t => t.InsertTime >= startDate) |
| | | .Select(t => new { t.InsertTime, t.TaskType }) |
| | | .ToListAsync(); |
| | | |
| | | var result = query |
| | | .GroupBy(t => GetWeekKey(t.InsertTime)) |
| | | .Select(g => new |
| | | { |
| | | Week = g.Key, |
| | | Inbound = g.Count(t => t.TaskType >= 500 && t.TaskType < 600), |
| | | Outbound = g.Count(t => t.TaskType >= 100 && t.TaskType < 200) |
| | | }) |
| | | .OrderBy(x => x.Week) |
| | | .ToList(); |
| | | |
| | | return WebResponseContent.Instance.OK(null, result); |
| | | } |
| | | |
| | | private string GetWeekKey(DateTime date) |
| | | { |
| | | // è·åå¨ä¸å¼å§çå¨ |
| | | var diff = (7 + (date.DayOfWeek - DayOfWeek.Monday)) % 7; |
| | | var monday = date.AddDays(-diff); |
| | | return monday.ToString("yyyy-Www"); |
| | | } |
| | | ``` |
| | | |
| | | - [ ] **Step 5: å®ç° MonthlyStats æ¥å£** |
| | | |
| | | ```csharp |
| | | public async Task<WebResponseContent> MonthlyStats([FromQuery] int months = 12) |
| | | { |
| | | if (months <= 0) months = 12; |
| | | |
| | | var startDate = DateTime.Today.AddMonths(-months + 1); |
| | | startDate = new DateTime(startDate.Year, startDate.Month, 1); |
| | | |
| | | var query = await _db.Queryable<Dt_Task_Hty>() |
| | | .Where(t => t.InsertTime >= startDate) |
| | | .Select(t => new { t.InsertTime, t.TaskType }) |
| | | .ToListAsync(); |
| | | |
| | | var result = query |
| | | .GroupBy(t => new { t.InsertTime.Year, t.InsertTime.Month }) |
| | | .Select(g => new |
| | | { |
| | | Month = $"{g.Key.Year}-{g.Key.Month:D2}", |
| | | Inbound = g.Count(t => t.TaskType >= 500 && t.TaskType < 600), |
| | | Outbound = g.Count(t => t.TaskType >= 100 && t.TaskType < 200) |
| | | }) |
| | | .OrderBy(x => x.Month) |
| | | .ToList(); |
| | | |
| | | return WebResponseContent.Instance.OK(null, result); |
| | | } |
| | | ``` |
| | | |
| | | - [ ] **Step 6: å®ç° StockAgeDistribution æ¥å£** |
| | | |
| | | ```csharp |
| | | public async Task<WebResponseContent> StockAgeDistribution() |
| | | { |
| | | var now = DateTime.Now; |
| | | var stocks = await _db.Queryable<Dt_StockInfo>() |
| | | .Select(s => s.CreateDate) |
| | | .ToListAsync(); |
| | | |
| | | var result = new[] |
| | | { |
| | | new { Range = "7天å
", Count = stocks.Count(s => (now - s).TotalDays <= 7) }, |
| | | new { Range = "7-30天", Count = stocks.Count(s => (now - s).TotalDays > 7 && (now - s).TotalDays <= 30) }, |
| | | new { Range = "30-90天", Count = stocks.Count(s => (now - s).TotalDays > 30 && (now - s).TotalDays <= 90) }, |
| | | new { Range = "90天以ä¸", Count = stocks.Count(s => (now - s).TotalDays > 90) } |
| | | }; |
| | | |
| | | return WebResponseContent.Instance.OK(null, result); |
| | | } |
| | | ``` |
| | | |
| | | - [ ] **Step 7: å®ç° StockByWarehouse æ¥å£** |
| | | |
| | | ```csharp |
| | | public async Task<WebResponseContent> StockByWarehouse() |
| | | { |
| | | var result = await _db.Queryable<Dt_StockInfo>() |
| | | .GroupBy(s => s.WarehouseId) |
| | | .Select(g => new |
| | | { |
| | | WarehouseId = g.Key, |
| | | Count = g.Count() |
| | | }) |
| | | .ToListAsync(); |
| | | |
| | | // èæ¥ä»åºåç§° |
| | | var warehouseIds = result.Select(x => x.WarehouseId).ToList(); |
| | | var warehouses = await _db.Queryable<Dt_Warehouse>() |
| | | .Where(w => warehouseIds.Contains(w.WarehouseId)) |
| | | .Select(w => new { w.WarehouseId, w.WarehouseName }) |
| | | .ToListAsync(); |
| | | |
| | | var finalResult = result.Select(r => |
| | | { |
| | | var wh = warehouses.FirstOrDefault(w => w.WarehouseId == r.WarehouseId); |
| | | return new |
| | | { |
| | | Warehouse = wh?.WarehouseName ?? $"ä»åº{r.WarehouseId}", |
| | | Count = r.Count |
| | | }; |
| | | }).ToList(); |
| | | |
| | | return WebResponseContent.Instance.OK(null, finalResult); |
| | | } |
| | | ``` |
| | | |
| | | - [ ] **Step 8: æäº¤ä»£ç ** |
| | | |
| | | ```bash |
| | | git add WIDESEA_WMSServer/Controllers/Dashboard/DashboardController.cs |
| | | git commit -m "feat(Dashboard): æ·»å ä»ªè¡¨çæ§å¶å¨ï¼å
å«6个ç»è®¡æ¥å£" |
| | | ``` |
| | | |
| | | --- |
| | | |
| | | ### Task 2: éåå端 Home.vue å®ç°ä»ªè¡¨ç |
| | | |
| | | **æä»¶:** |
| | | - Modify: `WIDESEA_WMSClient/src/views/Home.vue` |
| | | |
| | | **说æ:** éå为空ç½çé¦é¡µï¼å®ç°ä»ªè¡¨çå¾è¡¨å¸å± |
| | | |
| | | - [ ] **Step 1: éå Home.vue 模æ¿é¨å** |
| | | |
| | | ```vue |
| | | <template> |
| | | <div class="dashboard-container"> |
| | | <!-- é¡¶é¨ï¼æ¬æåºå
¥åºè¶å¿ (å
¨å®½) --> |
| | | <div class="chart-row full-width"> |
| | | <div class="chart-card"> |
| | | <div class="card-title">æ¬æåºå
¥åºè¶å¿</div> |
| | | <div id="chart-monthly-trend" class="chart-content"></div> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- 第äºè¡ï¼ä»æ¥/æ¬å¨åºå
¥åºå¯¹æ¯ --> |
| | | <div class="chart-row"> |
| | | <div class="chart-card"> |
| | | <div class="card-title">仿¥åºå
¥åºå¯¹æ¯</div> |
| | | <div id="chart-today" class="chart-content"></div> |
| | | </div> |
| | | <div class="chart-card"> |
| | | <div class="card-title">æ¬å¨åºå
¥åºå¯¹æ¯</div> |
| | | <div id="chart-week" class="chart-content"></div> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- 第ä¸è¡ï¼æ¬æå¯¹æ¯/åºåæ»é --> |
| | | <div class="chart-row"> |
| | | <div class="chart-card"> |
| | | <div class="card-title">æ¬æåºå
¥åºå¯¹æ¯</div> |
| | | <div id="chart-month" class="chart-content"></div> |
| | | </div> |
| | | <div class="chart-card"> |
| | | <div class="card-title">å½ååºåæ»é</div> |
| | | <div class="stock-total"> |
| | | <div class="total-number">{{ overviewData.TotalStock || 0 }}</div> |
| | | <div class="total-label">æç</div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- 第åè¡ï¼åºé¾åå¸/ä»åºåå¸ --> |
| | | <div class="chart-row"> |
| | | <div class="chart-card"> |
| | | <div class="card-title">åºååºé¾åå¸</div> |
| | | <div id="chart-stock-age" class="chart-content"></div> |
| | | </div> |
| | | <div class="chart-card"> |
| | | <div class="card-title">åä»åºåºååå¸</div> |
| | | <div id="chart-warehouse" class="chart-content"></div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | ``` |
| | | |
| | | - [ ] **Step 2: éåèæ¬é¨å** |
| | | |
| | | ```javascript |
| | | <script> |
| | | import * as echarts from "echarts"; |
| | | |
| | | export default { |
| | | name: "Home", |
| | | data() { |
| | | return { |
| | | charts: {}, |
| | | overviewData: { |
| | | TodayInbound: 0, |
| | | TodayOutbound: 0, |
| | | MonthInbound: 0, |
| | | MonthOutbound: 0, |
| | | TotalStock: 0 |
| | | }, |
| | | dailyData: [], |
| | | weeklyData: [], |
| | | monthlyData: [], |
| | | stockAgeData: [], |
| | | warehouseData: [] |
| | | }; |
| | | }, |
| | | mounted() { |
| | | this.initCharts(); |
| | | this.loadData(); |
| | | }, |
| | | beforeUnmount() { |
| | | Object.values(this.charts).forEach(chart => chart.dispose()); |
| | | }, |
| | | methods: { |
| | | initCharts() { |
| | | this.charts.monthlyTrend = echarts.init(document.getElementById("chart-monthly-trend")); |
| | | this.charts.today = echarts.init(document.getElementById("chart-today")); |
| | | this.charts.week = echarts.init(document.getElementById("chart-week")); |
| | | this.charts.month = echarts.init(document.getElementById("chart-month")); |
| | | this.charts.stockAge = echarts.init(document.getElementById("chart-stock-age")); |
| | | this.charts.warehouse = echarts.init(document.getElementById("chart-warehouse")); |
| | | }, |
| | | |
| | | async loadData() { |
| | | await this.loadOverview(); |
| | | await this.loadDailyStats(); |
| | | await this.loadWeeklyStats(); |
| | | await this.loadMonthlyStats(); |
| | | await this.loadStockAgeDistribution(); |
| | | await this.loadStockByWarehouse(); |
| | | }, |
| | | |
| | | async loadOverview() { |
| | | try { |
| | | const res = await this.http.get("/api/Dashboard/Overview"); |
| | | if (res.Status && res.Data) { |
| | | this.overviewData = res.Data; |
| | | this.updateTodayChart(); |
| | | this.updateWeekChart(); |
| | | this.updateMonthChart(); |
| | | } |
| | | } catch (e) { |
| | | console.error("å è½½æ»è§æ°æ®å¤±è´¥", e); |
| | | } |
| | | }, |
| | | |
| | | async loadDailyStats() { |
| | | try { |
| | | const res = await this.http.get("/api/Dashboard/DailyStats", { days: 30 }); |
| | | if (res.Status && res.Data) { |
| | | this.dailyData = res.Data; |
| | | } |
| | | } catch (e) { |
| | | console.error("å è½½æ¯æ¥ç»è®¡å¤±è´¥", e); |
| | | } |
| | | }, |
| | | |
| | | async loadWeeklyStats() { |
| | | try { |
| | | const res = await this.http.get("/api/Dashboard/WeeklyStats", { weeks: 12 }); |
| | | if (res.Status && res.Data) { |
| | | this.weeklyData = res.Data; |
| | | this.updateWeeklyTrendChart(); |
| | | } |
| | | } catch (e) { |
| | | console.error("å è½½æ¯å¨ç»è®¡å¤±è´¥", e); |
| | | } |
| | | }, |
| | | |
| | | async loadMonthlyStats() { |
| | | try { |
| | | const res = await this.http.get("/api/Dashboard/MonthlyStats", { months: 12 }); |
| | | if (res.Status && res.Data) { |
| | | this.monthlyData = res.Data; |
| | | this.updateMonthlyTrendChart(); |
| | | } |
| | | } catch (e) { |
| | | console.error("å è½½æ¯æç»è®¡å¤±è´¥", e); |
| | | } |
| | | }, |
| | | |
| | | async loadStockAgeDistribution() { |
| | | try { |
| | | const res = await this.http.get("/api/Dashboard/StockAgeDistribution"); |
| | | if (res.Status && res.Data) { |
| | | this.stockAgeData = res.Data; |
| | | this.updateStockAgeChart(); |
| | | } |
| | | } catch (e) { |
| | | console.error("å è½½åºé¾åå¸å¤±è´¥", e); |
| | | } |
| | | }, |
| | | |
| | | async loadStockByWarehouse() { |
| | | try { |
| | | const res = await this.http.get("/api/Dashboard/StockByWarehouse"); |
| | | if (res.Status && res.Data) { |
| | | this.warehouseData = res.Data; |
| | | this.updateWarehouseChart(); |
| | | } |
| | | } catch (e) { |
| | | console.error("å è½½ä»åºåå¸å¤±è´¥", e); |
| | | } |
| | | }, |
| | | |
| | | // æ´æ°ä»æ¥å¯¹æ¯å¾è¡¨ |
| | | updateTodayChart() { |
| | | const option = { |
| | | tooltip: { trigger: "axis" }, |
| | | legend: { data: ["å
¥åº", "åºåº"], textStyle: { color: "#fff" } }, |
| | | xAxis: { |
| | | type: "category", |
| | | data: ["仿¥"], |
| | | axisLabel: { color: "#fff" } |
| | | }, |
| | | yAxis: { |
| | | type: "value", |
| | | axisLabel: { color: "#fff" } |
| | | }, |
| | | series: [ |
| | | { name: "å
¥åº", type: "bar", data: [this.overviewData.TodayInbound], itemStyle: { color: "#5470c6" } }, |
| | | { name: "åºåº", type: "bar", data: [this.overviewData.TodayOutbound], itemStyle: { color: "#91cc75" } } |
| | | ] |
| | | }; |
| | | this.charts.today.setOption(option, true); |
| | | }, |
| | | |
| | | // æ´æ°æ¬å¨å¯¹æ¯å¾è¡¨ |
| | | updateWeekChart() { |
| | | // æ¬å¨æ°æ®ä» weeklyData ä¸è®¡ç®å½å¨æ°æ® |
| | | const thisWeek = this.getThisWeekData(this.weeklyData); |
| | | const option = { |
| | | tooltip: { trigger: "axis" }, |
| | | legend: { data: ["å
¥åº", "åºåº"], textStyle: { color: "#fff" } }, |
| | | xAxis: { |
| | | type: "category", |
| | | data: ["æ¬å¨"], |
| | | axisLabel: { color: "#fff" } |
| | | }, |
| | | yAxis: { |
| | | type: "value", |
| | | axisLabel: { color: "#fff" } |
| | | }, |
| | | series: [ |
| | | { name: "å
¥åº", type: "bar", data: [thisWeek.Inbound], itemStyle: { color: "#5470c6" } }, |
| | | { name: "åºåº", type: "bar", data: [thisWeek.Outbound], itemStyle: { color: "#91cc75" } } |
| | | ] |
| | | }; |
| | | this.charts.week.setOption(option, true); |
| | | }, |
| | | |
| | | getThisWeekData(weeklyData) { |
| | | if (!weeklyData || weeklyData.length === 0) return { Inbound: 0, Outbound: 0 }; |
| | | const thisWeekKey = this.getCurrentWeekKey(); |
| | | const thisWeek = weeklyData.find(w => w.Week === thisWeekKey); |
| | | return thisWeek || { Inbound: 0, Outbound: 0 }; |
| | | }, |
| | | |
| | | getCurrentWeekKey() { |
| | | const now = new Date(); |
| | | const diff = (7 + (now.getDay() - 1)) % 7; |
| | | const monday = new Date(now); |
| | | monday.setDate(now.getDate() - diff); |
| | | const year = monday.getFullYear(); |
| | | const month = monday.getMonth() + 1; |
| | | const day = monday.getDate(); |
| | | // ISO week start (Monday) |
| | | const jan1 = new Date(year, 0, 1); |
| | | const weekNum = Math.ceil(((monday - jan1) / 86400000 + jan1.getDay() + 1) / 7); |
| | | return `${year}-W${String(weekNum).padStart(2, "0")}`; |
| | | }, |
| | | |
| | | // æ´æ°æ¬æå¯¹æ¯å¾è¡¨ |
| | | updateMonthChart() { |
| | | const option = { |
| | | tooltip: { trigger: "axis" }, |
| | | legend: { data: ["å
¥åº", "åºåº"], textStyle: { color: "#fff" } }, |
| | | xAxis: { |
| | | type: "category", |
| | | data: ["æ¬æ"], |
| | | axisLabel: { color: "#fff" } |
| | | }, |
| | | yAxis: { |
| | | type: "value", |
| | | axisLabel: { color: "#fff" } |
| | | }, |
| | | series: [ |
| | | { name: "å
¥åº", type: "bar", data: [this.overviewData.MonthInbound], itemStyle: { color: "#5470c6" } }, |
| | | { name: "åºåº", type: "bar", data: [this.overviewData.MonthOutbound], itemStyle: { color: "#91cc75" } } |
| | | ] |
| | | }; |
| | | this.charts.month.setOption(option, true); |
| | | }, |
| | | |
| | | // æ´æ°æåº¦è¶å¿å¾è¡¨ï¼æçº¿+æ±ç¶ç»åï¼ |
| | | updateMonthlyTrendChart() { |
| | | const option = { |
| | | tooltip: { trigger: "axis" }, |
| | | legend: { data: ["å
¥åº", "åºåº"], textStyle: { color: "#fff" } }, |
| | | xAxis: { |
| | | type: "category", |
| | | data: this.monthlyData.map(m => m.Month), |
| | | axisLabel: { color: "#fff", rotate: 45 } |
| | | }, |
| | | yAxis: [ |
| | | { |
| | | type: "value", |
| | | name: "æ°é", |
| | | axisLabel: { color: "#fff" } |
| | | } |
| | | ], |
| | | series: [ |
| | | { name: "å
¥åº", type: "line", data: this.monthlyData.map(m => m.Inbound), itemStyle: { color: "#5470c6" } }, |
| | | { name: "åºåº", type: "line", data: this.monthlyData.map(m => m.Outbound), itemStyle: { color: "#91cc75" } } |
| | | ] |
| | | }; |
| | | this.charts.monthlyTrend.setOption(option, true); |
| | | }, |
| | | |
| | | // æ´æ°å¨è¶å¿å¾è¡¨ |
| | | updateWeeklyTrendChart() { |
| | | const option = { |
| | | tooltip: { trigger: "axis" }, |
| | | legend: { data: ["å
¥åº", "åºåº"], textStyle: { color: "#fff" } }, |
| | | xAxis: { |
| | | type: "category", |
| | | data: this.weeklyData.map(w => w.Week), |
| | | axisLabel: { color: "#fff", rotate: 45 } |
| | | }, |
| | | yAxis: { |
| | | type: "value", |
| | | axisLabel: { color: "#fff" } |
| | | }, |
| | | series: [ |
| | | { name: "å
¥åº", type: "bar", data: this.weeklyData.map(w => w.Inbound), itemStyle: { color: "#5470c6" } }, |
| | | { name: "åºåº", type: "bar", data: this.weeklyData.map(w => w.Outbound), itemStyle: { color: "#91cc75" } } |
| | | ] |
| | | }; |
| | | this.charts.monthlyTrend.setOption(option, true); |
| | | }, |
| | | |
| | | // æ´æ°åºé¾åå¸å¾è¡¨ |
| | | updateStockAgeChart() { |
| | | const option = { |
| | | tooltip: { trigger: "item" }, |
| | | legend: { data: this.stockAgeData.map(s => s.Range), textStyle: { color: "#fff" } }, |
| | | series: [ |
| | | { |
| | | type: "pie", |
| | | radius: "60%", |
| | | data: this.stockAgeData.map((s, i) => ({ |
| | | name: s.Range, |
| | | value: s.Count |
| | | })), |
| | | emphasis: { |
| | | itemStyle: { |
| | | shadowBlur: 10, |
| | | shadowOffsetX: 0, |
| | | shadowColor: "rgba(0, 0, 0, 0.5)" |
| | | } |
| | | } |
| | | } |
| | | ] |
| | | }; |
| | | this.charts.stockAge.setOption(option, true); |
| | | }, |
| | | |
| | | // æ´æ°ä»åºåå¸å¾è¡¨ |
| | | updateWarehouseChart() { |
| | | const option = { |
| | | tooltip: { trigger: "axis" }, |
| | | xAxis: { |
| | | type: "category", |
| | | data: this.warehouseData.map(w => w.Warehouse), |
| | | axisLabel: { color: "#fff", rotate: 30 } |
| | | }, |
| | | yAxis: { |
| | | type: "value", |
| | | axisLabel: { color: "#fff" } |
| | | }, |
| | | series: [ |
| | | { |
| | | type: "bar", |
| | | data: this.warehouseData.map(w => w.Count), |
| | | itemStyle: { color: "#5470c6" } |
| | | } |
| | | ] |
| | | }; |
| | | this.charts.warehouse.setOption(option, true); |
| | | } |
| | | } |
| | | }; |
| | | </script> |
| | | ``` |
| | | |
| | | - [ ] **Step 3: æ·»å æ ·å¼** |
| | | |
| | | ```vue |
| | | <style scoped> |
| | | .dashboard-container { |
| | | padding: 20px; |
| | | background-color: #0e1a2b; |
| | | min-height: calc(100vh - 60px); |
| | | } |
| | | |
| | | .chart-row { |
| | | display: flex; |
| | | gap: 20px; |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .chart-row.full-width { |
| | | width: 100%; |
| | | } |
| | | |
| | | .chart-card { |
| | | flex: 1; |
| | | background: rgba(255, 255, 255, 0.05); |
| | | border: 1px solid rgba(25, 186, 139, 0.17); |
| | | border-radius: 4px; |
| | | padding: 15px; |
| | | position: relative; |
| | | } |
| | | |
| | | .chart-card::before { |
| | | content: ""; |
| | | position: absolute; |
| | | top: 0; |
| | | left: 0; |
| | | width: 10px; |
| | | height: 10px; |
| | | border-top: 2px solid #02a6b5; |
| | | border-left: 2px solid #02a6b5; |
| | | } |
| | | |
| | | .chart-card::after { |
| | | content: ""; |
| | | position: absolute; |
| | | top: 0; |
| | | right: 0; |
| | | width: 10px; |
| | | height: 10px; |
| | | border-top: 2px solid #02a6b5; |
| | | border-right: 2px solid #02a6b5; |
| | | } |
| | | |
| | | .card-title { |
| | | color: #fff; |
| | | font-size: 16px; |
| | | text-align: center; |
| | | margin-bottom: 10px; |
| | | } |
| | | |
| | | .chart-content { |
| | | height: 280px; |
| | | width: 100%; |
| | | } |
| | | |
| | | .stock-total { |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: center; |
| | | justify-content: center; |
| | | height: 280px; |
| | | } |
| | | |
| | | .total-number { |
| | | font-size: 64px; |
| | | font-weight: bold; |
| | | color: #67caca; |
| | | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, PingFang SC, Microsoft YaHei, Helvetica Neue, Helvetica, Arial, sans-serif; |
| | | } |
| | | |
| | | .total-label { |
| | | font-size: 18px; |
| | | color: #fcf0d8; |
| | | margin-top: 10px; |
| | | } |
| | | |
| | | /* å
¨å®½å¾è¡¨ */ |
| | | .full-width .chart-card { |
| | | flex: none; |
| | | width: 100%; |
| | | } |
| | | |
| | | .full-width .chart-content { |
| | | height: 350px; |
| | | } |
| | | </style> |
| | | ``` |
| | | |
| | | - [ ] **Step 4: æäº¤ä»£ç ** |
| | | |
| | | ```bash |
| | | git add WIDESEA_WMSClient/src/views/Home.vue |
| | | git commit -m "feat(Home): éåé¦é¡µä¸ºä»ªè¡¨çå¾è¡¨é¡µé¢" |
| | | ``` |
| | | |
| | | --- |
| | | |
| | | ### Task 3: éªè¯å®ç° |
| | | |
| | | - [ ] **Step 1: æå»ºå端** |
| | | |
| | | ```bash |
| | | cd WIDESEA_WMSServer |
| | | dotnet build WIDESEA_WMSServer.sln |
| | | ``` |
| | | |
| | | é¢æï¼æå»ºæåï¼æ é误 |
| | | |
| | | - [ ] **Step 2: æå»ºå端** |
| | | |
| | | ```bash |
| | | cd WIDESEA_WMSClient |
| | | yarn build |
| | | ``` |
| | | |
| | | é¢æï¼æå»ºæåï¼æ é误 |
| | | |
| | | - [ ] **Step 3: å¯å¨å端æµè¯ API** |
| | | |
| | | ```bash |
| | | cd WIDESEA_WMSServer/WIDESEA_WMSServer |
| | | dotnet run |
| | | ``` |
| | | |
| | | ä½¿ç¨æµè§å¨æ Postman æµè¯ï¼ |
| | | - `GET http://localhost:9291/api/Dashboard/Overview` |
| | | - `GET http://localhost:9291/api/Dashboard/DailyStats?days=30` |
| | | - `GET http://localhost:9291/api/Dashboard/WeeklyStats?weeks=12` |
| | | - `GET http://localhost:9291/api/Dashboard/MonthlyStats?months=12` |
| | | - `GET http://localhost:9291/api/Dashboard/StockAgeDistribution` |
| | | - `GET http://localhost:9291/api/Dashboard/StockByWarehouse` |
| | | |
| | | 颿ï¼åæ¥å£è¿å JSON æ°æ®ï¼æ ¼å¼ç¬¦åè®¾è®¡ææ¡£ |
| | | |
| | | --- |
| | | |
| | | ## æ»ç» |
| | | |
| | | ### å端ï¼DashboardControllerï¼ |
| | | | æ¥å£ | è·¯ç± | 说æ | |
| | | |------|------|------| |
| | | | Overview | GET /api/Dashboard/Overview | æ»è§æ°æ® | |
| | | | DailyStats | GET /api/Dashboard/DailyStats?days=30 | æ¯æ¥ç»è®¡ | |
| | | | WeeklyStats | GET /api/Dashboard/WeeklyStats?weeks=12 | æ¯å¨ç»è®¡ | |
| | | | MonthlyStats | GET /api/Dashboard/MonthlyStats?months=12 | æ¯æç»è®¡ | |
| | | | StockAgeDistribution | GET /api/Dashboard/StockAgeDistribution | åºé¾åå¸ | |
| | | | StockByWarehouse | GET /api/Dashboard/StockByWarehouse | ä»åºåå¸ | |
| | | |
| | | ### å端ï¼Home.vueï¼ |
| | | | å¾è¡¨ | ç»ä»¶ ID | å¾è¡¨ç±»å | |
| | | |------|---------|----------| |
| | | | æ¬æåºå
¥åºè¶å¿ | chart-monthly-trend | æçº¿å¾ | |
| | | | 仿¥åºå
¥åºå¯¹æ¯ | chart-today | æ±ç¶å¾ | |
| | | | æ¬å¨åºå
¥åºå¯¹æ¯ | chart-week | æ±ç¶å¾ | |
| | | | æ¬æåºå
¥åºå¯¹æ¯ | chart-month | æ±ç¶å¾ | |
| | | | å½ååºåæ»é | (æ°åå¡ç) | - | |
| | | | åºååºé¾åå¸ | chart-stock-age | é¥¼å¾ | |
| | | | åä»åºåºååå¸ | chart-warehouse | æ±ç¶å¾ | |