index.vue 10.2 KB
<script lang="ts" setup>
  import { computed, nextTick, Ref, ref, unref } from 'vue';
  import { getDeviceHistoryInfo } from '/@/api/alarm/position';
  import { Empty, Spin } from 'ant-design-vue';
  import { useECharts } from '/@/hooks/web/useECharts';
  import { AggregateDataEnum } from '/@/views/device/localtion/config.data';
  import { useGridLayout } from '/@/hooks/component/useGridLayout';
  import { ColEx } from '/@/components/Form/src/types';
  import { useForm, BasicForm } from '/@/components/Form';
  import { formSchema, SchemaFiled } from './config';
  import BasicModal from '/@/components/Modal/src/BasicModal.vue';
  import { useModalInner } from '/@/components/Modal';
  import { getAllDeviceByOrg } from '/@/api/dataBoard';
  import { useHistoryData } from '/@/views/device/list/hook/useHistoryData';
  import { BasicColumn, BasicTable, useTable } from '/@/components/Table';
  import { formatToDateTime } from '/@/utils/dateUtil';
  import {
    ModeSwitchButton,
    TABLE_CHART_MODE_LIST,
    EnumTableChartMode,
  } from '/@/components/Widget';
  import { ModalParamsType } from '/#/utils';
  import { WidgetDataType } from '../../hooks/useDataSource';
  import { ExtraDataSource } from '../../types';
  import { OrderByEnum } from '/@/views/device/localtion/cpns/TimePeriodForm/config';
  import { useApp } from '../../hooks/useApp';

  type DeviceOption = Record<'label' | 'value' | 'organizationId', string>;

  defineEmits(['register']);

  const mode = ref<EnumTableChartMode>(EnumTableChartMode.CHART);

  const chartRef = ref();

  const loading = ref(false);

  const isNull = ref(false);

  const historyData = ref<{ ts: number; value: string; name: string }[]>([]);

  const { getIsAppPage } = useApp();

  const { deviceAttrs, getDeviceKeys, getSearchParams, setChartOptions, getDeviceAttribute } =
    useHistoryData();

  const { setOptions, destory } = useECharts(chartRef as Ref<HTMLDivElement>);

  function hasDeviceAttr() {
    return !!unref(deviceAttrs).length;
  }

  const [register, method] = useForm({
    schemas: formSchema(),
    baseColProps: useGridLayout(2, 3, 4) as unknown as ColEx,
    rowProps: {
      gutter: 10,
    },
    labelWidth: 120,
    fieldMapToTime: [
      [SchemaFiled.DATE_RANGE, [SchemaFiled.START_TS, SchemaFiled.END_TS], 'YYYY-MM-DD HH:mm:ss'],
    ],
    submitButtonOptions: {
      loading: loading as unknown as boolean,
    },
    async submitFunc() {
      search();
    },
  });

  const search = async () => {
    // 表单验证
    await method.validate();
    const value = method.getFieldsValue();
    const searchParams = getSearchParams(value);
    if (!hasDeviceAttr()) return;
    // 发送请求
    loading.value = true;
    const res = await getDeviceHistoryInfo(searchParams);
    historyData.value = getTableHistoryData(res);
    loading.value = false;
    // 判断数据对象是否为空
    if (!Object.keys(res).length) {
      isNull.value = false;
      return;
    } else {
      isNull.value = true;
    }

    const selectedKeys = unref(deviceAttrs).find(
      (item) => item.identifier === value[SchemaFiled.KEYS]
    );
    setOptions(setChartOptions(res, selectedKeys));
  };

  const getTableHistoryData = (record: Recordable<{ ts: number; value: string }[]>) => {
    const keys = Object.keys(record);
    const list = keys.reduce((prev, next) => {
      const list = record[next].map((item) => {
        return {
          ...item,
          name: next,
        };
      });
      return [...prev, ...list];
    }, []);
    return list;
  };

  const getIdentifierNameMapping = computed(() => {
    const mapping = {};
    unref(deviceAttrs).forEach((item) => {
      const { identifier, name } = item;
      mapping[identifier] = name;
    });
    return mapping;
  });

  const sortOrder = ref<any>('descend');
  const columns: BasicColumn[] | any = computed(() => {
    return [
      {
        title: '属性',
        dataIndex: 'name',
        format: (text) => {
          return unref(getIdentifierNameMapping)[text];
        },
      },
      {
        title: '值',
        dataIndex: 'value',
      },
      {
        title: '更新时间',
        dataIndex: 'ts',
        format: (val) => {
          return formatToDateTime(val, 'YYYY-MM-DD HH:mm:ss');
        },
        sorter: true,
        sortOrder: unref(sortOrder),
        sortDirections: ['descend', 'ascend', 'descend'],
      },
    ];
  });

  const [registerTable, { setColumns }] = useTable({
    showIndexColumn: false,
    showTableSetting: false,
    dataSource: historyData,
    maxHeight: 300,
    size: 'small',
    columns: unref(columns),
  });

  const getTableList = async (orderBy?: string) => {
    // 表单验证
    await method.validate();
    const value = method.getFieldsValue();
    const searchParams = getSearchParams(value);
    if (!hasDeviceAttr()) return;
    // 发送请求
    loading.value = true;
    const res = await getDeviceHistoryInfo(searchParams, orderBy);
    historyData.value = getTableHistoryData(res);
    loading.value = false;
  };

  const handleTableChange = async (_pag, _filters, sorter: any) => {
    sortOrder.value = sorter.order;
    await setColumns(unref(columns));
    if (sorter.field == 'ts') {
      if (sorter.order == 'descend') {
        getTableList('DESC');
      } else {
        getTableList('ASC');
      }
    }
  };

  const getDeviceDataKey = async (record: DeviceOption) => {
    const { organizationId, value } = record;
    try {
      const options = await getAllDeviceByOrg(organizationId);
      const record = options.find((item) => item.tbDeviceId === value);
      await getDeviceAttribute(record!);
      await nextTick();
      method.updateSchema({
        field: SchemaFiled.KEYS,
        componentProps: {
          options: unref(deviceAttrs).map((item) => ({ label: item.name, value: item.identifier })),
        },
      });
    } catch (error) {
      throw error;
    }
  };

  const handleModalOpen = async () => {
    await nextTick();

    method.setFieldsValue({
      [SchemaFiled.START_TS]: 1 * 24 * 60 * 60 * 1000,
      [SchemaFiled.LIMIT]: 7,
      [SchemaFiled.AGG]: AggregateDataEnum.NONE,
    });

    if (!hasDeviceAttr()) return;

    const { deviceId } = method.getFieldsValue();

    const res = await getDeviceHistoryInfo({
      entityId: deviceId,
      keys: unref(getDeviceKeys).join(),
      startTs: Date.now() - 1 * 24 * 60 * 60 * 1000,
      endTs: Date.now(),
      agg: AggregateDataEnum.NONE,
      limit: 7,
      orderBy: OrderByEnum.ASC,
    });
    historyData.value = getTableHistoryData(res);
    // 判断对象是否为空
    if (!Object.keys(res).length) {
      isNull.value = false;
      return;
    } else {
      isNull.value = true;
    }
    setOptions(setChartOptions(res));
  };

  const generateDeviceOptions = (dataSource: ExtraDataSource[]) => {
    const record: { [key: string]: boolean } = {};

    const options: DeviceOption[] = [];
    for (const item of dataSource) {
      const { deviceName, gatewayDevice, slaveDeviceId, organizationId } = item;
      let { deviceId } = item;
      if (gatewayDevice && slaveDeviceId) {
        deviceId = slaveDeviceId;
      }
      if (record[deviceId]) continue;
      options.push({
        label: deviceName,
        value: deviceId,
        organizationId,
      });
      record[deviceId] = true;
    }

    return options;
  };

  const [registerModal] = useModalInner(async (params: ModalParamsType<WidgetDataType>) => {
    deviceAttrs.value = [];
    loading.value = false;
    const { record } = params;
    const options = generateDeviceOptions(record.dataSource);
    await nextTick();
    method.updateSchema({
      field: SchemaFiled.DEVICE_ID,
      componentProps({ formActionType }) {
        const { setFieldsValue } = formActionType;
        return {
          options,
          onChange(_, record: DeviceOption) {
            getDeviceDataKey(record);
            setFieldsValue({ [SchemaFiled.KEYS]: null });
          },
        };
      },
    });

    if (options.length && options.at(0)?.value) {
      const record = options.at(0)!;
      await getDeviceDataKey(record);
      try {
        await method.setFieldsValue({ [SchemaFiled.DEVICE_ID]: record.value });
      } catch (error) {}
    }

    await handleModalOpen();
  });

  const handleCancel = () => {
    destory();
  };

  const switchMode = (flag: EnumTableChartMode) => {
    mode.value = flag;
  };
