Vue3中使用LogicFlow实现简单流程图

实现结果

在这里插入图片描述
实现功能:

  1. 拖拽创建节点
  2. 自定义节点/边
  3. 自定义快捷键
  4. 人员选择弹窗
  5. 右侧动态配置组件
  6. 配置项获取/回显
  7. 必填项验证

自定义节点与拖拽创建节点

拖拽节点面板node-panel.vue

<template>
  <div class="node-panel">
    <div
      v-for="(item, key) in state.nodePanel"
      :key="key"
      class="approve-node"
      @mousedown="dragNode(item)"
    >
      <div class="node-shape" :class="'node-' + item.type"></div>
      <div class="node-label">{{ item.text }}</div>
    </div>
  </div>
</template>

<script lang="ts" setup>
import { ILogicFlowNodePanelItem } from "@/types/logic-flow";
import LogicFlow from "@logicflow/core";
import { reactive } from "vue";
const props = defineProps<{ lf?: LogicFlow }>();
const state = reactive({
  nodePanel: [
    {
      type: "approver",
      text: "用户活动",
    },
    {
      type: "link",
      text: "连接点",
    },
    {
      type: "review",
      text: "传阅",
    },
  ],
});
const dragNode = (item: ILogicFlowNodePanelItem) => {
  props.lf?.dnd.startDrag({
    type: item.type,
    text: item.text,
  });
};
</script>

自定义节点/边index.ts

/**
 * @description 注册节点
 * @export
 * @param {LogicFlow} lf
 * @return {*}
 */
