index.vue 9.84 KB
<script lang="ts" setup>
  import { List, Card, Statistic, Button, Tooltip, Spin } from 'ant-design-vue';
  import { onMounted, ref, unref } from 'vue';
  import { PageWrapper } from '/@/components/Page';
  import { MoreOutlined, ShareAltOutlined } from '@ant-design/icons-vue';
  import { useCopyToClipboard } from '/@/hooks/web/useCopyToClipboard';
  import { useMessage } from '/@/hooks/web/useMessage';
  import Dropdown from '/@/components/Dropdown/src/Dropdown.vue';
  import { DropMenu } from '/@/components/Dropdown';
  import { DATA_BOARD_SHARE_URL, MoreActionEvent, VisualBoardPermission } from './config/config';
  import { useModal } from '/@/components/Modal';
  import PanelDetailModal from './components/PanelDetailModal.vue';
  import { getDataBoardList, deleteDataBoard } from '/@/api/dataBoard';
  import { DataBoardRecord } from '/@/api/dataBoard/model';
  import { ViewType } from './config/panelDetail';
  import { useRouter } from 'vue-router';
  import { getBoundingClientRect } from '/@/utils/domUtils';
  import Authority from '/@/components/Authority/src/Authority.vue';
  import { computed } from '@vue/reactivity';
  import { usePermission } from '/@/hooks/web/usePermission';
  import { encode } from './config/config';
  import { useForm, BasicForm } from '/@/components/Form';
  import { formSchema } from './config/searchForm';

  const ListItem = List.Item;
  const router = useRouter();

  const { createMessage, createConfirm } = useMessage();

  const listEL = ref();
  const loading = ref(false);
  const dataBoardList = ref<DataBoardRecord[]>([]);

  const [searchFormRegister, searchFormMethod] = useForm({
    schemas: formSchema,
    labelWidth: 80,
    layout: 'inline',
    submitButtonOptions: {
      loading: loading as unknown as boolean,
    },
    submitFunc: async () => {
      try {
        const params = searchFormMethod.getFieldsValue();
        await getDatasource(params);
      } catch (error) {}
    },
    resetFunc: async () => {
      try {
        await getDatasource();
      } catch (error) {}
    },
  });

  // about pagination
  const page = ref(1);
  const pageSize = ref(10);
  const total = ref(0);
  const paginationProp = ref({
    showSizeChanger: false,
    showQuickJumper: true,
    pageSize,
    current: page,
    size: 'small',
    total,
    showTotal: (total) => `总 ${total} 条`,
    onChange: pageChange,
    onShowSizeChange: pageSizeChange,
  });

  function pageChange(p, pz) {
    page.value = p;
    pageSize.value = pz;
    getDatasource();
  }
  function pageSizeChange(_current, size) {
    pageSize.value = size;
    getDatasource();
  }

  const createShareUrl = (boardId: string, tenantId: string, name: string) => {
    const { origin } = location;
    return `${origin}${DATA_BOARD_SHARE_URL(encode(boardId), encode(tenantId), encode(name))}`;
  };

  const { clipboardRef } = useCopyToClipboard();
  const handleCopyShareUrl = (record: DataBoardRecord) => {
    clipboardRef.value = createShareUrl(record.id, record.tenantId, record.name);
    unref(clipboardRef) ? createMessage.success('复制成功') : createMessage.error('未找到分享链接');
  };

  const { hasPermission } = usePermission();
  const dropMenuList = computed<DropMenu[]>(() => {
    const hasUpdatePermission = hasPermission(VisualBoardPermission.UPDATE);
    const hasDeletePermission = hasPermission(VisualBoardPermission.DELETE);
    const basicMenu: DropMenu[] = [];
    if (hasUpdatePermission)
      basicMenu.push({
        text: '编辑',
        event: MoreActionEvent.EDIT,
        icon: 'ant-design:edit-outlined',
      });
    if (hasDeletePermission)
      basicMenu.push({
        text: '删除',
        event: MoreActionEvent.DELETE,
        icon: 'ant-design:delete-outlined',
      });
    return basicMenu;
  });

  const getDatasource = async (params: Recordable = {}) => {
    try {
      loading.value = true;
      const { total, items } = await getDataBoardList({
        page: unref(paginationProp).current,
        pageSize: unref(paginationProp).pageSize,
        ...params,
      });
      dataBoardList.value = items;
      paginationProp.value.total = total;
    } catch (error) {
    } finally {
      loading.value = false;
    }
  };

  const handleMenuEvent = (event: DropMenu, record: DataBoardRecord) => {
    if (event.event === MoreActionEvent.EDIT) handleEdit(record);
    if (event.event === MoreActionEvent.DELETE) {
      createConfirm({
        iconType: 'warning',
        content: '是否确认删除操作?',
        onOk: () => handleRemove(record),
      });
    }
  };

  const handleEdit = (record: DataBoardRecord) => {
    openModal(true, {
      isEdit: true,
      ...record,
    });
  };

  const handleOpenDetailModal = () => {
    openModal(true, { isEdit: false });
  };

  const handleRemove = async (record: DataBoardRecord) => {
    try {
      await deleteDataBoard([record.id]);
      createMessage.success('删除成功');
      await getDatasource();
    } catch (error) {}
  };

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

  const handleViewBoard = (record: DataBoardRecord) => {
    const hasDetailPermission = hasPermission(VisualBoardPermission.DETAIL);
    if (hasDetailPermission) {
      const boardId = encode(record.id);
      const boardName = encode(record.name);
      router.push(`/visual/board/detail/${boardId}/${boardName}`);
    } else createMessage.warning('没有权限');
  };

  const handlePagenationPosition = () => {
    const clientHeight = document.documentElement.clientHeight;
    const rect = getBoundingClientRect(unref(listEL).$el!) as DOMRect;
    const paginationHeight = 32 + 24 + 16;
    const listContainerMarginBottom = 16;
    const listContainerHeight =
      clientHeight - rect.top - paginationHeight - listContainerMarginBottom;
    const listContainerEl = (unref(listEL).$el as HTMLElement).querySelector(
      '.ant-spin-container'
    ) as HTMLElement;
    listContainerEl &&
      (listContainerEl.style.height = listContainerHeight + 'px') &&
      (listContainerEl.style.overflowY = 'auto') &&
      (listContainerEl.style.overflowX = 'hidden');
  };

  onMounted(() => {
    getDatasource();
    handlePagenationPosition();
  });
