From 42c82b034ec6aba3664436c5dcf63e9b3511d7a5 Mon Sep 17 00:00:00 2001
From: wanshenmean <cathay_xy@163.com>
Date: 星期一, 30 三月 2026 11:30:47 +0800
Subject: [PATCH] docs: 添加首页仪表盘实现计划
---
Code/WMS/docs/superpowers/plans/2026-03-30-dashboard-chart-plan.md | 866 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 866 insertions(+), 0 deletions(-)
diff --git a/Code/WMS/docs/superpowers/plans/2026-03-30-dashboard-chart-plan.md b/Code/WMS/docs/superpowers/plans/2026-03-30-dashboard-chart-plan.md
new file mode 100644
index 0000000..4620f1c
--- /dev/null
+++ b/Code/WMS/docs/superpowers/plans/2026-03-30-dashboard-chart-plan.md
@@ -0,0 +1,866 @@
+# 棣栭〉浠〃鐩樺浘琛ㄥ姛鑳藉疄鐜拌鍒�
+
+> **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涓粺璁℃帴鍙o紝浣跨敤 SqlSugar 鐩存帴鏌ヨ Dt_Task_Hty锛堝凡瀹屾垚浠诲姟鍘嗗彶琛級鍜� Dt_StockInfo 琛�
+- 鍓嶇锛氶噸鍐� Home.vue锛屼娇鐢� ECharts 5.0.2 瀹炵幇浠〃鐩樺竷灞�锛屽鐢� bigdata.vue 涓殑 ECharts 浣跨敤妯″紡
+- 鏁版嵁鏉ユ簮锛欴t_Task_Hty.InsertTime锛堜换鍔″畬鎴愭椂闂达級锛孴askType 鍖哄垎鍏ュ簱(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: 鎻愪氦浠g爜**
+
+```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: 鎻愪氦浠g爜**
+
+```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 鏁版嵁锛屾牸寮忕鍚堣璁℃枃妗�
+
+---
+
+## 鎬荤粨
+
+### 鍚庣锛圖ashboardController锛�
+| 鎺ュ彛 | 璺敱 | 璇存槑 |
+|------|------|------|
+| 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 | 浠撳簱鍒嗗竷 |
+
+### 鍓嶇锛圚ome.vue锛�
+| 鍥捐〃 | 缁勪欢 ID | 鍥捐〃绫诲瀷 |
+|------|---------|----------|
+| 鏈湀鍑哄叆搴撹秼鍔� | chart-monthly-trend | 鎶樼嚎鍥� |
+| 浠婃棩鍑哄叆搴撳姣� | chart-today | 鏌辩姸鍥� |
+| 鏈懆鍑哄叆搴撳姣� | chart-week | 鏌辩姸鍥� |
+| 鏈湀鍑哄叆搴撳姣� | chart-month | 鏌辩姸鍥� |
+| 褰撳墠搴撳瓨鎬婚噺 | (鏁板瓧鍗$墖) | - |
+| 搴撳瓨搴撻緞鍒嗗竷 | chart-stock-age | 楗煎浘 |
+| 鍚勪粨搴撳簱瀛樺垎甯� | chart-warehouse | 鏌辩姸鍥� |
--
Gitblit v1.9.3