LogicFlow,画布流程设计方案

### 画布流程设计方案

#### 本次使用的是LogicFlow, 那LogicFlow 是什么?
LogicFlow 是一款流程图编辑框架,提供了一系列流程图交互、编辑所必需的功能和灵活的节点自定义、插件等拓展机制。LogicFlow 支持前端研发自定义开发各种逻辑编排场景,如流程图、ER 图、BPMN 流程等。在工作审批配置、机器人逻辑编排、无代码平台流程配置都有较好的应用。

### 创建一个实例

复制代码

import LogicFlow from "@logicflow/core";
import "@logicflow/core/dist/style/index.css";
import { useEffect, useRef } from "react";

export default function App() {
  const refContainer = useRef();
  useEffect(() => {
    const logicflow = new LogicFlow({
      container: refContainer.current,
      grid: true,
      width: 1000,
      height: 500,
    });
    logicflow.render();
  }, []);
  return <div className="App" ref={refContainer}></div>;
}

### LogicFlow 的图数据
const graphData = {
  nodes: [
    {
      id: "node_id_1",
      type: "rect",
      x: 100,
      y: 100,
      text: { x: 100, y: 100, value: "节点1" },
      properties: {},
    },
    {
      id: "node_id_2",
      type: "circle",
      x: 200,
      y: 300,
      text: { x: 300, y: 300, value: "节点2" },
      properties: {},
    },
  ],
  edges: [
    {
      id: "edge_id",
      type: "polyline",
      sourceNodeId: "node_id_1",
      targetNodeId: "node_id_2",
      text: { x: 139, y: 200, value: "连线" },
      startPoint: { x: 100, y: 140 },
      endPoint: { x: 200, y: 250 },
      pointsList: [
        { x: 100, y: 140 },
        { x: 100, y: 200 },
        { x: 200, y: 200 },
        { x: 200, y: 250 },
      ],
      properties: {},
    },
  ],
};

### 将图数据渲染到画布上
lf.render(graphData);
复制代码

### 认识 LogicFlow 的基础节点

LogicFlow 是基于 svg 做的流程图编辑框架,所以我们的节点和连线都是 svg 基本形状,对 LogicFlow 节点样式的修改,也就是对 svg 基本形状的修改。LogicFlow 内部存在 7 种基础节点,分别为:
矩形:rect
圆形: circle
椭圆: ellipse
多边形: polygon
菱形: diamond
文本: text
HTML: html

### 基于继承的自定义节点

LogicFlow 的基础节点是比较简单的,但是在业务中对节点外观要求可能有各种情况。LogicFlow 提供了非常强大的自定义节点功能,可以支持开发者自定义各种节点。

注意LogicFlow 推荐在实际应用场景中,所有的节点都使用自定义节点,将节点的 type 定义为符合项目业务意义的名称。而不是使用圆形、矩形这种仅表示外观的节点。

LogicFlow 是基于继承来实现自定义节点、边。开发者可以继承 LogicFlow 内置的节点,然后利用面向对象的重写机制。重写节点样式相关的方法,来达到自定义节点样式的效果。

### 二次自定义

import { RectResize } from "@logicflow/extension";
class CustomNodeModel extends RectResize.model {}
class CustomNode extends RectResize.view {}

### 自定义一个业务节点

我们以定义一个如下图所示的用户任务节点为例,来实现一个基于内置矩形节点的自定义节点。

### 1. 定义节点并注册

复制代码
// UserTaskNode.js
import { RectNode, RectNodeModel } from "@logicflow/core";

class UserTaskModel extends RectNodeModel {}

class UserTaskView extends RectNode {}

export default {
  type: "UserTask",
  view: UserTaskView,
  model: UserTaskModel,
};

// main.js
import UserTask from "./UserTaskNode.js";

const lf = new LogicFlow({
  container: document.querySelector("#container"),
});
lf.register(UserTask);

