123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753 |
- import { Graph, Cell, Node, Edge, EventArgs } from "@antv/x6";
- import { BorderSize, StructureType, TopicType } from "@/enum";
- import { addTopic, updateTopic } from "@/pages/mindmap/mindMap";
- import { cellStyle, MindMapProjectInfo, TopicItem } from "@/types";
- import { Dnd } from "@antv/x6-plugin-dnd";
- import { selectTopic } from "@/utils/mindmapHander";
- import { uuid } from "@repo/utils";
- import { getTheme } from "@/pages/mindmap/theme";
- import { traverseNode } from "@/utils/mindmapHander";
- import { EditMindMapElement, AddMindMapElement } from "@/api/systemDesigner";
- import { debounce, isEqual } from "lodash-es";
- enum positionType {
- left = "left",
- right = "right",
- outside = "outside",
- }
- // 拖拽主题时指示器
- let indicatorNode: Node | undefined;
- let indicatorEdge: Edge | undefined;
- // 插入的节点位置
- let insertNode: Node | undefined;
- let moveStart: { x: number; y: number } | undefined;
- let dragging = false;
- let currentShadowNode: Node | undefined;
- export const bindMindMapEvents = (
- graph: Graph,
- setMindProjectInfo?: (
- info: MindMapProjectInfo,
- init?: boolean,
- isSetting?: boolean,
- ignoreRender?: boolean
- ) => void,
- setSelectedCell?: (cell: Cell[]) => void,
- dndRef?: React.MutableRefObject<Dnd | undefined>
- ) => {
- graph.on("selection:changed", ({ selected }: { selected: Cell[] }) => {
- setSelectedCell?.(selected);
- });
- graph.on("node:mouseenter", (args) => {
- if (args.node.data?.type !== TopicType.main) {
- graph.disablePanning();
- }
- });
- graph.on("node:mouseleave", (args) => {
- graph.enablePanning();
- });
- /*********************************** 拖拽开始 *********************************/
- graph.on("node:mousedown", (args) => {
- moveStart = {
- x: args.x,
- y: args.y,
- };
- });
- graph.on("node:mousemove", (args) => {
- if (!args.node.data?.parentId || args.node.data?.shadow) {
- return;
- }
- if (
- !dragging &&
- moveStart &&
- (Math.abs(args.x - moveStart.x) > 5 || Math.abs(args.y - moveStart.y) > 5)
- ) {
- // 判断开始点是否有值,有值时当拖拽距离>5时开启影子节点
- dragging = true;
- const node = args.node;
- const shadowNode = graph.createNode({
- shape: "rect",
- width: node.size().width,
- height: node.size().height,
- x: args.x,
- y: args.y,
- label: node.data?.label,
- attrs: {
- body: {
- rx: node.data?.borderSize,
- ry: node.data?.borderSize,
- stroke: "#666",
- strokeWidth: 1,
- },
- },
- data: {
- ...node.data,
- shadow: true,
- },
- });
- setShadowMode(true, node, graph);
- currentShadowNode = shadowNode;
- dndRef?.current?.init();
- dndRef?.current?.start(shadowNode, args.e as unknown as MouseEvent);
- }
- });
- graph.on("node:mousemove", (args) => {
- // 节点拖拽-处理影子节点吸附问题
- if (currentShadowNode && setMindProjectInfo) {
- topicDragHander(graph, { x: args.x, y: args.y }, args.node);
- }
- });
- graph.on("node:mouseup", (args) => {
- // 拖拽结束
- if (indicatorNode && insertNode) {
- graph.removeCell(args.node.id + "-edge");
- setMindProjectInfo &&
- handleSwitchPosition(
- setMindProjectInfo,
- args.node.id,
- insertNode.id,
- undefined,
- graph
- );
- }
- // 成为自由节点
- if (
- currentShadowNode &&
- !indicatorNode &&
- args.node.data?.parentId &&
- canBeFreeNode(args.x, args.y, args.node)
- ) {
- setMindProjectInfo &&
- handleSwitchPosition(
- setMindProjectInfo,
- args.node.id,
- undefined,
- {
- x: args.x,
- y: args.y,
- },
- graph
- );
- }
- currentShadowNode && setShadowMode(false, args.node, graph);
- moveStart = undefined;
- dragging = false;
- currentShadowNode = undefined;
- dndRef?.current?.remove();
- if (indicatorEdge && indicatorNode) {
- graph.removeCells([indicatorEdge, indicatorNode]);
- indicatorEdge = undefined;
- indicatorNode = undefined;
- }
- });
- graph.on("node:move", (args) => {
- // 自由节点拖拽
- if (!args.node.data?.parentId) {
- setShadowMode(true, args.node, graph);
- }
- });
- graph.on("node:moving", (args) => {
- if (!args.node.data?.parentId && setMindProjectInfo) {
- setShadowMode(false, args.node, graph);
- topicDragHander(graph, { x: args.x, y: args.y }, args.node);
- }
- });
- graph.on("node:moved", (args) => {
- if (!args.node.data?.parentId) {
- setShadowMode(false, args.node, graph);
- }
- });
- /*********************************** 拖拽结束 *********************************/
- // 双击画布空白-新增自由主题
- graph.on("blank:dblclick", (args) => {
- if (setMindProjectInfo) {
- const topic = addTopic(
- TopicType.branch,
- setMindProjectInfo,
- graph,
- undefined,
- {
- x: args.x,
- y: args.y,
- setMindProjectInfo,
- type: TopicType.branch,
- label: "自由主题",
- borderSize: BorderSize.medium,
- }
- );
- selectTopic(graph, topic);
- }
- });
- /**
- * 节点数据更改
- */
- graph.on("node:change:data", (args) => {
- const { current, previous } = args;
- console.log("数据变更:", current, previous);
- // 收折子项 setMindProjectInfo更新会重新渲染
- if (current.collapsed !== previous.collapsed) {
- setMindProjectInfo &&
- updateTopic(
- args.cell.id,
- { collapsed: current.collapsed },
- setMindProjectInfo,
- graph
- );
- return;
- }
- if (current?.links && current.links.length !== previous?.links?.length) {
- setMindProjectInfo &&
- updateTopic(
- args.cell.id,
- { links: current.links },
- setMindProjectInfo,
- graph
- );
- }
- if (current?.border !== previous?.border) {
- setMindProjectInfo &&
- updateTopic(
- args.cell.id,
- { border: current.border },
- setMindProjectInfo,
- graph
- );
- }
- if (current?.summary !== previous?.summary) {
- setMindProjectInfo &&
- updateTopic(
- args.cell.id,
- { summary: current.summary },
- setMindProjectInfo,
- graph
- );
- }
- if (current?.extraModules !== previous?.extraModules) {
- setMindProjectInfo &&
- updateTopic(
- args.cell.id,
- { extraModules: current?.extraModules },
- setMindProjectInfo,
- graph
- );
- }
- // 改线段
- if (current?.edge && !isEqual(current.edge, previous?.edge)) {
- setMindProjectInfo &&
- updateTopic(
- args.cell.id,
- { edge: current.edge },
- setMindProjectInfo,
- graph
- );
- }
- // 本地缓存更新不会重新渲染
- if (args.cell.id.includes("-border")) {
- updateTopic(
- args.current.origin,
- { border: current },
- (info) => {
- setMindProjectInfo?.(info, false, false, true);
- },
- graph
- );
- } else {
- updateTopic(
- args.cell.id,
- current,
- (info) => {
- setMindProjectInfo?.(info, false, false, true);
- },
- graph
- );
- }
- });
- graph.on("node:resized", (args) => {
- args.node.setData({
- fixedWidth: true,
- width: args.node.size().width,
- height: args.node.size().height,
- });
- });
- graph.on(
- "node:change:position",
- debounce((args) => {
- const { current } = args;
- if (
- args.cell.isNode() &&
- !args.cell.data?.parentId &&
- args.cell.data.type !== TopicType.main
- ) {
- updateTopic(
- args.cell.id,
- { ...args.cell.data, x: current?.x, y: current?.y },
- (info) => {
- // localStorage.setItem("minMapProjectInfo", JSON.stringify(info));
- setMindProjectInfo && setMindProjectInfo(info);
- },
- graph
- );
- EditMindMapElement({
- ...args.cell.data,
- ...args.current,
- graphId: sessionStorage.getItem("projectId"),
- tools: "",
- });
- }
- }, 500)
- );
- /**
- * 连接线更改
- */
- graph.on("edge:change:*", (args) => {
- if (args.key === "vertices" || args.key === "labels") {
- const link = args.edge.toJSON();
- const source = args.edge.getSourceCell();
- source?.setData({
- links: source.data?.links.map((item: Edge.Properties) => {
- if (item.id === link.id) return link;
- return item;
- }),
- });
- }
- });
- /**
- * 连接线删除
- */
- graph.on("edge:removed", (args) => {
- if (args.edge.data?.isLink) {
- // @ts-ignore
- const source = graph.getCellById(args.edge.source?.cell as string);
- source?.setData(
- {
- links: source.data?.links?.filter(
- (item: Edge.Properties) => item.id !== args.edge.id
- ),
- },
- {
- deep: false,
- }
- );
- }
- });
- };
- const canBeFreeNode = (x: number, y: number, node: Node): boolean => {
- if (!moveStart) return false;
- return Math.abs(x - moveStart.x) > 50 || Math.abs(y - moveStart.y) > 50;
- };
- // 判断当前点在主图的位置
- const atNodePosition = (
- position: { x: number; y: number },
- x: number,
- y: number,
- width: number,
- height: number
- ): positionType => {
- if (
- position.x < x + width / 2 &&
- position.x > x &&
- position.y < y + height &&
- position.y > y
- ) {
- return positionType.left;
- }
- if (
- position.x < x + width &&
- position.x > x + width / 2 &&
- position.y < y + height &&
- position.y > y
- ) {
- return positionType.right;
- }
- return positionType.outside;
- };
- // 创建一个指示器
- const addIndicator = (
- x: number,
- y: number,
- graph: Graph,
- targetNode: Node,
- atPosition: positionType
- ) => {
- if (indicatorEdge && indicatorNode) {
- graph.removeCells([indicatorEdge, indicatorNode]);
- }
- indicatorNode = graph.addNode({
- shape: "rect",
- width: 100,
- height: 26,
- x,
- y,
- attrs: {
- body: {
- fill: "#fecb99",
- stroke: "#fc7e00",
- strokeWidth: 1,
- rx: 2,
- ry: 2,
- style: {
- opacity: 0.6,
- },
- },
- },
- });
- indicatorEdge = graph.addEdge({
- source: {
- cell: targetNode.id,
- anchor: {
- name: atPosition === positionType.left ? "left" : "right",
- args: {
- dx: atPosition === positionType.left ? 3 : -3,
- },
- },
- },
- target: {
- cell: indicatorNode.id,
- anchor: {
- name: atPosition === positionType.left ? "right" : "left",
- args: {
- dx: atPosition === positionType.left ? -3 : 3,
- },
- },
- },
- connector: "normal",
- attrs: {
- line: {
- stroke: "#fc7e00",
- strokeWidth: 2,
- sourceMarker: {
- name: "",
- },
- targetMarker: {
- name: "",
- },
- style: {
- opacity: 0.6,
- },
- },
- },
- });
- };
- // 添加指示器
- const setIndicator = (
- atPosition: positionType,
- targetNode: Node,
- graph: Graph
- ) => {
- switch (atPosition) {
- // 1、左侧位置
- case positionType.left: {
- const x = targetNode.position().x - 120;
- const y = targetNode.position().y + targetNode.size().height / 2 - 13;
- // 判断是左侧节点还是右侧节点
- if (targetNode.data?.parentId) {
- const parentNode = graph.getCellById(targetNode.data.parentId);
- // 左侧朝向的结构
- if (
- parentNode?.isNode() &&
- targetNode.position().x < parentNode.position().x
- ) {
- addIndicator(x, y, graph, targetNode, atPosition);
- }
- } else {
- // @ts-ignore
- const mindProjectInfo = graph.extendAttr.getMindProjectInfo();
- if (mindProjectInfo?.structure === StructureType.left) {
- addIndicator(x, y, graph, targetNode, atPosition);
- }
- }
- break;
- }
- // 2、右侧位置
- case positionType.right: {
- const x = targetNode.position().x + targetNode.size().width + 20;
- const y = targetNode.position().y + targetNode.size().height / 2 - 13;
- // 判断是左侧节点还是右侧节点
- if (targetNode.data?.parentId) {
- const parentNode = graph.getCellById(targetNode.data.parentId);
- // 右侧朝向的结构
- if (
- parentNode?.isNode() &&
- targetNode.position().x > parentNode.position().x
- ) {
- addIndicator(x, y, graph, targetNode, atPosition);
- }
- } else {
- // @ts-ignore
- const mindProjectInfo = graph.extendAttr.getMindProjectInfo();
- if (mindProjectInfo?.structure === StructureType.right) {
- addIndicator(x, y, graph, targetNode, atPosition);
- }
- }
- break;
- }
- // 外部位置
- case positionType.outside: {
- }
- }
- };
- // 判断目标节点是不是后代节点
- const isDescendantNode = (targetNode: Node, originNode: Node, graph: Graph) => {
- if (targetNode.data?.parentId === originNode.id) {
- return true;
- }
- let isChild = false; // 是后代节点
- const findParent = (parentId: string) => {
- const cell = graph.getCellById(parentId);
- if (cell?.isNode() && cell.data.parentId) {
- if (cell.data.parentId === originNode.id) {
- isChild = true;
- } else {
- findParent(cell.data.parentId);
- }
- }
- };
- targetNode.data?.parentId && findParent(targetNode.data.parentId);
- return isChild;
- };
- /**
- * 主题拖拽放置
- * @param graph
- * @param setMindProjectInfo
- */
- export const topicDragHander = (
- graph: Graph,
- position: { x: number; y: number },
- originNode: Node
- ) => {
- if (indicatorEdge) graph.removeCell(indicatorEdge);
- if (indicatorNode) graph.removeCell(indicatorNode);
- indicatorEdge = undefined;
- indicatorNode = undefined;
- // 1、 获取位置的地方是否有节点
- const nodes = graph.getNodes();
- nodes.forEach((targetNode) => {
- // 目标节点是自己、自己的后代、节点不做处理
- if (
- targetNode.id === originNode.id ||
- isDescendantNode(targetNode, originNode, graph)
- ) {
- return;
- }
- const { x, y } = targetNode.position();
- const { width, height } = targetNode.size();
- // 2、 找到是在节点哪个区域内
- const atPosition = atNodePosition(position, x, y, width, height);
- // 3、添加插入指示
- setIndicator(atPosition, targetNode, graph);
- // 4、 根据位置确定插入位置
- if ([positionType.left, positionType.right].includes(atPosition)) {
- insertNode = targetNode;
- }
- });
- };
- /**
- * 拖拽完毕,切换主题位置
- * @param setMindProjectInfo
- * @param sourceId 拖拽的节点
- * @param targetId 放置的节点
- * @param position 自由节点放置的位置
- * @returns
- */
- const handleSwitchPosition = (
- setMindProjectInfo: (info: MindMapProjectInfo) => void,
- sourceId: string,
- targetId?: string,
- position?: { x: number; y: number },
- graph?: Graph
- ) => {
- // @ts-ignore
- const mindmapProjectInfo: MindMapProjectInfo = graph.extendAttr.getMindProjectInfo();
- if (!mindmapProjectInfo) return;
- // 找到要拖拽的节点并删除
- let source: (TopicItem & cellStyle) | undefined;
- mindmapProjectInfo.topics.forEach((topic) => {
- if (topic.id === sourceId) {
- source = topic;
- }
- mindmapProjectInfo.topics = mindmapProjectInfo.topics.filter(
- (item) => item.id !== sourceId
- );
- });
- const topics = source
- ? mindmapProjectInfo.topics
- : traverseNode(mindmapProjectInfo.topics, (topic) => {
- const findItem = topic?.children?.find((item) => item.id === sourceId);
- if (findItem) {
- source = findItem;
- topic.children = topic.children?.filter(
- (item) => item.id !== sourceId
- );
- }
- });
- if (!source) return;
- // 处理节点样式
- const targetNode = targetId ? graph?.getCellById(targetId) : undefined;
- const theme = getTheme(
- mindmapProjectInfo.theme,
- targetNode?.isNode() && targetNode.data.type !== TopicType.main
- ? targetNode.data
- : undefined
- );
- source.type =
- targetNode?.data?.type === TopicType.main
- ? TopicType.branch
- : TopicType.sub;
- const themeObj = targetId ? theme[source.type] : theme[TopicType.branch];
- source.fill = {
- ...source.fill,
- ...themeObj.fill,
- };
- source.text = {
- ...source.text,
- ...themeObj.text,
- };
- source.stroke = {
- ...source.stroke,
- ...themeObj.stroke,
- };
- source.edge = {
- ...source.edge,
- ...themeObj.edge,
- };
- // 后代节点样式
- if (source.children)
- source.children = traverseNode(source.children, (topic) => {
- const theme = getTheme(mindmapProjectInfo.theme, source);
- topic.type = TopicType.sub;
- topic.fill = {
- ...topic.fill,
- ...theme[topic.type].fill,
- };
- topic.text = {
- ...topic.text,
- ...theme[topic.type].text,
- };
- topic.stroke = {
- ...topic.stroke,
- ...theme[topic.type].stroke,
- };
- topic.edge = {
- ...topic.edge,
- ...theme[topic.type].edge,
- };
- });
- if (targetId) {
- // 加入到目标节点下
- mindmapProjectInfo.topics = traverseNode(topics, (topic) => {
- if (topic.id === targetId) {
- if (!topic.children) {
- topic.children = [];
- }
- source &&
- topic.children.push({
- ...source,
- parentId: topic.id,
- });
- }
- });
- } else {
- const id = uuid();
- // 成为自由节点
- const freeTopic = {
- ...source,
- id,
- children: (source.children || []).map((item) => {
- item.parentId = id;
- return item;
- }),
- opacity: 100,
- x: position?.x,
- y: position?.y,
- type: TopicType.branch,
- parentId: null,
- links: (source.links || []).map((item) => {
- // 修改sourceId
- item.source = {
- ...item.source,
- id,
- };
- return item;
- }),
- };
- mindmapProjectInfo.topics.push(freeTopic);
- AddMindMapElement({
- ...freeTopic,
- graphId: sessionStorage.getItem("projectId"),
- });
- }
- setMindProjectInfo(mindmapProjectInfo);
- };
- /**
- * 设置影子模式-拖拽时变灰
- * @param node
- * @param graph
- */
- export const setShadowMode = (enable: boolean, node: Node, graph: Graph) => {
- const data = node.getData();
- node.setData({
- opacity: enable ? 60 : 100,
- });
- if (data?.children?.length) {
- traverseNode(data.children, (topic) => {
- const cell = graph.getCellById(topic.id);
- const edge = graph.getCellById(topic.id + "-edge");
- if (cell && cell.isNode()) {
- cell.setData({
- opacity: enable ? 60 : 100,
- });
- }
- if (edge && edge.isEdge()) {
- edge.setAttrs({
- line: {
- style: {
- opacity: enable ? 0.6 : 1,
- },
- },
- });
- }
- });
- }
- };
|