ReportPreviewModal.vue 9.31 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',
              ]"
              class="mb-20"
              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, item)"
                  v-bind="createPickerSearch()"
                  placeholder="请选择设备属性"
                  :options="item?.attributes?.map((item1) => ({ label: item1, value: item1 }))"
                />
              </div>
              <div class="w-full h-full flex justify-center items-center">
                <Empty v-show="item.notFoundData" description="暂无数据" class="text-dark-50" />
                <div
                  v-show="!item.notFoundData"
                  :id="`chart-${item.device}`"
                  class="w-full h-96"
                ></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, unref } 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';
  import { createPickerSearch } from '/@/utils/pickerSearch';

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

  interface ChartInstance {
    device: string;
    name: string;
    attributes: string[];
    active?: string;
    notFoundData?: boolean;
    loading?: boolean;
  }

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

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

  const chartInstance = ref<ChartInstance[]>([]);

  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 as string[])?.map((str) => str.replace(' ', '\n')),
      series: [chartDataConfig.at(0)?.series],
      legend: {
        data: Object.keys(result),
      },
    };
    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 unref(chartInstance)) {
          const { attributes, device } = item;

          const keys = attributes.length ? attributes.at(0) : '';

          const sendParams = {
            ...data.record.executeCondition.executeCondition,
            ...{
              keys,
            },
          };

          const result = await exportViewChartApi(device, sendParams);
          item.notFoundData = 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: '14%',
              right: '20%',
              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, item: ChartInstance) => {
    const sendParams = {
      ...currentRecord.executeCondition.executeCondition,
      ...{
        keys,
      },
    };
    try {
      loading.value = true;
      const result = await exportViewChartApi(device, sendParams);

      item.notFoundData = validateHasData(result);
      const { xAxisData, series } = getChartsOption(result as unknown as ResponsData);

      chartsInstance[device].setOption({
        series,
        xAxis: { data: xAxisData },
      } as echarts.EChartsOption);

      await nextTick();
      resize();
    } catch (error) {
    } finally {
      loading.value = false;
    }
  };

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

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

  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>
  .wrapper {
    margin: 10px 60px 60px 60px;
  }

  .inner {
    width: 400px;
    height: 400px;
  }

  .item {
    text-align: center;
    font-size: 200%;
    color: #fff;
  }

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