</script>

<template>
  <PageWrapper>
    <div class="flex items-center mb-3 bg-light-100 h-78px dark:text-gray-300 dark:bg-dark-900">
      <div class="text-lg ml-30px mr-9px font-bold">自定义看板</div>
      <Authority value="api:yt:data_board:add:post">
        <Button type="primary" @click="handleOpenDetailModal">创建看板</Button>
      </Authority>
    </div>
    <div class="bg-light-100 mb-6 w-full p-3 search-form dark:text-gray-300 dark:bg-dark-900">
      <BasicForm class="flex-auto w-full" @register="searchFormRegister" />
    </div>
    <Spin :spinning="loading">
      <List
        ref="listEL"
        :pagination="paginationProp"
        :data-source="dataBoardList"
        :grid="{ gutter: 20, column: 4, xs: 1, sm: 2, md: 2, lg: 3, xl: 3, xxl: 3 }"
        class="data-board-list"
      >
        <template #renderItem="{ item }">
          <ListItem>
            <Card class="data-card cursor-pointer">
              <template #title>
                <div class="font-bold">{{ item.name }}</div>
              </template>
              <template #extra>
                <Dropdown
                  v-if="dropMenuList.length"
                  :trigger="['click']"
                  @menu-event="(event) => handleMenuEvent(event, item)"
                  :drop-menu-list="dropMenuList"
                >
                  <MoreOutlined class="rotate-90 transform cursor-pointer" />
                </Dropdown>
              </template>
              <section @click="handleViewBoard(item)">
                <div class="flex data-card__info">
                  <div>
                    <div>组件数量</div>
                    <Statistic style="font-size: 22px" :value="item.componentNum">
                      <template #suffix>
                        <span class="text-sm">个</span>
                      </template>
                    </Statistic>
                  </div>
                </div>
                <div class="flex justify-between mt-4 text-sm" style="color: #999">
                  <div>
                    <span>
                      {{ item.viewType === ViewType.PRIVATE_VIEW ? '私有看板' : '公共看板' }}
                    </span>
                    <span
                      style="display: none"
                      v-if="item.viewType === ViewType.PUBLIC_VIEW && false"
                    >
                      <Tooltip title="点击复制分享链接">
                        <ShareAltOutlined class="ml-2" @click.stop="handleCopyShareUrl(item)" />
                      </Tooltip>
                    </span>
                  </div>
                  <div>{{ item.createTime }}</div>
                </div>
              </section>
            </Card>
          </ListItem>
        </template>
      </List>
    </Spin>
    <PanelDetailModal @register="registerModal" @change="getDatasource" />
  </PageWrapper>
</template>

<style scoped lang="less">
  .data-card:deep(.ant-card-head) {
    padding: 20px;
  }

  .data-card:deep(.ant-card-head-title) {
    padding: 0;
  }

  .data-card:deep(.ant-card-extra) {
    padding: 0;
  }

  .data-card:deep(.ant-card-body) {
    padding: 20px;
  }

  .data-card__info {
    color: #666;

    &::before {
      content: '';
      width: 54px;
      height: 54px;
      margin-right: 16px;
      background-image: url('/@/assets/svg/component.svg');
    }
  }

  .search-form {
    width: 100%;

    form {
      width: 100%;

      :deep(.ant-row) {
        width: 100%;
      }

      :deep(.ant-row > div:last-child) {
        flex: 1;
        max-width: 100%;
      }
    }
  }

  .data-board-list:deep(.ant-list-pagination) {
    padding: 10px;
    background-color: #fff;
  }

  [data-theme='dark'] .data-board-list:deep(.ant-list-pagination) {
    padding: 10px;
    background-color: #000;
  }
</style>