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