<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 full-width">
|
<div class="chart-card">
|
<div class="card-title">每日出入库趋势</div>
|
<div id="chart-daily" class="chart-content"></div>
|
</div>
|
</div>
|
|
<!-- 第四行:仓库分布 -->
|
<div class="chart-row">
|
<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: {},
|
dailyData: [],
|
monthlyData: [],
|
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.daily = echarts.init(document.getElementById("chart-daily"));
|
this.charts.warehouse = echarts.init(document.getElementById("chart-warehouse"));
|
},
|
|
async loadData() {
|
await this.loadMonthlyStats();
|
await this.loadDailyStats();
|
await this.loadStockByWarehouse();
|
},
|
|
async loadMonthlyStats() {
|
try {
|
const res = await this.http.get("/api/Dashboard/MonthlyStats", { months: 12 });
|
if (res.status && res.data) {
|
console.log("每月统计数据:", res.data);
|
this.monthlyData = res.data;
|
this.updateMonthlyTrendChart();
|
}
|
} catch (e) {
|
console.error("加载每月统计失败", e);
|
}
|
},
|
|
async loadDailyStats() {
|
try {
|
const res = await this.http.get("/api/Dashboard/DailyStats", { days: 30 });
|
if (res.status && res.data) {
|
console.log("每日统计数据:", res.data);
|
this.dailyData = res.data;
|
this.updateDailyChart();
|
}
|
} catch (e) {
|
console.error("加载每日统计失败", e);
|
}
|
},
|
|
async loadStockByWarehouse() {
|
try {
|
const res = await this.http.get("/api/Dashboard/StockByWarehouse");
|
if (res.status && res.data) {
|
console.log("仓库分布数据:", res.data);
|
this.warehouseData = res.data.data || res.data;
|
this.updateWarehouseChart();
|
}
|
} catch (e) {
|
console.error("加载仓库分布失败", e);
|
}
|
},
|
|
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);
|
},
|
|
updateDailyChart() {
|
const option = {
|
tooltip: { trigger: "axis" },
|
legend: { data: ["入库", "出库"], textStyle: { color: "#fff" } },
|
xAxis: {
|
type: "category",
|
data: this.dailyData.map(d => d.date),
|
axisLabel: {
|
color: "#fff",
|
interval: 0,
|
rotate: 45,
|
fontSize: 12,
|
margin: 10
|
},
|
axisTick: {
|
alignWithLabel: true
|
}
|
},
|
yAxis: {
|
type: "value",
|
axisLabel: { color: "#fff" }
|
},
|
grid: {
|
left: '3%',
|
right: '4%',
|
bottom: '15%',
|
top: '10%',
|
containLabel: true
|
},
|
series: [
|
{ name: "入库", type: "bar", data: this.dailyData.map(d => d.inbound), itemStyle: { color: "#5470c6" } },
|
{ name: "出库", type: "bar", data: this.dailyData.map(d => d.outbound), itemStyle: { color: "#91cc75" } }
|
]
|
};
|
this.charts.daily.setOption(option, true);
|
},
|
|
updateWarehouseChart() {
|
const warehouseNames = this.warehouseData.map(w => w.warehouse);
|
const totalStocks = this.warehouseData.map(w => w.total);
|
const hasStocks = this.warehouseData.map(w => w.hasStock);
|
const noStocks = this.warehouseData.map(w => w.noStock);
|
const hasStockPercentages = this.warehouseData.map(w => w.hasStockPercentage);
|
const noStockPercentages = this.warehouseData.map(w => w.noStockPercentage);
|
|
const option = {
|
tooltip: {
|
trigger: 'axis',
|
axisPointer: {
|
type: 'shadow'
|
},
|
formatter: function(params) {
|
let tip = params[0].name + '<br/>';
|
params.forEach(param => {
|
const dataIndex = param.dataIndex;
|
const warehouse = window.homeComponent.warehouseData[dataIndex];
|
|
if (param.seriesName === '已用容量') {
|
tip += `${param.marker}${param.seriesName}: ${param.value} (${warehouse.hasStockPercentage})<br/>`;
|
tip += `有库存: ${warehouse.hasStock}<br/>`;
|
tip += `无库存: ${warehouse.noStock}<br/>`;
|
tip += `总容量: ${warehouse.total}`;
|
} else if (param.seriesName === '剩余容量') {
|
tip += `${param.marker}${param.seriesName}: ${param.value} (${warehouse.noStockPercentage})<br/>`;
|
tip += `有库存: ${warehouse.hasStock}<br/>`;
|
tip += `无库存: ${warehouse.noStock}<br/>`;
|
tip += `总容量: ${warehouse.total}`;
|
}
|
});
|
return tip;
|
}
|
},
|
legend: {
|
data: ['已用容量', '剩余容量'],
|
textStyle: { color: '#fff' }
|
},
|
xAxis: {
|
type: 'category',
|
data: warehouseNames,
|
axisLabel: { color: '#fff', rotate: 30 }
|
},
|
yAxis: {
|
type: 'value',
|
axisLabel: { color: '#fff' }
|
},
|
series: [
|
{
|
name: '已用容量',
|
type: 'bar',
|
data: hasStocks.map((value, index) => ({
|
value: value,
|
label: {
|
show: true,
|
position: 'top',
|
formatter: '{c} {a|' + hasStockPercentages[index] + '}',
|
rich: {
|
a: {
|
lineHeight: 20,
|
borderColor: '#91cc75',
|
color: '#91cc75'
|
}
|
}
|
}
|
})),
|
itemStyle: { color: '#91cc75' }
|
},
|
{
|
name: '剩余容量',
|
type: 'bar',
|
data: noStocks.map((value, index) => ({
|
value: value,
|
label: {
|
show: true,
|
position: 'top',
|
formatter: '{c} {a|' + noStockPercentages[index] + '}',
|
rich: {
|
a: {
|
lineHeight: 20,
|
borderColor: '#fac858',
|
color: '#fac858'
|
}
|
}
|
}
|
})),
|
itemStyle: { color: '#fac858' }
|
}
|
]
|
};
|
|
window.homeComponent = this;
|
|
this.charts.warehouse.setOption(option, true);
|
}
|
}
|
};
|
</script>
|
|
<style scoped>
|
.dashboard-container {
|
padding: 20px;
|
color: #e0e0e0;
|
min-height: calc(100vh - 60px);
|
background: linear-gradient(135deg, #0f172a 0%, #1e293b 100%);
|
background-attachment: fixed;
|
}
|
|
.chart-row {
|
display: flex;
|
gap: 20px;
|
margin-bottom: 20px;
|
}
|
|
.chart-row.full-width {
|
width: 100%;
|
}
|
|
.chart-card {
|
flex: 1;
|
background: rgba(10, 16, 35, 0.6);
|
backdrop-filter: blur(10px);
|
border: 1px solid rgba(64, 224, 208, 0.3);
|
border-radius: 12px;
|
padding: 15px;
|
position: relative;
|
box-shadow:
|
0 0 15px rgba(0, 255, 255, 0.1),
|
inset 0 0 10px rgba(64, 224, 208, 0.1);
|
transition: all 0.3s ease;
|
overflow: hidden;
|
}
|
|
.chart-card:hover {
|
transform: translateY(-5px);
|
box-shadow:
|
0 0 25px rgba(0, 255, 255, 0.3),
|
inset 0 0 15px rgba(64, 224, 208, 0.2);
|
border: 1px solid rgba(64, 224, 208, 0.6);
|
}
|
|
.chart-card::before {
|
content: "";
|
position: absolute;
|
top: 0;
|
left: 0;
|
width: 10px;
|
height: 10px;
|
border-top: 2px solid #00ffff;
|
border-left: 2px solid #00ffff;
|
box-shadow: -2px -2px 10px #00ffff, 0 0 10px rgba(0, 255, 255, 0.7);
|
}
|
|
.chart-card::after {
|
content: "";
|
position: absolute;
|
top: 0;
|
right: 0;
|
width: 10px;
|
height: 10px;
|
border-top: 2px solid #00ffff;
|
border-right: 2px solid #00ffff;
|
box-shadow: 2px -2px 10px #00ffff, 0 0 10px rgba(0, 255, 255, 0.7);
|
}
|
|
.chart-card::before,
|
.chart-card::after {
|
animation: neon-flicker 2s infinite alternate;
|
}
|
|
@keyframes neon-flicker {
|
0%, 100% {
|
opacity: 1;
|
box-shadow: -2px -2px 10px #00ffff, 0 0 10px rgba(0, 255, 255, 0.7);
|
}
|
50% {
|
opacity: 0.8;
|
box-shadow: -2px -2px 5px #00ffff, 0 0 5px rgba(0, 255, 255, 0.5);
|
}
|
}
|
|
.card-title {
|
color: #00ffff;
|
font-size: 16px;
|
text-align: center;
|
margin-bottom: 10px;
|
text-shadow: 0 0 10px rgba(0, 255, 255, 0.7);
|
font-weight: 500;
|
}
|
|
.chart-content {
|
height: 280px;
|
width: 100%;
|
}
|
|
/* 全宽图表 */
|
.full-width .chart-card {
|
flex: none;
|
width: 100%;
|
}
|
|
.full-width .chart-content {
|
height: 350px;
|
}
|
|
/* 添加网格线效果 */
|
.dashboard-container::before {
|
content: "";
|
position: fixed;
|
top: 0;
|
left: 0;
|
right: 0;
|
bottom: 0;
|
background-image:
|
linear-gradient(rgba(64, 224, 208, 0.05) 1px, transparent 1px),
|
linear-gradient(90deg, rgba(64, 224, 208, 0.05) 1px, transparent 1px);
|
background-size: 30px 30px;
|
pointer-events: none;
|
z-index: -1;
|
}
|
</style>
|