lf.render({
  nodes: [
    {
      type: "UserTask",
      x: 100,
      y: 100,
    },
  ],
});
复制代码
从上面的代码,可以看到,在自定义一个节点的时候,我们需要定义节点的model和view。这是因为由于 LogicFlow 基于 MVVM 模式,所有自定义节点和连线的时候,我们需要自定义view和model。大多数情况下,需要通过重写定义model上获取样式相关的方法和重写view上的getShape来定义更复杂的节点外观。

### 2. 自定义节点 model

#### 自定义节点的样式属性

复制代码
1. getNodeStyle:支持重写,自定义节点样式属性 
class UserTaskModel extends RectNodeModel {
  getNodeStyle() {
    const style = super.getNodeStyle();
    style.stroke = "blue";
    style.strokeDasharray = "3 3";
    return style;
  }
}
2. getTextStyle:支持重写,自定义节点文本样式属性
3. getAnchorStyle:支持重写,自定义节点锚点样式属性
4. getAnchorLineStyle:支持重写,自定义节点锚点拖出连接线的样式属性
5. getOutlineStyle:支持重写,自定义节点轮廓框的样式属性
6. initNodeData: 支持重写,初始化节点数据,将传入的图数据(data)转换为节点属性, 所以需要调用super.initNodeData触发转换方法。
在super.initNodeData之前,对图数据进行处理。
在super.initNodeData之后,对节点属性进行初始化。
class UserTaskModel extends RectResize.model {
  initNodeData(data) {
    // 可以在super之前,强制设置节点文本位置不居中,而且在节点下面
    if (!data.text || typeof data.text === "string") {
      data.text = {
        value: data.text || "",
        x: data.x,
        y: data.y + 40,
      };
    }
    super.initNodeData(data);
    this.width = 100;
    this.height = 80;
  }
}

提示initNodeData 和 setAttributes 都可以对 nodeModel 的属性进行赋值,但是两者的区别在于:

initNodeData只在节点初始化的时候调用,用于初始化节点的属性。
setAttributes除了初始化调用外,还会在 properties 发生变化了调用。

7. setAttributes: 设置 model 形状属性,每次 properties 发生变化会触发
class UserTaskModel extends RectNodeModel {
  setAttributes() {
    const size = this.properties.scale || 1;
    this.width = 100 * size;
    this.height = 80 * size;
  }
}
8. getData:(不支持重写此方法)获取被保存时返回的数据。LogicFlow 有固定节点数据格式。如果期望在保存数据上添加数据,请添加到 properties 上。

const nodeModel = lf.getNodeModelById("node_1");
const nodeData = nodeModel.getData();
9. getProperties: (不支持重写此方法)获取节点属性

const nodeModel = lf.getNodeModelById("node_1");
const properties = nodeModel.getProperties();

10. updateText: 修改节点文本内容
const nodeModel = lf.getNodeModelById("node_1");
nodeModel.updateText("hello world");

11. setProperties: 设置节点 properties

lf.on("node:click", ({ data }) => {
  lf.getNodeModelById(data.id).setProperties({
    disabled: !data.properties.disabled,
    scale: 2,
  });
});

12. deleteProperty: 删除节点的某个属性
lf.on("node:click", ({ data }) => {
  lf.getNodeModelById(data.id).deleteProperty("disabled");
  lf.getNodeModelById(data.id).deleteProperty("scale");
});
复制代码

#### 自定义节点的形状属性

class customRectModel extends RectNodeModel {
  initNodeData(data) {
    super.initNodeData(data);
    this.width = 200;
    this.height = 80;
    this.radius = 50;
  }
}

#### 基于 properties 属性自定义节点样式

