vue2动态实现多Y轴echarts图表,及节点点击事件

父组件

javascript 复制代码
<template>
  <div class="app-container">
    <div class="content">
      <el-form
        :model="echartsqueryParams"
        ref="echartsqueryForm"
        :inline="true"
      >
        <el-form-item label="号" prop="furnaceNumber">
          <el-select
            v-model="echartsqueryParams.furnaceNumber"
            placeholder="请选择"
            style="width: 150px"
          >
            <el-option
              v-for="item in deviceOptions"
              :key="item"
              :label="item"
              :value="item"
            >
            </el-option>
          </el-select>
        </el-form-item>
        <el-form-item label="日期">
          <el-date-picker
            v-model="timeData"
            popper-class="noClear"
            size="small"
            value-format="yyyy-MM-dd HH:mm:ss"
            type="datetimerange"
            range-separator="-"
            start-placeholder="开始日期"
            end-placeholder="结束日期"
            :clearable="false"
            @change="handleChange"
          ></el-date-picker>
        </el-form-item>
        <el-form-item label="真空">
          <el-input
            style="width: 150px"
            v-model="csValue"
            placeholder="请输入"
            type="number"
            @blur="inputChange"
          />
        </el-form-item>
        <el-form-item label="时间间隔" prop="interval">
          <el-select
            v-model="echartsqueryParams.interval"
            placeholder="请选择"
            style="width: 150px"
          >
            <el-option
              v-for="item in options"
              :key="item.value"
              :label="item.label"
              :value="item.value"
            >
            </el-option>
          </el-select>
        </el-form-item>
        <el-form-item>
          <el-button
            icon="el-icon-printer"
            type="primary"
            @click="ceshi = !ceshi"
            size="mini"
            >打印</el-button
          >
          <el-button
            type="primary"
            icon="el-icon-search"
            size="mini"
            @click="handleQuery_echarts"
            >搜索</el-button
          >
          <el-button
            icon="el-icon-refresh"
            size="mini"
            @click="resetQuery_echarts"
            >重置</el-button
          >
        </el-form-item>
      </el-form>
      <div>
        <el-checkbox-group
          v-model="checkedCities"
          @change="handleCheckedCitiesChange"
        >
          <el-checkbox v-for="city in cities" :label="city" :key="city">{{
            city
          }}</el-checkbox>
        </el-checkbox-group>
      </div>
      <div style="margin-bottom: 15px"></div>
      <div
        v-loading="echartsLoading"
        style="width: 100%; height: calc(100% - 40px - 60px)"
      >
        <moreyaxis
          v-if="axiosData.length > 0"
          :legend="legend"
          :axiosData="axiosData"
          :yaxiosData="yaxios"
          :serios="serios"
          :colors="colors"
          :gridRIght="gridRIght"
        ></moreyaxis>
        <el-empty description="暂无数据" v-else></el-empty>
      </div>
    </div>
    <!-- 打印内容 -->
    <el-dialog
      class="dialogPrint"
      title="请确认打印内容"
      :visible.sync="ceshi"
      style="height: 90vh"
    >
      <div id="printMe" style="width: 100%">
        <div style="width: 1px; height: 1px"></div>
        <div v-if="echartsList.nodeTimes.length > 0">
          <div style="display: flex">
            <p style="margin: 0; margin-right: 20px">
              号:{{ echartsqueryParams.furnaceNumber }}
            </p>
            <p style="margin: 0; margin-right: 20px">
              开始时间:{{ echartsList.nodeTimes[0] }}
            </p>
            <p style="margin: 0">
              结束时间:{{
                echartsList.nodeTimes[Number(echartsList.nodeTimes.length) - 1]
              }}
            </p>
          </div>
          <ul
            style="
              padding: 0;
              display: grid;
              grid-template-columns: repeat(4, 1fr);
              grid-column-gap: 10px;
              grid-row-gap: 10px;
            "
          >
            <li v-for="(item, index) in echartsList.series" :key="index">
              <p
                style="
                  width: 100%;
                  border: 1px solid #f9f9f9;
                  padding: 10px;
                  margin: 0;
                "
              >
                <span
                  style="border-left: 3px solid #4068e0; padding-left: 5px"
                  >{{ item.name }}</span
                >
              </p>
              <div style="display: flex">
                <p
                  style="
                    font-size: 12px;
                    margin: 0;
                    width: 50%;
                    border: 1px solid #f9f9f9;
                    border-top: none;
                    padding: 10px;
                  "
                >
                  开始{{ item.name.substring(item.name.length - 2) }}:{{
                    item.values[0]
                  }}{{ item.unit }}
                </p>
                <p
                  style="
                    font-size: 12px;
                    margin: 0;
                    width: 50%;
                    border: 1px solid #f9f9f9;
                    border-top: none;
                    padding: 10px;
                  "
                >
                  结束{{ item.name.substring(item.name.length - 2) }}:{{
                    item.values[Number(item.values.length) - 1]
                  }}{{ item.unit }}
                </p>
              </div>
            </li>
          </ul>
        </div>
        <div style="width: 100%; height: 500px" v-if="axiosData.length > 0">
          <moreyaxis
            style="width: 100%; height: 500px"
            :legend="legend"
            :axiosData="axiosData"
            :yaxiosData="yaxios"
            :serios="serios"
            :colors="colors"
            :gridRIght="gridRIght"
          ></moreyaxis>
        </div>
      </div>

      <div slot="footer">
        <el-button @click="ceshi = false">取 消</el-button>
        <el-button type="primary" v-print="printObj">确 定</el-button>
      </div>
    </el-dialog>
  </div>
