import React from 'react';
import { Graph, Addon, Path, Cell, Model, Node, Markup } from '@antv/x6';
import { Drawer, Input, message, Modal, Table, Tooltip } from 'antd';
import AlgoNode from './AlgoNode';
import ToolBar from './ToolBar';
import DatasourceNodeInfo from './DatasourceNodeInfo';
import ScriptNodeInfo from './ScriptNodeInfo';
import OutputNodeInfo from './OutputNodeInfo';
import ContextMenuView from './ContextMenu';
import ReactDOM from 'react-dom';
import { nanoid } from 'nanoid';
import { signNodesSkip, DagToData } from './utils';
import {
  executeDag,
  debugStatus,
  getNodeLog,
  getNodeResult,
  saveNodeTable
} from './request';
import copy from 'copy-to-clipboard';

// 侧边栏UI组件
const { Stencil } = Addon;

// 节点状态接口
interface NodeStatus {
  id: string;
  status: 'default' | 'success' | 'failed' | 'running';
  type: 'script' | 'datasource' | 'outputsource';
  label?: string;
}

// 数据源节点
const dataSourceNodes = [
  {
    id: '0',
    shape: 'dag-node',
    height: 80,
    width: 180,
    data: {
      label: 'InputSource',
      status: 'default',
      type: 'datasource'
    },
    ports: {
      items: [{ id: 'bottomPort', group: 'bottom' }]
    }
  },
  {
    id: '10',
    shape: 'dag-node',
    height: 80,
    width: 180,
    data: {
      label: 'OutputSource',
      status: 'default',
      type: 'outputsource'
    },
    ports: {
      items: [{ id: 'topPort', group: 'top' }]
    }
  }
];
// 用户自定义脚本节点
const customScriptNodes = [
  {
    id: '5',
    shape: 'dag-node',
    data: {
      label: 'sql',
      status: 'default',
      type: 'script'
    },
    ports: {
      items: [
        { id: 'input0', group: 'top' },
        { id: 'r0', group: 'bottom' }
      ]
    }
  },
  {
    id: '6',
    shape: 'dag-node',
    data: {
      label: 'pyspark',
      status: 'default',
      type: 'script'
    },
    ports: {
      items: [
        { id: 'input0', group: 'top' },
        { id: 'r0', group: 'bottom' }
      ]
    }
  }
  // {
  //   id: '7',
  //   shape: 'dag-node',
  //   data: {
  //     label: 'python',
  //     status: 'default',
  //     type: 'script'
  //   },
  //   ports: {
  //     items: [
  //       // { id: 'topPort', group: 'top' },
  //       // { id: 'bottomPort', group: 'bottom' }
  //     ]
  //   }
  // }
];

// 注册算子节点
Graph.registerNode(
  'dag-node',
  {
    inherit: 'react-shape',
    width: 180,
    height: 50,
    component: <AlgoNode />,
    portMarkup: [Markup.getForeignObjectMarkup()],
    ports: {
      groups: {
        top: {
          position: 'top',
          attrs: {
            fo: {
              width: 10,
              height: 10,
              x: -5,
              y: -5,
              magnet: 'true'
            }
          }
        },
        bottom: {
          position: 'bottom',
          attrs: {
            fo: {
              width: 10,
              height: 10,
              x: -5,
              y: -5,
              magnet: 'true'
            }
          }
        }
      }
    }
  },
  true
);

// 注册边
Graph.registerEdge(
  'dag-edge',
  {
    inherit: 'edge',
    attrs: {
      line: {
        stroke: '#C2C8D5',
        strokeWidth: 1,
        targetMarker: 'block'
      }
    }
  },
  true
);