复制代码
class UserTaskModel extends RectNodeModel {
  initNodeData(data) {
    super.initNodeData(data);
    this.width = 80;
    this.height = 60;
    this.radius = 5;
  }
  getNodeStyle() {
    const style = super.getNodeStyle();
    const properties = this.properties;
    if (properties.statu === "pass") {
      style.stroke = "green";
    } else if (properties.statu === "reject") {
      style.stroke = "red";
    } else {
      style.stroke = "rgb(24, 125, 255)";
    }
    return style;
  }
}
复制代码

### 3. 自定义节点 view

LogicFlow 在自定义节点的model时,可以定义节点的基础形状、样式等属性。但是当开发者需要一个更加复杂的节点时,可以使用 LogicFlow 提供的自定义节点view的方式。
复制代码
class UserTaskView extends RectNode {
  private getLabelShape() {
    const { model } = this.props;
    const { x, y, width, height } = model;
    const style = model.getNodeStyle();
    return h(
      "svg",
      {
        x: x - width / 2 + 5,
        y: y - height / 2 + 5,
        width: 25,
        height: 25,
        viewBox: "0 0 1274 1024"
      },
      h("path", {
        fill: style.stroke,
        d:
          "M655.807326 287.35973m-223.989415 0a218.879 218.879 0 1 0 447.978829 0 218.879 218.879 0 1 0-447.978829 0ZM1039.955839 895.482975c-0.490184-212.177424-172.287821-384.030443-384.148513-384.030443-211.862739 0-383.660376 171.85302-384.15056 384.030443L1039.955839 895.482975z"
      })
    );
  }
  /**
   * 完全自定义节点外观方法
   */
  getShape() {
    const { model, graphModel } = this.props;
    const { x, y, width, height, radius } = model;
    const style = model.getNodeStyle();
    return h("g", {}, [
      h("rect", {
        ...style,
        x: x - width / 2,
        y: y - height / 2,
        rx: radius,
        ry: radius,
        width,
        height
      }),
      this.getLabelShape()
    ]);
  }
}
复制代码

### h 函数

h方法是 LogicFlow 对外暴露的渲染函数,其用法与react、vue的createElement一致。但是这里我们需要创建的是svg标签,所以需要有一定的 svg 基础知识。但是大多数情况下,我们不会涉及太复杂的知识,只是简单的矩形、圆形、多边形这种。
复制代码
h(nodeName, attributes, [...children])

// <text x="100" y="100">文本内容</text>
h('text', { x: 100, y: 100 }, ['文本内容'])

/**
 * <g>
 *   <rect x="100" y="100" stroke="#000000" strokeDasharray="3 3"></rect>
 *   <text x="100" y="100">文本内容</text>
 * </g>
 */

<!-- 外部的 h('g', {}, [...]) 表示一个 SVG <g> 元素,用于对其内部的元素进行分组。 -->
h('g',{}, [
  h('rect', { x: 100, y: 100, stroke: "#000000", strokeDasharray="3 3"}),
  h('text', { x: 100, y: 100 }, ['文本内容'])
])
复制代码

### getAnchorPoints

此方法作用就是定义锚点,锚点是用于连接线条的点,可以是多个,也可以是一个,返回的是一个数组,数组中的每一项都是一个对象,对象中包含 x 和 y 属性,分别表示锚点的 x 和 y 坐标。

### getShape

此方法作用就是定义最终渲染的图形, LogicFlow 内部会将其返回的内容插入到 svg DOM 上。
在 LogicFlow 所有的基础节点中,model里面的x,y都是统一表示中心点。但是getShape方法给我们提供直接生成 svg dom 的方式,在 svg 中, 对图形位置的控制则存在差异:
复制代码
const { x, y, width, height, radius } = this.props.model;
// svg dom <rect x="100" y="100" width="100" height="80">
h("rect", {
  ...style,
  x: x - width / 2,
  y: y - height / 2,
  rx: radius, // 注意这里是rx而不是radius
  ry: radius,
  width,
  height
}),
复制代码
自定义矩形的 view 时 radius 设置在model中,radius是矩形节点的形状属性。但是在自定义view时需要注意,svg 里面设置矩形的圆角并不是用radius,而是使用rx, ry。所以在自定义view的矩形时,需要将 model 中radius的赋值给rx和ry,否则圆角将不生效。

