/* eslint-disable */
import React, { useState, useEffect, useRef, useContext } from 'react';
import classNames from 'classnames';
import moment from 'moment';
import Empty from '@wisdom-components/empty';
import LoadBox from '@wisdom-components/loadbox';
import { message, Modal, ConfigProvider } from 'antd';
import PropTypes from 'prop-types';
import HistoryView from '@wisdom-components/ec_historyview';
import * as go from '@wisdom-components/gojs';
import GuidedDraggingTool from './js/GuidedDraggingTool';
import TopRotatingTool from './js/RotatingTool';
import BarLink from './js/BarLink';
import WaterFlowControlView from './js/WaterFlowControlView';
import {
  getSketchPadList,
  getSketchPadContent,
  getPointAddress,
  getHistoryInfo,
  getStatisticsInfo,
} from './apis';
import {
  deepCopy,
  hexToRgba,
  textStyle,
  querySkipUrl,
  isJson,
  stationData,
  isNumber,
} from './js/utils';
import './index.less';

const goJS = go.GraphObject.make;
let online = false;
let imgUrl = null;
let historyInfoParams = [];
let twoID = '';

let myDiagram = null;
let editionArr = [];
// const guidAggre = {};
let bindData = [];
const stationList = [];

let historyData = []; // 历史数据
let timeData = []; // 历史数据时间列表
let speed = 0; // 历史数据播放当前进度值
let times = 2; // 历史数据播放速度
let play = false; // 历史数据是否播放
let historyParams = {};

const waterFlow = new WaterFlowControlView();

