<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>
|
|
<script>
|
import * as echarts from "echarts";
|
|
export default {
|
name: "Home",
|
data() {
|
return {
|
charts: {},
|
overviewData: {
|
TodayInbound: 0,
|
TodayOutbound: 0,
|
MonthInbound: 0,
|
MonthOutbound: 0,
|
TotalStock: 0
|
},
|
weeklyData: [],
|
monthlyData: [],
|
stockAgeData: [],
|
warehouseData: []
|
};
|
},
|
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());
|
},
|
|
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.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 loadWeeklyStats() {
|
try {
|
const res = await this.http.get("/api/Dashboard/WeeklyStats", { weeks: 12 });
|
if (res.Status && res.Data) {
|
this.weeklyData = res.Data;
|
this.updateWeekChart();
|
}
|
} 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() {
|
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 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: "bar", 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);
|
},
|
|
updateStockAgeChart() {
|
const option = {
|
tooltip: { trigger: "axis" },
|
xAxis: {
|
type: "category",
|
data: this.stockAgeData.map(s => s.Range),
|
axisLabel: { color: "#fff" }
|
},
|
yAxis: {
|
type: "value",
|
axisLabel: { color: "#fff" }
|
},
|
series: [
|
{
|
type: "bar",
|
data: this.stockAgeData.map(s => s.Count),
|
itemStyle: { color: "#5470c6" }
|
}
|
]
|
};
|
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>
|
|
<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>
|