// 注册连接
Graph.registerConnector(
  'algo-connector',
  (s, e) => {
    const offset = 4;
    const deltaY = Math.abs(e.y - s.y);
    const control = Math.floor((deltaY / 3) * 2);

    const v1 = { x: s.x, y: s.y + offset + control };
    const v2 = { x: e.x, y: e.y - offset - control };

    return Path.normalize(
      `M ${s.x} ${s.y}
       L ${s.x} ${s.y + offset}
       C ${v1.x} ${v1.y} ${v2.x} ${v2.y} ${e.x} ${e.y - offset}
       L ${e.x} ${e.y}
      `
    );
  },
  true
);
export default class Dag extends React.Component<any, any> {
  // 容器DIV
  private container: any;
  // 侧边栏UI容器
  private stencilContainer: any;
  // 构造函数
  constructor(props: any) {
    super(props);
    this.state = {
      nodeInfoVisible: false,
      dagGraph: null,
      dagId: '',
      selectedNodeData: {},
      contextMenu: null,
      contextMenuNode: null,
      isOpenTable: false,
      isOpenLog: false,
      logData: '暂无数据',
      nodeTableData: [],
      nodeTableCol: [],
      nodeTablePins: [],
      selectedPin: 0,
      filename: '',
      currentDblNode: '',
      canBeginRun: false,
      scriptInputNodes: [],
      saveModalOpen: false,
      table_name: '',
      resultTableName: ''
    };
  }

  // 展示node节点信息
  showNodeInfo = () => {
    this.setState({ nodeInfoVisible: true });
  };

  // 隐藏node节点信息
  hideNodeInfo = () => {
    this.setState({ nodeInfoVisible: false });
  };

  // 右键菜单
  handleContextMenu = (event: React.MouseEvent) => {
    event.preventDefault();
    this.setState({
      contextMenu:
        this.state.contextMenu === null
          ? {
              mouseX: event.clientX + 2,
              mouseY: event.clientY - 6
            }
          : null
    });
  };

  formatSourceTable = (node_id: string) => {
    const allEdges = this.state.dagGraph.getEdges();
    const input_nodes = {} as any;
    allEdges.forEach(async (item: any) => {
      const target = item.getTargetNode().getData();
      const source = item.getSourceNode().getData();
      if (target.nodeId === node_id) {
        const pointId = item.getSource().port;
        const out_pin = item
          .getSourceNode()
          .getPortsByGroup('bottom')
          .findIndex((port: any) => port.id === pointId);
        const in_pin = item.getTarget().port;
        input_nodes[in_pin] = {
          out_pin,
          nodeId: source.nodeId,
          source_type: source.label,
          source_tableName: source.dataTableName,
          target_type: target.label
        };
      }
    });
    this.setState({ scriptInputNodes: input_nodes });
  };

  saveData = () => {
    const text = this.props.context.current;
    text.ready.then(() => {
      const dagJson: any = text.model.toJSON();
      this.props.saveGraph(
        DagToData(this.state.dagGraph, this.state.dagId, dagJson.requirements)
      );
    });
  };

  executeDagData = async (script_nodes: any, script_edges: any) => {
    const dag_script = JSON.stringify({
      sub_nodes: script_nodes,
      edges: script_edges
    });
    const params = {
      dag_uuid: this.state.dagId,
      dag_script
    };
    // 执行时间长，先变更状态再处理
    this.formatNodeStatus(script_nodes);
    this.saveData();
    const { data } = await executeDag(params);
    if (data.code === 200) {
      setTimeout(() => {
        this.refreshNodeStatus();
      }, 10000);
    } else {
      message.error(data.msg);
      const allNodes = this.state.dagGraph.getNodes();
      allNodes.forEach((item: any) => {
        const data = item.getData();
        if (data.status === 'running') {
          item.setData({ ...data, status: 'undone' });
        }
      });
      this.saveData();
    }
  };

  formatNodeStatus = (script_nodes: any) => {
    const runNodes = script_nodes.filter((item: any) => !item.skip);
    const runNodeIds = runNodes.map((item: any) => item.id);
    const allNodes = this.state.dagGraph.getNodes();
    allNodes.forEach((item: any) => {
      const data = item.getData();
      if (
        runNodeIds.includes(data.nodeId) &&
        !['outputsource', 'datasource'].includes(data.type)
      ) {
        item.setData({ ...data, status: 'running' });
      }
    });
  };

  formatNodeData = (script_nodes: any) => {
    const menuNode = this.state.contextMenuNode;
    const allNodes = this.state.dagGraph.getNodes();
    const inputs = script_nodes.find(
      (item: any) => item.id === menuNode.data.nodeId
    ).inputs;
    const keys = Object.keys(inputs);
    let format_nodes = [] as any;
    keys.forEach((key: any) => {
      const input_id = inputs[key][0];
      const input_type = allNodes.find(
        (item: any) => item.data.nodeId === input_id
      ).data.type;
      format_nodes = script_nodes.map((item: any) => {
        if (item.id === input_id && input_type === 'datasource') {
          item.skip = false;
        }
        return item;
      });
    });
    return format_nodes;
  };