### props

LogicFlow 是基于preact开发的,我们自定义节点 view 的时候,可以通过this.props获取父组件传递过来的数据。this.props对象包含两个属性,分别为:
model: 表示自定义节点的 model
graphModel: 表示 logicflow 整个图的 model

### 自定义连接规则校验

在某些时候,我们可能需要控制边的连接方式,比如开始节点不能被其它节点连接、结束节点不能连接其他节点、用户节点后面必须是判断节点等,要想达到这种效果,我们需要为节点设置以下两个属性。

1. sourceRules - 当节点作为边的起始节点(source)时的校验规则
2. targetRules - 当节点作为边的目标节点(target)时的校验规则
以正方形(square)为例,在边时我们希望它的下一节点只能是圆形节点(circle),那么我们应该给square添加作为source节点的校验规则。
复制代码
import { RectNode, RectNodeModel } from "@logicflow/core";
class SquareModel extends RectNodeModel {
  initNodeData(data) {
    super.initNodeData(data);

    const circleOnlyAsTarget = {
      message: "正方形节点下一个节点只能是圆形节点",
      validate: (sourceNode, targetNode, sourceAnchor, targetAnchor) => {
        return targetNode.type === "circle";
      },
    };
    this.sourceRules.push(circleOnlyAsTarget);
  }
}
复制代码
在上例中,我们为model的sourceRules属性添加了一条校验规则,校验规则是一个对象,我们需要为其提供messgage和validate属性。
message属性是当不满足校验规则时所抛出的错误信息,validate则是传入规则检验的回调函数。validate方法有四个参数,分别为边的起始节点(source)、目标节点(target)、起始锚点(sourceAnchor)、 目标锚点(targetAnchor),我们可以根据参数信息来决定是否通过校验,其返回值是一个布尔值。
3. moveRules: 限制节点移动, 在graphModel中支持添加全局移动规则,例如在移动 A 节点的时候,期望把 B 节点也一起移动了。
lf.graphModel.addNodeMoveRules((model, deltaX, deltaY) => {
  // 如果移动的是分组,那么分组的子节点也跟着移动。
  if (model.isGroup && model.children) {
    lf.graphModel.moveNodes(model.children, deltaX, deltaY, true);
  }
  return true;
});

### 自定义节点的锚点

对于各种基础类型节点,我们都内置了默认锚点。LogicFlow 支持通过重写获取锚点的方法来实现自定义节点的锚点。
复制代码
import { RectNode, RectNodeModel } from "@logicflow/core";

class SquareModel extends RectNodeModel {
  initNodeData(data) {
    super.initNodeData(data);

    const rule = {
      message: "只允许从右边的锚点连出",
      validate: (sourceNode, targetNode, sourceAnchor, targetAnchor) => {
        return sourceAnchor.name === "right";
      },
    };
    this.sourceRules.push(rule);
  }
  getAnchorStyle(anchorInfo) {
    const style = super.getAnchorStyle(anchorInfo);
    if (anchorInfo.type === "left") {
      style.fill = "red";
      style.hover.fill = "transparent";
      style.hover.stroke = "transpanrent";
      style.className = "lf-hide-default";
    } else {
      style.fill = "green";
    }
    return style;
  }
  getDefaultAnchor() {
    const { width, height, x, y, id } = this;
    return [
      {
        x: x - width / 2,
        y,
        type: "left",
        edgeAddable: false, // 控制锚点是否可以从此锚点手动创建连线。默认为true。
        id: `${id}_0`,
      },
      {
        x: x + width / 2,
        y,
        type: "right",
        id: `${id}_1`,
      },
    ];
  }
}