const ConfigurationView = (props) => {
  const { getPrefixCls } = useContext(ConfigProvider.ConfigContext);
  const prefixCls = getPrefixCls('ec-configuration-view');
  const [isHIModalVisible, setIsHIModalVisible] = useState(false); // 历史曲线模态框
  const [spinning, setSpinning] = useState(true); // 画板loading
  const [spinLoad, setSpinLoad] = useState(false);
  const [isEmpty, setIsEmpty] = useState(false); // 画板无数据状态
  const [description, setDescription] = useState(''); // 画板无数据描述

  twoID = `TDG${Date.now().toString(36)}`;

  const ConfigurationRef = useRef();
  const customBack = props.customBack ? props.customBack : () => {};
  const {
    devices = [],
    config,
    isZoom = false,
    flowShow = true,
    deviceName = [],
    dataType = '历史',
    statisticType = [],
  } = props;
  let devicesCode = [];
  const globalConfig = window.globalConfig || config;
  const siteCodeStr = globalConfig?.userInfo?.LocalSite || globalConfig?.userInfo?.site || '';
  let isClose = false;

  /** **********************************获取工艺图画板信息*********************** */
  const getConfiguraList = async () => {
    const url = globalConfig.mainserver ? globalConfig.mainserver : 'https://panda-water.cn/';
    imgUrl = online ? `${url}PandaMonitor/Monitor/` : `/PandaMonitor/Monitor/`;
    // 获取画板信息
    const drawInfo = await getSketchPadList({
      name: props.name,
      siteCode: siteCodeStr,
      version: '全部',
      _site: siteCodeStr,
    });
    if (drawInfo.code === 0) {
      const data = drawInfo.data ? (drawInfo.data.list ? drawInfo.data.list : []) : [];
      if (data.length > 0) {
        const num = data.length ? (data[0].num ? data[0].num * 1 : 0) : 0;
        const siteInfo = data.length ? (data[0].siteInfo ? JSON.parse(data[0].siteInfo) : {}) : {};
        for (let i = 0; i < num; i++) {
          const round = parseInt(i / 26);
          const remain = i % 26;
          if (round) {
            stationList.push(stationData[remain] + round);
          } else {
            stationList.push(stationData[remain]);
          }
        }
        const siteInfoArr = Object.getOwnPropertyNames(siteInfo);
        devicesCode = [];
        bindData = [];
        siteInfoArr.forEach((name, index) => {
          const deviceList =
            devices[index] || (siteInfo && siteInfo[name] ? siteInfo[name].Code || '' : '');
          bindData.push({
            code: deviceList,
            name,
            type: siteInfo && siteInfo[name] ? siteInfo[name].Type : '',
          });
          devicesCode.push(deviceList);
        });
        getDiagramJson(data[0], siteInfo);
      } else {
        setDescription('咦~未查询到工艺图画板信息哦~');
        setIsEmpty(true);
        setSpinning(false);
        return false;
      }
    } else {
      setDescription('咦~工艺图画板信息报错啦~');
      setIsEmpty(true);
      setSpinning(false);
      return message.error(drawInfo.msg);
    }

    // 获取点表信息
    const pointInfo = await getPointAddress({
      code: devicesCode.join(','),
      _site: siteCodeStr,
    });
    editionArr = deepCopy(pointInfo && pointInfo.data ? pointInfo.data : [], []);
  };

  /** *********************************节点展示逻辑****************************** */
  const showNodeMethod = (node, list) => {
    const realVal = list.Value * 1;
    let switchState;
    myDiagram.model.setDataProperty(node, 'realVal', realVal);
    if (node.switch === '是') {
      switchState = openValState(node.openVal, realVal) ? '开' : '关';
      myDiagram.model.setDataProperty(node, 'switchState', switchState);
    }
    if (!node.shType) return false;
    const patt = /[><=]/gi;
    let shRule = [];
    try {
      switch (node.category) {
        case 'svgCase': // 图片模型
          shRule = ruleOperation(node, realVal);
          if (node.shType === '模型切换') {
            myDiagram.model.setDataProperty(node, 'imgSrc', shRule ? shRule.attr : node.dtImgSrc);
          } else if (node.shType === '显隐展示') {
            myDiagram.model.setDataProperty(node, 'visible', shRule ? shRule.visible : true);
          }
          break;
        case 'nameCase': // 名称模型
          if (node.shType === '文本变化') {
            shRule = ruleOperation(node, realVal);
            myDiagram.model.setDataProperty(
              node,
              'fontStroke',
              shRule ? shRule.attr : node.dtFontStroke,
            );
            myDiagram.model.setDataProperty(node, 'text', shRule ? shRule.text : node.dtText);
          } else {
            shRule = ruleOperation(node, realVal);
            myDiagram.model.setDataProperty(
              node,
              'fillColor',
              hexToRgba(shRule ? shRule.attr : node.fill, node.opacity),
            );
          }
          break;
        case 'valCase': // 实时值模型
          const division = node.division || false;
          // 动画翻转
          if (node.effect)
            myDiagram.model.setDataProperty(node, 'flip', go.GraphObject.FlipHorizontal);
          if (node.shType === '值显示') {
            myDiagram.model.setDataProperty(
              node,
              'showVal',
              realVal < 0 ? 0 : division ? realVal.toLocaleString() : realVal,
            );
          } else {
            myDiagram.model.setDataProperty(
              node,
              'showVal',
              division ? realVal.toLocaleString() : realVal,
            );
          }
          // 动画还原
          myTimeout(() => {
            myDiagram.model.setDataProperty(node, 'flip', go.GraphObject.None);
          }, 100);
          if (node.stateName) return false;
          shRule = ruleOperation(node, realVal);
          myDiagram.model.setDataProperty(
            node,
            'fontStroke',
            shRule ? shRule.attr : node.fontStroke,
          );
          break;
        case 'waterCase': // 水池模型
          const height = node.height - node.strokeWidth * 2;
          let waterHight = (realVal * height) / node.poolHight;
          waterHight = waterHight >= height ? height : waterHight;
          myDiagram.model.setDataProperty(node, 'waterHight', waterHight);
          shRule = JSON.parse(node.shRule);
          shRule.forEach((item) => {
            const min = item.min && !isNaN(item.min * 1) ? item.min * 1 : 0;
            const max = item.max && !isNaN(item.max * 1) ? item.max * 1 : 0;
            if (realVal >= min && realVal < max)
              myDiagram.model.setDataProperty(
                node,
                'fillColor',
                hexToRgba(item.attr ? item.attr : node.fill, node.fillAlpha),
              );
          });
          break;
        case 'switchCase': // 开关模型
          shRule = ruleOperation(node, realVal);
          myDiagram.model.setDataProperty(
            node,
            'fillColor',
            hexToRgba(shRule ? shRule.attr : node.fill, node.opacity),
          );
          myDiagram.model.setDataProperty(node, 'switch', shRule ? '是' : '否');
          break;
        case 'rotateCase': // 状态模型
          shRule = ruleOperation(node, realVal);
          myDiagram.model.setDataProperty(node, 'imgSrc', shRule ? shRule.attr : node.dtImgSrc);
          break;
        case 'pointCase': // 点状态模型
          shRule = ruleOperation(node, realVal);
          myDiagram.model.setDataProperty(
            node,
            'fillColor',
            hexToRgba(shRule ? shRule.attr : node.fill, node.opacity),
          );
          break;
        case 'blenderCase': // 搅拌机模型
          break;
        case 'HBar': // 合管模型
          shRule = ruleOperation(node, realVal);
          myDiagram.model.setDataProperty(node, 'stroke', shRule ? shRule.attr : node.stroke);
          myDiagram.model.setDataProperty(
            node,
            'waterStroke',
            shRule ? shRule.text : node.waterStroke,
          );
          break;
        case 'speedCase': // 进度条模型
          shRule = ruleOperation(node, realVal);
          myDiagram.model.setDataProperty(
            node,
            'fillColor',
            hexToRgba(shRule ? shRule.attr : node.fill, node.opacity),
          );
          const { width } = node;
          let speedWidth = (realVal * width) / node.speedWidth;
          speedWidth = speedWidth >= width ? width : speedWidth;
          myDiagram.model.setDataProperty(node, 'lineWidth', speedWidth);
          break;
        case 'modelCase': // 模板块模型
          shRule = ruleOperation(node, realVal);
          if (node.shType === '层级展示') {
            myDiagram.model.setDataProperty(
              node,
              'zOrder',
              shRule ? shRule.text * 1 || node.dtzOrder : node.dtzOrder,
            );
          } else if (node.shType === '显隐展示') {
            myDiagram.model.setDataProperty(node, 'visible', shRule ? shRule.visible : true);
          }
          break;
        case 'ellipseCase': // 圆形模型
          shRule = ruleOperation(node, realVal);
          if (node.shType === '层级展示') {
            myDiagram.model.setDataProperty(
              node,
              'zOrder',
              shRule ? shRule.text * 1 || node.dtzOrder : node.dtzOrder,
            );
          } else if (node.shType === '显隐展示') {
            myDiagram.model.setDataProperty(node, 'visible', shRule ? shRule.visible : true);
          }
          break;
        case 'imgCase': // 图片模型
          shRule = ruleOperation(node, realVal);
          if (node.shType === '层级展示') {
            myDiagram.model.setDataProperty(
              node,
              'zOrder',
              shRule ? shRule.text * 1 || node.dtzOrder : node.dtzOrder,
            );
          } else if (node.shType === '显隐展示') {
            myDiagram.model.setDataProperty(node, 'visible', shRule ? shRule.visible : true);
          }
          break;
        case 'groupCase': // 分组模型
          shRule = ruleOperation(node, realVal);
          if (node.shType === '显隐展示') {
            myDiagram.model.setDataProperty(node, 'visible', shRule ? shRule.visible : true);
          }
          break;
        default:
          break;
      }
    } catch (err) {
      // console.log(err);
    }
  };

  /** *********************************节点状态展示逻辑****************************** */
  const stateMethod = (node, list) => {
    const realVal = list.Value * 1;
    if (!node.shType) return false;
    let shRule = [];
    try {
      switch (node.category) {
        case 'valCase': // 实时值模型
          // 颜色规则
          shRule = ruleOperation(node, realVal);
          myDiagram.model.setDataProperty(
            node,
            'fontStroke',
            shRule ? shRule.attr : node.fontStroke,
          );
          break;
        default:
          break;
      }
    } catch (err) {
      // console.log(err);
    }
  };

  /** ***********************************展示规则运算********************************* */
  const ruleOperation = (node, realVal) => {
    const patt = /[><=]/gi;
    const shRule = JSON.parse(node.shRule).find((rule) => {
      if (rule.val.toString().match(patt)) {
        const ruleStr = 'if(' + rule.val + '){ return true } else { return false }';
        try {
          return new Function('x', 'X', ruleStr)(realVal, realVal);
        } catch (err) {
          return false;
        }
      } else {
        return rule.val.toString().split(',').indexOf(realVal.toString()) > -1;
      }
    });
    return shRule;
  };

  /** ***********************************运行值规则运算********************************* */
  const openValState = (openVal, realVal) => {
    const patt = /[><=]/gi;
    if (openVal.toString().match(patt)) {
      const ruleStr = 'if(' + openVal + '){ return true } else { return false }';
      try {
        return new Function('x', 'X', ruleStr)(realVal, realVal);
      } catch (err) {
        return false;
      }
    } else {
      return openVal.toString().split(',').indexOf(realVal.toString()) > -1;
    }
  };

  /** **************************************合管****************************************** */
  const changLinkRouting = (e) => {
    const link = e.subject;
    if (link.toNode == null || link.fromNode == null) {
      return false;
    }
    if (link.fromNode.category === 'HBar' || link.toNode.category === 'HBar') {
      e.subject.routing = go.Link.Normal;
    }
  };

  /** ************************************创建连接点*********************************** */
  // 创建一个port,ID为name,spot控制其怎么被连接,放置于node的什么位置,output/input决定其哪里可以from和to
  const makePort = (name, spot, output, input) => {
    // the port is basically just a small transparent square
    return goJS(go.Shape, 'Circle', {
      fill: null, // not seen, by default; set to a translucent gray by showSmallPorts, defined below
      stroke: null,
      desiredSize: new go.Size(7, 7),
      alignment: spot, // align the port on the main Shape
      alignmentFocus: spot, // just inside the Shape
      portId: name, // declare this object to be a "port"
      fromSpot: spot,
      toSpot: spot, // declare where links may connect at this port
      fromLinkable: output,
      toLinkable: input, // declare whether the user may draw links to/from here
      cursor: 'pointer', // show a different cursor to indicate potential link point
    });
  };

  /** 动画设置*********************************************** */
  const animationSvg = () => {
    const diagram = myDiagram;
    const oldskips = diagram.skipsUndoManager;
    diagram.skipsUndoManager = true;
    diagram.nodes.map((node) => {
      const shape = node.findObject('animateSvg');
      if (!shape) return false;
      const gpRule = JSON.parse(node.data.gpRule || '[]').concat();
      const amTime = node.data.amTime || 0;
      if (!amTime) return false;
      gpRule.map((item) => {
        mySetInterval(() => {
          const { time = 0, fill = 100, scale = 1, angle = 0 } = item;
          myTimeout(() => {
            shape.opacity = (fill || 100) / 100;
            shape.scale = (scale || 1) * 1;
            shape.angle = (angle || 0) * 1;
          }, 0.01 * amTime * time);
        }, amTime * 1);
      });
    });
    diagram.skipsUndoManager = oldskips;
  };

  const myTimeout = (fn, delay) => {
    let timer;
    const stime = +new Date();
    const myLoop = () => {
      if (isClose) return timer && cancelAnimationFrame(timer);
      const etime = +new Date();
      if (stime + delay <= etime) {
        fn();
        return;
      }
      timer = requestAnimationFrame(myLoop);
    };
    timer = requestAnimationFrame(myLoop);
    return () => {
      cancelAnimationFrame(timer);
    };
  };

  const mySetInterval = (fn, interval) => {
    let timer;
    let stime = +new Date();
    let etime;
    let myLoop = () => {
      etime = +new Date();
      if (isClose) return timer && cancelAnimationFrame(timer);
      timer = requestAnimationFrame(myLoop);
      if (etime - stime >= interval) {
        stime = etime = +new Date();
        fn();
      }
    };
    return requestAnimationFrame(myLoop);
  };

  /** ******************************************水池效果****************************** */
  const waterSvg = () => {
    const diagram = myDiagram;
    // poolWater = setInterval(() => {
    mySetInterval(() => {
      const oldskips = diagram.skipsUndoManager;
      diagram.skipsUndoManager = true;
      diagram.nodes.each((node) => {
        const shape = node.findObject('waterSvg');
        if (!shape) return false;
        const range = (shape.range ? shape.range : 0) + 0.5;
        shape.range = range >= 5 ? 0 : range;
        shape.geometryString = `F M0 ${shape.range} L${shape.width} ${5 - shape.range} L${
          shape.width
        } ${shape.height} L0 ${shape.height}z`;
      });
      diagram.skipsUndoManager = oldskips;
    }, 100);
  };

  /** ***********************************水流效果********************************** */
  const loop = () => {
    const diagram = myDiagram;
    // tubeWater = setInterval(() => {
    mySetInterval(() => {
      const oldskips = diagram.skipsUndoManager;
      diagram.skipsUndoManager = true;
      diagram.links.each((link) => {
        const shape = link.findObject('PIPE');
        if (!shape) return false;
        if (link.data.isHavingDash) {
          link.zOrder = 1;
          shape.strokeWidth = link.data.defaultWidth || 3;
          const off = shape.strokeDashOffset - 3;
          shape.strokeDashOffset = off <= 0 ? 60 : off;
        } else {
          link.zOrder = 0;
          shape.strokeWidth = 0;
          shape.strokeDashOffset = 0;
        }
      });
      diagram.skipsUndoManager = oldskips;
    }, 60);
  };

  /** **************************************泵状态效果*************************** */
  const rotateSvg = () => {
    const diagram = myDiagram;
    // pumpType = setInterval(() => {
    mySetInterval(() => {
      const oldskips = diagram.skipsUndoManager;
      diagram.skipsUndoManager = true;
      diagram.nodes.each((node) => {
        const shape = node.findObject('rotateSvg');
        if (!shape) return false;
        const _node = node.data;
        if (_node.switchState !== '开' || _node.realVal === '--' || _node.switch !== '是')
          return false;
        const off = shape.angle + 60;
        shape.angle = off <= 360 ? off : 0;
      });
      diagram.skipsUndoManager = oldskips;
    }, 60);
  };

  /** *********************************搅拌机状态效果************************* */
  const blenderSvg = () => {
    const diagram = myDiagram;
    // blenderType = setInterval(() => {
    mySetInterval(() => {
      const oldskips = diagram.skipsUndoManager;
      diagram.skipsUndoManager = true;
      diagram.nodes.each((node) => {
        const shape = node.findObject('blenderSvg');
        if (!shape) return false;
        const _node = node.data;
        const srcStr = _node.dtImgSrc.split('/').pop();
        if (_node.switchState !== '开' || _node.realVal === '--' || _node.switch !== '是') {
          shape.source = require(`./images/组态/状态/${srcStr.replace(/[0-9]/gi, 1)}`);
          return false;
        }
        shape.flag = shape.flag || 1;
        const num = shape.source.match(/\d/)[0] * 1;
        let _num = 1;
        if (shape.flag === 1) {
          _num = num < 5 ? num + 1 : 4;
          if (num >= 5) shape.flag = 2;
        } else {
          _num = num > 1 ? num - 1 : 2;
          if (num <= 1) shape.flag = 1;
        }
        shape.source = require(`./images/组态/状态/${srcStr.replace(/[0-9]/gi, _num)}`);
      });
      diagram.skipsUndoManager = oldskips;
    }, 100);
  };

  /** *******************将myDiagram.model中的信息展示在画板上*********************** */
  const loadDiagramProperties = (e) => {
    const pos = myDiagram.model.modelData.position;
    if (pos) myDiagram.initialPosition = go.Point.parse(pos);
  };

  /** *******************绑定角色可见*************************** */
  const roleVisibleBinding = () => {
    return new go.Binding('visible', '', function (data) {
      if (!data.roles) return true;
      const roles = data.roles.split(',');
      const curRoleMap = {};
      globalConfig &&
        globalConfig.userInfo &&
        globalConfig.userInfo.roles &&
        globalConfig.userInfo.roles.forEach(function (role) {
          curRoleMap[role.OID] = role;
        });
      const samerole = roles.filter(function (roleID) {
        return !!curRoleMap[roleID];
      });
      return samerole.length > 0;
    });
  };

  /** ***********************************节点样式********************************** */
  const nodeStyle = () => {
    return [
      new go.Binding('location', 'loc', go.Point.parse).makeTwoWay(go.Point.stringify),
      {
        locationSpot: go.Spot.Center,
      },
    ];
  };

  /** ************************************联网判断******************************* */
  const onlineMethod = (pathImg, url) => {
    const ImgObj = new Image();
    ImgObj.src = pathImg;
    ImgObj.onload = () => {
      online = ImgObj.fileSize > 0 || (ImgObj.width > 0 && ImgObj.height > 0);
      getConfiguraList();
    };
    ImgObj.onerror = () => {
      online = false;
      getConfiguraList();
    };
  };

  useEffect(() => {
    if (!props.name) {
      setDescription('咦~工艺图配置信息不全哦~');
      setIsEmpty(true);
      setSpinning(false);
      return false;
    }
    const url = globalConfig.mainserver ? globalConfig.mainserver : 'https://panda-water.cn/';
    onlineMethod(`${url}civweb4/assets/images/bootPage/熊猫图标.png`, url);
    return () => {
      isClose = true;
      if (myDiagram) {
        myDiagram.removeDiagramListener('ViewportBoundsChanged', viewportBoundsChang);
        myDiagram.div = null;
        myDiagram = null;
      }
    };
  }, []);

  useEffect(() => {
    play = props.play || false;
    times = props.times || 2;
  }, [props.play, props.times]);

  useEffect(() => {
    speed = props.speed || 0;
  }, [props.speed]);

  useEffect(() => {
    historyParams = props.params;
    if (dataType === '历史') getHistoryData(false);
    if (dataType === '统计') getStatisticsData(false);
  }, [props.params]);

  // 图片模型翻转
  const pictureFlipMethod = (type) => {
    let flipType = '';
    switch (type) {
      case 'none':
        flipType = go.GraphObject.None;
        break;
      case 'horizontal':
        flipType = go.GraphObject.FlipHorizontal;
        break;
      case 'vertical':
        flipType = go.GraphObject.FlipVertical;
        break;
      case 'both':
        flipType = go.GraphObject.FlipBoth;
        break;
      default:
        flipType = go.GraphObject.None;
        break;
    }
    return flipType;
  };

  /** ************************************获取画板JSON******************************* */
  const getDiagramJson = async (list, siteInfo) => {
    const response = await getSketchPadContent({
      dimension: list.dimension,
      siteCode: list.siteCode,
      fileName: list.deployURL.split('\\').pop(),
      _site: siteCodeStr,
    });
    if (response.code === 0) {
      if (isClose) return false;
      const fromJson = response.data
        ? response.data
        : {
            linkFromPortIdProperty: 'fromPort',
            linkToPortIdProperty: 'toPort',
            nodeDataArray: [],
            linkDataArray: [],
          };
      // bindData = [];
      // devices.forEach((item, index) => {
      //   const name = `设备${stationList[index]}`;
      //   bindData.push({
      //     code: item,
      //     name,
      //     type: siteInfo && siteInfo[name] ? siteInfo[name].Type : '',
      //   });
      // });
      diagramRender(typeof fromJson === 'string' ? fromJson : JSON.stringify(fromJson), list);
      if (dataType === '历史') getHistoryData(true);
      if (dataType === '统计') getStatisticsData(true);
    } else {
      message.error(response.msg);
    }
  };

  /** ************************************历史数据获取******************************* */
  const getHistoryData = async (flag) => {
    try {
      if (!myDiagram) return false;
      setSpinLoad(true);
      speed = 0;
      const json = JSON.parse(myDiagram.model.toJson());
      const jsonCopy = JSON.parse(JSON.stringify(json));
      const acrossTables = [];
      bindData.map((list) => {
        let sensors = [];
        jsonCopy.nodeDataArray.forEach((item) => {
          item.shName && item.stationName === list.name && sensors.push(item.shName);
        });
        jsonCopy.linkDataArray.forEach((item) => {
          item.shName && item.stationName === list.name && sensors.push(item.shName);
        });
        if (sensors.length && list.code)
          acrossTables.push({
            deviceType: list.type,
            sensors: Array.from(new Set(sensors)).join(','),
            deviceCode: list.code,
          });
      });
      const params = {
        isDilute: false,
        zoom: '30',
        unit: 'h',
        ignoreOutliers: false,
        isVertical: false, // 是否查询竖表
        dateFrom: moment(new Date()).format('yyyy-MM-DD 00:00:00'),
        dateTo: moment(new Date()).format('yyyy-MM-DD 23:59:59'),
        ...historyParams,
        acrossTables,
      };
      const results = await getHistoryInfo(params);
      historyData = results?.data || [];
      let timeArr = [];
      historyData.forEach((item) => {
        const timeList = item.dataModel.map((list) => {
          return moment(list.pt).format('yyyy-MM-DD HH:mm:ss');
        });
        timeArr = timeArr.concat(timeList);
      });
      const _timeData = dataUnique(timeArr);
      timeData = _timeData.sort(function (a, b) {
        return new Date(a).getTime() - new Date(b).getTime();
      });
      chartHistoryDataRender(historyData);
      if (flag) historyTimeRender();
      setSpinLoad(false);
    } catch (err) {
      setSpinLoad(false);
    }
  };

  /** ************************************统计数据获取******************************* */
  const getStatisticsData = async (flag) => {
    try {
      if (!myDiagram) return false;
      setSpinLoad(true);
      speed = 0;
      const json = JSON.parse(myDiagram.model.toJson());
      const jsonCopy = JSON.parse(JSON.stringify(json));
      const acrossTables = [];
      bindData.map((list) => {
        let sensors = [];
        jsonCopy.nodeDataArray.forEach((item) => {
          item.shName && item.stationName === list.name && sensors.push(item.shName);
        });
        jsonCopy.linkDataArray.forEach((item) => {
          item.shName && item.stationName === list.name && sensors.push(item.shName);
        });
        if (sensors.length && list.code)
          acrossTables.push({
            accountName: list.type,
            nameTypeList: sensors.map((item) => {
              const listType = statisticType.find((arr) => {
                return arr.name === item;
              });
              return {
                name: item,
                type: listType?.type || 'Sub',
              };
            }),
            dateFrom: historyParams.dateFrom || moment(new Date()).format('yyyy-MM-DD 00:00:00'),
            dateTo: historyParams.dateTo || moment(new Date()).format('yyyy-MM-DD 23:59:59'),
            deviceCode: list.code,
          });
      });
      const params = {
        pageIndex: 1,
        pageSize: 999,
        q_DeviceReports: acrossTables,
        dateType: 'day',
        ...historyParams,
      };
      const results = await getStatisticsInfo(params);
      const res = results?.data?.list || [];
      let timeArr = [];
      let statisticsData = [];
      res.forEach((item) => {
        const listData = item.dNameDataList.map((list) => {
          return {
            code: item.code,
            eName: item.eName,
            eShortName: item.eShortName,
            ...list,
          };
        });
        statisticsData = statisticsData.concat(listData);
      });
      historyData = [].concat(statisticsData);
      historyData.forEach((item) => {
        const timeList = item.nameDate.map((list) => {
          return moment(list.time).format('yyyy-MM-DD HH:mm:ss');
        });
        timeArr = timeArr.concat(timeList);
      });
      const _timeData = dataUnique(timeArr);
      timeData = _timeData.sort(function (a, b) {
        return new Date(a).getTime() - new Date(b).getTime();
      });
      chartStatisticsDataRender(historyData);
      if (flag) historyTimeRender();
      setSpinLoad(false);
    } catch (err) {
      setSpinLoad(false);
    }
  };

  /** ****************************************数据去重******************************* */
  const dataUnique = (arr) => {
    return Array.from(new Set(arr));
  };

  /** ***********************************图表历史数据处理**************************** */
  const chartHistoryDataRender = (mqttData) => {
    const time = timeData[speed];
    loopLoadMethod(time);
    const json = JSON.parse(myDiagram.model.toJson());
    const jsonCopy = JSON.parse(JSON.stringify(json));
    const oldJson = deepCopy(jsonCopy);
    try {
      jsonCopy.linkDataArray.forEach((item) => {
        if (!item.shName || item.shType !== '线条展示') return false;
        mqttData.forEach((list) => {
          const bindList = bindData.find((arr) => {
            return arr.code === list.stationCode;
          });
          const pvList = list.dataModel.find((arr) => {
            return moment(arr.pt).format('yyyy-MM-DD HH:mm:ss') === time;
          });
          if (!bindList || item.stationName !== bindList.name) return false;
          if (
            !pvList ||
            pvList.PV === null ||
            list.sensorName !== item.shName ||
            item.realVal === pvList.pv
          )
            return false;
          item.realVal = pvList.pv * 1;
          const shRule = ruleOperation(item, item.realVal);
          if (shRule) {
            myDiagram.model.setDataProperty(item, 'stroke', shRule.attr);
            myDiagram.model.setDataProperty(item, 'waterStroke', shRule.text);
          } else {
            myDiagram.model.setDataProperty(item, 'stroke', item.stroke);
            myDiagram.model.setDataProperty(item, 'waterStroke', item.waterStroke);
          }
        });
      });
    } catch (e) {
      // 水流展示
    }

    try {
      jsonCopy.nodeDataArray.forEach((item) => {
        if (!(item.shName || item.figure === 'updateTime')) return false;
        const node = myDiagram.model.findNodeDataForKey(item.key);
        mqttData.forEach((list) => {
          if (node.figure === 'updateTime') {
            myDiagram.model.setDataProperty(
              node,
              'text',
              moment(list.pt).format('yyyy-MM-DD HH:mm:ss'),
            );
            return false;
          }
          const bindList = bindData.find((arr) => {
            return arr.code === list.stationCode;
          });
          const pvList = list.dataModel.find((arr) => {
            return moment(arr.pt).format('yyyy-MM-DD HH:mm:ss') === time;
          });
          if (!bindList || item.stationName !== bindList.name) return false;
          if (
            (!pvList ||
              pvList.pv === null ||
              list.sensorName !== item.shName ||
              item.realVal === pvList.pv) &&
            list.sensorName !== item.stateName
          )
            return false;
          pvList.Value = pvList.pv;
          if (list.sensorName === item.shName) showNodeMethod(node, pvList);
          if (list.sensorName === item.stateName) stateMethod(node, pvList);
        });
      });
    } catch (e) {
      // 节点展示
    }

    try {
      const jsonModel = waterFlow.waterFlowControlByDiagramJson(oldJson, myDiagram);
      if (!jsonModel) return false;
      const oldLink = myDiagram.model.linkDataArray;
      const dataLink = [];
      jsonModel.linkDataArray.forEach((item, index) => {
        const list = Object.assign({}, oldLink[index]);
        list.isHavingDash = item.isHavingDash;
        dataLink.push(list);
      });
      jsonModel.nodeDataArray.forEach((item) => {
        if (item.category === 'HBar') {
          const node = myDiagram.model.findNodeDataForKey(item.key);
          const waterStroke = item.typeDash ? 'transparent' : item.hBarClolor;
          if (item.typeDash != node.typeDash) {
            myDiagram.model.setDataProperty(node, 'waterStroke', waterStroke);
            myDiagram.model.setDataProperty(node, 'typeDash', item.typeDash);
          }
        }
      });
      dataLink.forEach((item) => {
        const node = myDiagram.findLinkForData(item);
        if (item.isHavingDash != node.data.isHavingDash)
          myDiagram.model.setDataProperty(node.data, 'isHavingDash', item.isHavingDash);
      });
    } catch (e) {
      // 水流展示
    }
  };

  /** ***********************************图表历史数据处理**************************** */
  const chartStatisticsDataRender = (mqttData) => {
    const time = timeData[speed];
    loopLoadMethod(time);
    const json = JSON.parse(myDiagram.model.toJson());
    const jsonCopy = JSON.parse(JSON.stringify(json));
    const oldJson = deepCopy(jsonCopy);
    try {
      jsonCopy.linkDataArray.forEach((item) => {
        if (!item.shName || item.shType !== '线条展示') return false;
        mqttData.forEach((list) => {
          const bindList = bindData.find((arr) => {
            return arr.code === list.code;
          });
          const pvList = list.nameDate.find((arr) => {
            return moment(arr.time).format('yyyy-MM-DD HH:mm:ss') === time;
          });
          if (!bindList || item.stationName !== bindList.name) return false;
          if (
            !pvList ||
            pvList.value === null ||
            list.dName !== item.shName ||
            item.realVal === pvList.value
          )
            return false;
          item.realVal = pvList.value * 1;
          const shRule = ruleOperation(item, item.realVal);
          if (shRule) {
            myDiagram.model.setDataProperty(item, 'stroke', shRule.attr);
            myDiagram.model.setDataProperty(item, 'waterStroke', shRule.text);
          } else {
            myDiagram.model.setDataProperty(item, 'stroke', item.stroke);
            myDiagram.model.setDataProperty(item, 'waterStroke', item.waterStroke);
          }
        });
      });
    } catch (e) {
      // 水流展示
    }

    try {
      jsonCopy.nodeDataArray.forEach((item) => {
        if (!(item.shName || item.figure === 'updateTime')) return false;
        const node = myDiagram.model.findNodeDataForKey(item.key);
        mqttData.forEach((list) => {
          if (node.figure === 'updateTime') {
            myDiagram.model.setDataProperty(
              node,
              'text',
              moment(list.value).format('yyyy-MM-DD HH:mm:ss'),
            );
            return false;
          }
          const bindList = bindData.find((arr) => {
            return arr.code === list.code;
          });
          const pvList = list.nameDate.find((arr) => {
            return moment(arr.time).format('yyyy-MM-DD HH:mm:ss') === time;
          });
          if (!bindList || item.stationName !== bindList.name) return false;
          if (
            (!pvList ||
              pvList.value === null ||
              list.dName !== item.shName ||
              item.realVal === pvList.value) &&
            list.dName !== item.stateName
          )
            return false;
          pvList.Value = pvList.value;
          if (list.dName === item.shName) showNodeMethod(node, pvList);
          if (list.dName === item.stateName) stateMethod(node, pvList);
        });
      });
    } catch (e) {
      // 节点展示
    }

    try {
      const jsonModel = waterFlow.waterFlowControlByDiagramJson(oldJson, myDiagram);
      if (!jsonModel) return false;
      const oldLink = myDiagram.model.linkDataArray;
      const dataLink = [];
      jsonModel.linkDataArray.forEach((item, index) => {
        const list = Object.assign({}, oldLink[index]);
        list.isHavingDash = item.isHavingDash;
        dataLink.push(list);
      });
      jsonModel.nodeDataArray.forEach((item) => {
        if (item.category === 'HBar') {
          const node = myDiagram.model.findNodeDataForKey(item.key);
          const waterStroke = item.typeDash ? 'transparent' : item.hBarClolor;
          if (item.typeDash != node.typeDash) {
            myDiagram.model.setDataProperty(node, 'waterStroke', waterStroke);
            myDiagram.model.setDataProperty(node, 'typeDash', item.typeDash);
          }
        }
      });
      dataLink.forEach((item) => {
        const node = myDiagram.findLinkForData(item);
        if (item.isHavingDash != node.data.isHavingDash)
          myDiagram.model.setDataProperty(node.data, 'isHavingDash', item.isHavingDash);
      });
    } catch (e) {
      // 水流展示
    }
  };

  /** **********************历史数据循环**************************** */
  const historyTimeRender = () => {
    historyInterval(() => {
      if (!play || !historyData.length || !timeData.length) return false;
      speed = speed + 1;
      if (speed >= timeData.length) {
        play = false;
        loopLoadMethod(timeData.slice(-1)[0]);
        return false;
      }
      if (dataType === '历史') chartHistoryDataRender(historyData);
      if (dataType === '统计') chartStatisticsDataRender(historyData);
    });
  };

  /** **********************历史数据循环函数**************************** */
  const historyInterval = (fn) => {
    let timer;
    let stime = +new Date();
    let etime;
    let myLoop = () => {
      etime = +new Date();
      if (isClose) return timer && cancelAnimationFrame(timer);
      if (etime - stime >= times * 1000) {
        stime = etime = +new Date();
        fn();
      }
      timer = requestAnimationFrame(myLoop);
    };
    return requestAnimationFrame(myLoop);
  };

  // 数据回调
  const loopLoadMethod = (time) => {
    props.callback && props.callback(speed, timeData.length, play, time);
  };

  /** **************************************跳转方法****************************************** */
  const menuJumpMethod = (data) => {
    const opRule = JSON.parse(data.opRule);
    const widget = opRule && opRule.widget ? opRule.widget : '';
    const params =
      opRule && opRule.params ? (isJson(opRule.params) && JSON.parse(opRule.params)) || {} : {};
    const list = querySkipUrl(globalConfig?.widgets || [], widget);
    if (!list || !widget) return false;
    window.history.pushState(params, null, `/civbase/${list.product || 'civweb4'}/${list.url}`);
  };

  /** **************************************历史模态渲染****************************************** */
  const historyModalRender = (data, list) => {
    historyInfoParams = [
      {
        deviceCode: list.code,
        sensors: data.shName,
        deviceType: list.type,
      },
    ];
    setIsHIModalVisible(true);
  };

  // 监听画布zoom变化
  const viewportBoundsChang = () => {
    myDiagram.contentAlignment = go.Spot.Default;
    myDiagram.contentAlignment = go.Spot.Center;
  };

  /** **********************************画布渲染************************************ */
  const diagramRender = (jsonStr, chartInfo) => {
    myDiagram = goJS(
      go.Diagram,
      twoID, // must name or refer to the DIV HTML element
      {
        initialContentAlignment: go.Spot.Center,
        contentAlignment: go.Spot.Center,
        allowDrop: false, // must be true to accept drops from the Palette 右边的面板允许防止图形
        draggingTool: new GuidedDraggingTool(),
        allowZoom: isZoom ? true : false,
        allowSelect: false,
        'draggingTool.dragsLink': true,
        isReadOnly: true,
        autoScale: isZoom ? go.Diagram.None : go.Diagram.Uniform, // 自适应,默认不自适应
        initialAutoScale: go.Diagram.Uniform, // 自适应,默认不自适应
        'draggingTool.isGridSnapEnabled': true,
        'linkingTool.isUnconnectedLinkValid': true,
        'animationManager.duration': 100,
        allowHorizontalScroll: isZoom ? true : false,
        // padding: 20,
        allowVerticalScroll: isZoom ? true : false,
        'linkingTool.portGravity': 20,
        'relinkingTool.isUnconnectedLinkValid': true,
        'relinkingTool.portGravity': 20,
        'draggingTool.horizontalGuidelineColor': 'blue',
        'draggingTool.verticalGuidelineColor': 'blue',
        'draggingTool.centerGuidelineColor': 'green',
        rotatingTool: goJS(TopRotatingTool), // defined below
        'rotatingTool.snapAngleMultiple': 15,
        'rotatingTool.snapAngleEpsilon': 15,
        'undoManager.isEnabled': true,
        LinkDrawn: changLinkRouting,
        // LinkReshaped: (e) => {
        //   e.subject.routing = go.Link.Orthogonal;
        // },
        'linkingTool.direction': go.LinkingTool.ForwardsOnly,
      },
    );

    /** **********************************分组模型************************************* */
    myDiagram.groupTemplate =
      ('groupCase',
      goJS(
        go.Group,
        'Auto',
        { ungroupable: true, zOrder: 1, visible: true },
        {
          // 设置其可选择
          selectable: false,
          layerName: 'Background',
        },
        new go.Binding('visible', 'visible').makeTwoWay(),
        goJS(
          go.Shape,
          'RoundedRectangle', // surrounds everything
          {
            parameter1: 10,
            fill: 'transparent',
            strokeWidth: 0,
            stroke: 'transparent',
          },
        ),
        goJS(
          go.Panel,
          'Auto', // position header above the subgraph
          goJS(
            go.Placeholder, // represents area for all member parts
            { background: 'transparent' },
          ),
        ),
      ));

    // 自定义矩形
    go.Shape.defineFigureGenerator('RoundedRectanglePlus', (shape, w, h) => {
      // this figure takes one parameter, the size of the corner
      let p1 = Infinity; // default corner size
      if (shape !== null) {
        const param1 = shape.parameter1;
        if (!isNaN(param1) && param1 >= 0) p1 = param1; // can't be negative or NaN
      }
      p1 = Math.min(p1, w / 2);
      p1 = Math.min(p1, h / 2); // limit by whole height or by half height?
      const geo = new go.Geometry();
      // a single figure consisting of straight lines and quarter-circle arcs
      geo.add(
        new go.PathFigure(0, p1)
          .add(new go.PathSegment(go.PathSegment.Arc, 180, 90, p1, p1, p1, p1))
          .add(new go.PathSegment(go.PathSegment.Line, w - p1, 0))
          .add(new go.PathSegment(go.PathSegment.Arc, 270, 90, w - p1, p1, p1, p1))
          .add(new go.PathSegment(go.PathSegment.Arc, 0, 90, w - p1, h - p1, p1, p1))
          .add(new go.PathSegment(go.PathSegment.Arc, 90, 90, p1, h - p1, p1, p1).close()),
      );
      // don't intersect with two top corners when used in an "Auto" Panel
      geo.spot1 = new go.Spot(0, 0, 0.3 * p1, 0.3 * p1);
      geo.spot2 = new go.Spot(1, 1, -0.3 * p1, 0);
      return geo;
    });

    /** *********************************节点模板************************************* */
    // 背景模板定义
    myDiagram.nodeTemplateMap.add(
      'bgCase',
      goJS(
        go.Node,
        'Spot',
        { locationSpot: go.Spot.Center, zOrder: 0 },
        // new go.Binding('zOrder', 'zOrder').makeTwoWay(),
        new go.Binding('location', 'loc', go.Point.parse).makeTwoWay(go.Point.stringify),
        new go.Binding('visible', 'visible').makeTwoWay(),
        new go.Binding('angle').makeTwoWay(),
        {
          // 设置其可选择
          selectable: false,
          layerName: 'Background',
        },
        // the main object is a Panel that surrounds a TextBlock with a Shape ~图形:Panel包围着TextBlock
        goJS(
          go.Panel,
          'Auto',
          {
            name: 'PANEL',
          },
          new go.Binding('desiredSize', 'size', go.Size.parse).makeTwoWay(go.Size.stringify),
          goJS(
            go.Picture,
            { width: 56, height: 56, scale: 1, source: '', background: '#2e3343' },
            new go.Binding('source', 'imgSrc', function (v) {
              const own = myDiagram ? myDiagram.model.findNodeDataForKey('bgCase') : {};
              props.bgMethod && props.bgMethod(own);
              return v
                ? `/PandaMonitor/Monitor/SketchPad/PreviewResource?name=${v}&_site=${siteCodeStr}`
                : '';
            }).makeTwoWay(),
            new go.Binding('scale', 'scale').makeTwoWay(),
            new go.Binding('width', 'width').makeTwoWay(),
            new go.Binding('height', 'height').makeTwoWay(),
            new go.Binding('background', 'background').makeTwoWay(),
          ),
        ),
      ),
    );

    // 表格节点定义
    myDiagram.nodeTemplateMap.add(
      'tableCase',
      goJS(
        go.Node,
        'Auto',
        { locationSpot: go.Spot.Center, zOrder: 1 },
        new go.Binding('zOrder', 'zOrder').makeTwoWay(),
        new go.Binding('location', 'loc', go.Point.parse).makeTwoWay(go.Point.stringify),
        new go.Binding('angle').makeTwoWay(),
        goJS(
          go.Shape,
          {
            fill: 'white',
            strokeWidth: 1,
            stroke: '#808080',
          },
          new go.Binding('stroke').makeTwoWay(),
          new go.Binding('strokeWidth').makeTwoWay(),
        ),
        goJS(
          go.Panel,
          'Table',
          {
            padding: 0,
            defaultRowSeparatorStroke: '#808080',
            defaultColumnSeparatorStroke: '#808080',
            defaultRowSeparatorStrokeWidth: 1,
            defaultColumnSeparatorStrokeWidth: 1,
            background: '#ffffff',
          },
          new go.Binding('background', 'fillColor').makeTwoWay(),
          new go.Binding('defaultRowSeparatorStroke', 'stroke').makeTwoWay(),
          new go.Binding('defaultRowSeparatorStrokeWidth', 'strokeWidth').makeTwoWay(),
          new go.Binding('defaultColumnSeparatorStroke', 'stroke').makeTwoWay(),
          new go.Binding('defaultColumnSeparatorStrokeWidth', 'strokeWidth').makeTwoWay(),
          new go.Binding('itemArray', 'content').makeTwoWay(),
          {
            // 表内容
            defaultAlignment: go.Spot.Left,
            itemTemplate: goJS(
              go.Panel,
              'TableRow',
              new go.Binding('itemArray', 'columns').makeTwoWay(),
              {
                itemTemplate: goJS(
                  go.Panel, // each of which as "attr" and "text" properties
                  'Spot',
                  { background: 'transparent', alignment: go.Spot.Center },
                  new go.Binding('column').makeTwoWay(),
                  new go.Binding('columnSpan', 'cSpan').makeTwoWay(),
                  new go.Binding('rowSpan', 'rSpan').makeTwoWay(),
                  new go.Binding('padding', 'padding', function (v) {
                    if (v && isNumber(v)) return v;
                    const padding = v ? v.split(',') : null;
                    return padding
                      ? new go.Margin(
                          padding?.[0] * 1 || 0,
                          padding?.[1] * 1 || 0,
                          padding?.[2] * 1 || 0,
                          padding?.[3] * 1 || 0,
                        )
                      : 0;
                  }).makeTwoWay(),
                  goJS(
                    go.Shape,
                    'RoundedRectanglePlus',
                    {
                      name: 'SHAPE',
                      fill: 'transparent',
                      stroke: '#ffffff',
                      strokeWidth: 0,
                      parameter1: 0,
                    },
                    new go.Binding('fill', 'background').makeTwoWay(),
                    new go.Binding('parameter1', 'radius').makeTwoWay(),
                    new go.Binding('stroke', 'bdColor').makeTwoWay(),
                    new go.Binding('strokeWidth', 'bdWidth').makeTwoWay(),
                    new go.Binding('width').makeTwoWay(),
                    new go.Binding('height').makeTwoWay(),
                  ),
                  goJS(
                    go.TextBlock,
                    textStyle(),
                    { editable: true },
                    {
                      // margin: new go.Margin(2, 10, 10, 2),
                      wrap: go.TextBlock.WrapFit,
                      textAlign: 'center',
                      font: 'bold 12px Helvetica, Arial, sans-serif',
                      stroke: '#454545',
                    },
                    new go.Binding('text').makeTwoWay(),
                    new go.Binding('font', 'style').makeTwoWay(),
                    new go.Binding('stroke', 'color').makeTwoWay(),
                    new go.Binding('textAlign', 'align').makeTwoWay(),
                    new go.Binding('maxSize', 'width', function (v) {
                      try {
                        return new go.Size(v - 20, NaN);
                      } catch (err) {
                        return new go.Size(NaN, NaN);
                      }
                    }).makeTwoWay(),
                    new go.Binding('minSize', 'width', function (v) {
                      try {
                        return new go.Size(v - 20, NaN);
                      } catch (err) {
                        return new go.Size(NaN, NaN);
                      }
                    }).makeTwoWay(),
                  ),
                ),
              },
            ),
          },
        ),
      ),
    );

    // img节点定义
    myDiagram.nodeTemplateMap.add(
      'imgCase',
      goJS(
        go.Node,
        'Spot',
        { locationSpot: go.Spot.Center, zOrder: 1, cursor: 'default' },
        new go.Binding('zOrder', 'zOrder').makeTwoWay(),
        new go.Binding('cursor', 'cursor').makeTwoWay(),
        new go.Binding('location', 'loc', go.Point.parse).makeTwoWay(go.Point.stringify),
        new go.Binding('angle').makeTwoWay(),
        roleVisibleBinding(), // 绑定角色可见

        // the main object is a Panel that surrounds a TextBlock with a Shape ~图形:Panel包围着TextBlock
        goJS(
          go.Panel,
          'Auto',
          {
            name: 'PANEL',
          },
          new go.Binding('desiredSize', 'size', go.Size.parse).makeTwoWay(go.Size.stringify),
          new go.Binding('visible', 'visible').makeTwoWay(),
          goJS(
            go.Picture,
            {
              name: 'animateSvg',
              width: 56,
              height: 56,
              column: 0,
              scale: 1,
              source: require('./images/组态/默认.png'),
              flip: go.GraphObject.None,
            },
            new go.Binding('source', 'imgSrc', function (v) {
              return !v
                ? require('./images/组态/默认.png')
                : `/PandaMonitor/Monitor/SketchPad/PreviewResource?name=${v}&_site=${siteCodeStr}`;
            }).makeTwoWay(),
            new go.Binding('flip', 'flip', pictureFlipMethod),
            new go.Binding('scale', 'scale').makeTwoWay(),
            new go.Binding('width', 'width').makeTwoWay(),
            new go.Binding('height', 'height').makeTwoWay(),
          ),
        ),
        {
          click(e, node) {
            const { data } = node;
            const list = bindData.find((item) => {
              return item.name === data.stationName;
            });
            if (!list) return false;
            // 画板跳转
            switch (data.opType) {
              case '画板跳转': // 图片模型
                break;
              case '功能跳转': // 功能模型
                menuJumpMethod(data);
                break;
              case '自定义交互': // 自定义交互
                customBack(data);
                break;
              default:
                break;
            }
          },
        },
      ),
    );

    // svg节点定义
    myDiagram.nodeTemplateMap.add(
      'svgCase',
      goJS(
        go.Node,
        'Spot',
        { locationSpot: go.Spot.Center, zOrder: 1, cursor: 'default' },
        new go.Binding('zOrder', 'zOrder').makeTwoWay(),
        new go.Binding('cursor', 'cursor').makeTwoWay(),
        new go.Binding('location', 'loc', go.Point.parse).makeTwoWay(go.Point.stringify),
        new go.Binding('angle').makeTwoWay(),
        roleVisibleBinding(), // 绑定角色可见
        goJS(
          go.Panel,
          'Auto',
          {
            name: 'PANEL',
          },
          new go.Binding('desiredSize', 'size', go.Size.parse).makeTwoWay(go.Size.stringify),
          new go.Binding('visible', 'visible').makeTwoWay(),
          goJS(
            go.Picture,
            {
              name: 'animateSvg',
              width: 56,
              height: 56,
              column: 0,
              scale: 1,
              source: '',
              flip: go.GraphObject.None,
            },
            new go.Binding('source', 'imgSrc', (v) => {
              return `${imgUrl}Model/Preview/${encodeURIComponent(v)}`;
            }),
            new go.Binding('scale', 'scale').makeTwoWay(),
            // new go.Binding('width', 'width').makeTwoWay(),
            // new go.Binding('height', 'height').makeTwoWay(),
            new go.Binding('width', 'width', function (v) {
              return (v * 1).toFixed(1) * 1;
            }).makeTwoWay(),
            new go.Binding('height', 'height', function (v) {
              return (v * 1).toFixed(1) * 1;
            }).makeTwoWay(),
            new go.Binding('flip', 'flip', pictureFlipMethod),
          ),
        ),
        {
          click(e, node) {
            const { data } = node;
            // 画板跳转
            switch (data.opType) {
              case '画板跳转': // 图片模型
                break;
              case '功能跳转': // 功能模型
                menuJumpMethod(data);
                break;
              case '自定义交互': // 自定义交互
                customBack(data);
                break;
              default:
                break;
            }
          },
        },
      ),
    );

    // 模板块定义
    myDiagram.nodeTemplateMap.add(
      'modelCase',
      goJS(
        go.Node,
        'Auto',
        nodeStyle(),
        'Spot',
        { locationSpot: go.Spot.Center, zOrder: 1 },
        new go.Binding('zOrder', 'zOrder').makeTwoWay(),
        new go.Binding('location', 'loc', go.Point.parse).makeTwoWay(go.Point.stringify),
        {
          // 设置其可改变大小
          resizeObjectName: 'SHAPE',
        },
        new go.Binding('angle').makeTwoWay(),
        roleVisibleBinding(), // 绑定角色可见
        goJS(
          go.Shape,
          'RoundedRectanglePlus',
          { name: 'SHAPE', fill: 'rgba(128,128,128,0.2)', stroke: 'gray', parameter1: 0 },
          new go.Binding('visible', 'visible').makeTwoWay(),
          new go.Binding('parameter1', 'radius').makeTwoWay(),
          new go.Binding('fill', 'fillColor').makeTwoWay(),
          new go.Binding('stroke').makeTwoWay(),
          new go.Binding('strokeWidth').makeTwoWay(),
          new go.Binding('desiredSize', 'size', go.Size.parse).makeTwoWay(go.Size.stringify),
        ),
      ),
    );

    // 圆形定义
    myDiagram.nodeTemplateMap.add(
      'ellipseCase',
      goJS(
        go.Node,
        'Auto',
        nodeStyle(),
        'Spot',
        { locationSpot: go.Spot.Center, zOrder: 1 },
        new go.Binding('zOrder', 'zOrder').makeTwoWay(),
        new go.Binding('location', 'loc', go.Point.parse).makeTwoWay(go.Point.stringify),
        {
          // 设置其可改变大小
          resizeObjectName: 'SHAPE',
        },
        new go.Binding('angle').makeTwoWay(),
        roleVisibleBinding(), // 绑定角色可见
        goJS(
          go.Shape,
          'Ellipse',
          { name: 'SHAPE', fill: 'rgba(128,128,128,0.2)', stroke: 'gray' },
          new go.Binding('visible', 'visible').makeTwoWay(),
          new go.Binding('fill', 'fillColor').makeTwoWay(),
          new go.Binding('stroke').makeTwoWay(),
          new go.Binding('strokeWidth').makeTwoWay(),
          new go.Binding('desiredSize', 'size', go.Size.parse).makeTwoWay(go.Size.stringify),
        ),
      ),
    );

    // 设备名称定义
    myDiagram.nodeTemplateMap.add(
      'deviceCase',
      goJS(
        go.Node,
        'Auto',
        nodeStyle(),
        'Spot',
        { locationSpot: go.Spot.Center, zOrder: 3, cursor: 'default' },
        new go.Binding('zOrder', 'zOrder').makeTwoWay(),
        new go.Binding('cursor', 'cursor').makeTwoWay(),
        new go.Binding('location', 'loc', go.Point.parse).makeTwoWay(go.Point.stringify),
        new go.Binding('angle').makeTwoWay(),
        roleVisibleBinding(), // 绑定角色可见
        goJS(
          go.Shape,
          'RoundedRectanglePlus',
          { name: 'SHAPE', strokeWidth: 10, stroke: '#000000', parameter1: 0 },
          new go.Binding('fill', 'fillColor').makeTwoWay(),
          new go.Binding('stroke').makeTwoWay(),
          new go.Binding('strokeWidth').makeTwoWay(),
          new go.Binding('parameter1', 'radius').makeTwoWay(),
          new go.Binding('desiredSize', 'size', go.Size.parse).makeTwoWay(go.Size.stringify),
        ),
        goJS(
          go.TextBlock,
          textStyle(),
          {
            margin: 5,
            maxSize: new go.Size(NaN, NaN),
            minSize: new go.Size(NaN, 1),
            wrap: go.TextBlock.WrapFit,
            textAlign: 'center',
            editable: true,
            font: 'bold 12px Helvetica, Arial, sans-serif',
            stroke: '#454545',
          },
          new go.Binding('text').makeTwoWay(),
          new go.Binding('font', 'fontStyle'),
          new go.Binding('stroke', 'fontStroke').makeTwoWay(),
          new go.Binding('textAlign', 'fontAlign'),
          new go.Binding('maxSize', 'textSize'),
          new go.Binding('minSize', 'textSize'),
        ),
        {
          click(e, node) {
            const { data } = node;
            const list = bindData.find((item) => {
              return item.name === data.stationName;
            });
            if (!list) return false;
            switch (data.opType) {
              case '画板跳转': // 图片模型
                break;
              case '功能跳转': // 功能模型
                menuJumpMethod(data);
                break;
              case '自定义交互': // 自定义交互
                customBack(data);
                break;
              default:
                break;
            }
          },
        },
      ),
    );

    // 数据源模型定义
    myDiagram.nodeTemplateMap.add(
      'dataSource',
      goJS(
        go.Node,
        'Auto',
        nodeStyle(),
        'Spot',
        { locationSpot: go.Spot.Center, zOrder: 3, cursor: 'default', visible: false },
        new go.Binding('zOrder', 'zOrder').makeTwoWay(),
        new go.Binding('cursor', 'cursor').makeTwoWay(),
        new go.Binding('location', 'loc', go.Point.parse).makeTwoWay(go.Point.stringify),
        new go.Binding('angle').makeTwoWay(),
        goJS(
          go.Shape,
          'RoundedRectanglePlus',
          { name: 'SHAPE', strokeWidth: 10, stroke: '#000000', parameter1: 0 },
          new go.Binding('fill', 'fillColor').makeTwoWay(),
          new go.Binding('stroke').makeTwoWay(),
          new go.Binding('strokeWidth').makeTwoWay(),
          new go.Binding('parameter1', 'radius').makeTwoWay(),
          new go.Binding('desiredSize', 'size', go.Size.parse).makeTwoWay(go.Size.stringify),
        ),
        goJS(
          go.TextBlock,
          textStyle(),
          {
            maxSize: new go.Size(NaN, NaN),
            minSize: new go.Size(NaN, 1),
            wrap: go.TextBlock.WrapFit,
            textAlign: 'center',
            editable: true,
            font: 'bold 12px Helvetica, Arial, sans-serif',
            stroke: '#454545',
          },
          new go.Binding('text').makeTwoWay(),
          new go.Binding('font', 'fontStyle'),
          new go.Binding('stroke', 'fontStroke').makeTwoWay(),
          new go.Binding('textAlign', 'fontAlign'),
          new go.Binding('maxSize', 'textSize'),
          new go.Binding('minSize', 'textSize'),
        ),
      ),
    );

    // 源数据模型定义
    myDiagram.nodeTemplateMap.add(
      'dataCase',
      goJS(
        go.Node,
        'Auto',
        nodeStyle(),
        'Spot',
        { locationSpot: go.Spot.Center, zOrder: 3, cursor: 'default' },
        new go.Binding('zOrder', 'zOrder').makeTwoWay(),
        new go.Binding('cursor', 'cursor').makeTwoWay(),
        new go.Binding('location', 'loc', go.Point.parse).makeTwoWay(go.Point.stringify),
        new go.Binding('angle').makeTwoWay(),
        roleVisibleBinding(), // 绑定角色可见
        goJS(
          go.Shape,
          'RoundedRectanglePlus',
          { name: 'SHAPE', strokeWidth: 10, stroke: '#000000', parameter1: 0 },
          new go.Binding('fill', 'fillColor').makeTwoWay(),
          new go.Binding('stroke').makeTwoWay(),
          new go.Binding('strokeWidth').makeTwoWay(),
          new go.Binding('parameter1', 'radius').makeTwoWay(),
          new go.Binding('desiredSize', 'size', go.Size.parse).makeTwoWay(go.Size.stringify),
        ),
        goJS(
          go.TextBlock,
          textStyle(),
          {
            // margin: 5,
            maxSize: new go.Size(NaN, NaN),
            minSize: new go.Size(NaN, 1),
            wrap: go.TextBlock.WrapFit,
            textAlign: 'center',
            editable: true,
            font: 'bold 12px Helvetica, Arial, sans-serif',
            stroke: '#454545',
          },
          new go.Binding('text').makeTwoWay(),
          new go.Binding('font', 'fontStyle'),
          new go.Binding('stroke', 'fontStroke').makeTwoWay(),
          new go.Binding('textAlign', 'fontAlign'),
          new go.Binding('maxSize', 'textSize'),
          new go.Binding('minSize', 'textSize'),
        ),
        {
          click(e, node) {
            const { data } = node;
            const list = bindData.find((item) => {
              return item.name === data.stationName;
            });
            if (!list) return false;
            switch (data.opType) {
              case '画板跳转': // 图片模型
                drawBoardMethod(data);
                break;
              case '功能跳转': // 功能模型
                menuJumpMethod(data);
                break;
              case '视频查看': // 视频查看
                break;
              case '自定义交互': // 自定义交互
                customBack(data);
                break;
              default:
                break;
            }
          },
        },
      ),
    );

    // 名称定义
    myDiagram.nodeTemplateMap.add(
      'nameCase',
      goJS(
        go.Node,
        'Auto',
        nodeStyle(),
        'Spot',
        { locationSpot: go.Spot.Center, zOrder: 3, cursor: 'default' },
        new go.Binding('zOrder', 'zOrder').makeTwoWay(),
        new go.Binding('cursor', 'cursor').makeTwoWay(),
        new go.Binding('location', 'loc', go.Point.parse).makeTwoWay(go.Point.stringify),
        new go.Binding('angle').makeTwoWay(),
        roleVisibleBinding(), // 绑定角色可见
        goJS(
          go.Shape,
          'RoundedRectanglePlus',
          { name: 'SHAPE', strokeWidth: 10, stroke: '#000000', parameter1: 0 },
          new go.Binding('fill', 'fillColor').makeTwoWay(),
          new go.Binding('stroke').makeTwoWay(),
          new go.Binding('strokeWidth').makeTwoWay(),
          new go.Binding('parameter1', 'radius').makeTwoWay(),
          new go.Binding('desiredSize', 'size', go.Size.parse).makeTwoWay(go.Size.stringify),
        ),
        goJS(
          go.TextBlock,
          textStyle(),
          {
            margin: 5,
            maxSize: new go.Size(NaN, NaN),
            minSize: new go.Size(NaN, 1),
            wrap: go.TextBlock.WrapFit,
            textAlign: 'center',
            editable: true,
            font: 'bold 12px Helvetica, Arial, sans-serif',
            stroke: '#454545',
          },
          new go.Binding('text').makeTwoWay(),
          new go.Binding('font', 'fontStyle'),
          new go.Binding('stroke', 'fontStroke').makeTwoWay(),
          new go.Binding('textAlign', 'fontAlign'),
          new go.Binding('maxSize', 'textSize'),
          new go.Binding('minSize', 'textSize'),
        ),
        {
          click(e, node) {
            const { data } = node;
            switch (data.opType) {
              case '画板跳转': // 图片模型
                break;
              case '功能跳转': // 功能模型
                menuJumpMethod(data);
                break;
              case '自定义交互': // 自定义交互
                customBack(data);
                break;
              default:
                break;
            }
          },
        },
      ),
    );

    // 更新时间定义
    myDiagram.nodeTemplateMap.add(
      'timeCase',
      goJS(
        go.Node,
        'Auto',
        nodeStyle(),
        'Spot',
        { locationSpot: go.Spot.Center, zOrder: 3, cursor: 'default' },
        new go.Binding('zOrder', 'zOrder').makeTwoWay(),
        new go.Binding('cursor', 'cursor').makeTwoWay(),
        new go.Binding('location', 'loc', go.Point.parse).makeTwoWay(go.Point.stringify),
        new go.Binding('angle').makeTwoWay(),
        roleVisibleBinding(), // 绑定角色可见
        goJS(
          go.Shape,
          'RoundedRectanglePlus',
          { name: 'SHAPE', strokeWidth: 10, stroke: '#000000', parameter1: 0 },
          new go.Binding('fill', 'fillColor').makeTwoWay(),
          new go.Binding('stroke').makeTwoWay(),
          new go.Binding('strokeWidth').makeTwoWay(),
          new go.Binding('parameter1', 'radius').makeTwoWay(),
          new go.Binding('desiredSize', 'size', go.Size.parse).makeTwoWay(go.Size.stringify),
        ),
        goJS(
          go.TextBlock,
          textStyle(),
          {
            // margin: 5,
            maxSize: new go.Size(NaN, NaN),
            minSize: new go.Size(NaN, 1),
            wrap: go.TextBlock.WrapFit,
            textAlign: 'center',
            editable: true,
            font: 'bold 12px Helvetica, Arial, sans-serif',
            stroke: '#454545',
          },
          new go.Binding('text').makeTwoWay(),
          new go.Binding('font', 'fontStyle'),
          new go.Binding('stroke', 'fontStroke').makeTwoWay(),
          new go.Binding('textAlign', 'fontAlign'),
          new go.Binding('maxSize', 'textSize'),
          new go.Binding('minSize', 'textSize'),
        ),
        {
          // define a tooltip for each node that displays the color as text
          toolTip: goJS(
            'ToolTip',
            goJS(
              go.TextBlock,
              { margin: 2 },
              new go.Binding('text', 'timeStr'),
              new go.Binding('visible', 'toolTip'),
            ),
            new go.Binding('visible', 'toolTip'),
          ),
        },
      ),
    );

    // 公用管定义
    myDiagram.nodeTemplateMap.add(
      'HBar',
      goJS(
        go.Node,
        'Auto',
        nodeStyle(),
        'Spot',
        { locationSpot: go.Spot.Center, zOrder: 1 },
        new go.Binding('zOrder', 'zOrder').makeTwoWay(),
        new go.Binding('location', 'loc', go.Point.parse).makeTwoWay(go.Point.stringify),
        new go.Binding('angle').makeTwoWay(),
        {
          // 设置其可选择
          selectable: false,
          layerName: 'Background',
        },
        roleVisibleBinding(), // 绑定角色可见
        goJS(
          go.Shape,
          'Rectangle',
          {
            name: 'SHAPE',
            height: 0,
            width: 120,
            fill: '#41BFEC',
            stroke: null,
            strokeWidth: 0,
            minSize: new go.Size(20, 0),
            maxSize: new go.Size(Infinity, 0),
          },
          new go.Binding('desiredSize', 'size', go.Size.parse).makeTwoWay(go.Size.stringify),
          new go.Binding('minSize', 'minSize').makeTwoWay(),
          new go.Binding('maxSize', 'maxSize').makeTwoWay(),
          new go.Binding('stroke', 'stroke').makeTwoWay(),
          new go.Binding('strokeWidth', 'strokeWidth').makeTwoWay(),
        ),
        goJS(
          go.Shape,
          {
            isPanelMain: true,
            stroke: 'white',
            strokeWidth: 3,
            height: 0,
            width: 100,
            name: 'PIPE',
            strokeDashArray: [20, 40],
          },
          new go.Binding('width').makeTwoWay(),
          new go.Binding('stroke', 'waterStroke').makeTwoWay(),
          new go.Binding('strokeWidth', 'waterWidth').makeTwoWay(),
          new go.Binding('strokeDashArray', 'strokeDashArray').makeTwoWay(),
          {
            portId: '',
            toLinkable: true,
            fromLinkable: true,
          },
        ),
      ),
    );

    // 值定义
    myDiagram.nodeTemplateMap.add(
      'valCase',
      goJS(
        go.Node,
        'Auto',
        nodeStyle(),
        'Spot',
        { locationSpot: go.Spot.Center, zOrder: 2, cursor: 'default' },
        new go.Binding('zOrder', 'zOrder').makeTwoWay(),
        new go.Binding('cursor', 'cursor').makeTwoWay(),
        new go.Binding('location', 'loc', go.Point.parse).makeTwoWay(go.Point.stringify),
        new go.Binding('angle').makeTwoWay(),
        roleVisibleBinding(), // 绑定角色可见
        goJS(
          go.Panel,
          'Horizontal',
          goJS(
            go.Panel,
            'Auto',
            goJS(
              go.Shape,
              'RoundedRectanglePlus',
              { name: 'SHAPE', strokeWidth: 10, stroke: '#000000', parameter1: 0 },
              new go.Binding('fill', 'fillColor'),
              new go.Binding('stroke'),
              new go.Binding('strokeWidth'),
              new go.Binding('parameter1', 'radius').makeTwoWay(),
              new go.Binding('desiredSize', 'size', go.Size.parse).makeTwoWay(go.Size.stringify),
            ),
            goJS(
              go.TextBlock,
              textStyle(),
              {
                maxSize: new go.Size(NaN, NaN),
                minSize: new go.Size(NaN, 1),
                wrap: go.TextBlock.WrapFit,
                textAlign: 'center',
                editable: true,
                font: 'bold 12px Helvetica, Arial, sans-serif',
                stroke: '#454545',
                flip: go.GraphObject.None,
              },
              new go.Binding('text', 'showVal'),
              new go.Binding('font', 'fontStyle'),
              new go.Binding('stroke', 'fontStroke'),
              new go.Binding('textAlign', 'fontAlign'),
              new go.Binding('maxSize', 'textSize'),
              new go.Binding('minSize', 'textSize'),
              new go.Binding('flip', 'flip'),
            ),
          ),
          goJS(
            go.TextBlock,
            textStyle(),
            {
              wrap: go.TextBlock.WrapFit,
              textAlign: 'center',
              editable: false,
              font: 'normal 10px Helvetica,Arial,sans-serif',
              stroke: '#ffffff',
            },
            new go.Binding('text', 'unitText'),
            new go.Binding('font', '', (v) => {
              return `normal ${v?.unitSize || 10}pt ${
                v?.unitStyle || 'Helvetica,Arial,sans-serif'
              }`;
            }),
            new go.Binding('stroke', 'unitColor'),
            new go.Binding('visible', '', (v) => {
              return (v?.unitSwitch && !!v?.unitText) || false;
            }),
            new go.Binding('margin', '', (v) => {
              const unitGap = v?.unitGap || '0,0,0,5';
              if (unitGap && isNumber(unitGap)) return unitGap;
              const margin = unitGap?.split(',') || null;
              return margin
                ? new go.Margin(
                    margin?.[0] * 1 || 0,
                    margin?.[1] * 1 || 0,
                    margin?.[2] * 1 || 0,
                    margin?.[3] * 1 || 0,
                  )
                : 0;
            }),
          ),
        ),
        {
          click(e, node) {
            const { data } = node;
            const list = bindData.find((item) => {
              return item.name === data.stationName;
            });
            if (!list) return false;
            // 历史查看
            if (data.opType && data.shName) historyModalRender(data, list);
          },
        },
      ),
    );

    // 连接点定义
    myDiagram.nodeTemplateMap.add(
      'linkPort',
      goJS(
        go.Node,
        'Auto',
        nodeStyle(),
        'Spot',
        { locationSpot: go.Spot.Center, zOrder: 1 },
        new go.Binding('zOrder', 'zOrder').makeTwoWay(),
        new go.Binding('location', 'loc', go.Point.parse).makeTwoWay(go.Point.stringify),
        new go.Binding('angle').makeTwoWay(),
        roleVisibleBinding(), // 绑定角色
        goJS(
          go.Panel,
          'Auto',
          {
            name: 'PANEL',
          },
          goJS(go.Shape, 'Rectangle', {
            fill: 'transparent',
            strokeWidth: 0,
            width: 8,
            height: 8,
            minSize: new go.Size(5, 5),
          }),
        ),
      ),
    );

    // 水池动效
    go.Shape.defineFigureGenerator('Pool', (shape, w, h) => {
      const geo = new go.Geometry();
      const fig = new go.PathFigure(0, 0, true); // starting point
      geo.add(fig);
      fig.add(new go.PathSegment(go.PathSegment.Line, 0.75 * w, 0));
      fig.add(new go.PathSegment(go.PathSegment.Line, w, 0.25 * h));
      fig.add(new go.PathSegment(go.PathSegment.Line, w, h));
      fig.add(new go.PathSegment(go.PathSegment.Line, 0, h).close());
      return geo;
    });

    // 定义水池
    myDiagram.nodeTemplateMap.add(
      'waterCase',
      goJS(
        go.Node,
        'Auto',
        nodeStyle(),
        'Spot',
        { locationSpot: go.Spot.Center, zOrder: 1 },
        new go.Binding('zOrder', 'zOrder').makeTwoWay(),
        new go.Binding('location', 'loc', go.Point.parse).makeTwoWay(go.Point.stringify),
        new go.Binding('angle').makeTwoWay(),
        roleVisibleBinding(), // 绑定角色可见
        goJS(
          go.Shape,
          'Rectangle',
          {
            name: 'SHAPE',
            alignment: go.Spot.Bottom,
            alignmentFocus: go.Spot.Bottom,
            fill: 'transparent',
            strokeWidth: 10,
            stroke: 'red',
            desiredSize: new go.Size(NaN, 26),
          },
          new go.Binding('width').makeTwoWay(),
          new go.Binding('height').makeTwoWay(),
          new go.Binding('stroke', 'stroke').makeTwoWay(),
          new go.Binding('strokeWidth', 'strokeWidth').makeTwoWay(),
        ),
        goJS(
          go.Shape,
          'Rectangle',
          {
            name: 'SHAPE',
            alignment: go.Spot.Bottom,
            alignmentFocus: go.Spot.Bottom,
            fill: '#ccc',
            strokeWidth: 10,
            stroke: 'transparent',
            desiredSize: new go.Size(NaN, 26),
          },
          new go.Binding('width').makeTwoWay(),
          new go.Binding('height').makeTwoWay(),
          new go.Binding('fill', 'waterColor').makeTwoWay(),
          new go.Binding('strokeWidth', 'strokeWidth').makeTwoWay(),
        ),
        goJS(
          go.Shape,
          'Pool',
          {
            name: 'waterSvg',
            alignment: go.Spot.Bottom,
            alignmentFocus: go.Spot.Bottom,
            fill: '#DEE0A3',
            stroke: 'transparent',
            strokeWidth: 10,
            minSize: new go.Size(NaN, 5),
            desiredSize: new go.Size(NaN, 20),
          },
          new go.Binding('width').makeTwoWay(),
          new go.Binding('height', 'waterHight').makeTwoWay(),
          new go.Binding('fill', 'fillColor').makeTwoWay(),
          new go.Binding('strokeWidth', 'strokeWidth').makeTwoWay(),
        ),
      ),
    );

    // 定义进度条
    myDiagram.nodeTemplateMap.add(
      'speedCase',
      goJS(
        go.Node,
        'Auto',
        nodeStyle(),
        'Spot',
        { locationSpot: go.Spot.Center, zOrder: 1 },
        new go.Binding('zOrder', 'zOrder').makeTwoWay(),
        new go.Binding('location', 'loc', go.Point.parse).makeTwoWay(go.Point.stringify),
        new go.Binding('angle').makeTwoWay(),
        roleVisibleBinding(), // 绑定角色可见
        goJS(
          go.Shape,
          'RoundedRectanglePlus',
          {
            name: 'SHAPE',
            alignment: go.Spot.Left,
            alignmentFocus: go.Spot.Left,
            strokeWidth: 2,
            stroke: '#FFFFFF',
            desiredSize: new go.Size(NaN, 26),
            fill: 'transparent',
            parameter1: 5,
          },
          new go.Binding('width').makeTwoWay(),
          new go.Binding('height').makeTwoWay(),
          new go.Binding('strokeWidth', 'strokeWidth').makeTwoWay(),
          new go.Binding('parameter1', 'radius').makeTwoWay(),
          new go.Binding('stroke', 'stroke').makeTwoWay(),
        ),
        goJS(
          go.Shape,
          'RoundedRectanglePlus',
          {
            name: 'SHAPE',
            alignment: go.Spot.Left,
            alignmentFocus: go.Spot.Left,
            fill: '#CCCCCC',
            strokeWidth: 2,
            stroke: 'transparent',
            desiredSize: new go.Size(NaN, 26),
            parameter1: 5,
          },
          new go.Binding('width').makeTwoWay(),
          new go.Binding('height').makeTwoWay(),
          new go.Binding('fill', 'waterColor').makeTwoWay(),
          new go.Binding('parameter1', 'radius').makeTwoWay(),
          new go.Binding('strokeWidth', 'strokeWidth').makeTwoWay(),
        ),
        goJS(
          go.Shape,
          'RoundedRectanglePlus',
          {
            name: 'speedSvg',
            alignment: go.Spot.Left,
            alignmentFocus: go.Spot.Left,
            fill: '#DEE0A3',
            stroke: 'transparent',
            strokeWidth: 2,
            minSize: new go.Size(NaN, 5),
            desiredSize: new go.Size(NaN, 20),
            parameter1: 5,
          },
          new go.Binding('width', 'lineWidth').makeTwoWay(),
          new go.Binding('height', 'height').makeTwoWay(),
          new go.Binding('parameter1', 'radius').makeTwoWay(),
          new go.Binding('fill', 'fillColor').makeTwoWay(),
          new go.Binding('strokeWidth', 'strokeWidth'),
        ),
      ),
    );

    // 泵状态设置
    myDiagram.nodeTemplateMap.add(
      'rotateCase',
      goJS(
        go.Node,
        'Table',
        nodeStyle(),
        'Spot',
        { locationSpot: go.Spot.Center, zOrder: 2 },
        new go.Binding('zOrder', 'zOrder').makeTwoWay(),
        new go.Binding('location', 'loc', go.Point.parse).makeTwoWay(go.Point.stringify),
        {
          // 设置其可改变大小
          resizeObjectName: 'SHAPE',
          rotatable: true,
        },
        roleVisibleBinding(), // 绑定角色可见
        goJS(
          go.Panel,
          'Table',
          {
            name: 'PANEL',
          },
          goJS(
            go.Shape,
            'Ellipse', // 定义形状
            { width: 37, height: 37, fill: 'transparent', stroke: 'transparent', strokeWidth: 1 },
            new go.Binding('width', 'widthBox').makeTwoWay(),
            new go.Binding('height', 'heightBox').makeTwoWay(),
          ),
          new go.Binding('desiredSize', 'size', go.Size.parse).makeTwoWay(go.Size.stringify),

          goJS(
            go.Picture,
            {
              name: 'rotateSvg',
              width: 26,
              height: 26,
              column: 0,
              scale: 1,
              source: require('./images/组态/状态/泵离线.svg'),
              angle: 0,
            },
            new go.Binding('source', 'imgSrc', (v) => {
              return require(`./images/组态/状态/${v.split('/').pop()}`);
            }).makeTwoWay(),
            new go.Binding('scale', 'scale').makeTwoWay(),
            new go.Binding('width', 'width').makeTwoWay(),
            new go.Binding('angle', 'angle').makeTwoWay(),
            new go.Binding('height', 'height').makeTwoWay(),
          ),
        ),
      ),
    );

    // 点状态设置
    myDiagram.nodeTemplateMap.add(
      'pointCase',
      goJS(
        go.Node,
        'Auto',
        nodeStyle(),
        'Spot',
        { locationSpot: go.Spot.Center, zOrder: 2 },
        new go.Binding('zOrder', 'zOrder').makeTwoWay(),
        new go.Binding('location', 'loc', go.Point.parse).makeTwoWay(go.Point.stringify),
        new go.Binding('angle').makeTwoWay(),
        roleVisibleBinding(), // 绑定角色可见
        goJS(
          go.Shape,
          'Ellipse',
          {
            width: 14,
            height: 14,
            name: 'SHAPE',
            fill: 'rgba(109, 122, 151, 1)',
            stroke: '#ffffff',
          },
          new go.Binding('fill', 'fillColor').makeTwoWay(),
          new go.Binding('stroke').makeTwoWay(),
          new go.Binding('strokeWidth').makeTwoWay(),
          new go.Binding('height', 'height').makeTwoWay(),
          new go.Binding('width', 'height').makeTwoWay(),
        ),
      ),
    );

    // 开关开设置
    myDiagram.nodeTemplateMap.add(
      'switchCase',
      goJS(
        go.Node,
        'Auto',
        nodeStyle(),
        'Spot',
        { locationSpot: go.Spot.Center, zOrder: 2, cursor: 'default' },
        new go.Binding('zOrder', 'zOrder').makeTwoWay(),
        new go.Binding('cursor', 'cursor').makeTwoWay(),
        new go.Binding('location', 'loc', go.Point.parse).makeTwoWay(go.Point.stringify),
        new go.Binding('angle').makeTwoWay(),
        roleVisibleBinding(), // 绑定角色可见
        goJS(
          go.Shape,
          'RoundedRectanglePlus',
          { name: 'SHAPE', strokeWidth: 10, stroke: '#000000', parameter1: 5 },
          new go.Binding('fill', 'fillColor'),
          new go.Binding('stroke'),
          new go.Binding('strokeWidth'),
          new go.Binding('parameter1', 'radius').makeTwoWay(),
          new go.Binding('desiredSize', 'size', go.Size.parse).makeTwoWay(go.Size.stringify),
        ),
        goJS(
          go.TextBlock,
          textStyle(),
          {
            maxSize: new go.Size(NaN, NaN),
            minSize: new go.Size(NaN, 1),
            wrap: go.TextBlock.WrapFit,
            textAlign: 'center',
            editable: true,
            font: 'bold 12px Helvetica, Arial, sans-serif',
            stroke: '#454545',
          },
          new go.Binding('text'),
          new go.Binding('font', 'fontStyle'),
          new go.Binding('stroke', 'fontStroke'),
          new go.Binding('textAlign', 'fontAlign'),
          new go.Binding('maxSize', 'textSize'),
          new go.Binding('minSize', 'textSize'),
        ),
        {
          click(e, node) {
            const { data } = node;
          },
        },
      ),
    );

    // 搅拌机状态设置
    myDiagram.nodeTemplateMap.add(
      'blenderCase',
      goJS(
        go.Node,
        'Table',
        nodeStyle(),
        'Spot',
        { locationSpot: go.Spot.Center, zOrder: 2 },
        new go.Binding('zOrder', 'zOrder').makeTwoWay(),
        new go.Binding('location', 'loc', go.Point.parse).makeTwoWay(go.Point.stringify),
        {
          // 设置其可改变大小
          resizeObjectName: 'SHAPE',
          rotatable: true,
        },
        new go.Binding('angle').makeTwoWay(),
        roleVisibleBinding(), // 绑定角色可见
        goJS(
          go.Panel,
          'Auto',
          {
            name: 'PANEL',
          },
          new go.Binding('desiredSize', 'size', go.Size.parse).makeTwoWay(go.Size.stringify),

          goJS(
            go.Picture,
            {
              name: 'blenderSvg',
              width: 42.5,
              height: 56,
              column: 0,
              scale: 1,
              source: require('./images/组态/状态/搅拌机双头1.svg'),
              angle: 0,
            },
            new go.Binding('source', 'imgSrc', (v) => {
              return require(`./images/组态/状态/${v.split('/').pop()}`);
            }).makeTwoWay(),
            new go.Binding('scale', 'scale').makeTwoWay(),
            new go.Binding('width', 'width').makeTwoWay(),
            new go.Binding('angle', 'angle').makeTwoWay(),
            new go.Binding('height', 'height').makeTwoWay(),
          ),
        ),
      ),
    );

    // 连接线装饰模板
    const linkSelectionAdornmentTemplate = goJS(
      go.Adornment,
      'Link',
      goJS(go.Shape, {
        isPanelMain: true,
        fill: null,
        stroke: 'deepskyblue',
        strokeWidth: 0,
      }),
    );

    /** *******************************单管连接方式****************************** */
    myDiagram.linkTemplate = goJS(
      BarLink,
      {
        curve: go.Link.JumpOver,
        toShortLength: 0,
        fromShortLength: 0,
        layerName: 'Background',
        routing: go.Link.Orthogonal, // 不同的位置进行不同的routing
        corner: 2,
        reshapable: true,
        resegmentable: true,
        relinkableFrom: true,
        relinkableTo: true,
        zOrder: 1,
      },
      new go.Binding('layerName', 'layerName').makeTwoWay(),
      new go.Binding('fromSpot', 'fromPort', (d) => {
        return spotConverter(d);
      }),
      new go.Binding('toSpot', 'toPort', (d) => {
        return spotConverter(d);
      }),
      new go.Binding('points').makeTwoWay(),
      roleVisibleBinding(), // 绑定角色可见
      goJS(
        go.Shape,
        { isPanelMain: true, stroke: '#41BFEC' /* blue */, strokeWidth: 6, name: 'changecolor' },
        new go.Binding('stroke', 'stroke'),
        new go.Binding('strokeWidth', 'strokeWidth'),
      ),
      goJS(
        go.Shape,
        {
          isPanelMain: true,
          stroke: 'white',
          strokeWidth: 3,
          name: 'PIPE',
          strokeDashArray: [20, 40],
        },
        new go.Binding('strokeWidth', 'waterWidth'),
        new go.Binding('stroke', 'waterStroke'),
      ),
    );

    /** *******************************sharpLine线条****************************** */
    myDiagram.linkTemplateMap.add(
      'sharpLine',
      goJS(
        BarLink,
        {
          curve: go.Link.JumpOver,
          resegmentable: true,
          adjusting: go.Link.Stretch,
          routing: go.Link.Normal,
          layerName: 'Background',
          routing: go.Link.Normal, //不同的位置进行不同的routing
          corner: 0,
          reshapable: true,
          resegmentable: true,
          relinkableFrom: true,
          relinkableTo: true,
          relinkableFrom: true,
          relinkableTo: true,
          zOrder: 1,
        },
        new go.Binding('layerName', 'layerName').makeTwoWay(),
        new go.Binding('fromSpot', 'fromPort', function (d) {
          return spotConverter(d);
        }),
        new go.Binding('toSpot', 'toPort', function (d) {
          return spotConverter(d);
        }),
        new go.Binding('points').makeTwoWay(),
        roleVisibleBinding(), // 绑定角色可见
        // mark each Shape to get the link geometry with isPanelMain: true
        goJS(
          go.Shape,
          {
            isPanelMain: true,
            stroke: '#41BFEC' /* blue*/,
            strokeWidth: 6,
            name: 'changecolor',
          },
          new go.Binding('stroke', 'stroke'),
          new go.Binding('strokeWidth', 'strokeWidth'),
        ),
        goJS(
          go.Shape,
          {
            isPanelMain: true,
            stroke: 'white',
            strokeWidth: 3,
            name: 'PIPE',
            strokeDashArray: [20, 40],
          },
          new go.Binding('strokeWidth', 'waterWidth'),
          new go.Binding('stroke', 'waterStroke'),
        ),
      ),
    );

    /** **************************************合管连接方式****************************************** */
    // myDiagram.linkTemplateMap.add(
    //   'linkToLink',
    //   goJS(
    //     'Link',
    //     { relinkableFrom: true, relinkableTo: true },
    //     goJS('Shape', {
    //       stroke: '#2D9945',
    //       strokeWidth: 2,
    //     }),
    //   ),
    // );

    const fromJson = JSON.parse(jsonStr);
    myTimeout(() => {
      loop();
      waterSvg();
      rotateSvg();
      blenderSvg();
      animationSvg();
    }, 100);
    const json = JSON.parse(JSON.stringify(fromJson));
    json.linkDataArray.forEach((item) => {
      item.isHavingDash = flowShow;
      item.realVal = '--';
      item.defaultWidth = item.waterWidth;
    });
    json.nodeDataArray.forEach((item) => {
      item.showVal = '--';
      item.realVal = '--';
      item.realType = '离线';
      item.Unit = '';
      item.switchState = '开';
      item.dtImgSrc = item.imgSrc || '';
      if (item.category === 'HBar') {
        item.hBarClolor = item.waterStroke;
        item.typeDash = false;
      }
      if (item.category === 'valCase') {
        if (item.shType === '') item.showVal = item.text;
      }
      if (item.category === 'nameCase') {
        item.dtFillColor = item.fillColor;
        item.dtStroke = item.stroke;
        item.dtFontStroke = item.fontStroke;
        item.dtText = item.text;
      }
      if (item.category === 'modelCase' || item.category === 'ellipseCase') {
        item.dtzOrder = item.zOrder;
      }
      if (item.category == 'deviceCase' && item.shType && deviceName && deviceName.length) {
        var device = deviceName.find(function (arr, index) {
          return '设备' + stationList[index] == item.stationName;
        });
        if (device) item.text = device;
      }
      // 兼容V1之前版本(部分展示可支持)
      if (chartInfo.version === 'V1') return false;
      item.shName = item.showName || '';
      item.hbControl = item.authoControl || '否';
      if (item.controlType === '开关展示') item.switch = '是';
      if (item.category === 'valCase') item.shType = '值显示';
    });
    // 监听画布变化
    myDiagram.addDiagramListener('ViewportBoundsChanged', viewportBoundsChang);
    myDiagram.model = go.Model.fromJson(json);
  };

  return (
    <div className={classNames(prefixCls)} ref={ConfigurationRef}>
      <div id={twoID} className={classNames('configurationView')}>
        <LoadBox spinning={spinning} />
        {isEmpty && <Empty theme={'dark'} description={description} />}
      </div>
      {spinLoad && (
        <div className={classNames('configurationLoad')}>
          <LoadBox spinning={spinLoad} />
        </div>
      )}
      {/* 历史曲线 */}
      {isHIModalVisible && (
        <Modal
          centered
          width={1200}
          footer={null}
          open={isHIModalVisible}
          onOk={() => setIsHIModalVisible(false)}
          onCancel={() => setIsHIModalVisible(false)}
          getContainer={ConfigurationRef.current}
          wrapClassName={classNames(`${prefixCls}-historyInfoModal`)}
        >
          <HistoryView deviceParams={historyInfoParams} />
        </Modal>
      )}
    </div>
  );
};

ConfigurationView.defaultProps = {
  name: '',
  devices: [],
  deviceName: [],
  config: {},
  isZoom: false,
  flowShow: true,
  customBack: () => {},
  speed: 0,
  play: false,
  times: 2,
  callback: (speed, total, play, time) => {},
  params: {},
  statisticType: [],
};

ConfigurationView.propTypes = {
  name: PropTypes.string,
  devices: PropTypes.array,
  deviceName: PropTypes.array,
  config: PropTypes.object,
  isZoom: PropTypes.bool,
  flowShow: PropTypes.bool,
  customBack: PropTypes.func,
  speed: PropTypes.number,
  play: PropTypes.bool,
  times: PropTypes.number,
  callback: PropTypes.func,
  params: PropTypes.object,
  statisticType: PropTypes.array,
};

export default ConfigurationView;