index.vue 12.5 KB
<script lang="ts" setup>
  import { computed, ref, toRaw, watch } from 'vue';
  import { BasicInfoForm } from './components/BasicInfoForm';
  import { ModalParamsType } from '/#/utils';
  import { BasicModal, useModalInner } from '/@/components/Modal';
  import { Divider, Tabs, Button, Spin } from 'ant-design-vue';
  import { DataSourceForm } from './components/DataSourceForm';
  import { WidgetLibrary } from '../widgetLibrary';
  import {
    CreateComponentType,
    PackagesCategoryEnum,
    PackagesCategoryNameEnum,
  } from '../packages/index.type';
  import { TextComponent1Config } from '../packages/components/Text/TextComponent1';
  import { DataSourceType, SelectedWidgetKeys } from './index.type';
  import { buildUUID } from '/@/utils/uuid';
  import { unref } from 'vue';
  import { AddDataComponentParams } from '/@/api/dataBoard/model';
  import { useCalcNewWidgetPosition } from '../palette/hooks/useCalcNewWidgetPosition';
  import { Layout } from 'vue3-grid-layout';
  import { useBoardId } from '../palette/hooks/useBoardId';
  import { addDataComponent, updateDataComponent } from '/@/api/dataBoard';
  import { useMessage } from '/@/hooks/web/useMessage';
  import { DataActionModeEnum } from '/@/enums/toolEnum';
  import { WidgetDataType } from '../palette/hooks/useDataSource';
  import { DATA_SOURCE_LIMIT_NUMBER } from '.';
  import { DataSource } from '../palette/types';
  import { useGetComponentConfig } from '../packages/hook/useGetComponetConfig';
  import { MessageAlert } from './components/MessageAlert';
  import { createSelectWidgetKeysContext, createSelectWidgetModeContext } from './useContext';
  import { useGetCategoryByComponentKey } from '../packages/hook/useGetCategoryByComponentKey';
  import { deleteFilePath } from '/@/api/oss/ossFileUploader';
  import { FileItem } from '/@/components/Form/src/components/ApiUpload.vue';

  const props = defineProps<{
    layout: Layout[];
  }>();

  const emit = defineEmits(['register', 'ok']);

  enum TabKeyEnum {
    BASIC = 'basic',
    VISUAL = 'visual',
  }

  const { boardId } = useBoardId();

  const { createMessage } = useMessage();

  const loading = ref(false);

  const dataSourceFormSpinning = ref(false);

  let firstEnter = true;

  const selectWidgetKeys = ref<SelectedWidgetKeys>({
    componentKey: TextComponent1Config.key,
    categoryKey: PackagesCategoryEnum.TEXT,
  });

  createSelectWidgetKeysContext(selectWidgetKeys);

  const getComponentConfig = computed<CreateComponentType>(() => {
    return useGetComponentConfig(unref(selectWidgetKeys).componentKey);
  });

  const activeKey = ref(TabKeyEnum.BASIC);

  const genNewDataSourceItem = () => {
    return {
      uuid: buildUUID(),
      componentInfo: unref(getComponentConfig).persetOption || {},
    } as DataSourceType;
  };

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

  const currentMode = ref<DataActionModeEnum>(DataActionModeEnum.CREATE);

  createSelectWidgetModeContext(currentMode);

  const currentRecord = ref<Nullable<Recordable>>({});

  const [registerModal, { closeModal }] = useModalInner(
    (params: ModalParamsType<WidgetDataType>) => {
      resetFormValues();
      const { mode, record } = params;
      currentMode.value = mode;
      currentRecord.value = record;
      firstEnter = false;
      if (mode === DataActionModeEnum.UPDATE) {
        const config = useGetComponentConfig(record.frontId);
        if (!config) return;
        selectWidgetKeys.value = {
          componentKey: config.componentConfig.key,
          categoryKey: config.componentConfig.package,
        };
        activeKey.value = TabKeyEnum.BASIC;
        setFormValues(record);
      } else {
        selectWidgetKeys.value = {
          componentKey: TextComponent1Config.key,
          categoryKey: PackagesCategoryEnum.TEXT,
        };
        activeKey.value = TabKeyEnum.BASIC;
        dataSource.value = [genNewDataSourceItem()];
      }
    }
  );

  const basicInfoFromEl = ref<Nullable<InstanceType<typeof BasicInfoForm>>>(null);

  const dataSourceFormEl = ref<Nullable<InstanceType<typeof DataSourceForm>>>(null);

  const handleTabsChange = (activeKey: TabKeyEnum) => {
    if (activeKey === TabKeyEnum.VISUAL) {
      dataSource.value = (dataSourceFormEl.value?.getFormValues() as DataSourceType[]) || [];
    }
    const value = basicInfoFromEl.value?.getFormValues() || {};
    currentRecord.value = { ...unref(currentRecord), ...value };
  };

  const handleNewRecord = () => {
    const { componentKey } = unref(selectWidgetKeys);
    if (componentKey === 'HumidityComponent2' && unref(dataSource).length >= 6) {
      createMessage.warning('绑定的数据源不能超过6条');
      return;
    }
    if (unref(dataSource).length >= DATA_SOURCE_LIMIT_NUMBER) {
      createMessage.warning('绑定的数据源不能超过10条');
      return;
    }
    dataSource.value.push(genNewDataSourceItem());
  };

  /**
   * @description 可视化组件变化 数据源组件变更 重新赋值表单
   */
  watch(
    () => selectWidgetKeys.value.componentKey,
    (value, oldValue) => {
      if (value) {
        const oldCategory = useGetCategoryByComponentKey(oldValue);
        const category = useGetCategoryByComponentKey(value);
        const needReset =
          [oldCategory, category].some((item) => item === PackagesCategoryEnum.CONTROL) &&
          oldCategory !== category &&
          firstEnter;

        dataSource.value = unref(dataSource).map((item) => ({
          ...item,
          ...(needReset ? { attribute: null } : {}),
          componentInfo: {
            ...toRaw(unref(getComponentConfig).persetOption),
            ...(firstEnter ? {} : item.componentInfo),
          },
        }));

        if ((window as any).requestIdleCallback as unknown as boolean) {
          (window as any).requestIdleCallback(
            () => {
              setFormValues({ dataSource: unref(dataSource) } as WidgetDataType);
            },
            { timeout: 500 }
          );
        } else {
          setTimeout(() => {
            setFormValues({ dataSource: unref(dataSource) } as WidgetDataType);
          }, 500);
        }

        firstEnter = true;
      }
    }
  );

  const validate = async () => {
    return await unref(dataSourceFormEl)?.validate?.();
  };

  const resetFormValues = () => {
    unref(basicInfoFromEl)?.resetFormValues();
    unref(dataSourceFormEl)?.resetFormValues();
  };

  const setFormValues = (data: WidgetDataType) => {
    const { dataSource: newDataSource } = data;
    const { name, remark } = unref(currentRecord) || {};
    dataSource.value = newDataSource;
    unref(basicInfoFromEl)?.setFormValues({ name, remark });
    dataSourceFormSpinning.value = true;
    setTimeout(() => {
      unref(dataSourceFormEl)?.setFormValues(newDataSource);
      dataSourceFormSpinning.value = false;
    }, 500);
  };

  const getFormValues = () => {
    const dataSource = (
      (unref(dataSourceFormEl)?.getFormValues() as unknown as DataSource[]) || []
    ).map((item) => {
      Reflect.deleteProperty(item, 'uuid');
      return item;
    });

    const basicInfo = unref(basicInfoFromEl)?.getFormValues();

    const layout = useCalcNewWidgetPosition(props.layout);

    const frontId = unref(selectWidgetKeys).componentKey;
    return {
      boardId: unref(boardId),
      record: {
        ...(unref(currentMode) === DataActionModeEnum.UPDATE
          ? { id: unref(currentRecord)?.id }
          : {}),
        ...basicInfo,
        dataSource,
        layout,
        frontId,
      },
    } as AddDataComponentParams;
  };

  const getVisualConfigTitle = computed(() => {
    const { categoryKey } = unref(selectWidgetKeys);
    const category = PackagesCategoryNameEnum[PackagesCategoryEnum[categoryKey]];
    const { componentConfig } = unref(getComponentConfig);
    return `${category} / ${componentConfig.title}`;
  });

  const countElementOccurrences = (arr) => {
    const countMap = {};

    arr.forEach((element) => {
      if (countMap[element]) {
        countMap[element]++;
      } else {
        countMap[element] = 1;
      }
    });

    return countMap;
  };

  const handleSubmit = async () => {
    const validateResult = await validate();
    if (validateResult && !validateResult.flag) {
      const { errors } = validateResult;
      if (errors && errors.errorFields.length) {
        const errorRecord = errors.errorFields[0];
        createMessage.warning(errorRecord.errors.join(''));
        if (activeKey.value === TabKeyEnum.VISUAL) {
          activeKey.value = TabKeyEnum.BASIC;
        }
        return;
      }
    }
    const value = getFormValues();
    const { record } = value || {};
    try {
      const currentRecordIconUrl = ref<any>([]);
      // 判断当前自定义组件表单以前的自定义图片呢url
      currentRecord.value?.dataSource.forEach((item) => {
        if (item.componentInfo?.customIcon) {
          item.componentInfo?.customIcon?.forEach((icon: FileItem) => {
            currentRecordIconUrl.value.push(icon.url);
          });
        }
      });
      // 取当前修改过后的自定义图片url
      const dataSourceUrl = record.dataSource?.map(
        (item) => item.componentInfo.customIcon?.[0].url
      );

      // 当前自定义组件取出要进行删除的图标url
      const dataSourceDeleteUrl = unref(currentRecordIconUrl).filter(
        (item) => !dataSourceUrl?.includes(item)
      );

      //查询外部所有组件的自定义图标的url
      const oldDataSource = props.layout;
      const customIconUrls = ref<any>([]);
      oldDataSource?.forEach((item: any) => {
        item.dataSource?.forEach((dataSource) => {
          if (dataSource.componentInfo?.customIcon) {
            dataSource.componentInfo?.customIcon?.forEach((icon: FileItem) => {
              customIconUrls.value.push(icon.url);
            });
          }
        });
      });
      // const dataSourceDeleteUrl = record.dataSource?.map((item) => item.componentInfo.deleteUrl);

      if (unref(customIconUrls) && unref(customIconUrls).length && dataSourceDeleteUrl?.length) {
        // 判断外部所有组件是否有dataSourceDeleteUrl使用中的url
        const deletePromise = unref(customIconUrls)?.filter((item) =>
          dataSourceDeleteUrl?.includes(item)
        );
        const deleteUrlInfo = countElementOccurrences(deletePromise);
        const deleteUrl = deletePromise?.filter((item) => deleteUrlInfo?.[item] == 1);
        Promise.all(
          deleteUrl.map((item) => {
            deleteFilePath(item);
          })
        );
      }
    } catch (err) {
      // eslint-disable-next-line no-console
      console.log(err);
    }

    try {
      loading.value = true;
      unref(currentMode) === DataActionModeEnum.UPDATE
        ? await updateDataComponent(value)
        : await addDataComponent(value);
      createMessage.success(
        `${unref(currentMode) === DataActionModeEnum.UPDATE ? '编辑' : '新增'}成功`
      );
      closeModal();
      emit('ok');
    } catch (error) {
      throw error;
    } finally {
      activeKey.value = TabKeyEnum.BASIC;
      selectWidgetKeys.value = {
        componentKey: TextComponent1Config.key,
        categoryKey: PackagesCategoryEnum.TEXT,
      };
      dataSource.value = [];
      loading.value = false;
    }
  };
