index.vue 8.4 KB
<script lang="ts" setup>
  import { Card, Statistic, Button, Tooltip } from 'ant-design-vue';
  import { unref, computed } from 'vue';
  import { MoreOutlined, ShareAltOutlined } from '@ant-design/icons-vue';
  import { useMessage } from '/@/hooks/web/useMessage';
  import Dropdown from '/@/components/Dropdown/src/Dropdown.vue';
  import { DropMenu } from '/@/components/Dropdown';
  import { MoreActionEvent, VisualBoardPermission } from './config/config';
  import { useModal } from '/@/components/Modal';
  import PanelDetailModal from './components/PanelDetailModal.vue';
  import { getDataBoardList, deleteDataBoard, shareBoard } from '/@/api/dataBoard';
  import { DataBoardRecord } from '/@/api/dataBoard/model';
  import { ViewType } from './config/panelDetail';
  import { useRouter } from 'vue-router';
  import { usePermission } from '/@/hooks/web/usePermission';
  import { encode } from './config/config';
  import { formSchema } from './config/searchForm';
  import { ShareModal } from '/@/views/common/ShareModal';
  import { ModalParamsType } from '/#/utils';
  import { DataActionModeEnum } from '/@/enums/toolEnum';
  import { useRole } from '/@/hooks/business/useRole';
  import { useClipboard } from '@vueuse/core';
  import { DATA_BOARD_SHARE_URL } from '../palette';
  import { BasicCardList, useCardList } from '/@/components/CardList';
  import Authority from '/@/components/Authority/src/Authority.vue';
  import { OrganizationIdTree, useOrganizationTree } from '../../common/organizationIdTree';

  const router = useRouter();

  const { createMessage, createConfirm } = useMessage();

  const [registerOrgTree, { clearSelected, getSelectKey }] = useOrganizationTree({
    onSelect: () => reload(),
  });

  const [registerCardList, { reload }] = useCardList({
    title: '数据看板',
    api: getDataBoardList,
    baseLayout: { col: 3, row: 3 },
    useSearchForm: true,
    formConfig: {
      schemas: formSchema,
      labelWidth: 80,
      resetFunc: async () => clearSelected(),
    },
    beforeFetch: async (params: Recordable) => {
      return { ...params, organizationId: getSelectKey() };
    },
  });

  const createShareUrl = (record: DataBoardRecord) => {
    const { origin } = location;
    const { id, publicId } = record;
    return `${origin}${DATA_BOARD_SHARE_URL(id, publicId)}`;
  };

  const { copied, copy } = useClipboard({ legacy: true });
  const handleCopyShareUrl = async (record: DataBoardRecord) => {
    await copy(createShareUrl(record));
    unref(copied) ? createMessage.success('复制成功') : createMessage.error('未找到分享链接');
  };

  const { isCustomerUser } = useRole();
  const { hasPermission } = usePermission();
  const dropMenuList = computed<DropMenu[]>(() => {
    const hasUpdatePermission = hasPermission(VisualBoardPermission.UPDATE);
    const hasDeletePermission = hasPermission(VisualBoardPermission.DELETE);
    const hasSharePermission = hasPermission(VisualBoardPermission.SHARE);
    let basicMenu: DropMenu[] = [];
    if (hasUpdatePermission)
      basicMenu.push({
        text: '编辑',
        event: MoreActionEvent.EDIT,
        icon: 'ant-design:edit-outlined',
      });
    if (hasSharePermission) {
      basicMenu.push({
        text: '分享',
        event: MoreActionEvent.SHARE,
        icon: 'ant-design:share-alt-outlined',
      });
    }

    if (hasDeletePermission)
      basicMenu.push({
        text: '删除',
        event: MoreActionEvent.DELETE,
        icon: 'ant-design:delete-outlined',
      });
    return basicMenu;
  });

  const handleOpenShareModal = (record: DataBoardRecord) => {
    openShareModal(true, {
      record,
      mode: DataActionModeEnum.READ,
      href: createShareUrl(record),
    } as ModalParamsType<DataBoardRecord>);
  };

  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),
      });
    }
    if (event.event === MoreActionEvent.SHARE) handleOpenShareModal(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('删除成功');
      reload();
    } catch (error) {}
  };

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

  const [registerShareModal, { openModal: openShareModal }] = useModal();

  const handleViewBoard = (record: DataBoardRecord) => {
    const hasDetailPermission = hasPermission(VisualBoardPermission.DETAIL);
    if (hasDetailPermission) {
      const boardId = encode(record.id);
      const boardName = encode(record.name);
      const organizationId = encode(record!.organizationId!);

      router.push(`/visual/board/detail/${boardId}/${boardName}/${organizationId}`);
    } else createMessage.warning('没有权限');
  };
</script>

<template>
  <section class="flex">
    <OrganizationIdTree @register="registerOrgTree" />
    <BasicCardList class="flex-auto p-4 w-3/4 xl:w-4/5 w-full" @register="registerCardList">
      <template #toolbar>
        <Authority :value="VisualBoardPermission.CREATE">
          <Button type="primary" @click="handleOpenDetailModal">新增看板</Button>
        </Authority>
      </template>
      <template #renderItem="{ item }: BasicCardListRenderItem<DataBoardRecord>">
        <Card class="data-card cursor-pointer">
          <template #title>
            <div class="font-bold">{{ item.name }}</div>
          </template>
          <template #extra>
            <Dropdown
              v-if="!isCustomerUser && 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 class="text-2xl" :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 class="flex min-w-20 mr-3">
                <span>
                  {{ item.viewType === ViewType.PRIVATE_VIEW ? '私有看板' : '公共看板' }}
                </span>
                <span v-if="item.viewType === ViewType.PUBLIC_VIEW">
                  <Tooltip title="点击复制分享链接">
                    <ShareAltOutlined class="ml-1" @click.stop="handleCopyShareUrl(item)" />
                  </Tooltip>
                </span>
              </div>
              <Tooltip placement="topLeft" :title="item.createTime">
                <div class="truncate">{{ item.createTime }}</div>
              </Tooltip>
            </div>
          </section>
        </Card>
      </template>
    </BasicCardList>
    <ShareModal @register="registerShareModal" :shareApi="shareBoard" @success="reload()" />
    <PanelDetailModal @register="registerModal" @change="reload()" />
  </section>
</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>