export function registeNode(lf: ShallowRef<LogicFlow | undefined>) {
  /**
   * @description 自定义开始节点
   */
  class StartNode extends CircleNode {
    getShape() {
      const { x, y } = this.props.model;
      const style = this.props.model.getNodeStyle();
      return h("g", {}, [
        h("circle", {
          ...style,
          cx: x,
          cy: y,
          r: 30,
          stroke: "#000",
          fill: "#000",
        }),
      ]);
    }
    getText() {
      const { x, y, text } = this.props.model;
      return h(
        "text",
        {
          x: x,
          y: y,
          fill: "#fff",
          textAnchor: "middle",
          alignmentBaseline: "middle",
          style: { fontSize: 12 },
        },
        text.value
      );
    }
  }
  class StartNodeModel extends CircleNodeModel {
    setAttributes() {
      this.r = 30;
      this.isSelected = false;
    }
    getConnectedTargetRules() {
      const rules = super.getConnectedTargetRules();
      const geteWayOnlyAsTarget = {
        message: "开始节点只能连出,不能连入!",
        validate: (source?: BaseNodeModel, target?: BaseNodeModel) => {
          let isValid = true;
          if (target) {
            isValid = false;
          }
          return isValid;
        },
      };
      rules.push(geteWayOnlyAsTarget);
      return rules;
    }
    getConnectedSourceRules() {
      const rules = super.getConnectedSourceRules();
      const onlyOneOutEdge = {
        message: "开始节点只能连出一条线!",
        validate: (source?: BaseNodeModel, target?: BaseNodeModel) => {
          let isValid = true;
          if (source?.outgoing.edges.length) {
            isValid = false;
          }
          return isValid;
        },
      };
      rules.push(onlyOneOutEdge);
      return rules;
    }
    createId() {
      return uuidv4();
    }
  }
  lf.value?.register({
    type: "start",
    view: StartNode,
    model: StartNodeModel,
  });
  /**
   * @description 自定义发起节点
   */
  class LaunchNode extends RectNode {
    getShape() {
      const { x, y, width, height, radius } = this.props.model;
      const style = this.props.model.getNodeStyle();
      return h("g", {}, [
        h("rect", {
          ...style,
          x: x - width / 2,
          y: y - height / 2,
          rx: radius,
          ry: radius,
          width: 120,
          height: 50,
          stroke: "#000",
          fill: "#000",
        }),
      ]);
    }
    getText() {
      const { x, y, text, width, height } = this.props.model;
      return h(
        "foreignObject",
        {
          x: x - width / 2,
          y: y - height / 2,
          className: "foreign-object",
          style: {
            width: width,
            height: height,
          },
        },
        [
          h(
            "p",
            {
              style: {
                fontSize: 12,
                width: width,
                height: height,
                lineHeight: height + "px",
                whiteSpace: "nowrap",
                overflow: "hidden",
                textOverflow: "ellipsis",
                textAlign: "center",
                padding: "0 8px",
                boxSizing: "border-box",
                margin: "0",
                color: "#fff",
              },
            },
            text.value
          ),
        ]
      );
    }
  }
  class LaunchModel extends RectNodeModel {
    setAttributes() {
      this.width = 120;
      this.height = 50;
      this.radius = 4;
      this.isSelected = false;
    }
    getConnectedSourceRules() {
      const rules = super.getConnectedSourceRules();
      const notAsTarget = {
        message: "不能连接自己",
        validate: (source?: BaseNodeModel, target?: BaseNodeModel) => {
          let isValid = true;
          if (source?.id === target?.id) {
            isValid = false;
          }
          return isValid;
        },
      };
      rules.push(notAsTarget);
      return rules;
    }
    createId() {
      return uuidv4();
    }
  }
  lf.value?.register({
    type: "launch",
    view: LaunchNode,
    model: LaunchModel,
  });
  /**
   * @description 自定义审批节点
   */
  class ApproverNode extends RectNode {
    getShape() {
      const { x, y, width, height, radius } = this.props.model;
      const style = this.props.model.getNodeStyle();
      return h("g", {}, [
        h("rect", {
          ...style,
          x: x - width / 2,
          y: y - height / 2,
          rx: radius,
          ry: radius,
          width: 120,
          height: 50,
          stroke: "#facd91",
          fill: "#facd91",
        }),
      ]);
    }
    getText() {
      const { x, y, text, width, height } = this.props.model;
      return h(
        "foreignObject",
        {
          x: x - width / 2,
          y: y - height / 2,
          className: "foreign-object",
          style: {
            width: width,
            height: height,
          },
        },
        [
          h(
            "p",
            {
              style: {
                fontSize: 12,
                width: width,
                height: height,
                lineHeight: height + "px",
                whiteSpace: "nowrap",
                overflow: "hidden",
                textOverflow: "ellipsis",
                textAlign: "center",
                padding: "0 8px",
                boxSizing: "border-box",
                margin: "0",
              },
            },
            text.value
          ),
        ]
      );
    }
  }
  class ApproverModel extends RectNodeModel {
    setAttributes() {
      this.width = 120;
      this.height = 50;
      this.radius = 4;
      this.isSelected = false;
    }
    getConnectedSourceRules() {
      const rules = super.getConnectedSourceRules();
      const notAsTarget = {
        message: "不能连接自己",
        validate: (source?: BaseNodeModel, target?: BaseNodeModel) => {
          let isValid = true;
          if (source?.id === target?.id) {
            isValid = false;
          }
          return isValid;
        },
      };
      rules.push(notAsTarget);
      return rules;
    }
    createId() {
      return uuidv4();
    }
  }
  lf.value?.register({
    type: "approver",
    view: ApproverNode,
    model: ApproverModel,
  });
  /**
   * @description 自定义连接点节点
   */
  class LinkNode extends RectNode {
    getShape() {
      const { x, y, width, height, radius } = this.props.model;
      const style = this.props.model.getNodeStyle();
      return h("g", {}, [
        h("rect", {
          ...style,
          x: x - width / 2,
          y: y - height / 2,
          rx: radius,
          ry: radius,
          width: 120,
          height: 50,
          stroke: "#caf982",
          fill: "#caf982",
        }),
      ]);
    }
    getText() {
      const { x, y, text, width, height } = this.props.model;
      return h(
        "foreignObject",
        {
          x: x - width / 2,
          y: y - height / 2,
          className: "foreign-object",
          style: {
            width: width,
            height: height,
          },
        },
        [
          h(
            "p",
            {
              style: {
                fontSize: 12,
                width: width,
                height: height,
                lineHeight: height + "px",
                whiteSpace: "nowrap",
                overflow: "hidden",
                textOverflow: "ellipsis",
                textAlign: "center",
                padding: "0 8px",
                boxSizing: "border-box",
                margin: "0",
              },
            },
            text.value
          ),
        ]
      );
    }
  }
  class LinkModel extends RectNodeModel {
    setAttributes() {
      this.width = 120;
      this.height = 50;
      this.radius = 4;
      this.isSelected = false;
    }
    getConnectedSourceRules() {
      const rules = super.getConnectedSourceRules();
      const notAsTarget = {
        message: "不能连接自己",
        validate: (source?: BaseNodeModel, target?: BaseNodeModel) => {
          let isValid = true;
          if (source?.id === target?.id) {
            isValid = false;
          }
          return isValid;
        },
      };
      rules.push(notAsTarget);
      return rules;
    }
    createId() {
      return uuidv4();
    }
  }
  lf.value?.register({
    type: "link",
    view: LinkNode,
    model: LinkModel,
  });
  /**
   * @description 自定义传阅节点
   */
  class ReviewNode extends RectNode {
    getShape() {
      const { x, y, width, height, radius } = this.props.model;
      const style = this.props.model.getNodeStyle();
      return h("g", {}, [
        h("rect", {
          ...style,
          x: x - width / 2,
          y: y - height / 2,
          rx: radius,
          ry: radius,
          width: 120,
          height: 50,
          stroke: "#81d3f8",
          fill: "#81d3f8",
        }),
      ]);
    }
    getText() {
      const { x, y, text, width, height } = this.props.model;
      return h(
        "foreignObject",
        {
          x: x - width / 2,
          y: y - height / 2,
          className: "foreign-object",
          style: {
            width: width,
            height: height,
          },
        },
        [
          h(
            "p",
            {
              style: {
                fontSize: 12,
                width: width,
                height: height,
                lineHeight: height + "px",
                whiteSpace: "nowrap",
                overflow: "hidden",
                textOverflow: "ellipsis",
                textAlign: "center",
                padding: "0 8px",
                boxSizing: "border-box",
                margin: "0",
              },
            },
            text.value
          ),
        ]
      );
    }
  }
  class ReviewModel extends RectNodeModel {
    setAttributes() {
      this.width = 120;
      this.height = 50;
      this.radius = 4;
      this.isSelected = false;
    }
    getConnectedSourceRules() {
      const rules = super.getConnectedSourceRules();
      const notAsTarget = {
        message: "不能连接自己",
        validate: (source?: BaseNodeModel, target?: BaseNodeModel) => {
          let isValid = true;
          if (source?.id === target?.id) {
            isValid = false;
          }
          return isValid;
        },
      };
      rules.push(notAsTarget);
      return rules;
    }
    createId() {
      return uuidv4();
    }
  }
  lf.value?.register({
    type: "review",
    view: ReviewNode,
    model: ReviewModel,
  });
  /**
   * @description 结束节点
   */
  class FinishNode extends CircleNode {
    getShape() {
      const { x, y } = this.props.model;
      const style = this.props.model.getNodeStyle();
      return h("g", {}, [
        h("circle", {
          ...style,
          cx: x,
          cy: y,
          r: 30,
          stroke: "#000",
          fill: "#000",
        }),
      ]);
    }
    getText() {
      const { x, y, text } = this.props.model;
      return h(
        "text",
        {
          x: x,
          y: y,
          fill: "#fff",
          textAnchor: "middle",
          alignmentBaseline: "middle",
          style: { fontSize: 12 },
        },
        text.value
      );
    }
  }
  class FinishModel extends CircleNodeModel {
    setAttributes() {
      this.r = 30;
      this.isSelected = false;
    }
    getConnectedSourceRules() {
      const rules = super.getConnectedSourceRules();
      const notAsTarget = {
        message: "终止节点不能作为连线的起点",
        validate: () => false,
      };
      rules.push(notAsTarget);
      return rules;
    }
    createId() {
      return uuidv4();
    }
  }
  lf.value?.register({
    type: "end",
    view: FinishNode,
    model: FinishModel,
  });
  /**
   * @description 虚线
   */
  class DashedLineModel extends PolylineEdgeModel {
    getEdgeStyle() {
      const style = super.getEdgeStyle();
      style.stroke = "#000";
      style.strokeDasharray = "3 3";
      return style;
    }
  }
  lf.value?.register({
    type: "dashedLine",
    view: PolylineEdge,
    model: DashedLineModel,
  });
  /**
   * @description 开始的连线
   */
  class StartPolylineModel extends PolylineEdgeModel {
    setAttributes() {
      this.isSelected = false;
      this.isHitable = false;
    }
  }
  lf.value?.register({
    type: "startPolyline",
    view: PolylineEdge,
    model: StartPolylineModel,
  });
}