  // 执行此处
  handleNodeRun = () => {
    const menuNode = this.state.contextMenuNode;
    const { script_nodes, script_edges } = signNodesSkip(
      this.state.dagGraph,
      menuNode,
      'handle_run'
    );
    this.executeDagData(this.formatNodeData(script_nodes), script_edges);
    this.setState({ contextMenu: null });
  };

  // 全部执行
  handleRunDag = () => {
    const { script_nodes, script_edges } = signNodesSkip(
      this.state.dagGraph,
      null,
      'run_all'
    );
    this.executeDagData(script_nodes, script_edges);
  };

  // 执行到此处
  handleRunEndNode = () => {
    const menuNode = this.state.contextMenuNode;
    const { script_nodes, script_edges } = signNodesSkip(
      this.state.dagGraph,
      menuNode,
      'handle_run_end'
    );
    this.executeDagData(script_nodes, script_edges);
    this.setState({ contextMenu: null });
  };
  // 从此处开始执行
  handleRunBeginNode = () => {
    const menuNode = this.state.contextMenuNode;
    const { script_nodes, script_edges } = signNodesSkip(
      this.state.dagGraph,
      menuNode,
      'handle_run_begin'
    );
    this.executeDagData(this.formatNodeData(script_nodes), script_edges);
    this.setState({ contextMenu: null });
  };

  // 关闭右键菜单
  handleContextMenuClose = () => {
    this.setState({ contextMenu: null });
  };

  formatTableData = (data: any) => {
    const col = data.header.map((item: any) => ({
      title: item,
      dataIndex: item,
      key: item
    }));
    const tableData = data.content.map((item: any, index: number) => {
      const obj = {
        key: index.toString()
      } as any;
      item.forEach((item: any, index: any) => {
        obj[col[index].key] = item;
      });
      return obj;
    });
    return {
      col,
      tableData
    };
  };

  fetchTableData = async (out_pin: any) => {
    const menuNode = this.state.contextMenuNode;
    const { nodeId } = menuNode.getData();
    const params = {
      dag_uuid: this.state.dagId,
      node_id: nodeId,
      out_pin: out_pin
    };
    const { data } = await getNodeResult(params);
    if (data.code === 200) {
      const { col, tableData } = this.formatTableData(data.data);
      this.setState({
        nodeTableCol: col,
        nodeTableData: tableData,
        resultTableName: data.data.table_name
      });
    } else {
      // message.error(data.msg);
    }
  };

  handleOpenTable = async () => {
    const menuNode = this.state.contextMenuNode;
    const out_pins = menuNode
      .getPortsByGroup('bottom')
      .map((_: any, index: any) => index);
    this.setState({
      nodeTablePins: out_pins
    });
    this.setState({ selectedPin: 0 });
    this.fetchTableData(0);
    this.setState({ contextMenu: null, isOpenTable: true });
  };

  handleOpenLog = async () => {
    const menuNode = this.state.contextMenuNode;
    const { nodeId } = menuNode.getData();
    const params = {
      dag_uuid: this.state.dagId,
      node_id: nodeId
    };
    const { data } = await getNodeLog(params);
    if (data.code === 200) {
      this.setState({ logData: data.data });
    } else {
      // message.error(data.msg);
    }
    this.setState({ contextMenu: null, isOpenLog: true });
  };