</template>
<script>
import moreyaxis from "@/views/echarts/moreyaxis.vue";
import { echartsData } from "@/api/collocation/records";
export default {
  components: { moreyaxis },
  data() {
    return {
      deviceOptions: [28030002, 13020101, 28030003, 13020100],
      ceshi: false,
      // 图表参数
      start: true,
      echartsList: {
        nodeTimes: [],
        series: [],
      },
      echartsLoading: true,
      colors: ["#6488FE", "#FF6F6F", "#FEE177", "#2AF0CF", "#08BE87"],
      legend: [],
      axiosData: [],
      serios: [],
      yaxios: [],
      // 查询参数
      echartsqueryParams: {
        startTime: null,
        endTime: null,
        interval: 30,
        id: null,
        // limit: 200,
        furnaceNumber: null,
      },
      timeData: [],
      options: [
        {
          label: "十秒",
          value: 10,
        },
        {
          label: "三十秒",
          value: 30,
        },
        {
          label: "一分钟",
          value: 60,
        },
        {
          label: "五分钟",
          value: 300,
        },
        {
          label: "十分钟",
          value: 600,
        },
        {
          label: "十五分钟",
          value: 900,
        },
        {
          label: "半小时",
          value: 1800,
        },
        {
          label: "一小时",
          value: 3600,
        },
      ],
      printObj: {
        id: "printMe",
        popTitle: "记录",
        closeCallback: this.closeCallback,
      },
      detailsTable: [],
      gridRIght: 0,
      csValue: null,
      cities: [],
      cityOptions: [],
      checkAll: false,
      isIndeterminate: true,
      checkedCities: [],
      data: {},
    };
  },
  methods: {
    handleChange(value) {
      if (value) {
        const start = new Date(value[0]);
        const end = new Date(value[1]);

        if (end - start > 24 * 60 * 60 * 1000) {
          this.$message.error("结束时间不能大于开始时间一天");
          this.timeData = null; // 清空选择
        }
      }
    },
    // 设备列表
    deviceData() {
      listDevice().then((res) => [console.log(res)]);
    },
    handleCheckedCitiesChange(value) {
      this.echartsSerios(this.data.series);
    },
    // 导出
    handleExport_echarts() {},
    // 图表搜索
    handleQuery_echarts() {
      this.echartsLoading = true;
      this.echartsDataList();
    },
    // 图表重置
    resetQuery_echarts() {
      this.updateCurrentTime();
      this.echartsLoading = true;
      this.echartsqueryParams.interval = null;
      this.echartsDataList();
    },
    // 图表数据解析
    echartsDataList(row) {
      this.echartsqueryParams.startTime = this.timeData[0];
      this.echartsqueryParams.endTime = this.timeData[1];
      echartsData(this.echartsqueryParams).then((res) => {
        this.data = res.data;
        this.checkedCities = this.data.series.map((i) => i.name);
        this.cityOptions = this.data.series.map((i) => i.name);
        this.cities = this.data.series.map((i) => i.name);
        this.axiosData = this.data.nodeTimes;
        this.legend = this.data.series.map((i) => i.name);
        this.echartsLoading = false;
        this.echartsList = JSON.parse(JSON.stringify(this.data));
        this.echartsList.series = this.echartsList.series.filter(
          (item) => item.name !== "真空"
        );
        this.echartsSerios(this.data.series);
      });
    },
    echartsSerios(data) {
      this.serios = data.map((i, index) => {
        const name = {
          name: i.name,
          type: "line",
          yAxisIndex: index,
          data: i.values,
          unit: i.unit,
          z: 5,
          zLevel: 5,
          symbolSize: 10,
          label: {
            show: true,
            color: "#999999", // 标签文字颜色
            fontSize: 12, // 标签文字大小
            fontWeight: 400,
            avoidLabelOverlap: true,
            overlap: false,
            formatter: function (params) {
              if (params.dataIndex % 4 === 0) {
                return params.value;
              } else {
                return ""; // 不显示标签以避免重叠
              }
            },
          },
        };
        if (i.name === "真空" && this.csValue != 0) {
          name.markLine = {
            data: [{ type: "average", name: i.name, yAxis: this.csValue }],
          };
        }
        return name;
      });

      let currentOffset = 0; // 总体的偏移量
      let nameLengthOffset = 0;
      this.yaxios = data.map((i, index) => {
        nameLengthOffset = i.name.length * 20; // 名称长度偏移量
        const name = {
          type: "value",
          name: i.name,
          position: "right",
          alignTicks: true,
          offset: currentOffset,
          axisLine: {
            show: true,
            lineStyle: {
              color: this.colors[index % this.colors.length],
            },
          },
          axisLabel: {
            formatter: "{value}" + i.unit,
          },
          axisTick: {
            show: true,
          },
        };

        // 判断哪个轴不显示
        if (this.checkedCities.indexOf(i.name) > -1) {
          name.show = true;
        } else {
          name.show = false;
          nameLengthOffset = nameLengthOffset - i.name.length * 20;
        }
        if (i.yAxisMin != null) {
          name.min = i.yAxisMin;
        }

        if (i.yAxisMax != null) {
          name.max = i.yAxisMax;
        }

        currentOffset += nameLengthOffset; // 更新偏移量
        return name;
      });

      this.gridRIght = currentOffset - nameLengthOffset / 2; // 更新网格右侧偏移量
    },
    updateCurrentTime() {
      const now = new Date();
      const year = now.getFullYear();
      const month = String(now.getMonth() + 1).padStart(2, "0");
      const day = String(now.getDate()).padStart(2, "0");
      const hours = String(now.getHours()).padStart(2, "0");
      const minutes = String(now.getMinutes()).padStart(2, "0");
      const seconds = String(now.getSeconds()).padStart(2, "0");
      // 获取前一个小时的时间
      const previousHour = new Date(now.getTime() - 3600000); // 3600000 毫秒 = 1 小时
      const previousYear = previousHour.getFullYear();
      const previousMonth = String(previousHour.getMonth() + 1).padStart(
        2,
        "0"
      );
      const previousDay = String(previousHour.getDate()).padStart(2, "0");
      const previousHours = String(previousHour.getHours()).padStart(2, "0");
      const previousMinutes = String(previousHour.getMinutes()).padStart(
        2,
        "0"
      );
      const previousSeconds = String(previousHour.getSeconds()).padStart(
        2,
        "0"
      );
      this.$set(this, "timeData", [
        `${previousYear}-${previousMonth}-${previousDay} ${previousHours}:${previousMinutes}:${previousSeconds}`,
        `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`,
      ]);

      // this.echartsqueryParams.furnaceNumber = 13020100;
      // this.timeData = ["2025-09-08 00:00:00", "2025-09-08 00:03:00"];
    },
    inputChange(value) {
      this.echartsDataList();
    },
  },
  created() {
    this.echartsqueryParams.furnaceNumber = this.deviceOptions[0];
    this.updateCurrentTime();
  },
  mounted() {
    this.echartsDataList();
  },
};
</script>
<style lang="scss" scoped>
ul li {
  list-style: none;
}
.app-container {
  width: 100%;
  height: calc(100vh - 100px);
  .content {
    width: 100%;
    height: 100%;
    overflow: auto;
  }
}
::v-deep .el-dialog {
  width: 80vw !important;
  border-radius: 8px;
  margin-bottom: 0;
  margin-top: 5vh !important;
  display: flex;
  flex-direction: column;
  max-height: calc(90vh - 100px);
  overflow: hidden;
  box-sizing: border-box;
  .el-dialog__header {
    padding-top: 14px;
  }
  .el-dialog__body {
    margin: 0 20px 20px 20px;
    padding: 0;
    overflow: auto;
  }
}
@media print {
  @page {
    size: auto;
    // margin: 3mm;
  }
}
</style>