注册logicflow并使用自定义节点

<template>
  <div class="logic-flow-container">
    <div class="logic-flow-header">
      <el-button type="primary" @click="getData">获取数据</el-button>
      <el-button type="primary" @click="submit">提交</el-button>
    </div>
    <div class="logic-flow-main">
      <div class="logic-flow" ref="logicFlowRef"></div>
      <Setting
        class="logic-flow-setting"
        :data="nodeData!"
        :lf="lf"
        :type="state.settingType"
      ></Setting>
      <NodePanel :lf="lf"></NodePanel>
    </div>
    <!-- 当lf有值 才能注册事件 -->
    <Control v-if="lf" :lf="lf"></Control>
  </div>
</template>

<script lang="ts">
export default { name: "LogicFlow" };
</script>
<script lang="ts" setup>
import LogicFlow from "@logicflow/core";
import "@logicflow/core/lib/style/index.css";
import "@logicflow/extension/lib/style/index.css";
import { onMounted, reactive, ref, ShallowRef, shallowRef } from "vue";
import NodePanel from "./components/node-panel.vue";
import { registeNode, registerKeyboard, requiredConfig } from "./index";
import { ElMessage } from "element-plus";
import Control from "./components/control.vue";
import Setting from "./components/setting.vue";
import { SettingType } from "@/types/logic-flow";

