CardMode.vue 11.7 KB
<script lang="ts" setup>
  import { PageWrapper } from '/@/components/Page';
  import { BasicForm, useForm } from '/@/components/Form';
  import { List, Button, Tooltip, Card, PaginationProps, Image } from 'ant-design-vue';
  import { ReloadOutlined, EyeOutlined, FormOutlined, MoreOutlined } from '@ant-design/icons-vue';
  import { computed, onMounted, reactive, ref, unref } from 'vue';
  import { CardLayoutButton, EnumTableCardMode, ModeSwitchButton } from '/@/components/Widget';
  import { Authority } from '/@/components/Authority';
  import {
    deviceConfigDelete,
    deviceConfigGetQuery,
    setDeviceProfileIsDefaultApi,
  } from '/@/api/device/deviceConfigApi';
  import { ProfileRecord } from '/@/api/device/model/deviceConfigModel';
  import { Dropdown } from '/@/components/Dropdown';
  import { defaultObj, searchFormSchema, DeviceTypeName } from './device.profile.data';
  import { useMessage } from '/@/hooks/web/useMessage';
  import { useSyncConfirm } from '/@/hooks/component/useSyncConfirm';
  import DeviceProfileModal from './DeviceProfileModal.vue';
  import DeviceProfileDrawer from './DeviceProfileDrawer.vue';
  import { useModal } from '/@/components/Modal';
  import { useDrawer } from '/@/components/Drawer';

  defineProps<{
    mode: Mode;
  }>();

  const emit = defineEmits(['changeMode']);

  enum DropMenuEvent {
    SET_DEFAULT = 'setDefault',
    DELETE = 'delete',
  }
  const IMAGE_FALLBACK =
    'data:image/png;base64,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';

  const { createMessage } = useMessage();
  const { createSyncConfirm } = useSyncConfirm();

  const [register, { getFieldsValue }] = useForm({
    showAdvancedButton: true,
    labelWidth: 100,
    compact: true,
    baseColProps: { span: 8 },
    schemas: searchFormSchema,
    submitFunc: async () => {
      getDataSource();
    },
  });

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

  const loading = ref(false);

  const pagination = reactive<PaginationProps>({
    size: 'small',
    showTotal: (total: number) => `共 ${total} 条数据`,
    current: 1,
    onChange: (page: number) => {
      pagination.current = page;
      getDataSource();
    },
  });

  const dataSource = ref<ProfileRecord[]>([]);

  const colNumber = ref(4);

  const getSelectAllFlag = computed(() => {
    return unref(dataSource).every((item) => item.checked);
  });

  const getCheckedRecord = computed(() => {
    return unref(dataSource)
      .filter((item) => item.checked)
      .map((item) => item.id);
  });

  const getDataSource = async () => {
    try {
      loading.value = true;
      const params = getFieldsValue();
      const { items, total } = await deviceConfigGetQuery({
        page: pagination.current,
        pageSize: unref(colNumber) * 2,
        ...params,
      });
      pagination.total = total;
      dataSource.value = items.map((item) => ({ ...item, checked: false }));
    } catch (error) {
    } finally {
      loading.value = false;
    }
  };

  const handleModeChange = (mode: EnumTableCardMode) => {
    emit('changeMode', mode);
  };

  const handleCheckCard = (item: ProfileRecord) => {
    item.checked = !item.checked;
  };

  const handleSelectAll = () => {
    dataSource.value = unref(dataSource).map((item) => {
      return {
        ...item,
        checked: !unref(getSelectAllFlag),
      };
    });
  };

  const handleCreate = () => {
    openModal(true, {
      isUpdate: false,
    });
  };

  const handleShowDetail = (record: ProfileRecord) => {
    openDrawer(true, { record });
  };

  const handleUpdate = (record: ProfileRecord) => {
    openModal(true, {
      record,
      isUpdate: true,
    });
  };

  const handleDelete = async (id: string[]) => {
    try {
      await createSyncConfirm({ iconType: 'warning', content: '是否确认删除操作?' });
      await deviceConfigDelete(id);
      createMessage.success('删除成功');
      await getDataSource();
    } catch (error) {
      throw error;
    }
  };

  const handleSetDefault = async (record: ProfileRecord) => {
    try {
      const { tbProfileId } = record;
      const data = await setDeviceProfileIsDefaultApi(tbProfileId, 'default', defaultObj);
      if (!data) return createMessage.error('设置该产品为默认失败');
      createMessage.success('设置该产品为默认成功');
      await getDataSource();
    } catch (error) {
      throw error;
    }
  };

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

