ReportPreviewModal.vue 8.77 KB
<template>
  <div>
    <BasicModal
      destroyOnClose
      v-bind="$attrs"
      width="60rem"
      :height="heightNum"
      @register="register"
      title="报表趋势图"
      :minHeight="500"
      :showOkBtn="false"
    >
      <div :class="[chartInstance.length % 2 !== 0 ? '' : 'wrapper']">
        <Spin :spinning="loading">
          <div
            v-if="chartInstance.length > 0"
            :class="[chartInstance.length % 2 !== 0 ? '' : 'chart-style']"
          >
            <div
              :class="[
                chartInstance.length % 2 !== 0 ? '' : 'inner',
                chartInstance.length % 2 !== 0 ? '' : 'item',
              ]"
              v-for="item in chartInstance"
              :key="item.device"
            >
              <p class="text-black text-lg">{{ item.name }}</p>
              <div class="flex text-black items-center">
                <div class="mr-4 text-sm">属性:</div>
                <Select
                  class="min-w-25"
                  v-model:value="item.active"
                  @change="(value) => handleChangeChars(value, item.device)"
                  placeholder="请选择设备属性"
                >
                  <Select.Option v-for="attr in item.attributes" :key="attr" :value="attr">
                    {{ attr }}
                  </Select.Option>
                </Select>
              </div>
              <div>
                <Empty v-show="notFoundData" />
                <div
                  v-show="!notFoundData"
                  :id="`chart-${item.device}`"
                  :style="{ height, width }"
                ></div>
              </div>
            </div>
          </div>
          <div v-else style="display: flex; justify-content: center; align-items: center">
            <div style="position: relative; left: 0; top: 3rem">暂无数据</div>
          </div>
        </Spin>
      </div>
    </BasicModal>
  </div>