const logicFlowRef = ref<HTMLDivElement>();
const nodeData = ref<LogicFlow.NodeData | LogicFlow.EdgeData>(); // 节点数据
const state = reactive({
  settingType: "all" as SettingType,
});
const lf = shallowRef<LogicFlow>();

const getSettingInfo = (data: LogicFlow.NodeData | LogicFlow.EdgeData) => {
  switch (data.type) {
    case "launch":
      nodeData.value = data;
      state.settingType = data.type;
      break;
    case "approver":
      nodeData.value = data;
      state.settingType = data.type;
      break;
    case "link":
      nodeData.value = data;
      state.settingType = data.type;
      break;
    case "review":
      nodeData.value = data;
      state.settingType = data.type;
      break;
    case "polyline":
    case "dashedLine":
      nodeData.value = data;
      state.settingType = data.type;
      break;
  }
};
/**
 * @description 注册事件
 */
const initEvent = (lf: ShallowRef<LogicFlow | undefined>) => {
  lf.value?.on("blank:click", (e) => {
    state.settingType = "all";
  });
  lf.value?.on("node:mousedown", ({ data }) => {
    lf.value?.selectElementById(data.id, false);
    getSettingInfo(data);
  });
  lf.value?.on("edge:click", ({ data }) => {
    lf.value?.selectElementById(data.id, false);
    getSettingInfo(data);
  });
  lf.value?.on("connection:not-allowed", (data) => {
    ElMessage.error(data.msg);
    return false;
  });
  lf.value?.on("node:dnd-add", ({ data }) => {
    // 选中节点 更改信息
    lf.value?.selectElementById(data.id, false);
    getSettingInfo(data);
    lf.value?.container.focus(); // 聚焦 能够使用键盘操作
  });
};
/**
 * @description 获取数据
 */
const getData = () => {
  console.log(lf.value?.getGraphData());
};
/**
 * @description 提交 验证数据
 */
const submit = () => {
  const { nodes } = lf.value?.getGraphData() as LogicFlow.GraphData;
  for (let index = 0; index < nodes.length; index++) {
    const data = nodes[index];
    const { properties } = data;
    // 循环配置项
    for (const key in properties) {
      // 数组配置项 判断是否为空
      if (Array.isArray(properties[key])) {
        if (requiredConfig[key] && properties[key].length === 0) {
          return ElMessage.error(
            `${data.text?.value}节点 ${requiredConfig[key]}`
          );
        }
      } else {
        // 非数组配置项 判断是否为空
        if (requiredConfig[key] && !properties[key]) {
          return ElMessage.error(
            `${data.text?.value}节点 ${requiredConfig[key]}`
          );
        }
      }
    }
  }
  console.log(lf.value?.getGraphData());
};

