index.vue 10.6 KB
<script lang="ts" setup>
  import { Button, PageHeader, Empty } from 'ant-design-vue';
  import { GridItem, GridLayout } from 'vue3-grid-layout';
  import { nextTick, onMounted, ref } from 'vue';
  import WidgetWrapper from '../components/WidgetWrapper/WidgetWrapper.vue';
  import BaseWidgetHeader from '../components/WidgetHeader/BaseWidgetHeader.vue';
  import { DropMenu } from '/@/components/Dropdown';
  import DataBindModal from './components/DataBindModal.vue';
  import { useModal } from '/@/components/Modal';
  import { DEFAULT_WIDGET_HEIGHT, DEFAULT_WIDGET_WIDTH, MoreActionEvent } from '../config/config';
  import {
    addDataComponent,
    deleteDataComponent,
    getDataComponent,
    updateDataBoardLayout,
  } from '/@/api/dataBoard';
  import { useRoute, useRouter } from 'vue-router';
  import { computed, unref } from '@vue/reactivity';
  import { DataComponentRecord, DataSource, Layout } from '/@/api/dataBoard/model';
  import { frontComponentMap } from './config/help';
  import { calcScale } from './config/util';
  import { useMessage } from '/@/hooks/web/useMessage';
  import { DataBoardLayoutInfo } from '../types/type';
  import { WidgetComponentType } from './config/visualOptions';
  import Authority from '/@/components/Authority/src/Authority.vue';
  import { useSocketConnect } from '../hook/useSocketConnect';

  const ROUTE = useRoute();

  const ROUTER = useRouter();

  const { createMessage, createConfirm } = useMessage();
  const getBoardId = computed(() => {
    return (ROUTE.params as { id: string }).id;
  });

  const widgetEl = new Map<string, Fn>();

  const dataBoardList = ref<DataBoardLayoutInfo[]>([]);

  const draggable = ref(true);
  const resizable = ref(true);

  const GirdLayoutColNum = 24;
  const GridLayoutMargin = 10;

  const handleBack = () => {
    ROUTER.go(-1);
  };

  function updateSize(i: string, _newH: number, _newW: number, newHPx: number, newWPx: number) {
    newWPx = Number(newWPx);
    newHPx = Number(newHPx);

    const data = dataBoardList.value.find((item) => item.i === i)!;
    const length = data.record.dataSource.length || 0;

    const row = Math.floor(Math.pow(length, 0.5));
    const col = Math.floor(length / row);
    let width = Math.floor(100 / col);
    let height = Math.floor(100 / row);

    const WHRatio = newWPx / newHPx;
    const HWRatio = newHPx / newWPx;

    if (WHRatio > 1.6) {
      width = Math.floor(100 / length);
      height = 100;
    }

    if (HWRatio > 1.6) {
      height = Math.floor(100 / length);
      width = 100;
    }

    data.record.dataSource = data?.record.dataSource.map((item) => {
      return {
        ...item,
        width,
        height,
        radio: calcScale(newWPx, newHPx, width, height),
      };
    });

    nextTick(() => {
      const updateFn = widgetEl.get(i);
      if (updateFn) updateFn();
    });
  }

  const itemResize = (i: string, newH: number, newW: number, newHPx: number, newWPx: number) => {
    updateSize(i, newH, newW, newHPx, newWPx);
  };

  const itemResized = (i: string, newH: number, newW: number, newHPx: number, newWPx: number) => {
    handleSaveLayoutInfo();
    updateSize(i, newH, newW, newHPx, newWPx);
  };

  const itemContainerResized = (
    i: string,
    newH: number,
    newW: number,
    newHPx: number,
    newWPx: number
  ) => {
    updateSize(i, newH, newW, newHPx, newWPx);
  };

  const itemMoved = (i: string) => {
    handleSaveLayoutInfo();
    updateCharts(i);
  };

  const updateCharts = (i: string) => {
    nextTick(() => {
      const updateFn = widgetEl.get(i);
      if (updateFn) updateFn();
    });
  };

  const setComponentRef = (el: Element, record: DataBoardLayoutInfo) => {
    if (widgetEl.has(record.i)) widgetEl.delete(record.i);
    if (el && (el as unknown as { update: Fn }).update)
      widgetEl.set(record.i, (el as unknown as { update: Fn }).update);
  };

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

  const handleMoreAction = (event: DropMenu, id: string) => {
    if (event.event === MoreActionEvent.DELETE) {
      createConfirm({
        iconType: 'warning',
        content: '是否确认删除?',
        onOk: () => handleDelete(id),
      });
    }
    if (event.event === MoreActionEvent.EDIT) handleUpdate(id);
    if (event.event === MoreActionEvent.COPY) handleCopy(id);
  };

  const handleOpenCreatePanel = () => {
    openModal(true);
  };

  const handleSaveLayoutInfo = async () => {
    try {
      const layoutInfo = unref(dataBoardList).map((item) => {
        return {
          id: item.i,
          h: item.h,
          w: item.w,
          x: item.x,
          y: item.y,
        } as Layout;
      });

      await updateDataBoardLayout({
        boardId: unref(getBoardId),
        layout: layoutInfo,
      });
    } catch (error) {}
  };

  const { beginSendMessage } = useSocketConnect(dataBoardList);

  const getDataBoardComponent = async () => {
    try {
      // dataBoardList.value = [];
      const data = await getDataComponent(unref(getBoardId));
      dataBoardList.value = data.data.componentData.map((item) => {
        const index = data.data.componentLayout.findIndex((each) => item.id === each.id);
        let layout;
        if (!~index) {
          layout = {};
        } else {
          layout = data.data.componentLayout[index];
        }
        return {
          i: item.id,
          w: layout.w || DEFAULT_WIDGET_WIDTH,
          h: layout.h || DEFAULT_WIDGET_HEIGHT,
          x: layout.x || 0,
          y: layout.y || 0,
          record: {
            ...item,
            width: 100,
            height: 100,
          },
        };
      });
      beginSendMessage();
    } catch (error) {}
  };

  const getComponent = (record: DataComponentRecord) => {
    const frontComponent = record.frontId;
    const component = frontComponentMap.get(frontComponent as WidgetComponentType);
    return component?.Component;
  };

  const getComponentConfig = (
    record: DataBoardLayoutInfo['record'],
    dataSourceRecord: DataSource
  ) => {
    const frontComponent = record.frontId;
    const component = frontComponentMap.get(frontComponent as WidgetComponentType);
    return component?.transformConfig(component.ComponentConfig, record, dataSourceRecord);
  };

  const handleUpdate = async (id: string) => {
    const record = unref(dataBoardList).find((item) => item.i === id);
    openModal(true, { isEdit: true, ...record });
  };

  const handleCopy = async (id: string) => {
    const record = unref(dataBoardList).find((item) => item.i === id);
    try {
      await addDataComponent({
        boardId: unref(getBoardId),
        record: {
          dataBoardId: unref(getBoardId),
          frontId: record?.record.frontId,
          dataSource: record?.record.dataSource,
        },
      });
      createMessage.success('复制成功');
      getDataBoardComponent();
    } catch (error) {}
  };

  const handleDelete = async (id: string) => {
    try {
      await deleteDataComponent([id]);
      createMessage.success('删除成功');
      await getDataBoardComponent();
    } catch (error) {
      // createMessage.error('删除失败');
    }
  };

  onMounted(() => {
    getDataBoardComponent();
  });