子组件

javascript 复制代码
<template>
  <div ref="chartRef" style="width: 100%; height: 100%"></div>
</template>

<script>
import * as echarts from "echarts";

export default {
  name: "moreyaxis",
  props: {
    legend: {
      type: Array,
      default: () => [],
    },
    axiosData: {
      type: Array,
      default: () => [],
    },
    yaxiosData: {
      type: Array,
      default: () => [],
    },
    serios: {
      type: Array,
      default: () => [],
    },
    colors: {
      type: Array,
      default: () => [],
    },
    gridRIght: {
      type: Number,
      default: () => 0,
    },
  },
  data() {
    return {
      chartInstance: null,
      selectedPoints: [], // 用于存储选中的节点
    };
  },
  watch: {
    yaxiosData: {
      deep: true,
      handler() {
        this.selectedPoints = [];
        this.destroyChart();
        this.initChart();
      },
    },
  },
  mounted() {
    if (this.serios.length > 0) {
      this.initChart();
    }
    this.setupResizeObserver();
  },
  beforeDestroy() {
    if (this.chartInstance) {
      this.chartInstance.dispose();
    }
  },
  methods: {
    initChart() {
      if (!this.chartInstance) {
        this.chartInstance = echarts.init(this.$refs.chartRef);
      }
      let currentData = null; //
      const option = {
        color: this.colors,
        tooltip: {
          trigger: "axis",
          axisPointer: {
            type: "cross",
          },
          textStyle: {
            fontSize: 10,
          },
          // triggerOn: "click",
          alwaysShowContent: false, // 关键:永久显示
          enterable: false,
          show: true, // 默认不显示
        },
        dataZoom: [
          {
            type: "inside",
          },
        ],
        grid: {
          top: "60px",
          left: "40px",
          bottom: "40px",
          right: this.gridRIght + "px",
        },
        legend: {
          data: this.legend,
          top: 0,
        },
        xAxis: [
          {
            boundaryGap: false,
            type: "category",
            axisTick: {
              alignWithLabel: true,
            },
            data: this.axiosData,
            axisLabel: {
              formatter: function (params) {
                var newParamsName = "";
                var paramsNameNumber = params.length;
                var provideNumber = 10;
                var rowNumber = Math.ceil(paramsNameNumber / provideNumber);
                for (let row = 0; row < rowNumber; row++) {
                  newParamsName +=
                    params.substring(
                      row * provideNumber,
                      (row + 1) * provideNumber
                    ) + "\n";
                }
                return newParamsName;
              },
              color: "#999",
            },
          },
        ],
        yAxis: this.yaxiosData,
        series: this.serios,
        graphic: {
          elements: this.selectedPoints.map((point) => ({
            type: "group",
            children: [
              {
                z: 10,
                zLevel: 10,
                type: "rect",
                shape: { width: 140, height: 90 },
                style: {
                  fill: "#FFF",
                  // stroke: "#666", // 边框颜色
                  // lineWidth: 1, // 边框宽度
                  shadowBlur: 5, // 阴影模糊度
                  shadowColor: "#666", // 阴影颜色
                  shadowOffsetX: 2, // 阴影水平偏移
                  shadowOffsetY: 2, // 阴影垂直偏移
                  padding: ["10px", "5px"],
                },
              },
              {
                type: "text",
                z: 10,
                zLevel: 10,
                style: {
                  text: this.formatTooltipContent(point),
                  fill: "#999",
                  fontSize: 12,
                  textBaseline: "middle",
                  padding: [10, 0, 0, 0],
                },
                position: [10, 0],
              },
            ],
            position: [point.x, point.y],
            draggable: true,
          })),
        },
      };
      this.chartInstance.setOption(option);

      // 监听点击事件
      this.chartInstance.on("click", (params) => {
        if (params.componentType === "series") {
          const existingPoint = this.selectedPoints.find(
            (point) =>
              point.seriesName === params.seriesName &&
              point.dataIndex === params.dataIndex
          );
          if (existingPoint) {
            this.selectedPoints = this.selectedPoints.filter(
              (point) =>
                point.seriesName !== params.seriesName ||
                point.dataIndex !== params.dataIndex
            );
          } else {
            this.selectedPoints.push({
              seriesIndex: params.seriesIndex,
              name: params.name,
              seriesName: params.seriesName,
              value: params.value,
              x: params.event.offsetX,
              y: params.event.offsetY,
              dataIndex: params.dataIndex,
            });
          }
          // this.selectedPoints.forEach((item) => {
          //   this.chartInstance.dispatchAction({
          //     type: "showTip",
          //     // 系列的 index,在 tooltip 的 trigger 为 axis 的时候可选。
          //     seriesIndex: item.seriesIndex,
          //     // 数据项的 index,如果不指定也可以通过 name 属性根据名称指定数据项
          //     dataIndex: item.dataIndex,
          //   });
          // });

          this.destroyChart();
          this.initChart();
        }
      });
    },
    formatTooltipContent(point) {
      // 格式化 tooltip 内容,显示多个轴的数据
      let content = `${this.axiosData[point.dataIndex]}\n`;
      this.serios.forEach((series) => {
        if (series.data[point.dataIndex] !== undefined) {
          content += `${series.name}: ${series.data[point.dataIndex]}${
            series.unit
          }\n`;
        }
      });
      return content;
    },
    destroyChart() {
      if (this.chartInstance) {
        this.chartInstance.dispose();
        this.chartInstance = null;
      }
    },
    setupResizeObserver() {
      const resizeObserver = new ResizeObserver((entries) => {
        for (let entry of entries) {
          if (entry.target === this.$refs.chartRef) {
            if (this.chartInstance) {
              this.chartInstance.resize();
            }
          }
        }
      });
      resizeObserver.observe(this.$refs.chartRef);
    },
  },
};
</script>

<style scoped>
/* 添加一些样式 */
</style>

源代码记录

相关推荐
文心快码BaiduComate2 小时前
用Zulu轻松搭建国庆旅行4行诗网站
前端·javascript·后端
正义的大古3 小时前
OpenLayers地图交互 -- 章节十八:拖拽旋转和缩放交互详解
javascript·vue.js·openlayers
行者..................3 小时前
手动编译 OpenCV 4.1.0 源码,生成 ARM64 动态库 (.so),然后在 Petalinux 中打包使用。
前端·webpack·node.js
小爱同学_4 小时前
一次面试让我重新认识了 Cursor
前端·面试·程序员
golang学习记4 小时前
AI 乱写代码?不是模型不行,而是你的 VS Code 缺了 Context!MCP 才是破局关键
前端
星光不问赶路人4 小时前
Vite 中的 import.meta.glob vs 动态导入:该用哪个?
前端·vite
疯狂踩坑人4 小时前
【万字长文】让面试没有难撕的JS基础题
javascript·面试
z_y_j2299704385 小时前
服务器中使用Docker部署前端项目
服务器·前端·docker·容器
极客小俊5 小时前
【浅谈javascript禁术】 eval函数暗藏玄机?
javascript