onMounted(() => {
  lf.value = new LogicFlow({
    container: logicFlowRef.value!,
    grid: true,
    keyboard: {
      enabled: true,
      shortcuts: registerKeyboard(lf, nodeData),
    },
    textEdit: false,
  });
  registeNode(lf);
  initEvent(lf);
  lf.value.render({
    nodes: [
      {
        id: "node_1",
        type: "start",
        x: 100,
        y: 300,
        properties: {
          width: 60,
          height: 60,
        },
        text: {
          x: 100,
          y: 300,
          value: "开始",
        },
      },
      {
        id: "node_2",
        type: "launch",
        x: 100,
        y: 400,
        properties: {
          width: 120,
          height: 50,
        },
        text: {
          x: 100,
          y: 400,
          value: "发起流程",
        },
      },
      {
        id: "node_3",
        type: "end",
        x: 100,
        y: 600,
        properties: {
          width: 60,
          height: 60,
        },
        text: {
          x: 100,
          y: 600,
          value: "结束",
        },
      },
    ],
    edges: [
      {
        id: "edge_1",
        type: "startPolyline",
        sourceNodeId: "node_1",
        targetNodeId: "node_2",
      },
      {
        id: "edge_2",
        type: "polyline",
        sourceNodeId: "node_2",
        targetNodeId: "node_3",
      },
    ],
  });
  lf.value.translateCenter(); // 将图形移动到画布中央
});
</script>

右侧的配置设置

  1. 通过componentIs实现不同的配置组件
  2. 通过logicflow的setProperties()函数,将配置项注入节点/边的properties对象中,目的是传参和回显的时候方便
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

人员选择组件

正选、反选、回显,可作为一个单独组件使用,目前使用的是el-tree,数据量大时可考虑虚拟树
在这里插入图片描述

<template>
  <MyDialog
    v-model="state.visible"
    title="选择人员"
    width="800px"
    @close="close"
    @cancel="close"
    @submit="submit"
  >
    <div class="type">
      <label>
        <span>发起人:</span>
        <el-radio-group v-model="state.type">
          <el-radio value="1">指定人员</el-radio>
          <el-radio value="2">角色</el-radio>
        </el-radio-group>
      </label>
    </div>
    <div class="panel">
      <div class="left-panel">
        <div class="panel-title">人员选择</div>
        <div class="search">
          <el-input
            v-model="state.filterText"
            style="width: 100%"
            placeholder="请输入筛选内容"
          />
        </div>
        <div class="content">
          <el-tree
            ref="treeRef"
            :data="state.data"
            show-checkbox
            node-key="key"
            :check-on-click-node="true"
            :filter-node-method="filterNode"
            @check-change="checkChange"
          />
        </div>
      </div>
      <div class="right-panel">
        <div class="panel-title">已选择</div>
        <div class="content checked-content">
          <el-tag
            v-for="tag in state.checkedList"
            :key="tag.key"
            closable
            type="primary"
            @close="handleClose(tag.key)"
          >
            {{ tag.label }}
          </el-tag>
        </div>
      </div>
    </div>
  </MyDialog>
</template>

<script lang="ts">
export default { name: "ChoosePerson" };
</script>
<script lang="ts" setup>
import { ElTree } from "element-plus";
import { nextTick, reactive, ref, watch } from "vue";
interface Tree {
  [key: string]: any;
}
const state = reactive({
  visible: false,
  type: "1",
  filterText: "",
  value: [],
  data: [
    {
      label: "张三",
      key: "1",
    },
    {
      label: "李四",
      key: "2",
    },
    {
      label: "王五",
      key: "3",
      children: [
        {
          label: "王五1",
          key: "31",
        },
        {
          label: "王五2",
          key: "32",
        },
      ],
    },
  ],
  checked: [] as string[],
  checkedList: [] as { label: string; key: string }[],
});
const treeRef = ref<InstanceType<typeof ElTree>>();
const emits = defineEmits(["submit"]);
/**
 * @description 筛选节点
 */
watch(
  () => state.filterText,
  (val) => {
    treeRef.value!.filter(val);
  }
);
const open = (checked: string[]) => {
  state.visible = true;
  nextTick(() => {
    state.checked = checked;
    treeRef.value?.setCheckedKeys([...checked], false);
  });
};
const close = () => {
  state.visible = false;
  state.filterText = "";
};
const submit = () => {
  emits("submit", state.checked, state.checkedList);
  close();
};
/**
 * @description 筛选节点
 */
