index.vue 9.6 KB
<script lang="ts" setup>
  import { Button, PageHeader } 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 } from 'vue-router';
  import { computed, unref } from '@vue/reactivity';
  import { DataComponentRecord, DataSource } from '/@/api/dataBoard/model';
  import { frontComponentMap, FrontComponentType } from './config/help';
  import { useMessage } from '/@/hooks/web/useMessage';
  import { DataBoardLayoutInfo } from '../types/type';

  const ROUTE = useRoute();

  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 defaultWidth = 6;
  const defaultHeight = 6;

  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,
      };
    });

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

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

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

  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 getDataBoardComponent = async () => {
    try {
      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 || defaultWidth,
          h: layout.h || defaultHeight,
          x: layout.x || 0,
          y: layout.y || 0,
          record: {
            ...item,
            width: 100,
            height: 100,
          },
        };
      });
      console.log(unref(dataBoardList));
    } catch (error) {}
  };

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

  const getComponentConfig = (record: DataComponentRecord, dataSourceRecord: DataSource) => {
    const frontComponent = record.frontId;
    const component = frontComponentMap.get(frontComponent as FrontComponentType);
    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);
    console.log({ record });
    try {
      const data = await addDataComponent({
        boardId: unref(getBoardId),
        record: {
          dataBoardId: unref(getBoardId),
          frontId: record?.record.frontId,
          dataSource: record?.record.dataSource,
        },
      });
      createMessage.success('复制成功');
      const id = data.data.id;
      await updateDataBoardLayout({
        boardId: unref(getBoardId),
        layout: [{ id, w: DEFAULT_WIDGET_WIDTH, h: DEFAULT_WIDGET_HEIGHT, x: 0, y: 0 }],
      });
      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="水电表看板">
      <template #extra>
        <Button type="primary" @click="handleOpenCreatePanel">创建组件</Button>
      </template>
      <div>
        <span class="mr-3 text-gray-400">已创建组件:</span>
        <span class="text-cyan-400"> {{ 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="itemResized"
          @moved="updateCharts"
          @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"
                v-bind="getComponentConfig(item.record, record)"
              />
            </template>
          </WidgetWrapper>
        </GridItem>
      </GridLayout>
    </section>
  </section>
  <DataBindModal @register="register" @submit="getDataBoardComponent" />
</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>