  // 挂载
  componentDidMount(): void {
    // 创建图
    const graph: Graph = new Graph({
      // 容器
      container: this.container,
      // 格子
      // grid: true,
      background: {
        color: '#FBFCFF'
      },
      // 拖动
      panning: {
        enabled: true,
        eventTypes: ['leftMouseDown', 'mouseWheel']
      },
      // 放大缩小
      mousewheel: {
        enabled: true,
        modifiers: 'ctrl',
        factor: 1.1,
        maxScale: 1.5,
        minScale: 0.5
      },
      // 交互触发高亮
      highlighting: {
        magnetAdsorbed: {
          name: 'stroke',
          args: {
            attrs: {
              fill: '#fff',
              stroke: '#31d0c6',
              strokeWidth: 4
            }
          }
        }
      },
      // 连接
      connecting: {
        snap: true,
        allowBlank: false,
        allowLoop: false,
        highlight: true,
        connector: 'algo-connector',
        connectionPoint: 'anchor',
        anchor: 'center',
        // 验证
        validateMagnet({ magnet }) {
          return magnet.getAttribute('port-group') !== 'top';
        },
        validateConnection({
          targetPort,
          sourceCell,
          targetCell,
          targetMagnet
        }) {
          if (!targetCell) return false;
          const incomingEdges = this.getIncomingEdges(targetCell);
          const existPorts = incomingEdges?.map(edge => {
            return edge.getTargetPortId();
          });
          const jugeExist = existPorts?.indexOf(targetPort as any);
          return (
            targetMagnet?.getAttribute('port-group') === 'top' &&
            (jugeExist === undefined || jugeExist === -1)
          );
        },
        createEdge() {
          return graph.createEdge({
            shape: 'dag-edge',
            attrs: {
              line: {
                strokeDasharray: '5 5'
              }
            },
            zIndex: -1
          });
        }
      },
      // 选择
      selecting: {
        enabled: true,
        multiple: true,
        rubberEdge: true,
        rubberNode: true,
        modifiers: 'shift',
        rubberband: true
      },
      // 快捷键
      keyboard: {
        enabled: true,
        global: true
      },
      onPortRendered(args) {
        const selectors = args.contentSelectors;
        const port = args.port;
        const container = selectors && selectors.foContent;
        if (container) {
          ReactDOM.render(
            (
              <Tooltip title={port.id}>
                <div className="my-port" />
              </Tooltip>
            ) as any,
            container as HTMLElement
          );
        }
      }
    });

    // 删除节点
    graph.bindKey(['delete', 'backspace'], () => {
      graph.getSelectedCells().forEach((item: any) => {
        item.remove();
      });
    });

    // 监听边连接
    graph.on('edge:connected', ({ edge }) => {
      edge.attr({
        line: {
          strokeDasharray: ''
        }
      });
    });

    // 监听状态改变
    graph.on('node:change:data', ({ node }) => {
      const edges = graph.getIncomingEdges(node);
      const { status } = node.getData() as NodeStatus;
      edges?.forEach(edge => {
        if (status === 'running') {
          edge.attr('line/strokeDasharray', 5);
          edge.attr('line/style/animation', 'running-line 30s infinite linear');
        } else {
          edge.attr('line/strokeDasharray', '');
          edge.attr('line/style/animation', '');
        }
      });
    });

    // 监听节点被双击
    graph.on(
      'node:dblclick',
      (args: { cell: Cell; node: Node; options: Model.SetOptions }) => {
        const nodeData = args.cell.data;
        this.setState({
          currentDblNode: args.node,
          selectedNodeData: nodeData
        });
        if (args.node.data.type === 'script') {
          this.formatSourceTable(args.node.data.nodeId);
        }
        this.showNodeInfo();
      }
    );

    //监听右键
    graph.on('node:contextmenu', ({ node, e }) => {
      if (node.data.type === 'script') {
        this.setState({ contextMenuNode: node });
        const allEdges = this.state.dagGraph.getEdges();
        const input_nodes = [];
        for (const item of allEdges) {
          const target = item.getTargetNode().getData();
          const source = item.getSourceNode().getData();
          if (target.nodeId === node.data.nodeId) {
            input_nodes.push(source);
          }
        }

        const passNodes = input_nodes.filter(
          item => item.status === 'success' || item.type === 'datasource'
        );
        this.setState({ canBeginRun: passNodes.length === input_nodes.length });
        this.handleContextMenu(e);
      }
    });

    const text = this.props.context.current;
    text.ready.then(() => {
      const dagJson: any = text.model.toJSON();
      if (dagJson?.graph) {
        graph.fromJSON(dagJson.graph);
        this.setState({
          dagId:
            dagJson?.dag_id ||
            nanoid().replaceAll('-', 'a').replaceAll('_', 'b')
        });
      } else {
        this.setState({
          dagId: nanoid().replaceAll('-', 'a').replaceAll('_', 'b')
        });
      }
      this.refreshNodeStatus();
    });

    // 内容居中
    graph.centerContent();

    // 创建侧边栏
    const stencil = new Stencil({
      title: '算子检索',
      target: graph,
      // 搜索
      search(cell, keyword) {
        return cell.data.label.indexOf(keyword) !== -1;
      },
      placeholder: '搜索算子',
      notFoundText: '未找到相关算子',
      scaled: true,
      // animation: true,
      // 侧边栏项宽高
      stencilGraphWidth: 200,
      stencilGraphHeight: 200,
      layoutOptions: {
        columns: 1,
        dx: 55,
        rowHeight: 'compact'
      },
      // 侧边栏项目分组
      groups: [
        {
          name: 'dataSource',
          title: '数据源'
        },
        // {
        //   name: 'dataHandle',
        //   title: '数据处理',
        //   collapsed: true
        // },
        // {
        //   name: 'otherScript',
        //   title: '其它',
        //   collapsed: true
        // },
        {
          name: 'customScript',
          title: '自定义脚本'
        }
      ],
      // 拖拽事件 处理节点数据
      getDropNode(draggingNode: Node): Node {
        const newNode = draggingNode.clone();
        initNodeData(graph, newNode);
        return newNode;
      }
    });

    // 添加到侧边栏容器内
    this.stencilContainer.appendChild(stencil.container);

    // 将可拖拽项加载到侧边栏中
    stencil.load(dataSourceNodes, 'dataSource');
    stencil.load(customScriptNodes, 'customScript');
    stencil.resizeGroup('dataSource', {
      width: 200,
      height: dataSourceNodes.length * 100
    });
    stencil.resizeGroup('customScript', {
      width: 200,
      height: customScriptNodes.length * 80
    });

    // 设置图
    this.setState({ dagGraph: graph });
  }

