index.vue 10.5 KB
<script lang="ts" setup>
  import { onMounted, unref } from 'vue';
  import { ref } from 'vue';
  import { Empty, Spin, Button } from 'ant-design-vue';
  import { getBoundingClientRect } from '/@/utils/domUtils';
  import { GridItem, GridLayout } from 'vue3-grid-layout';
  import {
    DEFAULT_MAX_COL,
    DEFAULT_MIN_HEIGHT,
    DEFAULT_MIN_WIDTH,
    DEFAULT_ITEM_MARIGN,
    VisualComponentPermission,
    PHONE_SIZE,
  } from './index';
  import { useDragGridLayout } from './hooks/useDragGridLayout';
  import { WidgetHeader, WidgetWrapper } from './components/WidgetWrapper';
  import { computed } from 'vue';
  import { WidgetDataType, useDataSource } from './hooks/useDataSource';
  import { WidgetDistribute } from './components/WidgetDistribute';
  import { DataSourceBindPanel } from '../dataSourceBindPanel';
  import { PageHeader } from './components/PagerHeader';
  import { useShare } from './hooks/useShare';
  import { useApp } from './hooks/useApp';
  import { useRole } from '/@/hooks/business/useRole';
  import { Authority } from '/@/components/Authority';
  import { useModal } from '/@/components/Modal';
  import { ModalParamsType } from '/#/utils';
  import { DataActionModeEnum } from '/@/enums/toolEnum';
  import { HistoryTrendModal } from './components/HistoryTrendModal';
  import { AlarmTimeModal } from './components/AlarmTimeModal';
  import { watch } from 'vue';
  import { useRootSetting } from '/@/hooks/setting/useRootSetting';
  import { ThemeEnum } from '/@/enums/appEnum';
  import { createDataBoardContext } from './hooks/useDataBoardContext';
  import { useSocket } from '/@/views/visual/packages/hook/socket/useSocket';
  import { createAlarmContext } from './hooks/useAlarmTime';
  import { createHistoryContext } from './hooks/useHistoryForm';
  import { MoreOutlined, LeftOutlined } from '@ant-design/icons-vue';
  import WIFISVG from '/@/assets/svg/wifi.svg';
  import SIGNALSVG from '/@/assets/svg/signal.svg';
  import BATTERYSVG from '/@/assets/svg/battery.svg';
  import { useRoute } from 'vue-router';

  const props = defineProps<{
    value?: Recordable;
  }>();

  const getProps = computed(() => props);

  const containerRefEl = ref<Nullable<HTMLDivElement>>(null);

  const containerRectRef = ref<DOMRect>({} as unknown as DOMRect);

  const ROUTE = useRoute();

  const { loading, draggable, resizable, dataSource, rawDataSource, setLayoutInfo, getDataSource } =
    useDataSource(getProps);

  const { resize, resized, moved, containerResized } = useDragGridLayout(dataSource, setLayoutInfo);

  const [register, { openModal }] = useModal();

  const [registerTrendModal, { openModal: openTrendModal }] = useModal();

  /**
   * @description 获取画板宽高
   */
  onMounted(() => {
    const rect = getBoundingClientRect(unref(containerRefEl)!);
    if (rect) {
      containerRectRef.value = rect as DOMRect;
    }
  });
  const { getIsSharePage } = useShare();

  // getIsAppPage 是否是小程序进入的页面  isPhone 是否是创建的手机端
  const { getIsAppPage, isPhone } = useApp();
  const { isCustomerUser } = useRole();
  const handleOpenCreatePanel = () => {
    openModal(true, { mode: DataActionModeEnum.CREATE } as ModalParamsType);
  };

  const handleUpdateWidget = (data: WidgetDataType) => {
    openModal(true, { mode: DataActionModeEnum.UPDATE, record: data } as ModalParamsType);
  };

  const handleOpenTrend = (data: WidgetDataType) => {
    openTrendModal(true, { mode: DataActionModeEnum.READ, record: data } as ModalParamsType);
  };

  // 设备告警时间选择
  const [registerAlarmModal, { openModal: openAlarmModal }] = useModal();
  const handleOpenAlarm = (data: WidgetDataType) => {
    openAlarmModal(true, { mode: DataActionModeEnum.READ, record: data } as ModalParamsType);
  };

  const alarmForm = ref<{
    time?: number | string;
    pageSize: number;
    startTs?: number | string;
    endTs?: string | number;
  }>({
    time: 2592000000,
    pageSize: 20,
    startTs: undefined,
    endTs: undefined,
  });

  //告警发送数据
  const getAlarmForm = (values) => {
    const { way, pageSize, startTs, endTs } = values;
    if (way == 'timePeriod') {
      alarmForm.value = {
        time: undefined,
        startTs,
        endTs,
        pageSize,
      };
    } else
      alarmForm.value = {
        time: startTs,
        pageSize,
        startTs: undefined,
        endTs: undefined,
      };
  };
  createAlarmContext({ alarmForm: alarmForm });

  // 历史数据发送
  const historyForm = ref({
    startTs: Date.now() - 30 * 24 * 60 * 60 * 1000,
    endTs: Date.now(),
    agg: 'NONE',
    limit: 30,
    interval: undefined,
    way: 'latest',
  });

  const getHistoryForm = (values) => {
    const { startTs, endTs, agg, limit, interval, way } = values;
    historyForm.value = {
      startTs,
      endTs,
      agg,
      limit,
      interval,
      way,
    };
    if (way === 'latest') {
      historyForm.value.startTs = Date.now() - startTs;
      historyForm.value.endTs = Date.now();
    }
  };
  createHistoryContext({ historyForm: historyForm, getHistoryForm });

  const { send, close } = useSocket(dataSource);

  createDataBoardContext({ send, close });

  const { getDarkMode } = useRootSetting();
  watch(
    getIsSharePage,
    (value) => {
      if (value) {
        const root = document.querySelector('#app');
        (root as HTMLDivElement).style.backgroundColor =
          unref(getDarkMode) === ThemeEnum.LIGHT ? '#F5F5F5' : '#1b1b1b';
      }
    },
    { immediate: true }
  );
  watch(
    getIsAppPage,
    (value) => {
      if (value) {
        const root = document.querySelector('#app');
        (root as HTMLDivElement).style.backgroundColor =
          unref(getDarkMode) === ThemeEnum.LIGHT ? '#F5F5F5' : '#1b1b1b';
      }
    },
    { immediate: true }
  );
  const getDataBoardName = computed(() => {
    return decodeURIComponent((ROUTE.params as { boardName: string }).boardName || '');
  });

  const phoneSize = ref({
    width: 375,
    height: 667,
  });