const filterNode = (value: string, data: Tree) => {
  if (!value) return true;
  return data.label.includes(value);
};
/**
 * @description 选中节点
 */
const checkChange = () => {
  // 已选的id string[] 用来提交
  state.checked = treeRef.value
    ?.getCheckedNodes(true, false)
    .map((item) => item.key) as string[];
  // 已选的对象 {label: string; key: string}[] 用来展示tag
  state.checkedList = treeRef.value
    ?.getCheckedNodes(true, false)
    .map((item) => {
      return {
        label: item.label,
        key: item.key,
      };
    })!;
};
/**
 * @description 删除已选人员
 */
const handleClose = (key: string) => {
  state.checkedList = state.checkedList.filter((item) => item.key !== key);
  treeRef.value?.setCheckedKeys(
    state.checkedList.map((item) => item.key),
    false
  );
};
/**
 * @description 清空已选人员
 */
const clear = () => {
  state.checkedList = [];
  state.checked = [];
  treeRef.value?.setCheckedKeys([], false);
};
defineExpose({
  open,
  clear,
});
</script>

<style lang="scss" scoped>
.type {
  display: flex;
  align-items: center;
  margin-bottom: 20px;
  span {
    margin-right: 10px;
  }
  label {
    display: flex;
    align-items: center;
  }
}
.panel {
  width: 100%;
  display: flex;
  .left-panel {
    flex: 1;
    border: 1px solid #ccc;
    border-radius: 4px;
    .search {
      padding: 6px 10px;
    }
  }
  .right-panel {
    flex: 1;
    margin-left: 20px;
    border: 1px solid #ccc;
    border-radius: 4px;
  }
  .panel-title {
    padding: 10px 0;
    font-size: 14px;
    font-weight: bold;
    background-color: #f5f5f5;
    text-align: center;
  }
  .content {
    max-height: 400px;
    min-height: 200px;
    overflow: auto;
  }
  .checked-content {
    padding: 6px 10px;
    .el-tag + .el-tag {
      margin-left: 10px;
    }
  }
}
</style>

自定义快捷键,根据源码改编

/**
 * @description 注册键盘事件
 * @export
 * @param {(ShallowRef<LogicFlow | undefined>)} lf
 * @param {(Ref<LogicFlow.NodeData | LogicFlow.EdgeData | undefined>)} nodeData
 * @return {*}
 */
export function registerKeyboard(
  lf: ShallowRef<LogicFlow | undefined>,
  nodeData: Ref<LogicFlow.NodeData | LogicFlow.EdgeData | undefined>
) {
  let copyNodes = undefined as LogicFlow.NodeData[] | undefined;
  let TRANSLATION_DISTANCE = 40;
  let CHILDREN_TRANSLATION_DISTANCE = 40;
  const cv = [
    {
      keys: ["ctrl + c", "cmd + c"],
      callback: () => {
        copyNodes = lf.value?.getSelectElements().nodes;
      },
    },
    {
      keys: ["ctrl + v", "cmd + v"],
      callback: () => {
        const startOrEndNode = copyNodes?.find(
          (node) =>
            node.type === "start" ||
            node.type === "end" ||
            node.type === "launch"
        );
        if (startOrEndNode) {
          return true;
        }
        if (copyNodes) {
          lf.value?.clearSelectElements();
          copyNodes.forEach(function (node) {
            node.x += TRANSLATION_DISTANCE;
            node.y += TRANSLATION_DISTANCE;
            node.text!.x += TRANSLATION_DISTANCE;
            node.text!.y += TRANSLATION_DISTANCE;
            return node;
          });
          let addElements = lf.value?.addElements(
            { nodes: copyNodes, edges: [] },
            CHILDREN_TRANSLATION_DISTANCE
          );
          if (!addElements) return true;
          addElements.nodes.forEach(function (node) {
            nodeData.value = node.getData();
            return lf.value?.selectElementById(node.id, true);
          });
          CHILDREN_TRANSLATION_DISTANCE =
            CHILDREN_TRANSLATION_DISTANCE + TRANSLATION_DISTANCE;
        }
        return false;
      },
    },
    {
      keys: ["backspace"],
      callback: () => {
        const elements = lf.value?.getSelectElements(true);
        if (elements) {
          lf.value?.clearSelectElements();
          elements.edges.forEach(function (edge) {
            return edge.id && lf.value?.deleteEdge(edge.id);
          });
          elements.nodes.forEach(function (node) {
            if (
              node.type === "start" ||
              node.type === "end" ||
              node.type === "launch"
            ) {
              return true;
            }
            return node.id && lf.value?.deleteNode(node.id);
          });
          return false;
        }
      },
    },
  ];
  return cv;
}