  // 卸载
  componentWillUnmount(): void {
    this.state.dagGraph.dispose();
  }

  //刷新状态
  async refreshNodeStatus() {
    const { data } = await debugStatus(this.state.dagId);
    if (data.code === 200) {
      const successIds = [] as string[];
      const failedIds = [] as string[];
      const keys = Object.keys(data.data.nodes);
      keys.forEach(item => {
        if (data.data.nodes[item] === 'success') successIds.push(item);
        if (data.data.nodes[item] === 'failed') failedIds.push(item);
      });
      const allNodes = this.state.dagGraph.getNodes();
      allNodes.forEach((item: any) => {
        const data = item.getData();
        if (!['outputsource', 'datasource'].includes(data.type)) {
          if (successIds.includes(data.nodeId)) {
            item.setData({ ...data, status: 'success' });
          }
          if (failedIds.includes(data.nodeId)) {
            item.setData({ ...data, status: 'failed' });
          }
        }
      });
      // this.props.saveGraph(DagToData(this.state.dagGraph, this.state.dagId));
      if ([2, 3].includes(data.data.job)) {
        allNodes.forEach((item: any) => {
          const data = item.getData();
          if (data.status === 'running') {
            item.setData({ ...data, status: 'undone' });
          }
        });
        this.syncOutputSourceFields();
      } else {
        setTimeout(() => {
          this.refreshNodeStatus();
        }, 10000);
      }
    } else {
      // message.error(data.msg);
    }
  }

  // 输出源字段同步
  syncOutputSourceFields = async () => {
    const allEdges = this.state.dagGraph.getEdges();
    for (const item of allEdges) {
      const target = item.getTargetNode().getData();
      const source = item.getSourceNode().getData();
      if (target.type === 'outputsource') {
        const pointId = item.getSource().port;
        const out_pin = item
          .getSourceNode()
          .getPortsByGroup('bottom')
          .findIndex((port: any) => port.id === pointId);
        const params = {
          dag_uuid: this.state.dagId,
          node_id: source.nodeId,
          out_pin
        };
        const { data } = await getNodeResult(params);
        if (data.code === 200) {
          const output_data = data.data.header.map((field: string) => {
            const str = field.split(':');
            return {
              dataField: str[0],
              dataType: str[1]
            };
          });
          item.getTargetNode().setData(
            {
              ...target,
              outputSource: output_data,
              dataTableId: '',
              tablePath: ''
            },
            { overwrite: true }
          );
        }
      }
    }
    this.saveData();
  };

