useSaveAndRedo.ts 4.87 KB
import type { VueFlowStore, Getters, Elements } from '@vue-flow/core';
import { ComputedRef, computed, ref, unref } from 'vue';
import { BasicNodeBindData, EdgeData, NodeData } from '../types/node';
import { EntryCategoryComponentEnum } from '../enum/category';
import { useBasicDataTransform } from './useBasicDataTransform';
import { getRuleChainData, saveRuleChainData } from '/@/api/ruleDesigner';
import { ConnectionItemType, RuleChainType } from '../types/ruleNode';
import { useInputNode } from './useInputNode';
import { buildUUID } from '/@/utils/uuid';
import { useRoute } from 'vue-router';

const ignoreNodeKeys: string[] = [EntryCategoryComponentEnum.INPUT];

export function useSaveAndRedo() {
  const changeMarker = ref(false);

  const loading = ref(false);

  const redoDataRef = ref<Elements>([]);

  const route = useRoute();

  const getRuleChainId = computed(() => (route.params as Record<'id', string>).id);

  const { mergeData, deconstructionData } = useBasicDataTransform();

  const triggerChange = () => {
    changeMarker.value = true;
  };

  const resetChange = () => {
    changeMarker.value = false;
  };

  /**
   * @description 保存连接信息
   */
  function getConnections(
    nodesRef: ComputedRef<Getters['getNodes']> | Getters['getNodes'],
    edges: ComputedRef<Getters['getEdges']>
  ) {
    const nodeIndexMap = new Map();

    const connections: ConnectionItemType[] = [];

    unref(nodesRef).forEach((item, index) => {
      nodeIndexMap.set(item.id, index);
    });

    for (const item of unref(edges)) {
      const { data, target, source } = item;
      const { data: bindData } = data as EdgeData;
      const { type } = bindData || {};
      const fromIndex = nodeIndexMap.get(source);
      const toIndex = nodeIndexMap.get(target);
      type?.forEach((key) => {
        connections.push({ fromIndex, toIndex, type: key });
      });
    }

    return connections;
  }

  function getNodes(nodesRef: ComputedRef<Getters['getNodes']> | Getters['getNodes']) {
    const nodes: BasicNodeBindData[] = [];

    for (const node of unref(nodesRef)) {
      const nodeData = node.data as NodeData;

      if (ignoreNodeKeys.includes(nodeData.config?.key as string)) continue;

      const data = nodeData.data;

      nodes.push(mergeData(data, nodeData, node));
    }

    return nodes;
  }

  function getFirsetNodeIndex(
    nodesRef: ComputedRef<Getters['getNodes']> | Getters['getNodes'],
    edges: ComputedRef<Getters['getEdges']>
  ) {
    const inputNode = unref(edges).find(
      (item) => (item.sourceNode.data as NodeData).config?.key === EntryCategoryComponentEnum.INPUT
    );

    if (inputNode) {
      const targetId = inputNode.target;
      const index = unref(nodesRef).findIndex((item) => item.id === targetId);
      return index;
    }
  }

  const handleApplyChange = (flowActionType: VueFlowStore) => {
    if (!unref(changeMarker)) return;

    const edgesRef = flowActionType.getEdges;

    const extraIgnoreNodeRef = unref(flowActionType.getNodes).filter(
      (item) => !ignoreNodeKeys.includes((item.data as NodeData).config?.key as string)
    );

    const connections = getConnections(extraIgnoreNodeRef, edgesRef);

    const nodes = getNodes(extraIgnoreNodeRef);

    const firstNodeIndex = getFirsetNodeIndex(extraIgnoreNodeRef, edgesRef);

    handleSaveRuleChain(connections, nodes, firstNodeIndex);
  };

  const handleRedoChange = (flowActionType: VueFlowStore) => {
    if (!unref(changeMarker)) return;
    flowActionType.setElements(unref(redoDataRef));
    resetChange();
  };

  async function handleSaveRuleChain(
    connections: ConnectionItemType[],
    nodes: BasicNodeBindData[],
    firstNodeIndex?: number
  ) {
    try {
      loading.value = true;
      const data = await saveRuleChainData({
        connections,
        nodes,
        firstNodeIndex,
        ruleChainId: {
          entityType: 'RULE_CHAIN',
          id: unref(getRuleChainId),
        },
      });

      parseRuleChain(data);

      resetChange();
    } finally {
      loading.value = false;
    }
  }

  async function getCurrentPageMetaData(flowActionType: VueFlowStore) {
    try {
      loading.value = true;

      const data = await getRuleChainData(unref(getRuleChainId));

      const elements = parseRuleChain(data);

      flowActionType.setElements(elements);

      resetChange();
    } finally {
      loading.value = false;
    }
  }

  function parseRuleChain(ruleChain: RuleChainType) {
    const inputId = buildUUID();

    const { getInputNodeConfig } = useInputNode();

    const value = deconstructionData(ruleChain, inputId);

    const { nodes = [], edges = [] } = value || {};

    const inputNode = getInputNodeConfig(inputId);

    const elements = [inputNode, ...nodes, ...edges];

    redoDataRef.value = elements;

    return elements;
  }

  return {
    loading,
    changeMarker,
    triggerChange,
    handleApplyChange,
    handleRedoChange,
    getCurrentPageMetaData,
  };
}