<template>
  <PageWrapper dense contentFullHeight contentClass="flex">
    <section class="flex-auto p-4 w-full profile-list">
      <div class="flex-auto w-full bg-light-50 dark:bg-dark-900 p-4">
        <BasicForm @register="register" />
      </div>
      <List
        ref="listEl"
        :loading="loading"
        class="flex-auto bg-light-50 dark:bg-dark-900 !p-2 !mt-4"
        position="bottom"
        :pagination="pagination"
        :data-source="dataSource"
        :grid="{ gutter: 4, column: colNumber }"
      >
        <template #header>
          <div class="flex gap-3 justify-end">
            <Button type="primary" @click="handleCreate">新增产品</Button>
            <Button type="primary" @click="handleSelectAll">
              {{ getSelectAllFlag ? '反选' : '全选' }}
            </Button>
            <Button
              type="primary"
              danger
              :disabled="!getCheckedRecord.length"
              @click="handleDelete(getCheckedRecord)"
            >
              批量删除
            </Button>
            <ModeSwitchButton :value="$props.mode" @change="handleModeChange" />
            <CardLayoutButton v-model:value="colNumber" @change="getDataSource" />
            <Tooltip title="刷新">
              <Button type="primary" @click="getDataSource">
                <ReloadOutlined :spin="loading" />
              </Button>
            </Tooltip>
          </div>
        </template>
        <template #renderItem="{ item }">
          <List.Item>
            <Card
              hoverable
              @click="handleCheckCard(item)"
              :class="item.checked ? '!border-blue-500 !border-2' : ''"
            >
              <template #cover>
                <div class="h-full w-full !flex justify-center items-center text-center">
                  <Image
                    @click.stop
                    :height="144"
                    :src="item.image"
                    placeholder
                    :fallback="IMAGE_FALLBACK"
                  />
                </div>
              </template>
              <template class="ant-card-actions" #actions>
                <Authority>
                  <Tooltip title="详情">
                    <EyeOutlined key="setting" @click.stop="handleShowDetail(item)" />
                  </Tooltip>
                </Authority>
                <Authority>
                  <Tooltip title="编辑">
                    <FormOutlined key="edit" @click.stop="handleUpdate(item)" />
                  </Tooltip>
                </Authority>
                <Dropdown
                  :trigger="['hover']"
                  :drop-menu-list="[
                    {
                      text: '默认',
                      event: DropMenuEvent.SET_DEFAULT,
                      icon: 'ant-design:unordered-list-outlined',
                      onClick: handleSetDefault.bind(null, item),
                    },
                    {
                      text: '删除',
                      event: DropMenuEvent.DELETE,
                      icon: 'ant-design:delete-outlined',
                      onClick: handleDelete.bind(null, [item.id]),
                    },
                  ]"
                >
                  <MoreOutlined @click.stop class="transform rotate-90" />
                </Dropdown>
              </template>
              <Card.Meta>
                <template #title>
                  <span class="truncate"> {{ item.name }} </span>
                </template>
                <template #description>
                  <div class="truncate h-11">
                    <div class="truncate">{{ DeviceTypeName[item.deviceType] }} </div>
                    <div class="truncate">{{ item.transportType }} </div>
                  </div>
                </template>
              </Card.Meta>
            </Card>
          </List.Item>
        </template>
      </List>
    </section>
    <DeviceProfileModal @register="registerModal" @success="getDataSource" />
    <DeviceProfileDrawer @register="registerDrawer" />
  </PageWrapper>
</template>

<style lang="less" scoped>
  .profile-list:deep(.ant-image-img) {
    width: 100% !important;
    height: 100% !important;
  }
</style>