仓库地址
在线预览

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/910749.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

本地部署运行 HuggingFace Diffuser 大模型

最近需要篡改大模型验证篡改定位水印的泛化性&#xff0c;但是由于网络连接原因无法直接使用&#x1f917;s Diffusers library &#xff0c;在网上找到了以下本地部署的方法。 目录 下载模型&#xff0c;部署至服务器上 1&#xff09;huggingface官网下载 2&#xff09;gi…

Bert框架详解(下)

一、Bert模型网络结构 1、Add与Normalize Add&#xff1a;将前面的数据传到后面层&#xff0c;残差网络同理。 Normalize &#xff1a;归一化&#xff0c;与batch normalize同理。 2、outputs(shifted right) outputs&#xff08;shifted right&#xff09;&#xff1a;指在…

操作系统学习笔记-3.2虚拟内存

文章目录 虚拟内存请求分页管理方式页面置换算法最佳置换算法工作原理OPT 算法的示例最佳置换算法的优点和缺点 先进先出置换算法最近最久未使用时钟置换算法时钟置换算法的工作原理&#xff1a;算法的步骤&#xff1a; 改进型时钟置换算法改进型时钟置换算法的特点&#xff1a…

【数学】通用三阶矩阵特征向量的快速求法 超简单!!!

目录 三个定理1、3个特征值&#xff08;即根互不相等&#xff09;例题实践2、2个特征值&#xff08;即有一个双重根&#xff09;3、1个特征值&#xff08;即有一个三重根&#xff09;定理证明 三个定理 本定理适用于 所有三阶矩阵 的特征向量求法&#xff01; 1、3个特征值&…

16通道AD采集方案,基于复旦微ARM + FPGA国产SoC处理器平台

测试数据汇总 表 1 本文带来的是基于复旦微FMQL20S400M四核ARM Cortex-A7(PS端) + FPGA可编程逻辑资源(PL端)异构多核SoC处理器设计的全国产工业评估板的AD采集案例。本次案例演示的开发环境如下: Windows开发环境:Windows 7 64bit、Windows 10 64bit PL端开发环境:P…

文件系统和日志管理

文件系统和日志管理 文件系统&#xff1a;文件系统提供了一个接口&#xff0c;用户用来访问硬件设备&#xff08;硬盘、光驱&#xff09;------------- 在硬件设备上对文件的管理 1、文件存储在硬盘上&#xff08;机械硬盘&#xff1a;一个扇区 2、文件中硬盘上的最小存储单位…

数据结构---排序总结

1.排序的时间复杂度&#xff08;均为平均值&#xff09; O(n^2) &#xff1a;冒泡排序&#xff0c;选择排序&#xff0c;插入排序。 O(n * log(n))&#xff1a;堆排序&#xff0c;快速排序&#xff0c;归并排序。 O(n^1.3)&#xff1a;希尔排序 2.空间复杂度&#xff1a; O(n) …

数据结构:七种排序及总结

文章目录 排序一插入排序1直接插入排序2希尔排序二选择排序3直接选择排序4堆排序三 交换排序5冒泡排序6快速排序四 归并排序7归并排序源码 排序 我们数据结构常见的排序有四大种&#xff0c;四大种又分为七小种&#xff0c;如图所示 排序&#xff1a;所谓排序&#xff0c;就是…

【操作系统】基于环形队列的生产消费模型

目录 一、单生产单消费 1.环形队列的实现 (1) void P(sem_t &sem); (2) void V(sem_t &sem); (3) RingQueue() (4) ~RingQueue() (5) void Push(const T &in); (6) void Pop(T *out); 2.上层调用逻辑 二、多生产多消费 1.环形队列的实现 (1) RingQueue…

Linux下的WatchDog

