| | |
| | | |
| | | private string GetWeekKey(DateTime date) |
| | | { |
| | | // 获取周一开始的周 |
| | | // 获取周一开始的周 (ISO 8601) |
| | | var diff = (7 + (date.DayOfWeek - DayOfWeek.Monday)) % 7; |
| | | var monday = date.AddDays(-diff); |
| | | return monday.ToString("yyyy-Www"); |
| | | var weekNum = System.Globalization.CultureInfo.InvariantCulture |
| | | .Calendar.GetWeekOfYear(monday, System.Globalization.CalendarWeekRule.FirstFourDayWeek, DayOfWeek.Monday); |
| | | return $"{monday.Year}-W{weekNum:D2}"; |
| | | } |
| | | ``` |
| | | |
| | |
| | | public async Task<WebResponseContent> StockAgeDistribution() |
| | | { |
| | | var now = DateTime.Now; |
| | | var stocks = await _db.Queryable<Dt_StockInfo>() |
| | | .Select(s => s.CreateDate) |
| | | .ToListAsync(); |
| | | |
| | | // 使用 SQL 直接分组统计,避免加载所有数据到内存 |
| | | 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) } |
| | | new { Range = "7天内", Count = await _db.Queryable<Dt_StockInfo>().Where(s => EF.Functions.DateDiffDay(s.CreateDate, now) <= 7).CountAsync() }, |
| | | new { Range = "7-30天", Count = await _db.Queryable<Dt_StockInfo>().Where(s => EF.Functions.DateDiffDay(s.CreateDate, now) > 7 && EF.Functions.DateDiffDay(s.CreateDate, now) <= 30).CountAsync() }, |
| | | new { Range = "30-90天", Count = await _db.Queryable<Dt_StockInfo>().Where(s => EF.Functions.DateDiffDay(s.CreateDate, now) > 30 && EF.Functions.DateDiffDay(s.CreateDate, now) <= 90).CountAsync() }, |
| | | new { Range = "90天以上", Count = await _db.Queryable<Dt_StockInfo>().Where(s => EF.Functions.DateDiffDay(s.CreateDate, now) > 90).CountAsync() } |
| | | }; |
| | | |
| | | return WebResponseContent.Instance.OK(null, result); |
| | |
| | | MonthOutbound: 0, |
| | | TotalStock: 0 |
| | | }, |
| | | dailyData: [], |
| | | weeklyData: [], |
| | | monthlyData: [], |
| | | stockAgeData: [], |
| | |
| | | mounted() { |
| | | this.initCharts(); |
| | | this.loadData(); |
| | | window.addEventListener("resize", this.handleResize); |
| | | }, |
| | | beforeUnmount() { |
| | | window.removeEventListener("resize", this.handleResize); |
| | | Object.values(this.charts).forEach(chart => chart.dispose()); |
| | | }, |
| | | methods: { |
| | | handleResize() { |
| | | Object.values(this.charts).forEach(chart => chart.resize()); |
| | | }, |
| | | methods: { |
| | | initCharts() { |
| | | this.charts.monthlyTrend = echarts.init(document.getElementById("chart-monthly-trend")); |
| | |
| | | |
| | | async loadData() { |
| | | await this.loadOverview(); |
| | | await this.loadDailyStats(); |
| | | await this.loadWeeklyStats(); |
| | | await this.loadMonthlyStats(); |
| | | await this.loadStockAgeDistribution(); |
| | |
| | | } |
| | | }, |
| | | |
| | | 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(); |
| | | this.updateWeekChart(); |
| | | } |
| | | } catch (e) { |
| | | console.error("加载每周统计失败", e); |
| | |
| | | this.charts.month.setOption(option, true); |
| | | }, |
| | | |
| | | // 更新月度趋势图表(折线+柱状组合) |
| | | // 更新月度趋势图表(折线图) |
| | | updateMonthlyTrendChart() { |
| | | const option = { |
| | | tooltip: { trigger: "axis" }, |
| | |
| | | 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); |