</script>

<template>
  <BasicModal
    @register="registerModal"
    @cancel="handleCancel"
    :destroy-on-close="true"
    :show-ok-btn="false"
    cancel-text="关闭"
    :width="getIsAppPage ? '100%' : '75%'"
    title="历史趋势"
  >
    <section
      class="flex flex-col p-4 h-full w-full min-w-7/10"
      style="color: #f0f2f5; background-color: #f0f2f5"
    >
      <section class="bg-white my-3 p-2">
        <BasicForm @register="register" />
      </section>
      <section class="bg-white p-3" style="min-height: 350px">
        <Spin :spinning="loading" :absolute="true">
          <div
            v-show="mode === EnumTableChartMode.CHART"
            class="flex h-70px items-center justify-end p-2"
          >
            <ModeSwitchButton
              v-model:value="mode"
              :mode="TABLE_CHART_MODE_LIST"
              @change="switchMode"
            />
          </div>

          <div
            v-show="isNull && mode === EnumTableChartMode.CHART"
            ref="chartRef"
            :style="{ height: '350px', width: '100%' }"
          >
          </div>
          <Empty
            v-if="mode === EnumTableChartMode.CHART"
            class="h-350px flex flex-col justify-center items-center"
            description="暂无数据,请选择设备查询"
            v-show="!isNull"
          />

          <BasicTable
            v-show="mode === EnumTableChartMode.TABLE"
            @change="handleTableChange"
            @register="registerTable"
          >
            <template #toolbar>
              <div class="flex h-70px items-center justify-end p-2">
                <ModeSwitchButton
                  v-model:value="mode"
                  :mode="TABLE_CHART_MODE_LIST"
                  @change="switchMode"
                />
              </div>
            </template>
          </BasicTable>
        </Spin>
      </section>
    </section>
  </BasicModal>
</template>

<style scoped></style>