  // 容器
  refContainer = (container: HTMLDivElement) => {
    this.container = container;
  };

  // 侧边栏
  refStencil = (container: HTMLDivElement) => {
    this.stencilContainer = container;
  };

  saveTableData = async () => {
    if (this.state.table_name && !this.state.table_name.includes(' ')) {
      const menuNode = this.state.contextMenuNode;
      const { nodeId } = menuNode.getData();
      const params = {
        name: this.state.table_name,
        user_name: 'test',
        user_id: 'test',
        project_id: 'test',
        dag_uuid: this.state.dagId,
        node_id: nodeId,
        out_pin: this.state.selectedPin
      };
      const { data } = await saveNodeTable(params);
      if (data.code === 200) {
        message.success('另存为成功');
        this.setState({ saveModalOpen: false });
      } else {
        message.error(data.msg);
      }
    } else {
      message.error('请输入正确的名字');
    }
  };

  updateNodeStatus = (nodeId: any, edges: any) => {
    edges.forEach((edge: any) => {
      const source = edge.getSourceNode().getData();
      const target = edge.getTargetNode().getData();
      if (source.nodeId === nodeId && target.type !== 'outputsource') {
        edge.getTargetNode().setData(
          {
            ...target,
            status: 'undone'
          },
          { overwrite: true }
        );
        const filterEdges = edges.filter((item: any) => item !== edge);
        this.updateNodeStatus(target.nodeId, filterEdges);
      }
    });
  };

  saveChangeStatus = (nodeId: any) => {
    const allEdges = this.state.dagGraph.getEdges();
    let current_target = '' as any;
    let current_edge = '' as any;
    allEdges.forEach((edge: any) => {
      const target = edge.getTargetNode().getData();
      // 将当前点击节点状态改为未执行
      if (target.nodeId === nodeId && target.type === 'script') {
        current_target = target;
        current_edge = edge;
      }
    });
    if (current_edge) {
      current_edge?.getTargetNode().setData(
        {
          ...current_target,
          status: 'undone'
        },
        { overwrite: true }
      );
    }
    // 只有一个节点的时候
    if (allEdges.length === 0) {
      const current_node = this.state.dagGraph.getNodes();
      const data = current_node[0].getData();
      if (data.type === 'script') {
        current_node[0].setData(
          {
            ...data,
            status: 'undone'
          },
          {
            overwrite: true
          }
        );
      }
    }
    this.updateNodeStatus(nodeId, allEdges);
  };

  copyTableName = () => {
    copy(this.state.resultTableName);
    message.success('复制成功');
  };