</script>

<template>
  <section class="bg-light-50 flex flex-col overflow-hidden h-full w-full">
    <PageHeader title="水电表看板" @back="handleBack">
      <template #extra>
        <Authority value="api:yt:dataBoardDetail:post">
          <Button type="primary" @click="handleOpenCreatePanel">创建组件</Button>
        </Authority>
      </template>
      <div>
        <span class="mr-3 text-gray-400">已创建组件:</span>
        <span style="color: #409eff"> {{ dataBoardList.length }}个</span>
      </div>
    </PageHeader>
    <section class="flex-1">
      <GridLayout
        v-model:layout="dataBoardList"
        :col-num="GirdLayoutColNum"
        :row-height="30"
        :margin="[GridLayoutMargin, GridLayoutMargin]"
        :is-draggable="draggable"
        :is-resizable="resizable"
        :vertical-compact="true"
        :use-css-transforms="true"
        style="width: 100%"
      >
        <GridItem
          v-for="item in dataBoardList"
          :key="item.i"
          :static="item.static"
          :x="item.x"
          :y="item.y"
          :w="item.w"
          :h="item.h"
          :i="item.i"
          :style="{ display: 'flex', flexWrap: 'wrap' }"
          class="grid-item-layout"
          @resized="itemResized"
          @resize="itemResize"
          @moved="itemMoved"
          @container-resized="itemContainerResized"
        >
          <WidgetWrapper
            :key="item.i"
            :ref="(el: Element) => setComponentRef(el, item)"
            :data-source="item.record.dataSource"
          >
            <template #header>
              <!-- <div>header</div> -->
              <BaseWidgetHeader :id="item.record.id" @action="handleMoreAction" />
            </template>
            <template #controls="{ record, add, remove, update }">
              <component
                :is="getComponent(item.record)"
                :add="add"
                :remove="remove"
                :update="update"
                :radio="record.radio"
                v-bind="getComponentConfig(item.record, record)"
              />
            </template>
          </WidgetWrapper>
        </GridItem>
      </GridLayout>
      <Empty v-if="!dataBoardList.length" />
    </section>
    <DataBindModal @register="register" @submit="getDataBoardComponent" />
  </section>
</template>

<style>
  .vue-grid-item:not(.vue-grid-placeholder) {
    background: #fcfcfc;
    border: 1px solid black;
  }
  .vue-grid-item .resizing {
    opacity: 0.9;
  }
  .vue-grid-item .static {
    background: #cce;
  }
  .vue-grid-item .text {
    font-size: 24px;
    text-align: center;
    position: absolute;
    top: 0;
    bottom: 0;
    left: 0;
    right: 0;
    margin: auto;
    height: 100%;
    width: 100%;
  }
  .vue-grid-item .no-drag {
    height: 100%;
    width: 100%;
  }
  .vue-grid-item .minMax {
    font-size: 12px;
  }
  .vue-grid-item .add {
    cursor: pointer;
  }
  .vue-draggable-handle {
    position: absolute;
    width: 20px;
    height: 20px;
    top: 0;
    left: 0;
    background: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='10' height='10'><circle cx='5' cy='5' r='5' fill='#999999'/></svg>")
      no-repeat;
    background-position: bottom right;
    padding: 0 8px 8px 0;
    background-repeat: no-repeat;
    background-origin: content-box;
    box-sizing: border-box;
    cursor: pointer;
  }

  .container {
    display: grid;
    grid-template-columns: 3;
    grid-row: 3;
  }

  .grid-item-layout {
    overflow: hidden;
    border: 1px solid #eee !important;
    background-color: #fcfcfc !important;
  }
</style>