<template>
|
<div class="dashboard">
|
<!-- 顶部统计卡片 -->
|
<div class="top-cards">
|
<div class="stat-card revenue">
|
<div class="card-icon">
|
<i class="el-icon-money"></i>
|
</div>
|
<div class="card-content">
|
<div class="card-title">营收金额</div>
|
<div class="card-value">
|
¥{{
|
homePageData.revenueAmount
|
? formatThousand(homePageData.revenueAmount)
|
: "--"
|
}}
|
</div>
|
<div class="card-trend" v-if="homePageData.trend == '+'">
|
<span class="trend-label">较昨日</span>
|
<span class="trend-value up">+ {{ homePageData.changeRate }}</span>
|
</div>
|
<div class="card-trend" v-if="homePageData.trend == '-'">
|
<span class="trend-label">较昨日</span>
|
<span class="trend-value down"
|
>- {{ homePageData.changeRate }}</span
|
>
|
</div>
|
</div>
|
</div>
|
|
<div class="stat-card supply">
|
<div class="card-icon">
|
<i class="el-icon-truck"></i>
|
</div>
|
<div class="card-content">
|
<div class="card-title">供应量</div>
|
<div class="card-value">
|
{{
|
homePageData.saleQuantity
|
? formatThousand(homePageData.saleQuantity)
|
: "--"
|
}}吨
|
</div>
|
<div class="card-trend" v-if="homePageData.trendQuantity == '+'">
|
<span class="trend-label">较昨日</span>
|
<span class="trend-value up"
|
>+ {{ homePageData.saleQuantityRate }}</span
|
>
|
</div>
|
<div class="card-trend" v-if="homePageData.trendQuantity == '-'">
|
<span class="trend-label">较昨日</span>
|
<span class="trend-value down"
|
>- {{ homePageData.saleQuantityRate }}</span
|
>
|
</div>
|
</div>
|
</div>
|
</div>
|
|
<!-- 中间图表区域 -->
|
<div class="chart-section">
|
<div class="chart-container">
|
<div class="chart-title">营收分布</div>
|
<div ref="pieChart" class="chart-content pie-chart"></div>
|
</div>
|
|
<div class="chart-container">
|
<div class="chart-title">
|
<span>供应量趋势</span>
|
<div>
|
<el-date-picker
|
:locale="zhCN"
|
v-model="selectMonth"
|
type="monthrange"
|
placeholder="选择日期"
|
format="YYYY/MM"
|
value-format="YYYY-MM"
|
range-separator="至"
|
start-placeholder="开始日期"
|
end-placeholder="结束日期"
|
@change="searchMonth"
|
/>
|
</div>
|
</div>
|
<div ref="areaChart" class="chart-content area-chart"></div>
|
</div>
|
</div>
|
|
<!-- 底部三栏布局 -->
|
<div class="bottom-section">
|
<!-- 库存统计 -->
|
<div class="bottom-card inventory">
|
<div class="card-header">
|
<h3>库存统计</h3>
|
</div>
|
<div class="inventory-items">
|
<div class="inventory-item" v-for="(item, index) in inventoryList.Yvalues" :key="index">
|
<div class="item-name">{{ inventoryList.Xkeys[index]? inventoryList.Xkeys[index] : "--"}}</div>
|
<div class="item-value">{{ item ? formatThousand(item) : "0" }}</div>
|
<div class="item-status">吨</div>
|
</div>
|
</div>
|
</div>
|
|
<!-- 柱状图 -->
|
<div class="bottom-card chart">
|
<div class="card-header">
|
<h3>月度对比</h3>
|
</div>
|
<div ref="barChart" class="chart-content bar-chart"></div>
|
</div>
|
|
<!-- 销售数据表格 -->
|
<div class="bottom-card table">
|
<div class="card-header">
|
<h3>销售数据</h3>
|
</div>
|
<el-table
|
:data="salesData"
|
style="width: 100%"
|
:header-cell-style="tableHeaderStyle"
|
>
|
<el-table-column
|
prop="coalName"
|
label="产品"
|
align="center"
|
mini-width="50"
|
></el-table-column>
|
<el-table-column
|
prop="inventoryQuantity"
|
label="数量"
|
align="center"
|
mini-width="50"
|
></el-table-column>
|
<el-table-column
|
prop="totalAmount"
|
label="金额"
|
align="center"
|
mini-width="50"
|
></el-table-column>
|
</el-table>
|
</div>
|
</div>
|
</div>
|
</template>
|
|
<!-- 删除多余的 script 结束标签 -->
|
<script setup>
|
import { getCoalInfo, getYearlySales } from "@/api/home/index";
|
import { ref, onMounted, nextTick } from "vue";
|
import zhCn from "element-plus/dist/locale/zh-cn.mjs";
|
|
// 兼容模板变量名,暴露给模板使用
|
const zhCN = zhCn;
|
import * as echarts from "echarts";
|
|
const homePageData = ref({});
|
const selectMonth = ref([]);
|
|
// 生成无限随机颜色(HSL算法保证高辨识度、柔和不刺眼)
|
function generateRandomColors(count = 10) {
|
const colors = [];
|
const goldenAngle = 137.508; // 黄金角度,保证颜色分布均匀
|
|
for (let i = 0; i < count; i++) {
|
// 使用黄金角度分割确保颜色差异大
|
const hue = (i * goldenAngle) % 360;
|
|
// 饱和度:40-70% 避免过于鲜艳
|
const saturation = 40 + Math.random() * 30;
|
|
// 明度:45-75% 避免过暗或过亮
|
const lightness = 45 + Math.random() * 30;
|
|
colors.push(
|
`hsl(${Math.round(hue)}, ${Math.round(saturation)}%, ${Math.round(
|
lightness
|
)}%)`
|
);
|
}
|
|
return colors;
|
}
|
|
// HSL转16进制(可选,如果需要hex格式)
|
function hslToHex(hsl) {
|
const match = hsl.match(/hsl\((\d+),\s*(\d+)%,\s*(\d+)%\)/);
|
if (!match) return hsl;
|
|
const h = parseInt(match[1]) / 360;
|
const s = parseInt(match[2]) / 100;
|
const l = parseInt(match[3]) / 100;
|
|
const hue2rgb = (p, q, t) => {
|
if (t < 0) t += 1;
|
if (t > 1) t -= 1;
|
if (t < 1 / 6) return p + (q - p) * 6 * t;
|
if (t < 1 / 2) return q;
|
if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
|
return p;
|
};
|
|
let r, g, b;
|
if (s === 0) {
|
r = g = b = l;
|
} else {
|
const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
|
const p = 2 * l - q;
|
r = hue2rgb(p, q, h + 1 / 3);
|
g = hue2rgb(p, q, h);
|
b = hue2rgb(p, q, h - 1 / 3);
|
}
|
|
const toHex = (c) => {
|
const hex = Math.round(c * 255).toString(16);
|
return hex.length === 1 ? "0" + hex : hex;
|
};
|
|
return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
|
}
|
|
// 便捷方法:直接获取16进制颜色数组
|
function getRandomHexColors(count = 1) {
|
return generateRandomColors(count).map(hslToHex);
|
}
|
|
// 千分位格式化函数
|
function formatThousand(num) {
|
if (typeof num === "number") return num.toLocaleString();
|
if (typeof num === "string") {
|
const n = Number(num.replace(/,/g, ""));
|
if (isNaN(n)) return num;
|
return n.toLocaleString();
|
}
|
return num;
|
}
|
|
// 销售数据原始
|
const salesData = ref([]);
|
|
const tableHeaderStyle = {
|
backgroundColor: "#f5f7fa",
|
color: "#606266",
|
fontSize: "12px",
|
};
|
|
// 图表ref
|
const pieChart = ref(null);
|
const areaChart = ref(null);
|
const barChart = ref(null);
|
|
// 饼图初始化
|
const initPieChart = () => {
|
const chart = echarts.init(pieChart.value);
|
const option = {
|
tooltip: {
|
trigger: "item",
|
formatter: "{a} <br/>{b}: {c} ({d}%)",
|
},
|
legend: {
|
orient: "vertical",
|
left: "right",
|
top: "center",
|
textStyle: {
|
fontSize: 12,
|
},
|
},
|
series: [
|
{
|
name: "营收分布",
|
type: "pie",
|
radius: ["30%", "70%"],
|
center: ["40%", "50%"],
|
avoidLabelOverlap: false,
|
label: {
|
show: false,
|
position: "center",
|
},
|
emphasis: {
|
label: {
|
show: true,
|
fontSize: "16",
|
fontWeight: "bold",
|
},
|
},
|
labelLine: {
|
show: false,
|
},
|
data: revenueDistribution.value,
|
},
|
],
|
};
|
chart.setOption(option);
|
window.addEventListener("resize", () => {
|
chart.resize();
|
});
|
};
|
|
// 面积图初始化
|
const initAreaChart = () => {
|
const chart = echarts.init(areaChart.value);
|
const option = {
|
title: {
|
show: supplyTrend.value.length == 0, // 没数据才显示
|
extStyle: {
|
color: "grey",
|
fontSize: 20,
|
},
|
text: "暂无数据",
|
left: "center",
|
top: "center",
|
},
|
tooltip: {
|
trigger: "axis",
|
axisPointer: {
|
type: "cross",
|
label: {
|
backgroundColor: "#6a7985",
|
},
|
},
|
},
|
legend: {
|
data: ["供应量"],
|
top: 10,
|
},
|
grid: {
|
left: "3%",
|
right: "4%",
|
bottom: "3%",
|
containLabel: true,
|
},
|
xAxis: [
|
{
|
type: "category",
|
boundaryGap: false,
|
data: supplyTrend.value.Xkeys || [],
|
axisLabel: {
|
fontSize: 12,
|
},
|
},
|
],
|
yAxis: [
|
{
|
type: "value",
|
axisLabel: {
|
fontSize: 12,
|
},
|
},
|
],
|
series: [
|
{
|
name: "供应量",
|
type: "line",
|
stack: "Total",
|
areaStyle: {
|
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
{ offset: 0, color: "rgba(64, 158, 255, 0.3)" },
|
{ offset: 1, color: "rgba(64, 158, 255, 0.1)" },
|
]),
|
},
|
emphasis: {
|
focus: "series",
|
},
|
data: supplyTrend.value.Yvalues || [],
|
lineStyle: {
|
color: "#409EFF",
|
},
|
itemStyle: {
|
color: "#409EFF",
|
},
|
},
|
],
|
};
|
chart.setOption(option);
|
window.addEventListener("resize", () => {
|
chart.resize();
|
});
|
};
|
|
// 柱状图初始化
|
const initBarChart = () => {
|
const chart = echarts.init(barChart.value);
|
const option = {
|
tooltip: {
|
trigger: "axis",
|
axisPointer: {
|
type: "shadow",
|
},
|
},
|
grid: {
|
left: "3%",
|
right: "4%",
|
bottom: "3%",
|
containLabel: true,
|
},
|
xAxis: {
|
type: "category",
|
data: resultMonthList.value.Xkeys || [],
|
axisLabel: {
|
fontSize: 11,
|
},
|
},
|
yAxis: {
|
type: "value",
|
axisLabel: {
|
fontSize: 11,
|
},
|
},
|
series: [
|
{
|
name: "销量",
|
type: "bar",
|
data: resultMonthList.value.Yvalues || [],
|
itemStyle: {
|
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
{ offset: 0, color: "#409EFF" },
|
{ offset: 1, color: "#79bbff" },
|
]),
|
},
|
barWidth: "60%",
|
},
|
],
|
};
|
chart.setOption(option);
|
window.addEventListener("resize", () => {
|
chart.resize();
|
});
|
};
|
// 收入分布数据
|
const revenueDistribution = ref([]);
|
|
// 初始化所有图表
|
const initCharts = () => {
|
initPieChart();
|
initAreaChart();
|
initBarChart();
|
};
|
|
const getList = async () => {
|
try {
|
searchMonth();
|
const res = await getCoalInfo();
|
homePageData.value = res.data || {};
|
revenueDistribution.value = [];
|
if (homePageData.value.revenueDistribution) {
|
Object.keys(homePageData.value.revenueDistribution).forEach((key) => {
|
let obj = {};
|
obj.name = key;
|
obj.value = homePageData.value.revenueDistribution[key];
|
obj.itemStyle = {
|
color: getRandomHexColors(1)[0], // 使用随机颜色
|
};
|
revenueDistribution.value.push(obj);
|
});
|
}
|
if (homePageData.value.inventory) {
|
let inventoryListXkeys = Object.keys(homePageData.value.inventory);
|
let inventoryListYvalues = Object.values(homePageData.value.inventory);
|
inventoryList.value = {
|
Xkeys: inventoryListXkeys,
|
Yvalues: inventoryListYvalues,
|
};
|
}
|
if(homePageData.value.resultMouth){
|
let resultMonthXkeys = Object.keys(homePageData.value.resultMouth);
|
let resultMonthYvalues = Object.values(homePageData.value.resultMouth);
|
resultMonthList.value = {
|
Xkeys: resultMonthXkeys,
|
Yvalues: resultMonthYvalues,
|
};
|
console.log(resultMonthList.value);
|
}
|
if(homePageData.value.salesResults){
|
salesData.value = homePageData.value.salesResults;
|
}
|
// 数据加载完成后重新初始化图表
|
nextTick(() => {
|
initCharts();
|
});
|
} catch (error) {
|
console.error("获取煤种信息失败:", error);
|
}
|
};
|
const inventoryList = ref([]);
|
const resultMonthList = ref([]);
|
|
const supplyTrend = ref({});
|
const searchMonth = async () => {
|
let res = await getYearlySales({
|
timeRange: selectMonth.value ? selectMonth.value : null,
|
});
|
let Xkeys = Object.keys(res.data.data);
|
let Yvalues = Object.values(res.data.data);
|
supplyTrend.value = {
|
Xkeys,
|
Yvalues,
|
};
|
};
|
onMounted(() => {
|
getList();
|
});
|
</script>
|
|
<style scoped lang="scss">
|
.dashboard {
|
padding: 20px;
|
background-color: #f5f7fa;
|
min-height: 91vh;
|
box-sizing: border-box;
|
}
|
|
/* 顶部统计卡片 */
|
.top-cards {
|
display: flex;
|
gap: 20px;
|
margin-bottom: 20px;
|
}
|
|
.stat-card {
|
flex: 1;
|
background: white;
|
border-radius: 8px;
|
padding: 20px;
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
display: flex;
|
align-items: center;
|
gap: 15px;
|
}
|
|
.card-icon {
|
width: 60px;
|
height: 60px;
|
border-radius: 50%;
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
font-size: 24px;
|
color: white;
|
}
|
|
.revenue .card-icon {
|
background: linear-gradient(135deg, #409eff, #79bbff);
|
}
|
|
.supply .card-icon {
|
background: linear-gradient(135deg, #67c23a, #95d475);
|
}
|
|
.card-content {
|
flex: 1;
|
}
|
|
.card-title {
|
font-size: 14px;
|
color: #909399;
|
margin-bottom: 8px;
|
}
|
|
.card-value {
|
font-size: 24px;
|
font-weight: bold;
|
color: #303133;
|
margin-bottom: 5px;
|
}
|
|
.card-trend {
|
font-size: 12px;
|
}
|
|
.trend-label {
|
color: #909399;
|
margin-right: 5px;
|
}
|
|
.trend-value.up {
|
color: #67c23a;
|
}
|
.trend-value.down {
|
color: #f56c6c;
|
}
|
|
/* 中间图表区域 */
|
.chart-section {
|
display: flex;
|
gap: 20px;
|
margin-bottom: 20px;
|
}
|
.el-scrollbar__view {
|
width: 100%;
|
}
|
.chart-container {
|
flex: 1;
|
background: white;
|
border-radius: 8px;
|
padding: 20px;
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
}
|
|
.chart-title {
|
font-size: 16px;
|
font-weight: bold;
|
color: #303133;
|
margin-bottom: 15px;
|
padding-bottom: 10px;
|
border-bottom: 2px solid #f0f0f0;
|
display: flex;
|
justify-content: space-between;
|
}
|
|
.chart-content {
|
height: 280px;
|
}
|
|
/* 底部三栏布局 */
|
.bottom-section {
|
display: flex;
|
gap: 20px;
|
}
|
|
.bottom-card {
|
flex: 1;
|
background: white;
|
border-radius: 8px;
|
padding: 20px;
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
}
|
|
.card-header {
|
margin-bottom: 15px;
|
padding-bottom: 10px;
|
border-bottom: 2px solid #f0f0f0;
|
}
|
|
.card-header h3 {
|
margin: 0;
|
font-size: 16px;
|
font-weight: bold;
|
color: #303133;
|
}
|
|
/* 库存统计样式 */
|
.inventory-items {
|
display: flex;
|
flex-direction: column;
|
gap: 12px;
|
}
|
|
.inventory-item {
|
display: flex;
|
justify-content: space-between;
|
align-items: center;
|
padding: 12px;
|
background: #f8f9fa;
|
border-radius: 6px;
|
border-left: 3px solid #409eff;
|
}
|
|
.item-name {
|
font-weight: bold;
|
color: #303133;
|
}
|
|
.item-value {
|
color: #606266;
|
font-size: 14px;
|
}
|
|
.item-status {
|
padding: 2px 8px;
|
border-radius: 12px;
|
font-size: 12px;
|
font-weight: bold;
|
}
|
|
.item-status.normal {
|
background: #f0f9ff;
|
color: #67c23a;
|
}
|
|
.item-status.low {
|
background: #fef0e6;
|
color: #e6a23c;
|
}
|
|
/* 柱状图容器 */
|
.bar-chart {
|
height: 200px;
|
}
|
|
/* 表格样式调整 */
|
.bottom-card.table {
|
width: 100%;
|
}
|
|
.bottom-card.table .el-table {
|
font-size: 12px;
|
}
|
|
.bottom-card.table .el-table td,
|
.bottom-card.table .el-table th {
|
padding: 8px 0;
|
}
|
:deep(.el-scrollbar__view) {
|
width: 100% !important;
|
}
|
:deep(.el-table__header, ) {
|
width: 100% !important;
|
}
|
:deep(.el-table__body, ) {
|
width: 100% !important;
|
}
|
/* 响应式设计 */
|
@media (max-width: 1200px) {
|
.bottom-section {
|
flex-direction: column;
|
}
|
|
.chart-section {
|
flex-direction: column;
|
}
|
}
|
|
@media (max-width: 768px) {
|
.top-cards {
|
flex-direction: column;
|
}
|
|
.dashboard {
|
padding: 10px;
|
}
|
|
.stat-card {
|
padding: 15px;
|
}
|
|
.card-value {
|
font-size: 20px;
|
}
|
}
|
</style>
|