基于 @antv/x6 实现流程图

文章目录

  • [1. 源码及其效果图](#1. 源码及其效果图)
    • [1.1 下载所需npm包](#1.1 下载所需npm包)
    • [2.1 效果图](#2.1 效果图)
    • [1.3 完整代码](#1.3 完整代码)
      • [1.3.1 x6Config.js](#1.3.1 x6Config.js)
      • [1.3.2 x6.vue](#1.3.2 x6.vue)

1. 源码及其效果图

1.1 下载所需npm包

javascript 复制代码
npm install @antv/x6

2.1 效果图

1.3 完整代码

1.3.1 x6Config.js

javascript 复制代码
import { Graph } from "@antv/x6";

const groupMock = {
  groups: {
    top: {
      position: "top",
      attrs: {
        circle: {
          magnet: true,
          stroke: "#8f8f8f",
          r: 1,
          style: {
            visibility: "hidden",
          },
        },
      },
    },
    left: {
      position: "left",
      attrs: {
        circle: {
          magnet: true,
          stroke: "#8f8f8f",
          r: 1,
          style: {
            visibility: "hidden",
          },
        },
      },
    },
    right: {
      position: "right",
      attrs: {
        circle: {
          magnet: true,
          stroke: "#8f8f8f",
          r: 1,
          style: {
            visibility: "hidden",
          },
        },
      },
    },
    bottom: {
      position: "bottom",
      attrs: {
        circle: {
          magnet: true,
          stroke: "#8f8f8f",
          r: 1,
          style: {
            visibility: "hidden",
          },
        },
      },
    },
    bottomR: {
      position: {
        name: "absolute",
        args: { x: 70, y: 50 },
      },
      attrs: {
        circle: {
          magnet: true,
          stroke: "#8f8f8f",
          r: 1,
          style: {
            visibility: "hidden",
          },
        },
      },
    },
  },
};

export const antvX6TypeConfig = (width, headerWidth) => {

  Graph.registerNode(
    "lane-contanier",
    {
      inherit: "rect",
      markup: [
        {
          tagName: "rect",
          selector: "body",
        },
        {
          tagName: "rect",
          selector: "name-rect",
        },
        {
          tagName: "text",
          selector: "name-text",
        },
      ],
      attrs: {
        body: {
          fill: "#FFF",
          stroke: "#5F95FF",
          strokeWidth: 1,
        },
        "name-rect": {
          width: headerWidth,
          height: 40,
          fill: "#5F95FF",
          stroke: "#fff",
          strokeWidth: 1,
          x: -1,
        },
        "name-text": {
          ref: "name-rect",
          refY: 0.5,
          refX: 0.5,
          textAnchor: "middle",
          fontWeight: "bold",
          fill: "#fff",
          fontSize: 18,
        },
      },
    },
    true
  );

  Graph.registerNode(
    "lane",
    {
      inherit: "rect",
      markup: [
        {
          tagName: "rect",
          selector: "body",
        },
        {
          tagName: "rect",
          selector: "name-rect",
        },
        {
          tagName: "text",
          selector: "name-text",
        },
      ],
      attrs: {
        body: {
          fill: "#FFF",
          stroke: "#5F95FF",
          strokeWidth: 1,
        },
        "name-rect": {
          width: width,
          height: 40,
          fill: "#5F95FF",
          stroke: "#fff",
          strokeWidth: 1,
          x: -1,
        },
        "name-text": {
          ref: "name-rect",
          refY: 0.5,
          refX: 0.5,
          textAnchor: "middle",
          fontWeight: "bold",
          fill: "#fff",
          fontSize: 18,
        },
      },
    },
    true
  );

  Graph.registerNode(
    "lane-rect",
    {
      inherit: "rect",
      width: 100,
      height: 60,
      attrs: {
        body: {
          strokeWidth: 1,
          stroke: "#5F95FF",
          fill: "#EFF4FF",
        },
        text: {
          fontSize: 18,
          fill: "#262626",
        },
      },
      ports: {
        ...groupMock,
        items: [
          { id: "top-1", group: "top" },
          { id: "left-1", group: "left" },
          { id: "right-1", group: "right" },
          { id: "bottom-1", group: "bottom" },
          { id: "bottom-1R", group: "bottomR" },
        ],
      },
    },
    true
  );

  Graph.registerNode(
    "lane-polygon",
    {
      inherit: "polygon",
      width: 80,
      height: 80,
      attrs: {
        body: {
          strokeWidth: 1,
          stroke: "#5F95FF",
          fill: "#EFF4FF",
          refPoints: "0,10 10,0 20,10 10,20",
        },
        text: {
          fontSize: 18,
          fill: "#262626",
        },
      },
      ports: {
        ...groupMock,
        items: [
          { id: "top-2", group: "top" },
          { id: "left-2", group: "left" },
          { id: "right-2", group: "right" },
          { id: "bottom-2", group: "bottom" },
        ],
      },
    },
    true
  );

  Graph.registerEdge(
    "lane-edge",
    {
      inherit: "edge",
      attrs: {
        line: {
          stroke: "#8A9EB8",
          strokeWidth: 2,
        },
      },
      label: {
        attrs: {
          label: {
            fill: "#8A9EB8",
            fontSize: 18,
          },
        },
      },
    },
    true
  );

  Graph.registerEdge(
    "lane-edge-revoke",
    {
      inherit: "edge",
      attrs: {
        line: {
          stroke: "#E96F0C",
          strokeWidth: 2,
        },
      },
      label: {
        attrs: {
          label: {
            fill: "#E96F0C",
            fontSize: 18,
          },
        },
      },
    },
    true
  );
}

// 盒子 - 流程已经过
const success_box = {
  attrs: {
    body: {
      fill: "#5cbf77",
      stroke: "#5cbf77",
      strokeWidth: 1
    },
    text: {
      fill: "#262626",
      lineHeight: 24,
      fontSize: 18,
    }
  }
}
// 盒子 - 未经过
const never_passed_box = {
  attrs: {
    body: {
      fill: "#f56c6c",
      stroke: "#f56c6c",
      strokeWidth: 1
    },
    text: {
      fill: "#262626",
      lineHeight: 24,
      fontSize: 18,
    }
  }
}
// 盒子 - 流程到达
const success_box_active = {
  attrs: {
    body: {
      fill: "#ffc000",
      stroke: "#ffc000",
      strokeWidth: 1
    },
    text: {
      fill: "#262626",
      lineHeight: 24,
      fontSize: 18,
    }
  }
}
// 盒子 - 跳过
const success_box_skip = {
  attrs: {
    body: {
      fill: "#bfbfbf",
      stroke: "#bfbfbf",
      strokeWidth: 1
    },
    text: {
      fill: "#262626",
      lineHeight: 24,
      fontSize: 18,
    }
  }
}
// 线条 - 流程已经过 - 是
const success_line = {
  labels: [{
    position: 0.5, // 沿路径50%位置
    attrs: {
      text: {
        text: "是",
        fill: "#5cbf77",
        stroke: "#5cbf77",
        fontSize: 18,
      }
    }
  }],
  attrs: {
    line: {
      stroke: "#5cbf77",
      strokeWidth: 2
    }
  },
  label: {
    attrs: {
      label: {
        text: "是",
        fill: "#5cbf77",
        fontSize: 18,
      },
    },
  },
}
// 线条 - 流程未经过 - 是
const never_passed_line = {
  labels: [{
    position: 0.5, // 沿路径50%位置
    attrs: {
      text: {
        text: "是",
        fill: "#f56c6c",
        stroke: "#f56c6c",
        fontSize: 18,
      }
    }
  }],
  attrs: {
    line: {
      stroke: "#f56c6c",
      strokeWidth: 2
    }
  },
  label: {
    attrs: {
      label: {
        text: "是",
        fill: "#f56c6c",
        fontSize: 18,
      },
    },
  },
}
// 线条 - 流程跳过 - 是
const success_line_skip = {
  labels: [{
    position: 0.5, // 沿路径50%位置
    attrs: {
      text: {
        text: "是",
        fill: "#bfbfbf",
        stroke: "#bfbfbf",
        fontSize: 18,
      }
    }
  }],
  attrs: {
    line: {
      stroke: "#bfbfbf",
      strokeWidth: 2
    }
  },
  label: {
    attrs: {
      label: {
        text: "是",
        fill: "#bfbfbf",
        fontSize: 18,
      },
    },
  },
}
// 线条 - 流程跳过 - 否
const success_line_skip_no = {
  labels: [{
    position: 0.5, // 沿路径50%位置
    attrs: {
      text: {
        text: "否",
        fill: "#bfbfbf",
        stroke: "#bfbfbf",
        fontSize: 18,
      }
    }
  }],
  attrs: {
    line: {
      stroke: "#bfbfbf",
      strokeWidth: 2
    }
  },
  label: {
    attrs: {
      label: {
        text: "否",
        fill: "#bfbfbf",
        fontSize: 18,
      },
    },
  },
}
// 线条 - 流程已经过-否字
const success_line_no = {
  labels: [{
    position: 0.5, // 沿路径50%位置
    attrs: {
      text: {
        text: "否",
        fill: "#5cbf77",
        stroke: "#5cbf77",
        fontSize: 18,
      }
    }
  }],
  attrs: {
    line: {
      stroke: "#5cbf77",
      strokeWidth: 2
    }
  },
  label: {
    attrs: {
      label: {
        text: "否",
        fill: "#5cbf77",
        fontSize: 18,
      },
    },
  },
}
// 线条 - 未经过-否字
const never_passed_line_no = {
  labels: [{
    position: 0.5, // 沿路径50%位置
    attrs: {
      text: {
        text: "否",
        fill: "#f56c6c",
        stroke: "#f56c6c",
        fontSize: 18,
      }
    }
  }],
  attrs: {
    line: {
      stroke: "#f56c6c",
      strokeWidth: 2
    }
  },
  label: {
    attrs: {
      label: {
        text: "否",
        fill: "#f56c6c",
        fontSize: 18,
      },
    },
  },
}
// 线条 - 流程已经过 - 无文字
const success_line_no_text = {
  attrs: {
    line: {
      stroke: "#5cbf77",
      strokeWidth: 2
    }
  },
}
// 线条 - 流程未经过 - 无文字
const never_passed_line_no_text = {
  attrs: {
    line: {
      stroke: "#f56c6c",
      strokeWidth: 2
    }
  },
}
// 线条 - 流程跳过 - 无文字
const success_line_skip_no_text = {
  labels: [{
    position: 0.5, // 沿路径50%位置
    attrs: {
      text: {
        text: "",
        fill: "#bfbfbf",
        stroke: "#bfbfbf",
        fontSize: 18,
      }
    }
  }],
  attrs: {
    line: {
      stroke: "#bfbfbf",
      strokeWidth: 2
    }
  },
  label: {
    attrs: {
      label: {
        text: "",
        fill: "#bfbfbf",
        fontSize: 18,
      },
    },
  },
}

export const dataMock = (node, flowLogRepVOS, width = 300, height = 800) => {
  return [
    {
      id: "1",
      shape: "lane",
      width: width,
      height: height,
      position: {
        x: 60,
        y: 40,
      },
      label: "节点1",
    }, {
      id: "2",
      shape: "lane",
      width: width,
      height: height,
      position: {
        x: 60 + width,
        y: 40,
      },
      label: "节点2",
    }, {
      id: "3",
      shape: "lane",
      width: width,
      height: height,
      position: {
        x: 60 + width * 2,
        y: 40,
      },
      label: "节点3",
    }, {
      id: "4",
      shape: "lane",
      width: width,
      height: height,
      position: {
        x: 60 + width * 3,
        y: 40,
      },
      label: "节点4",
    }, {
      id: "5",
      shape: "lane",
      width: width,
      height: height,
      position: {
        x: 60 + width * 4,
        y: 40,
      },
      label: "节点5",
    }, {
      id: "6",
      shape: "lane",
      width: width,
      height: height,
      position: {
        x: 60 + width * 5,
        y: 40,
      },
      label: "节点6",
    }, {
      id: "7",
      shape: "lane",
      width: width,
      height: height,
      position: {
        x: 60 + width * 6,
        y: 40,
      },
      label: "节点7\n(审批)",
    }, {
      id: "8",
      shape: "lane",
      width: width,
      height: height,
      position: {
        x: 60 + width * 7,
        y: 40,
      },
      label: "节点8",
    }, {
      id: "9",
      shape: "lane-rect",
      width: 100,
      height: 50,
      position: {
        x: 190,
        y: 120,
      },
      label: "开始",
      attrs: {
        body: {
          rx: 30,
          ry: 20,
          fill: "#5cbf77",
        },
      },
      parent: "1",
    }, {
      id: "10", // 10
      shape: "lane-rect",
      width: 310,
      height: 120,
      position: {
        x: 85,
        y: 260
      },
      label: "提交",
      ...(node >= 10 && node < 20 ? success_box_active : node >= 20 ? success_box : never_passed_box),
    }, {
      id: "11", // 20
      shape: "lane-rect",
      width: 240,
      height: 120,
      position: {
        x: 125 + width,
        y: 260
      },
      label: "审批",
      ...(node >= 20 && node < 30 ? success_box_active : node >= 30 ? success_box : never_passed_box),
    }, {
      id: "12", // 20+
      shape: "lane-polygon",
      width: 240,
      height: 80,
      position: {
        x: 125 + width,
        y: 480
      },
      label: '审批是否通过',
      ...(node >= 30 ? success_box : never_passed_box),
    }, {
      id: "13", // 30
      shape: "lane-rect",
      width: 240,
      height: 120,
      position: {
        x: 125 + width * 2,
        y: 260
      },
      label: "审批",
      ...(node >= 30 && node < 40 ? success_box_active : node >= 40 ? success_box : never_passed_box),
    }, {
      id: "14", // 30+
      shape: "lane-polygon",
      width: 240,
      height: 80,
      position: {
        x: 125 + width * 2,
        y: 480
      },
      label: '审批是否通过',
      ...(node >= 40 ? success_box : never_passed_box),
    }, {
      id: "15", // 40
      shape: "lane-rect",
      width: 240,
      height: 120,
      position: {
        x: 125 + width * 3,
        y: 260
      },
      label: "审批",
      ...(node >= 40 && node < 50 ? success_box_active : node >= 50 ? success_box : never_passed_box),
    }, {
      id: "16", // 40+
      shape: "lane-polygon",
      width: 240,
      height: 80,
      position: {
        x: 125 + width * 3,
        y: 480
      },
      label: '审批是否通过',
      ...(node >= 50 ? success_box : never_passed_box),
    }, {
      id: "17", // 50
      shape: "lane-rect",
      width: 240,
      height: 120,
      position: {
        x: 125 + width * 4,
        y: 260
      },
      label: "审批",
      ...(node >= 50 && node < 60 ? success_box_active : node >= 60 ? success_box : never_passed_box),
    }, {
      id: "18", // 50+
      shape: "lane-polygon",
      width: 240,
      height: 80,
      position: {
        x: 125 + width * 4,
        y: 480
      },
      label: '审批是否通过',
      ...(node >= 60 ? success_box : never_passed_box),
    }, {
      id: "20", // 60
      shape: "lane-rect",
      width: 240,
      height: 120,
      position: {
        x: 125 + width * 5,
        y: 260
      },
      label: "审批",
      ...(node >= 60 && node < 70 ? success_box_active : node >= 70 && flowLogRepVOS.some(x => x.node > 59 && x.node < 70) ? success_box : node >= 70 && !flowLogRepVOS.some(x => x.node > 59 && x.node < 70) ? success_box_skip : never_passed_box),
    }, {
      id: "21", // 60+
      shape: "lane-polygon",
      width: 240,
      height: 80,
      position: {
        x: 125 + width * 5,
        y: 480
      },
      label: '审批是否通过',
      ...(node >= 70 && flowLogRepVOS.some(x => x.node > 59 && x.node < 70) ? success_box : node >= 70 && !flowLogRepVOS.some(x => x.node > 59 && x.node < 70) ? success_box_skip : never_passed_box),

    }, {
      id: "22", // 70
      shape: "lane-rect",
      width: 240,
      height: 120,
      position: {
        x: 125 + width * 6,
        y: 760
      },
      label: "抄送",
      ...(node >= 70 && node < 80 ? success_box_active : node >= 80 ? success_box : never_passed_box),
    }, {
      id: "23", // 80
      shape: "lane-rect",
      width: 240,
      height: 120,
      position: {
        x: 125 + width * 7,
        y: 760
      },
      label: "抄送",
      ...(node >= 80 && node < 90 ? success_box_active : node >= 90 ? success_box : never_passed_box),
    }, {
      id: "24",
      shape: "lane-rect",
      width: 100,
      height: 50,
      position: {
        x: 195 + width * 7,
        y: 960
      },
      label: "结束",
      attrs: {
        body: {
          rx: 30,
          ry: 20,
        },
      },
      attrs: {
        body: {
          rx: 30,
          ry: 20,
          fill: node > 100 ? "#5cbf77" : '#f56c6c',
        }
      },
    }, {
      id: "25",
      shape: "lane-edge",
      source: { cell: "9", port: "bottom-2" },
      target: { cell: "10", port: "top-1" },
      ...success_line_no_text,
    }, {
      id: "26",
      shape: "lane-edge",
      source: { cell: "10", port: "right-2" },
      target: { cell: "11", port: "left-1" },
      ...(node >= 20 ? success_line_no_text : never_passed_line_no_text),
    }, {
      id: "27",
      shape: "lane-edge",
      source: { cell: "11", port: "bottom-2" },
      target: { cell: "12", port: "top-1" },
      ...(node >= 30 ? success_line_no_text : never_passed_line_no_text),
    }, {
      id: "28",
      shape: "lane-edge",
      source: { cell: "12", port: "right-2" },
      target: { cell: "13", port: "left-1" },
      ...(node >= 30 ? success_line : never_passed_line),
      vertices: [{ x: 180 + width * 1.6, y: 500 }],
    }, {
      id: "29",
      shape: "lane-edge",
      source: { cell: "12", port: "bottom-2" },
      target: { cell: "10", port: "bottom-1" },
      label: "否",
      vertices: [{ x: 200 + width * 1.1, y: 620 }],
    }, {
      id: "30",
      shape: "lane-edge",
      source: { cell: "13", port: "bottom-2" },
      target: { cell: "14", port: "top-1" },
      ...(node >= 40 ? success_line_no_text : never_passed_line_no_text),
    }, {
      id: "31",
      shape: "lane-edge",
      source: { cell: "14", port: "right-2" },
      target: { cell: "15", port: "left-1" },
      ...(node >= 40 ? success_line : never_passed_line),
      vertices: [{ x: 180 + width * 2.6, y: 500 }],
    }, {
      id: "32",
      shape: "lane-edge",
      source: { cell: "14", port: "bottom-2" },
      target: { cell: "10", port: "bottom-1" },
      label: "否",
      vertices: [{ x: 200 + width * 2.1, y: 620 }],
    }, {
      id: "33",
      shape: "lane-edge",
      source: { cell: "15", port: "bottom-2" },
      target: { cell: "16", port: "top-1" },
      ...(node >= 50 ? success_line_no_text : never_passed_line_no_text),
    }, {
      id: "34",
      shape: "lane-edge",
      source: { cell: "16", port: "right-2" },
      target: { cell: "17", port: "left-1" },
      ...(node >= 50 ? success_line : never_passed_line),
      vertices: [{ x: 180 + width * 3.6, y: 500 }],
    }, {
      id: "35",
      shape: "lane-edge",
      source: { cell: "16", port: "bottom-2" },
      target: { cell: "10", port: "bottom-1" },
      label: "否",
      vertices: [{ x: 200 + width * 2.1, y: 620 }],
    }, {
      id: "36",
      shape: "lane-edge",
      source: { cell: "17", port: "bottom-2" },
      target: { cell: "18", port: "top-1" },
      ...(node >= 60 ? success_line_no_text : never_passed_line_no_text),
    }, {
      id: "37",
      shape: "lane-edge",
      source: { cell: "18", port: "bottom-2" },
      target: { cell: "10", port: "bottom-1" },
      label: "否",
      vertices: [{ x: 200 + width * 4.1, y: 620 }],
    }, {
      id: "38",
      shape: "lane-edge",
      source: { cell: "18", port: "right-2" },
      target: { cell: "20", port: "top-2" },
      ...(node >= 60 ? success_line : never_passed_line),
      vertices: [{ x: 200 + width * 4.55, y: 500 }],
    }, {
      id: "40",
      shape: "lane-edge",
      source: { cell: "20", port: "bottom-2" },
      target: { cell: "21", port: "top-1" },
      ...(node >= 70 ? success_line_no_text : never_passed_line_no_text),
    }, {
      id: "41",
      shape: "lane-edge",
      source: { cell: "21", port: "bottom-2" },
      target: { cell: "10", port: "bottom-1" },
      label: "否",
      vertices: [{ x: 200 + width * 5.1, y: 620 }],
    }, {
      id: "43",
      shape: "lane-edge",
      source: { cell: "21", port: "right-2" },
      target: { cell: "22", port: "left-1" },
      ...(node >= 70 ? success_line : never_passed_line),
      vertices: [{ x: 200 + width * 5.5, y: 820 }],
    }, {
      id: "44",
      shape: "lane-edge",
      source: { cell: "22", port: "right-2" },
      target: { cell: "23", port: "left-1" },
      ...(node >= 80 ? success_line_no_text : never_passed_line_no_text),
    }, {
      id: "45",
      shape: "lane-edge",
      source: { cell: "23", port: "bottom-2" },
      target: { cell: "24", port: "top-1" },
      ...(node >= 100 ? success_line_no_text : never_passed_line_no_text),
    },
  ]
}

1.3.2 x6.vue

javascript 复制代码
<template>
  <div id="flowContainer"></div>
</template>

<script setup name="setup">
import { onMounted, ref, nextTick } from 'vue'
import { Graph } from "@antv/x6";
import { antvX6TypeConfig, dataMock } from "./x6Config.js";

const initGraph = (
  node,
  flowLogRepVOS
) => {
  antvX6TypeConfig(366, 2700)
  const graph = new Graph({
    container: document.getElementById("flowContainer"),
    height: 950,
    connecting: {
      router: "orth",
    },
    interacting: { nodeMovable: false, edgeMovable: false },
    translating: {
      restrict (cellView) {
        const cell = cellView.cell;
        const parentId = cell.prop("parent");
        if (parentId) {
          const parentNode = graph.getCellById(parentId);
          if (parentNode) {
            return parentNode.getBBox().moveAndExpand({
              x: 0,
              y: 30,
              width: 0,
              height: -30,
            });
          }
        }
        return cell.getBBox();
      },
    },
  });

  // 添加节点事件监听
  graph.on('node:click', ({ node }) => {
    console.log('节点被点击:', node.id);
    // 可以在这里添加自定义点击逻辑
  });

  graph.on('node:dblclick', ({ node }) => {
    console.log('节点被双击:', node.id);
  });

  graph.on('node:mouseenter', ({ node }) => {
    node.attr('body/stroke', '#ff0000'); // 鼠标移入高亮边框
  });

  graph.on('node:mouseleave', ({ node }) => {
    node.attr('body/stroke', '#5F95FF'); // 鼠标移出恢复边框
  });

  graph.on('node:contextmenu', ({ e, node }) => {
    e.preventDefault(); // 阻止默认右键菜单
    console.log('节点右键点击:', node.id);
    // 可以在这里显示自定义右键菜单
  });

  // 其他可用事件
  graph.on('node:selected', ({ node }) => {
    console.log('节点被选中:', node.id);
  });

  graph.on('node:unselected', ({ node }) => {
    console.log('节点取消选中:', node.id);
  });

  Promise.resolve(
    dataMock(node, flowLogRepVOS, 366, 1000)
  ).then((data) => {
    const cells = [];
    data.forEach((item) => {
      if (item.shape.includes("lane-edge")) {
        cells.push(graph.createEdge(item));
      } else {
        cells.push(graph.createNode(item));
      }
    });
    graph.resetCells(cells);
    graph.zoomToFit({ padding: 10, maxScale: 1 });
    nextTick(() => {
      let designWidth = 1920 - document.body.offsetWidth < 10 && document.body.offsetWidth < 1920 ? document.body.offsetWidth : 1920; //设计稿的宽度,根据实际项目调整
      let dom = document.querySelector("#flowContainer");
      if (dom) {
        graph.translate(-28 * (dom.clientWidth / designWidth), 0);
      }
    })
  });
};

onMounted(() => {
  initGraph(40, [{ node: 10 }])
})

// ------- 流程配置 --------

</script>

<style scoped lang="less"></style>
相关推荐
青柠编程5 小时前
基于 Spring Boot 与 Vue 的前后端分离课程答疑平台架构设计
vue.js·spring boot·后端
要加油哦~6 小时前
vue 构建工具如何选择 | vue-cli 和 vite的区别
前端·javascript·vue.js
Restart-AHTCM6 小时前
ES6核心基础
vue.js·前端框架·es6
李剑一7 小时前
为了免受再来一刀的痛苦,我耗时两天开发了一款《提肛助手》
前端·vue.js·rust
岁月宁静8 小时前
# Node.js+Vue3.5 实战:豆包快速 / 深度思考模型的流式调用方案
vue.js·人工智能·node.js
记得坚持9 小时前
vue2插槽
前端·vue.js
带只拖鞋去流浪9 小时前
Vue.js响应式API
前端·javascript·vue.js
前端小灰狼9 小时前
Ant Design Vue Vue3 table 表头筛选重置不清空Bug
前端·javascript·vue.js·bug
Copper peas9 小时前
Vue 中的 v-model 指令详解
前端·javascript·vue.js