  // 最后输出模板
  render() {
    return (
      <div className="app">
        <Modal
          title="查看结果表"
          open={this.state.isOpenTable}
          onCancel={() => this.setState({ isOpenTable: false })}
          footer={null}
          width={'60%'}
        >
          <div className="table_toolbars">
            <div className="tables_tab">
              {this.state.nodeTablePins.map((item: number) => (
                <span
                  key={item}
                  className={
                    item === this.state.selectedPin
                      ? 'tables_tab tab_selected'
                      : 'tables_tab'
                  }
                  onClick={() => {
                    this.setState({ selectedPin: item });
                    this.fetchTableData(item);
                  }}
                >
                  表{item}
                </span>
              ))}
            </div>
            <div className="table_info">
              <span className="toolbars_label">总数</span>
              <span>{this.state.nodeTableData.length}</span>
              <span className="toolbars_label">列数</span>
              <span>{this.state.nodeTableCol.length}</span>
              <span
                className="save_tab"
                onClick={() => {
                  this.copyTableName();
                }}
              >
                复制表名
              </span>
              <span
                className="save_tab"
                onClick={() => {
                  this.setState({ saveModalOpen: true });
                }}
              >
                另存为
              </span>
            </div>
          </div>
          <Table
            columns={this.state.nodeTableCol}
            dataSource={this.state.nodeTableData}
            pagination={{ pageSize: 20 }}
            scroll={{ x: this.state.nodeTableCol * 120, y: 450 }}
          />
        </Modal>
        <Modal
          title="重命名"
          open={this.state.saveModalOpen}
          onOk={this.saveTableData}
          cancelText="取消"
          okText="确认"
          onCancel={() => {
            this.setState({ saveModalOpen: false });
          }}
        >
          <Input
            value={this.state.table_name}
            onChange={e => {
              this.setState({ table_name: e.target.value.trim() });
            }}
          />
        </Modal>
        <Modal
          title="查看执行日志"
          open={this.state.isOpenLog}
          onCancel={() => this.setState({ isOpenLog: false })}
          footer={null}
          width={'60%'}
          bodyStyle={{ padding: 0 }}
        >
          <pre className="log">{this.state.logData}</pre>
        </Modal>
        {/* 侧边栏 */}
        <div className="app-stencil" ref={this.refStencil} />
        {/* 容器 */}
        <div className="app-content">
          <div className="app-graph" ref={this.refContainer} />
          <ContextMenuView
            contextStatus={this.state.contextMenu}
            contextMenuNode={this.state.contextMenuNode}
            canBeginRun={this.state.canBeginRun}
            handleClose={this.handleContextMenuClose}
            handleRunBeginNode={this.handleRunBeginNode}
            handleRunEndNode={this.handleRunEndNode}
            handleRun={this.handleNodeRun}
            handleOpenTable={this.handleOpenTable}
            handleOpenLog={this.handleOpenLog}
          />
          {/* 节点信息 */}
          <Drawer
            title={this.state.selectedNodeData?.label || 'NodeTitle'}
            placement="right"
            closable={false}
            maskClosable={false}
            destroyOnClose={true}
            onClose={this.hideNodeInfo}
            open={this.state.nodeInfoVisible}
            getContainer={false}
            style={{ position: 'absolute' }}
          >
            {/* 数据源节点 */}
            {this.state.selectedNodeData?.type === 'datasource' && (
              <DatasourceNodeInfo
                nodeInfo={this.state.selectedNodeData}
                node={this.state.currentDblNode}
                close={this.hideNodeInfo}
                changeStatus={(node_id: any) => this.saveChangeStatus(node_id)}
              />
            )}
            {/* 脚本节点 */}
            {this.state.selectedNodeData?.type === 'script' && (
              <ScriptNodeInfo
                nodeInfo={this.state.selectedNodeData}
                graph={this.state.dagGraph}
                node={this.state.currentDblNode}
                scriptInputs={this.state.scriptInputNodes}
                dagId={this.state.dagId}
                close={this.hideNodeInfo}
                changeStatus={(node_id: any) => this.saveChangeStatus(node_id)}
              />
            )}
            {/* 输出源节点 */}
            {this.state.selectedNodeData?.type === 'outputsource' && (
              <OutputNodeInfo
                nodeInfo={this.state.selectedNodeData}
                node={this.state.currentDblNode}
                close={this.hideNodeInfo}
                changeStatus={(node_id: any) => this.saveChangeStatus(node_id)}
              />
            )}
          </Drawer>
          {/* 工具栏 */}
          <ToolBar
            className="app-tool"
            graph={this.state.dagGraph}
            saveGraph={this.props.saveGraph}
            dagId={this.state.dagId}
            context={this.props.context}
            runDag={this.handleRunDag}
          />
        </div>
      </div>
    );
  }
}

// 初始化节点数据
function initNodeData(graph: Graph, newNode: Node<Node.Properties>) {
  // 节点id
  newNode.data.id = newNode.id;
  newNode.data.nodeId = nanoid().replaceAll('-', 'a').replaceAll('_', 'b');
  newNode.data.nodeName = `node_${graph.getNodes().length + 1}`;
  switch (newNode.data.type) {
    case 'datasource':
      newNode.data.inputSource = undefined;
      newNode.data.dataTableId = undefined;
      newNode.data.dataTableName = undefined;
      newNode.data.tablePath = 'node';
      break;
    case 'script':
      newNode.data.paramText = undefined;
      newNode.data.scriptText = '';
      newNode.data.outputNumber = 1;
      newNode.data.inputNumber = 1;
      newNode.data.packageData = undefined;
      newNode.data.status = 'undone';
      break;
    case 'outputsource':
      newNode.data.dataTableId = undefined;
      newNode.data.outputSource = [];
      newNode.data.tablePath = 'node';
    default:
  }
}
