TransferTableModal.vue 10.2 KB
<script lang="ts" setup>
  import { Button, Tabs, Select } from 'ant-design-vue';
  import { remove, uniqBy, cloneDeep } from 'lodash';
  import { nextTick, onMounted, ref, unref, toRaw, watch } from 'vue';
  import {
    deviceTableColumn,
    deviceTableFormSchema,
    TransferTableProps,
  } from './TransferTableModal.config';
  import { devicePage } from '/@/api/device/deviceManager';
  import { DeviceModel as RawDeviceModal } from '/@/api/device/model/deviceModel';
  import { BasicModal, useModal } from '/@/components/Modal';
  import { BasicTable, useTable } from '/@/components/Table';
  import { FETCH_SETTING } from '/@/components/Table/src/const';
  import { useDesign } from '/@/hooks/web/useDesign';
  import { isFunction } from '/@/utils/is';

  interface DeviceModel extends RawDeviceModal {
    disabled?: boolean;
  }

  const props = withDefaults(
    defineProps<{
      params?: Recordable;
      value?: (Recordable & DeviceModel)[];
      maxTagLength?: number;
      openModalValidate?: () => boolean;
      primaryKey?: string;
      transformValue?: (list: Recordable[]) => any;
      disabled?: boolean;
    }>(),
    {
      value: () => [],
      maxTagLength: 2,
      primaryKey: 'tbDeviceId',
      params: () => ({}),
    }
  );

  const emit = defineEmits(['update:value']);

  enum Active {
    PENDING = 'pending',
    SELECTED = 'selected',
  }

  const activeKey = ref(Active.PENDING);

  const { prefixCls } = useDesign('transfer-table-modal');

  const pendingTotalList = ref<DeviceModel[]>([]);

  const pendingConfirmQueue = ref<DeviceModel[]>([]);

  const selectedTotalList = ref<DeviceModel[]>([]);

  const selectedConfirmQueue = ref<DeviceModel[]>([]);

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

  const [regsterPendingTable, pendingTableActionType] = useTable({
    ...TransferTableProps,
    rowKey: props.primaryKey,
    api: devicePage,
    immediate: false,
    clickToRowSelect: false,
    beforeFetch: (params) => {
      const { params: otherParams } = props;
      Object.assign(params, { ...otherParams, selected: false });
      return params;
    },
    afterFetch: (list: DeviceModel[]) => {
      pendingTotalList.value = list;
      return unref(pendingTotalList);
    },
    pagination: { hideOnSinglePage: false },
    rowSelection: {
      type: 'checkbox',
      getCheckboxProps: (record: DeviceModel) => {
        const { primaryKey } = props;
        const checked = unref(selectedTotalList).map((item) => item[primaryKey]);
        return {
          disabled: checked.includes(record[props.primaryKey]!),
        };
      },
      onSelect: (_record: Recordable, _selected: boolean, selectedRows: DeviceModel[]) => {
        const { primaryKey } = props;
        const checked = unref(selectedTotalList).map((item) => item[primaryKey]);
        pendingConfirmQueue.value = selectedRows.filter(
          (item) => !checked.includes(item[primaryKey]!)
        );
      },
      onSelectAll: (_selected: boolean, selectedRows: DeviceModel[]) => {
        const { primaryKey } = props;
        const checked = unref(selectedTotalList).map((item) => item[primaryKey]);
        pendingConfirmQueue.value = selectedRows.filter(
          (item) => !checked.includes(item[primaryKey]!)
        );
      },
    },
  });

  const [registerSelectedTable, selectedTableActionType] = useTable({
    formConfig: {
      layout: 'inline',
      labelWidth: 80,
      schemas: deviceTableFormSchema,
      actionColOptions: { span: 6 },
    },
    size: 'small',
    maxHeight: 240,
    useSearchForm: true,
    columns: deviceTableColumn,
    api: async (params) => {
      const { name = '', deviceType = '' } = params || {};
      const items = unref(selectedTotalList).filter((item) => {
        return (
          (item.name.toUpperCase().includes(name.toUpperCase()) ||
            item.alias?.toUpperCase().includes(name.toUpperCase())) &&
          item.deviceType.toUpperCase().includes(deviceType.toUpperCase())
        );
      });
      return {
        items,
        total: items.length,
      };
    },
    showIndexColumn: false,
    pagination: { hideOnSinglePage: false },
    fetchSetting: FETCH_SETTING,
    rowKey: props.primaryKey,
    dataSource: selectedTotalList,
    clickToRowSelect: false,
    beforeFetch: (params) => {
      const { params: otherParams } = props;
      Object.assign(params, { ...otherParams, selected: false });
      return params;
    },
    rowSelection: {
      type: 'checkbox',
      onSelect: (_record: Recordable, _selected: boolean, selectedRows: DeviceModel[]) => {
        selectedConfirmQueue.value = selectedRows;
      },
      onSelectAll: (_selected: boolean, selectedRows: DeviceModel[]) => {
        selectedConfirmQueue.value = selectedRows;
      },
    },
  });

  const handleTriggerUpdateValue = () => {
    let list: Recordable[] = cloneDeep(toRaw(unref(selectedTotalList)));
    const { transformValue } = props;
    if (transformValue && isFunction(transformValue)) list = transformValue(list);

    emit('update:value', list);
  };

  const handleSelected = () => {
    const { primaryKey } = props;
    const _list = [...unref(selectedTotalList), ...unref(pendingConfirmQueue)];
    selectedTotalList.value = uniqBy(_list, primaryKey);
    pendingConfirmQueue.value = [];

    handleTriggerUpdateValue();
    pendingTableActionType.setTableData([]);
    nextTick(() => pendingTableActionType.setTableData(unref(pendingTotalList)));
  };

  const handleRemoveSelected = () => {
    const { primaryKey } = props;
    const selectedIds = unref(selectedConfirmQueue).map((selected) => selected[primaryKey]);
    remove(unref(selectedTotalList), (item) => {
      const flag = selectedIds.includes(item[primaryKey]);
      if (flag) {
        pendingTableActionType.deleteSelectRowByKey(item[primaryKey]);
      }
      return flag;
    });

    handleTriggerUpdateValue();

    selectedTableActionType.clearSelectedRowKeys();
    selectedConfirmQueue.value = [];
    selectedTableActionType.reload();
  };

  const handleCheckoutPanel = async (key: Active) => {
    if (key === Active.PENDING) {
      pendingTableActionType.setTableData([]);
      await nextTick();
      pendingTableActionType.setTableData(unref(pendingTotalList));
    } else {
      await nextTick();
      selectedTableActionType.reload();
    }
  };

  const handleOpenModal = async () => {
    const { openModalValidate } = props;

    if (openModalValidate && isFunction(openModalValidate) && !openModalValidate()) return;

    activeKey.value = Active.PENDING;
    openModal(true);
    await nextTick();
    pendingTableActionType.reload();
  };

  watch(
    () => props.params,
    () => {
      selectedTotalList.value = [];
      selectedConfirmQueue.value = [];
      pendingConfirmQueue.value = [];
      try {
        pendingTableActionType.clearSelectedRowKeys();
        selectedTableActionType.clearSelectedRowKeys();
      } catch (error) {}
    }
  );

  onMounted(async () => {
    const { params } = props;
    if (!params?.convertConfigId || !params?.deviceProfileIds) {
      return;
    }
    const { items } = await devicePage({ page: 1, pageSize: 10, ...params, selected: true });
    selectedTotalList.value = items;
  });