看门狗&#x1f415; 看门狗简介 看门狗定时器&#xff08;Watchdog Timer&#xff09;是一种定时器&#xff0c;用于检测系统是否正常运行。如果系统在规定时间内没有向看门狗定时器发送复位信号&#xff0c;看门狗定时器就会产生复位信号&#xff0c;使系统复位。看门狗定时…

基于SpringBoot的速食零食商城+LW示例参考

1.项目介绍 功能模块&#xff1a;管理端&#xff08;用户管理、账号管理、商品分类管理、商品信息管理、订单管理等&#xff09;&#xff0c;用户端&#xff08;商品信息、登录注册、我的订单等&#xff09;技术栈&#xff1a;SpringBoot&#xff0c;thymeleaf&#xff0c;MyB…

springboot020基于Java的免税商品优选购物商城设计与实现

&#x1f345;点赞收藏关注 → 文档最下方联系方式领取本源代码、数据库&#x1f345; 本人在Java毕业设计领域有多年的经验&#xff0c;陆续会更新更多优质的Java实战项目希望你能有所收获&#xff0c;少走一些弯路。&#x1f345;关注我不迷路&#x1f345; 一 、设计说明 1…

认识物联网

新一代信息技术 物联网 物物相连的互联网&#xff0c;即物联网&#xff0c;又称传感器常见的传感器 • 温度传感器 • 压力传感器 • 声音传感器 • 02 • */08521 物联网概念 • 通过射频识别&#xff0c;红外传感器&#xff0c;全球定位系统GPS&#xff0c;激光扫描…

CODESYS可视化桌面屏保-动态气泡制作详细案例

#一个用于可视化(HMI)界面的动态屏保的详细制作案例程序# 前言: 在工控自动化设备上,为了防止由于人为误触发或操作引起的故障,通常在触摸屏(HMI)增加屏幕保护界面,然而随着PLC偏IT化的发展,在控制界面上的美观程度也逐渐向上位机或网页前端方面发展,本篇模仿Windows…

Java基础——反射

反射是框架设计的灵魂 &#xff08;使用的前提条件&#xff1a;必须先得到代表的字节码的Class&#xff0c;Class类用于表示.class文件&#xff08;字节码&#xff09;&#xff09; 翻译成人话就是&#xff1a;反射技术&#xff0c;指的是加载类的字节码到内存&#xff0c;并以…

Node.js——文件上传

文件上传 插件&#xff1a;formidable&#xff0c;地址&#xff1a;https://www.npmjs.com/package/formidable&#xff0c;参考里面with Express.js部分。 html部分截图参考&#xff1a; 用express-generator生成的示例代码&#xff1a; const formidable require(formi…

PCA9632笔记

个人学习笔记&#xff0c;有错漏。具体请以官方数据手册为准 I2C地址 PCA9632使用I2C通信&#xff0c;I2C设备地址固定 发出START后输出访问设备地址&#xff08;8bit版本地址固定&#xff09; 0x62&#xff08;7位地址&#xff09; 地址最后一位为1读 为0写 8位写地址 0xC4…

【算法】递归+回溯+剪枝:78.子集

目录 1、题目链接 2、题目 3、解法(回溯剪枝) 4、代码 1、题目链接 78.子集&#xff08;LeetCode&#xff09; 2、题目 3、解法(回溯剪枝) 思路&#xff1a; 枚举子集&#xff08;答案&#xff09;的第一个数选谁&#xff0c;第二个数选谁&#xff0c;第三个数选谁&#x…

HCIP(7)-边界网关协议BGP基本配置(对等体peer,宣告network,引入import)

边界网关协议&#xff08;Border Gateway Protocol&#xff0c;BGP&#xff09;是一种用来在路由选择域之间交换网络层可达性信息&#xff08;Network Layer Reachability Information&#xff0c;NLRI&#xff09;的路由选择协议。由于不同的管理机构分别控制着他们各自的路由…

基于python的机器学习(二)—— 使用Scikit-learn库

目录 一、样本及样本划分 1.1 划分样本的方法 1.1.1 train_test_split()函数 1.1.2 时间序列划分 1.1.3 交叉验证 二、导入或创建数据集 2.1 导入Sklearn自带的样本数据集 2.2 利用Sklearn生成随机的数据集 2.3 读入自己创建的数据集 2.3.1 用Python直接读取文本文件…