</script>

<template>
  <section
    ref="containerRefEl"
    class="palette w-full h-full flex-col bg-neutral-100 flex dark:bg-dark-700 dark:text-light-50"
  >
    <PageHeader v-if="!getIsAppPage" :widget-number="dataSource.length">
      <Authority :value="VisualComponentPermission.CREATE">
        <Button
          v-if="!getIsSharePage && !isCustomerUser"
          type="primary"
          @click="handleOpenCreatePanel"
        >
          创建组件
        </Button>
      </Authority>
    </PageHeader>

    <Spin :spinning="loading">
      <div class="w-full h-full flex justify-center items-center mb-3">
        <div
          :style="
            !getIsAppPage && isPhone()
              ? {
                  width: phoneSize.width + 'px',
                  height: phoneSize.height + 'px',
                  border: '2px solid #e5e7eb',
                }
              : { width: '100%', height: '100%' }
          "
          style="border-radius: 1%"
        >
          <!-- 手机端的模拟样式 -->
          <div
            v-if="!getIsAppPage && isPhone()"
            style="height: 60px; background: white"
            class="px-1 py-1 relative"
          >
            <div class="flex justify-between">
              <div>thingskit</div>
              <div class="flex items-center">
                <img :src="WIFISVG" alt="" class="w-3 h-3" />
                <img :src="SIGNALSVG" alt="" class="w-3 h-3 mx-1" />
                <img :src="BATTERYSVG" alt="" class="w-4 h-4 rotate-45" />
                <h1 class="mb-0">18:13</h1>
              </div>
            </div>
            <div class="flex items-center justify-between">
              <LeftOutlined class="transform cursor-pointer text-lg" />
              <h1 class="font-bold">{{ getDataBoardName }}</h1>
              <MoreOutlined class="transform rotate-90 cursor-pointer text-lg" />
            </div>
          </div>

          <div
            id="appLayoutId"
            :style="
              !getIsAppPage && isPhone() ? { height: `calc(${phoneSize.height}px - 67px)` } : {}
            "
            class="overflow-y-scroll"
          >
            <GridLayout
              v-model:layout="dataSource"
              :col-num="DEFAULT_MAX_COL"
              :row-height="30"
              :margin="[DEFAULT_ITEM_MARIGN, DEFAULT_ITEM_MARIGN]"
              :is-draggable="draggable"
              :is-resizable="resizable"
              :vertical-compact="true"
              :use-css-transforms="true"
              style="width: 100%"
              :style="{
                '--is-app': getIsAppPage ? 'auto' : 'none',
              }"
            >
              <GridItem
                v-for="item in dataSource"
                :key="item.i"
                :static="item.static"
                :x="item.x"
                :y="item.y"
                :w="item.w"
                :h="item.h"
                :i="item.i"
                :min-h="isPhone() ? PHONE_SIZE.DEFAULT_MIN_HEIGHT : DEFAULT_MIN_HEIGHT"
                :min-w="isPhone() ? PHONE_SIZE.DEFAULT_MIN_WIDTH : DEFAULT_MIN_WIDTH"
                :style="{ display: 'flex', flexWrap: 'wrap' }"
                class="grid-item-layout"
                @resized="resized"
                @resize="resize"
                @moved="moved"
                @container-resized="containerResized"
                drag-ignore-from=".no-drag"
              >
                <WidgetWrapper>
                  <template #header>
                    <WidgetHeader
                      :raw-data-source="rawDataSource"
                      :source-info="item"
                      @update="handleUpdateWidget"
                      @open-trend="handleOpenTrend"
                      @open-alarm="handleOpenAlarm"
                      @ok="getDataSource"
                    />
                  </template>
                  <WidgetDistribute :source-info="item" />
                </WidgetWrapper>
              </GridItem>
            </GridLayout>
          </div>
          <Empty
            v-if="!dataSource.length"
            :class="isPhone() ? 'absolute' : 'fixed'"
            class="top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2"
          />
        </div>
      </div>
    </Spin>

    <DataSourceBindPanel @register="register" :layout="dataSource" @ok="getDataSource" />

    <!-- 趋势 -->
    <HistoryTrendModal @register="registerTrendModal" />

    <!-- 选择时间 -->
    <AlarmTimeModal @register="registerAlarmModal" @getAlarmForm="getAlarmForm" />
  </section>
</template>

<style lang="less">
  .vue-grid-item {
    pointer-events: painted;
    touch-action: var(--is-app) !important;
  }
</style>