</script>

<template>
  <section>
    <BasicModal
      @register="registerModal"
      title="设备选择"
      width="60%"
      :wrapClassName="prefixCls"
      :showOkBtn="false"
      cancelText="关闭"
    >
      <section class="bg-gray-100">
        <Tabs v-model:active-key="activeKey" type="card" @change="handleCheckoutPanel">
          <Tabs.TabPane :key="Active.PENDING">
            <template #tab>
              <div class="flex items-center justify-center">
                <span>待选设备</span>
              </div>
            </template>
            <BasicTable @register="regsterPendingTable">
              <template #toolbar>
                <section class="flex w-full justify-end items-center">
                  <div class="text-blue-400">
                    <span class="mr-2">选择设备:</span>
                    <span>{{ pendingConfirmQueue.length }}</span>
                  </div>
                </section>
              </template>
            </BasicTable>
            <section class="flex justify-end px-4 pb-4">
              <Button
                type="primary"
                @click="handleSelected"
                :disabled="!pendingConfirmQueue.length"
              >
                <span>确定已选</span>
              </Button>
            </section>
          </Tabs.TabPane>
          <Tabs.TabPane :key="Active.SELECTED">
            <template #tab>
              <div class="flex items-center justify-center">
                <span>已选设备</span>
              </div>
            </template>
            <BasicTable @register="registerSelectedTable">
              <template #toolbar>
                <section class="flex w-full justify-end items-center">
                  <div class="text-blue-400">
                    <span class="mr-2">选择设备:</span>
                    <span>{{ selectedConfirmQueue.length }}</span>
                  </div>
                </section>
              </template>
            </BasicTable>
            <section class="flex justify-end px-4 pb-4">
              <Button
                type="primary"
                :disabled="!selectedConfirmQueue.length"
                @click="handleRemoveSelected"
              >
                <span>移除已选</span>
              </Button>
            </section>
          </Tabs.TabPane>
        </Tabs>
      </section>
    </BasicModal>
    <Select
      placeholder="请选择设备"
      :disabled="disabled"
      :value="selectedTotalList.map((item) => item[primaryKey])"
      mode="multiple"
      :maxTagCount="3"
      :open="false"
      @dropdownVisibleChange="handleOpenModal"
      :options="
        selectedTotalList.map((item) => ({
          ...item,
          label: item.alias || item.name,
          value: item[primaryKey],
        }))
      "
    />
  </section>
</template>

<style lang="less">
  @prefix-cls: ~'@{namespace}-transfer-table-modal';

  .@{prefix-cls} {
    .vben-basic-table {
      padding-top: 0;
    }

    .vben-basic-form > .ant-row {
      width: 100%;
    }

    .ant-tabs-top-bar {
      background-color: #fff;
    }

    .ant-table-placeholder {
      height: auto !important;
    }
  }
</style>