BasicCardList.vue 5.76 KB
<script setup lang="ts">
  import { BasicForm, FormActionType, useForm } from '/@/components/Form';
  import { List, ListProps, Button, Tooltip } from 'ant-design-vue';
  import { computed, Ref, ref, toRaw, unref, useSlots, watch } from 'vue';
  import { useCardListScroll } from './hooks/useCardListScroll';
  import { BasicCardListPropsType, CardListActionType } from './types';
  import CardListLayout from './components/CardListLayout.vue';
  import { usePagination } from './hooks/usePagination';
  import { useLoading } from './hooks/useLoading';
  import { useCardListData } from './hooks/useCardListData';
  import { useListGrid } from './hooks/useListGrid';
  import { FETCH_SETTING } from '../../Table/src/const';
  import { useCardForm } from './hooks/useCardForm';
  import { Icon } from '/@/components/Icon';

  const emit = defineEmits<{
    (eventName: 'fetchSuccess', result: { items: Recordable[]; total: number }): void;
    (eventName: 'fetchError', error: Error): void;
    (
      eventName: 'register',
      cardListActionType: CardListActionType,
      formActions: FormActionType
    ): void;
  }>();

  const slots = useSlots();

  const props = withDefaults(defineProps<BasicCardListPropsType>(), {
    showCardListHeader: true,
    showCardListSetting: true,
    useSearchForm: false,
    gutter: 16,
    immediate: true,
    fetchSetting: () => FETCH_SETTING,
    autoCreateKey: true,
  });

  const tableData = ref<Recordable[]>([]);

  const listElRef = ref<Nullable<ComponentElRef>>(null);

  const innerPropsRef = ref<BasicCardListPropsType>();

  const [registerForm, formActions] = useForm();

  const getProps = computed<BasicCardListPropsType>(() => {
    return { ...props, ...(unref(innerPropsRef) || {}) } as BasicCardListPropsType;
  });

  const { getListGrid, cardListLayout } = useListGrid(getProps);

  const { getPaginationInfo, getPagination, setPagination } = usePagination(
    getProps,
    cardListLayout
  );

  const { loading, setLoading, getLoading } = useLoading();

  const { getDataSourceRef, getRowKey, fetch, reload } = useCardListData(getProps, formActions, {
    setLoading,
    getPaginationInfo,
    emit,
    tableData,
    setPagination,
  });

  const { redoHeight, containerHeight } = useCardListScroll(
    listElRef as Ref<Nullable<ComponentElRef>>,
    getProps,
    getDataSourceRef
  );

  const { replaceFormSlotKey, getFormProps, getFormSlotKeys } = useCardForm(
    getProps,
    slots,
    fetch,
    getLoading
  );

  function handleCardListChange() {
    reload();
  }

  const getBindData = computed<ListProps>(() => {
    const dataSource = unref(getDataSourceRef);

    return {
      dataSource,
      grid: unref(getListGrid),
      loading: unref(loading),
      pagination: {
        ...toRaw(unref(getPaginationInfo)),
        onChange: (current: number, pageSize: number) => {
          setPagination({ current, pageSize });
          handleCardListChange();
        },
      },
      rowKey: unref(getRowKey),
    } as ListProps;
  });

  watch(cardListLayout, () => {
    const pageSize = cardListLayout.row * cardListLayout.col;
    setPagination({ pageSize });
    handleCardListChange();
  });

  const CartListActionType: CardListActionType = {
    setProps,
    setLoading,
    setPagination,
    getPagination,
    reload,
  };

  function setProps(props: Partial<BasicCardListPropsType> = {}) {
    innerPropsRef.value = { ...(unref(innerPropsRef) || {}), ...props } as BasicCardListPropsType;
  }

  emit('register', CartListActionType, formActions);
</script>

<template>
  <main class="basic-card-list p-4 flex flex-col w-full h-full">
    <section v-if="getProps.useSearchForm" class="w-full bg-light-50 dark:bg-dark-900 p-4 mb-4">
      <BasicForm
        @register="registerForm"
        v-bind="getFormProps"
        @advanced-change="redoHeight"
        @reset="reload()"
        @submit="reload()"
      >
        <template #[replaceFormSlotKey(item)]="data" v-for="item in getFormSlotKeys">
          <slot :name="item" v-bind="data"></slot>
        </template>
      </BasicForm>
    </section>

    <section class="w-full bg-light-50 dark:bg-dark-900 p-4 flex-auto h-full">
      <List ref="listElRef" v-bind="getBindData">
        <template #header>
          <section class="flex justify-between items-center">
            <div>
              <span v-if="getProps.title" class="font-bold text-base px-4">
                {{ getProps.title }}
              </span>
              <slot v-else name="title"></slot>
            </div>
            <div class="flex flex-auto justify-end mr-4">
              <slot name="toolbar"></slot>
            </div>
            <div v-if="showCardListSetting" class="flex gap-4">
              <CardListLayout v-model:row="cardListLayout.row" v-model:col="cardListLayout.col" />
              <Tooltip title="刷新">
                <Button type="primary" @click="reload()" :loading="loading">
                  <Icon icon="ant-design:reload-outlined" :size="20" />
                </Button>
              </Tooltip>
            </div>
          </section>
        </template>
        <template #renderItem="{ item }">
          <List.Item :style="{ '--totalHeight': containerHeight }">
            <slot name="renderItem" :item="item" :totalHeight="containerHeight"></slot>
          </List.Item>
        </template>
      </List>
    </section>
  </main>
</template>

<style lang="less" scoped>
  .basic-card-list {
    :deep(.ant-list) {
      .ant-spin-container {
        overflow-x: hidden;
        overflow-y: auto;
      }

      .ant-list-header {
        border-bottom: transparent;
      }

      .ant-list-pagination {
        margin-top: 16px;
        height: 25px;
      }

      .ant-list-empty-text {
        height: 100%;
        display: flex;
        align-items: center;
        justify-content: center;
      }
    }
  }
</style>