</template>
<script setup lang="ts">
  import { ref, PropType, nextTick, shallowReactive, onMounted, onUnmounted } from 'vue';
  import { BasicModal, useModalInner } from '/@/components/Modal';
  import * as echarts from 'echarts';
  import { exportViewChartApi } from '/@/api/export/exportManager';
  import moment from 'moment';
  import { ExecuteReportRecord } from '/@/api/export/model/exportModel';
  import { Select, Spin, Empty } from 'ant-design-vue';

  interface ResponsData {
    attr: string;
    val: { ts: number; value: string }[];
  }

  defineProps({
    width: {
      type: String as PropType<string>,
      default: '100%',
    },
    height: {
      type: String as PropType<string>,
      default: '400px',
    },
  });
  defineEmits(['register']);
  const heightNum = ref(800);

  let currentRecord: ExecuteReportRecord = {} as unknown as ExecuteReportRecord;

  const chartInstance = ref<
    { device: string; name: string; attributes: string[]; active?: string }[]
  >([]);

  const notFoundData = ref(false);

  const chartsInstance = shallowReactive<{ [key: string]: echarts.ECharts }>({});
  //生成随机颜色
  let getRandomColor = function () {
    return (
      'rgb(' +
      Math.round(Math.random() * 255) +
      ',' +
      Math.round(Math.random() * 255) +
      ',' +
      Math.round(Math.random() * 10) +
      ')'
    );
  };

  const getChartsOption = (result: ResponsData) => {
    const generateInfo = Object.entries(result).map((item) => {
      const [attr, val] = item;
      return { attr, val } as { attr: string; val: { ts: number; value: string }[] };
    });
    const chartDataConfig =
      generateInfo.map((item) => {
        const seriesData = item.val.map((m1) => Number(m1.value));
        return {
          xAxis: item.val.map((m) => moment(m.ts).format('YYYY-MM-DD HH:mm:ss')),
          series: {
            name: item.attr,
            type: 'line',
            data: seriesData,
            lineStyle: {
              color: getRandomColor(),
            },
          },
        } as echarts.EChartsOption;
      }) || ([] as echarts.EChartsOption[]);
    const chartOption = {
      xAxisData: chartDataConfig.at(0)?.xAxis?.map(function (str) {
        return str.replace(' ', '\n');
      }),
      series: [chartDataConfig.at(0)?.series],
    };

    return chartOption;
  };

  const [register, { setModalProps }] = useModalInner(
    async (data: { record: ExecuteReportRecord }) => {
      setModalProps({ loading: true });
      try {
        currentRecord = data.record;
        const deviceInfo = data.record.executeCondition.executeAttributes || [];
        chartInstance.value = deviceInfo.map((item) => ({
          ...item,
          active: item.attributes.at(0),
        }));
        for (const item of deviceInfo) {
          const { attributes, device } = item;
          const keys = attributes.length ? attributes.at(0) : '';
          const sendParams = {
            ...data.record.executeCondition.executeCondition,
            ...{
              keys,
            },
          };

          const result = await exportViewChartApi(device, sendParams);
          validateHasData(result);
          const { xAxisData, series } = getChartsOption(result as unknown as ResponsData);

          await nextTick();
          chartsInstance[device] = echarts.init(
            document.getElementById(`chart-${device}`) as HTMLElement
          );

          const chartOption = {
            title: {
              left: 'center',
            },
            tooltip: {
              trigger: 'axis',
              axisPointer: {
                type: 'cross',
                animation: false,
                label: {
                  backgroundColor: '#505765',
                },
              },
            },
            legend: {
              data: attributes,
              top: '20px',
            },
            toolbox: {},
            grid: {
              left: '8%',
              right: '8%',
              bottom: 80,
              containLabel: true,
            },
            dataZoom: [
              {
                show: true,
                realtime: true,
                start: 0,
                end: 15,
                height: '30',
              },
              {
                type: 'inside',
                realtime: true,
                start: 0,
                end: 15,
                height: '30',
              },
            ],
            xAxis: {
              type: 'category',
              data: xAxisData,
              boundaryGap: false,
              axisPointer: { type: 'shadow' },
              axisLabel: {
                show: false,
                interval: 0,
                rotate: 65,
                textStyle: {
                  color: '#000',
                  fontSize: 10,
                },
              },
              axisLine: {
                show: true,
                interval: 0,
                lineStyle: {
                  color: 'RGB(210,221,217)',
                },
              },
            },
            yAxis: {
              type: 'value',
              boundaryGap: false,
            },
            series,
          };
          chartsInstance[device].setOption(chartOption);
          //自适应
          // window.addEventListener('resize', () => {
          //   chartsInstance[device].resize();
          // });
        }
      } catch (error) {
        throw error;
      } finally {
        setModalProps({ loading: false });
      }
    }
  );
  const loading = ref(false);
  const renderCharts = async (device: string, keys: string) => {
    const sendParams = {
      ...currentRecord.executeCondition.executeCondition,
      ...{
        keys,
      },
    };
    try {
      loading.value = true;
      const result = await exportViewChartApi(device, sendParams);
      validateHasData(result);
      const { xAxisData, series } = getChartsOption(result as unknown as ResponsData);

      chartsInstance[device].setOption({
        series,
        xAxis: { data: xAxisData },
      } as echarts.EChartsOption);
    } catch (error) {
    } finally {
      loading.value = false;
    }
  };

  const validateHasData = (record: Recordable) => {
    notFoundData.value = false;
    const { val = [], attr } = (record as unknown as ResponsData) || {};
    if (!attr || !val.length) {
      notFoundData.value = true;
    }
  };

  const handleChangeChars = (value: string, device: string) => {
    renderCharts(device, value);
  };

  const resize = () => {
    Object.keys(chartsInstance).forEach((key) => {
      chartsInstance[key].resize();
    });
  };

  onMounted(() => {
    window.addEventListener('resize', resize);
  });

  onUnmounted(() => {
    window.removeEventListener('resize', resize);
  });
</script>
<style lang="less" scoped>
  @import url('./ReportPreviewModal.less');

  .chart-style {
    display: flex;
    flex-wrap: wrap;
    width: 825px;
    margin-top: 10px;
    justify-content: space-between;
  }
</style>