class SqlNode extends RectNode {
  /**
   * 1.1.7版本后支持在view中重写锚点形状。
   * 重写锚点新增
   */
  getAnchorShape(anchorData) {
    const { x, y, type } = anchorData;
    return h("rect", {
      x: x - 5,
      y: y - 5,
      width: 10,
      height: 10,
      className: `custom-anchor ${
        type === "left" ? "incomming-anchor" : "outgoing-anchor"
      }`
    });
  }
}
复制代码

### 自定义 HTML 节点

LogicFlow 内置了基础的 HTML 节点和其他基础节点不一样,我们可以利用 LogicFlow 的自定义机制,实现各种形态的 HTML 节点,而且 HTML 节点内部可以使用任意框架进行渲染。
 
复制代码
class UmlModel extends HtmlNodeModel {
  setAttributes() {
    this.text.editable = false; // 禁止节点文本编辑
    // 设置节点宽高和锚点
    const width = 200;
    const height = 130;
    this.width = width;
    this.height = height;
    this.anchorsOffset = [
      [width / 2, 0],
      [0, height / 2],
      [-width / 2, 0],
      [0, -height / 2],
    ];
  }
}
class UmlNode extends HtmlNode {
  currentProperties: string;
  setHtml(rootEl: HTMLElement) {
    const { properties } = this.props.model;

    const el = document.createElement("div");
    el.className = "uml-wrapper";
    const html = `
      <div>
        <div class="uml-head">Head</div>
        <div class="uml-body">
          <div>+ ${properties.name}</div>
          <div>+ ${properties.body}</div>
        </div>
        <div class="uml-footer">
          <div>+ setHead(Head $head)</div>
          <div>+ setBody(Body $body)</div>
        </div>
      </div>
    `;
    el.innerHTML = html;
    // 需要先把之前渲染的子节点清除掉。
    rootEl.innerHTML = "";
    rootEl.appendChild(el);
  }
}
复制代码

#### 使用 react 编写 html 节点

复制代码
import { HtmlNodeModel, HtmlNode } from "@logicflow/core";
import React from "react";
import ReactDOM from "react-dom";
import "./uml.css";

function Hello(props) {
  return (
    <>
      <h1 className="box-title">title</h1>
      <div className="box-content">
        <p>{props.name}</p>
        <p>{props.body}</p>
        <p>content3</p>
      </div>
    </>
  );
}

class BoxxModel extends HtmlNodeModel {
  setAttributes() {
    this.text.editable = false;
    const width = 200;
    const height = 116;
    this.width = width;
    this.height = height;
    this.anchorsOffset = [
      [width / 2, 0],
      [0, height / 2],
      [-width / 2, 0],
      [0, -height / 2],
    ];
  }
}
class BoxxNode extends HtmlNode {
  setHtml(rootEl: HTMLElement) {
    const { properties } = this.props.model;
    ReactDOM.render(
      <Hello name={properties.name} body={properties.body} />,
      rootEl
    );
  }
}

const boxx = {
  type: "boxx",
  view: BoxxNode,
  model: BoxxModel,
};

export default boxx;


// page.jsx

import box from './box.tsx';
export default function PageIndex() {
  useEffect(() => {
    const lf = new LogicFlow({
      ...config,
      container: document.querySelector('#graph_html') as HTMLElement
    });
    lf.register(box);
    lf.render({
      nodes: [
        {
          id: 11,
          type: 'boxx',
          x: 350,
          y: 100,
          properties: {
            name: 'turbo',
            body: 'hello'
          }
        },
      ]
    });
    lf.on('node:click', ({ data}) => {
      lf.setProperties(data.id, {
        name: 'turbo',
        body: Math.random()
      })
    });
  }, []);

  return (
    <>
      <div id="graph_html" className="viewport" />
    </>
  )
}
复制代码

### 边 Edge

和节点一样,LogicFlow 也内置一些基础的边。LogicFlow 的内置边包括:

