zhang_12370
4 天以前 e986cee1c804ecdf6d03c080ce9a8bb187f724a4
src/views/index.vue
@@ -8,24 +8,50 @@
        </div>
        <div class="card-content">
          <div class="card-title">营收金额</div>
          <div class="card-value">¥1,234,567</div>
          <div class="card-trend">
          <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">+12.5%</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">8,965 吨</div>
          <div class="card-trend">
          <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">+8.2%</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>
@@ -37,9 +63,25 @@
        <div class="chart-title">营收分布</div>
        <div ref="pieChart" class="chart-content pie-chart"></div>
      </div>
      <div class="chart-container">
        <div class="chart-title">供应量趋势</div>
        <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>
@@ -52,25 +94,10 @@
          <h3>库存统计</h3>
        </div>
        <div class="inventory-items">
          <div class="inventory-item">
            <div class="item-name">原煤</div>
            <div class="item-value">15,432 吨</div>
            <div class="item-status normal">正常</div>
          </div>
          <div class="inventory-item">
            <div class="item-name">精煤</div>
            <div class="item-value">8,765 吨</div>
            <div class="item-status normal">正常</div>
          </div>
          <div class="inventory-item">
            <div class="item-name">焦煤</div>
            <div class="item-value">3,241 吨</div>
            <div class="item-status low">偏低</div>
          </div>
          <div class="inventory-item">
            <div class="item-name">块煤</div>
            <div class="item-value">6,789 吨</div>
            <div class="item-status normal">正常</div>
          <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>
@@ -93,236 +120,388 @@
          style="width: 100%"
          :header-cell-style="tableHeaderStyle"
        >
          <el-table-column prop="product" label="产品" width="80"></el-table-column>
          <el-table-column prop="quantity" label="数量" width="80"></el-table-column>
          <el-table-column prop="amount" label="金额" width="90"></el-table-column>
          <el-table-column prop="status" label="状态" width="70">
            <template #default="scope">
              <el-tag
                :type="scope.row.status === '已完成' ? 'success' : 'warning'"
                size="small"
              >
                {{ scope.row.status }}
              </el-tag>
            </template>
          </el-table-column>
          <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>
import * as echarts from 'echarts'
<!-- 删除多余的 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";
export default {
  name: 'Dashboard',
  data() {
    return {
      salesData: [
        { product: '原煤', quantity: '1,234吨', amount: '¥456,789', status: '已完成' },
        { product: '精煤', quantity: '567吨', amount: '¥234,567', status: '已完成' },
        { product: '焦煤', quantity: '890吨', amount: '¥345,678', status: '进行中' },
        { product: '块煤', quantity: '432吨', amount: '¥123,456', status: '已完成' },
        { product: '煤泥', quantity: '678吨', amount: '¥234,567', status: '进行中' }
      ],
      tableHeaderStyle: {
        backgroundColor: '#f5f7fa',
        color: '#606266',
        fontSize: '12px'
      }
    }
  },
  mounted() {
    this.$nextTick(() => {
      this.initCharts()
    })
  },
  methods: {
    initCharts() {
      this.initPieChart()
      this.initAreaChart()
      this.initBarChart()
    },
    initPieChart() {
      const chart = echarts.init(this.$refs.pieChart)
      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: [
              { value: 335, name: '原煤', itemStyle: { color: '#409EFF' } },
              { value: 310, name: '精煤', itemStyle: { color: '#67C23A' } },
              { value: 234, name: '焦煤', itemStyle: { color: '#E6A23C' } },
              { value: 135, name: '块煤', itemStyle: { color: '#F56C6C' } },
              { value: 155, name: '其他', itemStyle: { color: '#909399' } }
            ]
          }
        ]
      }
      chart.setOption(option)
      // 响应式
      window.addEventListener('resize', () => {
        chart.resize()
      })
    },
    initAreaChart() {
      const chart = echarts.init(this.$refs.areaChart)
      const option = {
        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: ['1月', '2月', '3月', '4月', '5月', '6月', '7月'],
            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: [1200, 1320, 1010, 1340, 900, 1230, 1100],
            lineStyle: {
              color: '#409EFF'
            },
            itemStyle: {
              color: '#409EFF'
            }
          }
        ]
      }
      chart.setOption(option)
      // 响应式
      window.addEventListener('resize', () => {
        chart.resize()
      })
    },
    initBarChart() {
      const chart = echarts.init(this.$refs.barChart)
      const option = {
        tooltip: {
          trigger: 'axis',
          axisPointer: {
            type: 'shadow'
          }
        },
        grid: {
          left: '3%',
          right: '4%',
          bottom: '3%',
          containLabel: true
        },
        xAxis: {
          type: 'category',
          data: ['原煤', '精煤', '焦煤', '块煤', '煤泥'],
          axisLabel: {
            fontSize: 11
          }
        },
        yAxis: {
          type: 'value',
          axisLabel: {
            fontSize: 11
          }
        },
        series: [
          {
            name: '销量',
            type: 'bar',
            data: [320, 302, 301, 334, 290],
            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 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,
  };
  nextTick(() => {
      initAreaChart();
    });
};
onMounted(() => {
  getList();
});
</script>
<style scoped lang="scss">
@@ -363,11 +542,11 @@
}
.revenue .card-icon {
  background: linear-gradient(135deg, #409EFF, #79bbff);
  background: linear-gradient(135deg, #409eff, #79bbff);
}
.supply .card-icon {
  background: linear-gradient(135deg, #67C23A, #95d475);
  background: linear-gradient(135deg, #67c23a, #95d475);
}
.card-content {
@@ -397,7 +576,10 @@
}
.trend-value.up {
  color: #67C23A;
  color: #67c23a;
}
.trend-value.down {
  color: #f56c6c;
}
/* 中间图表区域 */
@@ -406,7 +588,7 @@
  gap: 20px;
  margin-bottom: 20px;
}
.el-scrollbar__view{
.el-scrollbar__view {
  width: 100%;
}
.chart-container {
@@ -424,6 +606,8 @@
  margin-bottom: 15px;
  padding-bottom: 10px;
  border-bottom: 2px solid #f0f0f0;
  display: flex;
  justify-content: space-between;
}
.chart-content {
@@ -471,7 +655,7 @@
  padding: 12px;
  background: #f8f9fa;
  border-radius: 6px;
  border-left: 3px solid #409EFF;
  border-left: 3px solid #409eff;
}
.item-name {
@@ -493,12 +677,12 @@
.item-status.normal {
  background: #f0f9ff;
  color: #67C23A;
  color: #67c23a;
}
.item-status.low {
  background: #fef0e6;
  color: #E6A23C;
  color: #e6a23c;
}
/* 柱状图容器 */
@@ -519,22 +703,21 @@
.bottom-card.table .el-table th {
  padding: 8px 0;
}
:deep(.el-scrollbar__view){
:deep(.el-scrollbar__view) {
  width: 100% !important;
}
:deep(.el-table__header,){
    width: 100% !important;
  }
:deep(.el-table__body,){
    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;
  }
@@ -544,15 +727,15 @@
  .top-cards {
    flex-direction: column;
  }
  .dashboard {
    padding: 10px;
  }
  .stat-card {
    padding: 15px;
  }
  .card-value {
    font-size: 20px;
  }