</script>

<template>
  <BasicModal
    title="自定义组件"
    width="70%"
    :destroy-on-close="true"
    @register="registerModal"
    @ok="handleSubmit"
    :ok-button-props="{ loading }"
  >
    <Tabs v-model:active-key="activeKey" type="card" @change="handleTabsChange" :animated="true">
      <Tabs.TabPane tab="基础配置" :key="TabKeyEnum.BASIC">
        <Divider orientation="left">基础信息</Divider>

        <BasicInfoForm ref="basicInfoFromEl" />

        <MessageAlert :select-widget-keys="selectWidgetKeys" />

        <Divider orientation="left">数据源配置</Divider>

        <Spin :spinning="dataSourceFormSpinning">
          <DataSourceForm
            ref="dataSourceFormEl"
            :key="getComponentConfig.componentConfig.datasourceConKey"
            :select-widget-keys="selectWidgetKeys"
            v-model:dataSource="dataSource"
            :component-config="getComponentConfig"
          />
        </Spin>

        <div class="flex justify-center">
          <Button type="primary" @click="handleNewRecord">添加数据源</Button>
        </div>
      </Tabs.TabPane>
      <Tabs.TabPane :key="TabKeyEnum.VISUAL">
        <template #tab>
          <span>可视化配置</span>
          <span class="mx-1">-</span>
          <span> {{ getVisualConfigTitle }}</span>
        </template>
        <WidgetLibrary v-model:checked="selectWidgetKeys" />
      </Tabs.TabPane>
    </Tabs>
  </BasicModal>
</template>