1. 直线(line)
2. 直角折线(polyline)
3. 贝塞尔曲线(bezier)

#### 基于 React 组件自定义边

使用以下方法可以基于 React 组件自定义边,你可以在边上添加任何你想要的 React 组件,甚至将原有的边通过样式隐藏,使用 React 重新绘制
复制代码
import React from "react";
import ReactDOM from "react-dom";
import { BaseEdgeModel, LineEdge, h } from "@logicflow/core";

const DEFAULT_WIDTH = 48;
const DEFAULT_HEIGHT = 32;

class CustomEdgeModel extends BaseEdgeModel {
  getEdgeStyle() {
    const edgeStyle = super.getEdgeStyle();
    //可以自己设置线的显示样式,甚至隐藏掉原本的线,自己用react绘制
    edgeStyle.strokeDasharray = "4 4";
    edgeStyle.stroke = "#DDDFE3";
    return edgeStyle;
  }
}

const CustomLine: React.FC = () => {
  return <div className="custom-edge">aaa</div>;
};

class CustomEdgeView extends LineEdge {
  getEdge() {
    const { model } = this.props;
    const { customWidth = DEFAULT_WIDTH, customHeight = DEFAULT_HEIGHT } =
      model.getProperties();
    const id = model.id;
    const edgeStyle = model.getEdgeStyle();
    const { startPoint, endPoint, arrowConfig } = model;
    const lineData = {
      x1: startPoint.x,
      y1: startPoint.y,
      x2: endPoint.x,
      y2: endPoint.y,
    };
    const positionData = {
      x: (startPoint.x + endPoint.x - customWidth) / 2,
      y: (startPoint.y + endPoint.y - customHeight) / 2,
      width: customWidth,
      height: customHeight,
    };
    const wrapperStyle = {
      width: customWidth,
      height: customHeight,
    };

    setTimeout(() => {
      ReactDOM.render(<CustomLine />, document.querySelector("#" + id));
    }, 0);
    return h("g", {}, [
      h("line", { ...lineData, ...edgeStyle, ...arrowConfig }),
      h("foreignObject", { ...positionData }, [
        h("div", {
          id,
          style: wrapperStyle,
          className: "lf-custom-edge-wrapper",
        }),
      ]),
    ]);
  }
  getAppend() {
    return h("g", {}, []);
  }
}

export default {
  type: "CustomEdge",
  view: CustomEdgeView,
  model: CustomEdgeModel,
};
复制代码

### 主题 Theme

LogicFlow 提供了设置主题的方法,便于用户统一设置其内部所有元素的样式。
设置方式有两种:

初始化LogicFlow时作为配置传入
初始化后,调用LogicFlow的 setTheme 方法
复制代码
// 方法1:new LogicFlow时作为配置传入
const config = {
  domId: 'app',
  width: 1000,
  height: 800,
  style: { // 设置默认主题样式
    rect: { // 矩形样式
      ...
    },
    circle: { // 圆形样式
      ...
    },
    nodeText: { // 节点文本样式
      ...
    },
    edgeText: { // 边文本样式
      ...
    },
    anchor: { // 锚点样式
      ...
    }
    ...
  }
}
const lf = new LogicFlow(config);

// 方法2: 调用LogicFlow的setTheme方法
lf.setTheme({ // 设置默认主题样式
  rect: { // 矩形样式
    ...
  },
  circle: { // 圆形样式
    ...
  },
  nodeText: { // 节点文本样式
    ...
  },
  edgeText: { // 边文本样式
    ...
  },
  anchor: { // 锚点样式
    ...
  }
  ...
})
复制代码

### 事件 Event

监听事件

<!-- lf实例上提供on方法支持监听事件。 -->
lf.on("node:dnd-add", (data) => {});

<!-- LogicFlow 支持用逗号分割事件名。 -->
lf.on("node:click,edge:click", (data) => {});

自定义事件

除了 lf 上支持的监听事件外,还可以使用eventCenter对象来监听和触发事件。eventCenter是一个graphModel上的一个属性。所以在自定义节点的时候,我们可以使用eventCenter触发自定义事件。
复制代码
class ButtonNode extends HtmlNode {
  setHtml(rootEl) {
    const { properties } = this.props.model;

    const el = document.createElement("div");
    el.className = "uml-wrapper";
    const html = `
      <div>
        <div class="uml-head">Head</div>
        <div class="uml-body">
          <div><button onclick="setData()">+</button> ${properties.name}</div>
          <div>${properties.body}</div>
        </div>
        <div class="uml-footer">
          <div>setHead(Head $head)</div>
          <div>setBody(Body $body)</div>
        </div>
      </div>
    `;
    el.innerHTML = html;
    rootEl.innerHTML = "";
    rootEl.appendChild(el);
    window.setData = () => {
      const { graphModel, model } = this.props;
      graphModel.eventCenter.emit("custom:button-click", model);
    };
  }
}
复制代码

### 拖拽创建节点 Dnd

拖拽需要结合图形面板来实现,步骤:创建面板 → 拖拽初始化 → 监听 drop 事件创建节点
lf.dnd.startDrag({
  type,
  text: `${type}节点`,
});
通过上面的代码可以看出,将节点通过div标签+css样式的方式绘制到面板中,并为其绑定onMouseDown事件,当拖拽图形时,会触发lf.dnd.startDrag函数,表示开始拖拽,并传入选中图形的配置,startDrag入参格式:
lf.dnd.startDrag(nodeConfig: NodeConfig):void

type NodeConfig = {
  id?: string; // 不建议直接传id, logicflow id不允许重复
  type: string;
  text?: TextConfig;
  properties?: Record<string, unknown>;
};
拖拽结束鼠标松开时,将当前鼠标的位置转换为画布上的坐标,并以此为节点的中心点坐标x、y,合并拖拽节点传入的nodeConfig,监听到 drop 事件后会调用lf.addNode方法创建节点。

注意: 如果是用图片作为配置面板中添加节点的元素,需要将其设置为不可拖动的,解决办法参考下面
1. 如果是用图片作为触发元素,可以考虑设置dragable为false或者将其作为背景图。
2. 如果触发元素被a标签包裹,获取其它可以拖动的元素。这个时候可以设置user-select: none。

注意 如果遇到拖拽添加节点报错“不存在 id 为 xx 的节点”,需要在 mousedown 时触发dnd.startDrag。

1. 给图片添加point-events:none属性
2. 使用背景图来显示图标
3. 使用dndPanel拖拽组件来实现拖拽面板。http://logic-flow.org/guide/extension/component-dnd-panel.html#%E5%90%AF%E7%94%A8

### 对齐线 Snapline
对齐线能够在节点移动过程中,将移动节点的位置与画布中其他节点位置进行对比,辅助位置调整。位置对比有如下两个方面。
1. 节点中心位置
2. 节点的边框

对齐线使用
普通编辑模式下,默认开启对齐线,也可通过配置进行关闭。
在静默模式下,无法移动节点,所以关闭了对齐线功能,无法通过配置开启。

// 关闭对齐线功能
const lf = new LogicFlow({
  snapline: false,
});
对齐线样式设置
对齐线的样式包括颜色和宽度,可以通过设置主题的方式进行修改。
复制代码
// 默认配置
{
  stroke: '#1E90FF',
  strokeWidth: 1,
}
// 修改对齐线样式
lf.setTheme({
  snapline: {
    stroke: '#1E90FF', // 对齐线颜色
    strokeWidth: 1, // 对齐线宽度
  },
})
复制代码

 

### 参考文献:

文档地址: https://site.logic-flow.cn/docs/#/zh/


posted @   君临天下之徐少  阅读(2317)  评论(0编辑  收藏  举报
编辑推荐:
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
点击右上角即可分享
微信分享提示