antv/x6自定义节点+小地图+复制/删除节点+拖拽生成节点

1、 下载x6:

npm i @antv/x6@2.17.1

 同步下载所需要用到的x6插件:

"@antv/x6-plugin-clipboard": "^2.0.0", // 如果使用剪切板功能,需要安装此包

"@antv/x6-plugin-history": "^2.0.0", // 如果使用撤销重做功能,需要安装此包

"@antv/x6-plugin-keyboard": "^2.0.0", // 如果使用快捷键功能,需要安装此包

"@antv/x6-plugin-minimap": "^2.0.0", // 如果使用小地图功能,需要安装此包

"@antv/x6-plugin-scroller": "^2.0.0", // 如果使用滚动画布功能,需要安装此包

"@antv/x6-plugin-selection": "^2.0.0", // 如果使用框选功能,需要安装此包

"@antv/x6-plugin-snapline": "^2.0.0", // 如果使用对齐线功能,需要安装此包

"@antv/x6-plugin-dnd": "^2.0.0", // 如果使用 dnd 功能,需要安装此包

"@antv/x6-plugin-stencil": "^2.0.0", // 如果使用 stencil 功能,需要安装此包

"@antv/x6-plugin-transform": "^2.0.0", // 如果使用图形变换功能,需要安装此包

"@antv/x6-plugin-export": "^2.0.0", // 如果使用图片导出功能,需要安装此包

"@antv/x6-react-components": "^2.0.0", // 如果使用配套 UI 组件,需要安装此包

"@antv/x6-react-shape": "^2.0.0", // 如果使用 react 渲染功能,需要安装此包

"@antv/x6-vue-shape": "^2.0.0" // 如果使用 vue 渲染功能,需要安装此包

 2、页面中引入x6

<template><div class="entry-container" ref="screenRef"><div class="mapBox"><div id="mapBody"></div></div><div id="minimap"></div><TeleportContainer />  // 很重要,必须将TeleportContainer标签放置在html结构中</div></div>
</template><script setup>
import { register, getTeleport } from "@antv/x6-vue-shape/lib";
import { Graph, Path, Edge, Platform } from "@antv/x6";
import { Transform } from "@antv/x6-plugin-transform";
import { Selection } from "@antv/x6-plugin-selection";
import { Snapline } from "@antv/x6-plugin-snapline";
import { Keyboard } from "@antv/x6-plugin-keyboard";
import { Clipboard } from "@antv/x6-plugin-clipboard";
import { History } from "@antv/x6-plugin-history";
import { Scroller } from "@antv/x6-plugin-scroller";
import { Dnd } from "@antv/x6-plugin-dnd";
import { MiniMap } from "@antv/x6-plugin-minimap";
</script>

3、注册自定义节点:

// 引入自定义节点
import GroupNode from "./components/groupNode.vue";
import mapDrawer from "./components/mapDrawer.vue";// 注册外部节点
register({shape: "ArtNode",inherit: "vue-shape",width: 300,height: 72,component: ArtNode,ports: { ...ports },
});// 注册群组节点
register({shape: "GroupNode",inherit: "vue-shape",width: 325,height: 210,component: GroupNode,ports: { ...ports },
});const ports = {groups: {in: {position: "left",attrs: {circle: {r: 5,magnet: true,stroke: "#ccc",strokeWidth: 2,fill: "#fff",},},},out: {position: "right",attrs: {circle: {r: 5,magnet: true,stroke: "#ccc",strokeWidth: 2,fill: "#fff",},},},},items: [{id: "port1",group: "in",},{id: "port2",group: "out",},],
};

4、注册x6插件

// 注册插件
const handleUsePlugin = () => {graph.use(new Transform({resizing: true,rotating: true,})).use(new Selection({rubberband: true,showNodeSelectionBox: true,})).use(new Scroller({enabled: true,autoResize: true,pannable: true, //是否启用画布平移能力modifiers: ["alt", "ctrl", "meta"], //设置修饰键后需要点击鼠标并按下修饰键才能触发画布拖拽})).use(new MiniMap({container: document.getElementById("minimap"),width: 200,height: 160,scalable: false,minScale: 0.1,maxScale: 2,padding: 10,})).use(new Snapline()).use(new Keyboard()).use(new Clipboard()).use(new History());
};

5、注册边&连接器

Edge.config({markup: [{tagName: "path",selector: "wrap",attrs: {fill: "none",cursor: "pointer",stroke: "transparent",strokeLinecap: "round",},},{tagName: "path",selector: "line",attrs: {fill: "none",pointerEvents: "none",},},],connector: { name: "curveConnector" },attrs: {wrap: {connection: true,strokeWidth: 10,strokeLinejoin: "round",},line: {connection: true,stroke: "#A2B1C3",strokeWidth: 1,targetMarker: {name: "classic",size: 6,},},},
});// 注册连接器
Graph.registerConnector("curveConnector",(sourcePoint, targetPoint) => {const hgap = Math.abs(targetPoint.x - sourcePoint.x);const path = new Path();path.appendSegment(Path.createSegment("M", sourcePoint.x - 4, sourcePoint.y));path.appendSegment(Path.createSegment("L", sourcePoint.x + 12, sourcePoint.y));// 水平三阶贝塞尔曲线path.appendSegment(Path.createSegment("C",sourcePoint.x < targetPoint.x? sourcePoint.x + hgap / 2: sourcePoint.x - hgap / 2,sourcePoint.y,sourcePoint.x < targetPoint.x? targetPoint.x - hgap / 2: targetPoint.x + hgap / 2,targetPoint.y,targetPoint.x - 6,targetPoint.y));path.appendSegment(Path.createSegment("L", targetPoint.x + 2, targetPoint.y));return path.serialize();},true
);Graph.registerEdge("data-processing-curve", Edge, true);

6、初始化x6画布

// 创建画布
const handleInit = () => {// 创建画布graph = new Graph({container: document.getElementById("mapBody"),grid: true,width: 1500,height: 1200,autoResize: true,// 设置画布背景颜色background: {color: "#f0f3f5",},// 画布缩放 按住ctrlmousewheel: {enabled: true,global: true,modifiers: ["ctrl", "meta"],},// 设置连接线connecting: {snap: true, //开启和关闭连线过程中自动吸附anchor: "center",connectionPoint: "boundary",allowLoop: false, // 是否允许创建循环连线,即边的起始节点和终止节点为同一节点,默认为 trueallowBlank: false, // 是否允许连接到画布空白位置的点highlight: true,sourceAnchor: {name: "left",args: {dx: Platform.IS_SAFARI ? 4 : 8,},},targetAnchor: {name: "right",args: {dx: Platform.IS_SAFARI ? 4 : -8,},},//创建边createEdge() {return graph.createEdge({shape: "data-processing-curve",attrs: {line: {stroke: "#A2B1C3",strokeWidth: 1,targetMarker: {name: "block",width: 12,height: 8,},},},zIndex: -1,});},validateConnection({ targetMagnet }) {return !!targetMagnet;},},highlighting: {magnetAdsorbed: {name: "stroke",args: {attrs: {fill: "#006eff",stroke: "#006eff",},},},},});graph.centerContent();handleUsePlugin();

7、给x6画布绑定事件(实现复制、粘贴、删除、撤销、回复等功能)

// 画布方法&快捷键
const handleGraphMethods = () => {// 撤销/复原graph.bindKey(["meta+z", "ctrl+z"], () => {if (graph.canUndo()) {graph.undo();}return false;});graph.bindKey(["meta+y", "ctrl+y"], () => {if (graph.canRedo()) {graph.redo();}return false;});//复制graph.bindKey(["meta+c", "ctrl+c"], () => {const cells = graph.getSelectedCells();if (cells.length) {graph.copy(cells);}return false;});// 粘贴graph.bindKey(["meta+v", "ctrl+v"], () => {if (!graph.isClipboardEmpty()) {const cells = graph.paste({ offset: 32 });cells.forEach((item) => {let node = item.store.data;node.shape !== "data-processing-curve" && (node.data.id = node.id);});graph.cleanSelection();graph.select(cells);}return false;});// 删除graph.bindKey("backspace", () => {const cells =  graph.getSelectedCells();if (cells.length) {graph.removeCells(cells);}});// 右键菜单graph.on("node:contextmenu", ({ e, x, y, cell, view }) => {handleContextmenu(e);});// 历史改变graph.on("history:change", () => {canRedo.value = graph.canRedo();canUndo.value = graph.canUndo();});//  选中节点graph.on("cell:selected", ({ cell, index, options }) => {selectCell.value = cell;});// 取消选中节点graph.on("cell:unselected", ({ cell, index, options }) => {selectCell.value = null;});// 双击节点graph.on("cell:dblclick", ({ e, x, y, cell, view }) => {if (graph.isNode(cell)) {drawerData.value = JSON.parse(JSON.stringify(cell.getData()));drawerData.value.showDrawer = true;}});
};

8、拖拽生成节点

// 指定画布拖拽区dnd = new Dnd({target: graph,scaled: false,dndContainer: document.querySelector(".treeContent"),// 确保拖拽节点时和拖拽完成后节点id一致getDragNode: (node) => node.clone({ keepId: true }),getDropNode: (node) => {// 拖拽结束后let data = node.getData();drawerData.value = {...data,id: node.id,isCreate: true,showDrawer: true,};return node.clone({ keepId: true });},});handleGraphMethods();
};// 拖拽生成节点
const handleCreateNode = (type, e) => {// graph.cleanSelection();const node =type === "group"? graph.createNode({shape: "GroupNode",data: {type: "group",group: [{ type: "inside" }, { type: "outside" }],},}): graph.createNode({shape: "ArtNode",data: {type,},});dnd.start(node, e);
};

 9、保存/设置节点数据

// 保存
const handleToJson = () => {const json = graph.toJSON();json.knwlgMapId = route.query.pkId;submitNode(json).then(() => {proxy.$modal.msgSuccess("保存成功!");});
};// 获取地图数据
const getMapDetail = () => {loading.value = true;getNodeDetail({ knwlgMapId: route.query.pkId }).then(({ data: cells }) => {graph.fromJSON({ cells });loading.value = false;}).catch(() => {loading.value = false;});
};

完整代码:

<template><div class="entry-container" ref="screenRef" v-loading="loading"><div class="treeContent" style="width: 240px"><div class="flex mb20"><div class="mr5">插入节点</div><el-tooltip placement="bottom"><template #content>* 创建节点方式:<br />按住内部节点/外部节点/文章集合后往右侧区域拖拽<br />* 节点/边操作快捷键:<br />删除:点击选中节点/边后按Backspace键<br />撤销:操作后Ctrl+z<br />恢复:操作后Ctrl+y<br />复制:选中节点/边后Ctrl+c Ctrl+v<br />多选:直接框选或按住Ctrl单点<br />* 画布操作快捷键:<br />按住Ctrl+鼠标左键可拖拽画布<br />按住Ctrl+鼠标滚轮可缩放画布<br /></template><el-icon class="mr5" :size="14" color="#006eff"><QuestionFilled/></el-icon></el-tooltip></div><div class="sigleGroup"><divclass="shapeItem flex"v-for="(item, index) in shapeList":key="index"@mousedown="handleCreateNode(item.type, $event)"><img class="mr10" :src="item.imgSrc" /><span>{{ item.name }}</span></div></div></div><div class="mapContent"><div class="mapHeader flex"><div class="flex"><div v-if="!mapInfo.showIpt">{{ mapInfo.knwlgMapName }}</div><el-inputv-elseref="mapNameRef"maxlength="20"autofocusv-model.trim="mapInfo.knwlgMapName"placeholder="输入地图名称"show-word-limitclearablestyle="width: 200px"@blur="handleBlur"></el-input><el-buttonv-if="!mapInfo.showIpt"linktype="primary"icon="Edit"@click="handleShowIpt"></el-button></div><div class="flex"><el-tooltip content="撤销"><el-button:disabled="!canUndo"linktype="primary"icon="RefreshLeft"@click="handleUndo"></el-button></el-tooltip><el-tooltip content="恢复"><el-button:disabled="!canRedo"linktype="primary"icon="RefreshRight"@click="handleRedo"></el-button></el-tooltip><el-tooltip content="删除选中的线或节点"><el-button:disabled="!selectCell"linktype="primary"icon="Delete"@click="handleDelete()"></el-button></el-tooltip><el-tooltip content="复制选中的线或节点"><el-button:disabled="!selectCell"linktype="primary"icon="CopyDocument"@click="handleCopy"></el-button></el-tooltip><el-tooltip content="全屏"><el-buttonlinktype="primary"icon="FullScreen"@click="toggle"></el-button></el-tooltip><el-divider direction="vertical" /><el-button type="primary" @click="handleToJson">保存</el-button><el-button @click="handleClosePage">关闭</el-button></div></div><div class="mapBox"><div id="mapBody"></div></div><div id="minimap"></div><TeleportContainer /></div><mapDrawerv-model="drawerData"@handleUpdateData="handleUpdateData"@handleDelCell="handleDelete"/></div>
</template><script setup name="Ekms/map/grapth">
import { getNodeDetail, submitNode, editName } from "@/api/ekms/map";
import { register, getTeleport } from "@antv/x6-vue-shape/lib";
import { Graph, Path, Edge, Platform } from "@antv/x6";
import { Transform } from "@antv/x6-plugin-transform";
import { Selection } from "@antv/x6-plugin-selection";
import { Snapline } from "@antv/x6-plugin-snapline";
import { Keyboard } from "@antv/x6-plugin-keyboard";
import { Clipboard } from "@antv/x6-plugin-clipboard";
import { History } from "@antv/x6-plugin-history";
import { Scroller } from "@antv/x6-plugin-scroller";
import { Dnd } from "@antv/x6-plugin-dnd";
import { MiniMap } from "@antv/x6-plugin-minimap";
import { useFullscreen } from "@vueuse/core";
import ContextMenu from "@imengyu/vue3-context-menu";
import useTagsViewStore from "@/store/modules/tagsView";
import { ports, shapeList } from "./components/constans.js";
import ArtNode from "./components/artNode.vue";
import GroupNode from "./components/groupNode.vue";
import mapDrawer from "./components/mapDrawer.vue";const { proxy } = getCurrentInstance();
const screenRef = ref(null);
const { toggle } = useFullscreen(screenRef);
const route = useRoute();
const router = useRouter();
const mapInfo = ref({originName: route.query.knwlgMapName,knwlgMapName: route.query.knwlgMapName,
});let graph = null;
let dnd = null;
const TeleportContainer = getTeleport();
const canRedo = ref(false);
const canUndo = ref(false);
const loading = ref(false);
// const routeId = ref(route.query.pkId);
const selectCell = ref(null);
const drawerData = ref({showDrawer: false,title: "",group: [{ type: "inside" }, { type: "outside" }],
});onMounted(() => {if (!route.query.pkId) return;handleInit();// window.addEventListener("resize", resizeFn);
});// const resizeFn = () => {
//   const width = document.querySelector(".mapContent").offsetWidth;
//   const height = document.querySelector(".mapContent").offsetHeight;
//   graph.resize(width, height);
// };// onActivated(() => {
//   if (routeId.value != route.query.pkId) {
//     routeId.value = route.query.pkId;
//     graph && graph.dispose();
//     handleInit();
//   } else {
//     resizeFn();
//     graph.centerContent();
//   }
// });const handleShowIpt = () => {mapInfo.value.showIpt = true;nextTick(() => {proxy.$refs.mapNameRef.input.focus();});
};const handleBlur = async () => {mapInfo.value.showIpt = false;!mapInfo.value.knwlgMapName?.length &&(mapInfo.value.knwlgMapName = route.query.knwlgMapName);await editName({pkId: route.query.pkId,knwlgMapName: mapInfo.value.knwlgMapName,});
};// 获取地图数据
const getMapDetail = () => {loading.value = true;getNodeDetail({ knwlgMapId: route.query.pkId }).then(({ data: cells }) => {graph.fromJSON({ cells });loading.value = false;}).catch(() => {loading.value = false;});
};// 注册外部节点
register({shape: "ArtNode",inherit: "vue-shape",width: 300,height: 72,component: ArtNode,ports: { ...ports },
});// 注册群组节点
register({shape: "GroupNode",inherit: "vue-shape",width: 325,height: 210,component: GroupNode,ports: { ...ports },
});Edge.config({markup: [{tagName: "path",selector: "wrap",attrs: {fill: "none",cursor: "pointer",stroke: "transparent",strokeLinecap: "round",},},{tagName: "path",selector: "line",attrs: {fill: "none",pointerEvents: "none",},},],connector: { name: "curveConnector" },attrs: {wrap: {connection: true,strokeWidth: 10,strokeLinejoin: "round",},line: {connection: true,stroke: "#A2B1C3",strokeWidth: 1,targetMarker: {name: "classic",size: 6,},},},
});// 注册连接器
Graph.registerConnector("curveConnector",(sourcePoint, targetPoint) => {const hgap = Math.abs(targetPoint.x - sourcePoint.x);const path = new Path();path.appendSegment(Path.createSegment("M", sourcePoint.x - 4, sourcePoint.y));path.appendSegment(Path.createSegment("L", sourcePoint.x + 12, sourcePoint.y));// 水平三阶贝塞尔曲线path.appendSegment(Path.createSegment("C",sourcePoint.x < targetPoint.x? sourcePoint.x + hgap / 2: sourcePoint.x - hgap / 2,sourcePoint.y,sourcePoint.x < targetPoint.x? targetPoint.x - hgap / 2: targetPoint.x + hgap / 2,targetPoint.y,targetPoint.x - 6,targetPoint.y));path.appendSegment(Path.createSegment("L", targetPoint.x + 2, targetPoint.y));return path.serialize();},true
);Graph.registerEdge("data-processing-curve", Edge, true);// 创建画布
const handleInit = () => {// 创建画布graph = new Graph({container: document.getElementById("mapBody"),grid: true,width: 1500,height: 1200,autoResize: true,// 设置画布背景颜色background: {color: "#f0f3f5",},// 画布缩放 按住ctrlmousewheel: {enabled: true,global: true,modifiers: ["ctrl", "meta"],},// 设置连接线connecting: {snap: true, //开启和关闭连线过程中自动吸附anchor: "center",connectionPoint: "boundary",allowLoop: false, // 是否允许创建循环连线,即边的起始节点和终止节点为同一节点,默认为 trueallowBlank: false, // 是否允许连接到画布空白位置的点highlight: true,sourceAnchor: {name: "left",args: {dx: Platform.IS_SAFARI ? 4 : 8,},},targetAnchor: {name: "right",args: {dx: Platform.IS_SAFARI ? 4 : -8,},},//创建边createEdge() {return graph.createEdge({shape: "data-processing-curve",attrs: {line: {stroke: "#A2B1C3",strokeWidth: 1,targetMarker: {name: "block",width: 12,height: 8,},},},zIndex: -1,});},validateConnection({ targetMagnet }) {return !!targetMagnet;},},highlighting: {magnetAdsorbed: {name: "stroke",args: {attrs: {fill: "#006eff",stroke: "#006eff",},},},},});// resizeFn();graph.centerContent();getMapDetail();handleUsePlugin();// 指定画布拖拽区dnd = new Dnd({target: graph,scaled: false,dndContainer: document.querySelector(".treeContent"),// 确保拖拽节点时和拖拽完成后节点id一致getDragNode: (node) => node.clone({ keepId: true }),getDropNode: (node) => {// 拖拽结束后let data = node.getData();drawerData.value = {...data,id: node.id,isCreate: true,showDrawer: true,};return node.clone({ keepId: true });},});handleGraphMethods();
};// 画布方法&快捷键
const handleGraphMethods = () => {// 撤销/复原graph.bindKey(["meta+z", "ctrl+z"], () => {if (graph.canUndo()) {graph.undo();}return false;});graph.bindKey(["meta+y", "ctrl+y"], () => {if (graph.canRedo()) {graph.redo();}return false;});//复制graph.bindKey(["meta+c", "ctrl+c"], () => {const cells = graph.getSelectedCells();if (cells.length) {graph.copy(cells);}return false;});// 粘贴graph.bindKey(["meta+v", "ctrl+v"], () => {if (!graph.isClipboardEmpty()) {const cells = graph.paste({ offset: 32 });cells.forEach((item) => {let node = item.store.data;node.shape !== "data-processing-curve" && (node.data.id = node.id);});graph.cleanSelection();graph.select(cells);}return false;});// 删除graph.bindKey("backspace", () => {handleDelete();});// 右键菜单graph.on("node:contextmenu", ({ e, x, y, cell, view }) => {handleContextmenu(e);});// 历史改变graph.on("history:change", () => {canRedo.value = graph.canRedo();canUndo.value = graph.canUndo();});//  选中节点graph.on("cell:selected", ({ cell, index, options }) => {selectCell.value = cell;});// 取消选中节点graph.on("cell:unselected", ({ cell, index, options }) => {selectCell.value = null;});// 双击节点graph.on("cell:dblclick", ({ e, x, y, cell, view }) => {if (graph.isNode(cell)) {drawerData.value = JSON.parse(JSON.stringify(cell.getData()));drawerData.value.showDrawer = true;}});
};// 注册插件
const handleUsePlugin = () => {graph.use(new Transform({resizing: true,rotating: true,})).use(new Selection({rubberband: true,showNodeSelectionBox: true,})).use(new Scroller({enabled: true,autoResize: true,pannable: true, //是否启用画布平移能力modifiers: ["alt", "ctrl", "meta"], //设置修饰键后需要点击鼠标并按下修饰键才能触发画布拖拽})).use(new MiniMap({container: document.getElementById("minimap"),width: 200,height: 160,scalable: false,minScale: 0.1,maxScale: 2,padding: 10,})).use(new Snapline()).use(new Keyboard()).use(new Clipboard()).use(new History());
};// 拖拽生成节点
const handleCreateNode = (type, e) => {// graph.cleanSelection();const node =type === "group"? graph.createNode({shape: "GroupNode",data: {type: "group",group: [{ type: "inside" }, { type: "outside" }],},}): graph.createNode({shape: "ArtNode",data: {type,},});dnd.start(node, e);
};// 修改节点内容
const handleUpdateData = (id, value) => {let cell = graph.getCellById(id || selectCell.value.id);cell.updateData(value);if (drawerData.value.type === "group") {cell.resize(325, 50 + drawerData.value.group.length * 82);}drawerData.value.showDrawer = false;
};// 撤销
const handleUndo = () => {graph.undo();
};// 恢复
const handleRedo = () => {graph.redo();
};// 复制
const handleCopy = () => {const cells = graph.getSelectedCells();if (cells.length) {graph.copy(cells);if (!graph.isClipboardEmpty()) {const cells = graph.paste({ offset: 32 });graph.cleanSelection();graph.select(cells);}}
};// 右键菜单
const handleContextmenu = (e) => {const cells = graph.getSelectedCells();ContextMenu.showContextMenu({x: e.pageX,y: e.pageY,items: [{label: "复制(Ctrl+c&Ctrl+z)",disabled: cells.length === 0 ? true : false,onClick: () => {if (cells.length) {graph.copy(cells);if (!graph.isClipboardEmpty()) {const cells = graph.paste({ offset: 32 });// 解决复制的节点修改失效问题cells.forEach((item) => {let node = item.store.data;node.shape !== "data-processing-curve" &&(node.data.id = node.id);});graph.cleanSelection();graph.select(cells);}}},},{label: "删除(Backspace)",disabled: cells.length === 0 ? true : false,onClick: () => {if (cells.length) {graph.removeCells(cells);}},},],});
};// 删除节点/边
const handleDelete = (id) => {// 判断是新增删除还是修改删除const cells = id ? [graph.getCellById(id)] : graph.getSelectedCells();if (cells.length) {graph.removeCells(cells);}
};// 保存
const handleToJson = () => {const json = graph.toJSON();json.knwlgMapId = route.query.pkId;submitNode(json).then(() => {proxy.$modal.msgSuccess("保存成功!");});
};// 关闭页面
const handleClosePage = () => {let tag = useTagsViewStore().visitedViews.find((item) => item.path === "/ekms/map/grapth");proxy.$tab.closePage(tag).then(() => {router.push("/ekms/map/mapLibrary");});
};onBeforeUnmount(() => {graph && graph.dispose();
});
</script><style lang="scss" scoped>
.entry-container {display: flex;position: relative;background: #f0f3f5;height: calc(100vh - 70px);:deep(.treeContent) {padding: 20px;min-width: 240px;.shapeItem {padding: 0 10px;margin-bottom: 10px;height: 44px;line-height: 20px;font-size: 14px;background: #f6f8fd;border-radius: 4px;user-select: none;cursor: move;&:hover {border: 1px solid rgba(0, 112, 255, 1);}img {width: 28px;height: 28px;}}}:deep(.mapContent) {flex: 1;position: relative;.mapHeader {padding: 0 20px;width: 100%;height: 40px;line-height: 40px;background: #fff;justify-content: space-between;box-shadow: 0px 1px 0px 0px rgba(215, 220, 230, 1);}.mapBox {width: calc(100vw - 245px);height: calc(100vh - 110px);}#minimap {position: absolute;right: 0;bottom: 0;}}
}
:deep(#mapBody) {.x6-widget-selection-box {border-color: rgba(0, 110, 255, 1);}.x6-widget-transform {display: none !important;}.x6-edge-selected path:nth-child(2) {stroke: #006eff;stroke-dasharray: 8px, 2px;}
}
</style>

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

链表--543. 二叉树的直径/medium 理解度C

543. 二叉树的直径 1、题目2、题目分析3、复杂度最优解代码示例4、适用场景 1、题目 给你一棵二叉树的根节点&#xff0c;返回该树的 直径 。 二叉树的 直径 是指树中任意两个节点之间最长路径的 长度 。这条路径可能经过也可能不经过根节点 root 。 两节点之间路径的 长度 …

Gitee开源项目issue模板怎么写

一&#xff0c;目录和配置结构 itee Issue 模板配置存储于仓库的默认分支下 .gitee/ISSUE_TEMPLATE 隐藏目录中。 以下是一个完整 Issue 模板配置和对应的目录结构参考&#xff1a; 注意 如果你在另一个分支中创建模板&#xff0c;配置将不会生效&#xff0c;相关的功能协作…

face_recognition和图像处理中left、top、right、bottom解释

face_recognition.face_locations 介绍 加载图像文件后直接调用face_recognition.face_locations(image)&#xff0c;能定位所有图像中识别出的人脸位置信息&#xff0c;返回值是列表形式&#xff0c;列表中每一行是一张人脸的位置信息&#xff0c;包括[top, right, bottom, l…

SQL注入:报错注入

SQL注入系列文章&#xff1a;初识SQL注入-CSDN博客 SQL注入&#xff1a;联合查询的三个绕过技巧-CSDN博客 目录 什么是报错注入&#xff1f; 报错注入常用的3个函数 UpdateXML ExtractValue Floor rand&#xff08;随机数&#xff09; floor&#xff08;向上取整&…

项目性能优化之用compression-webpack-plugin插件开启gzip压缩

背景&#xff1a;vue项目打包发布后&#xff0c;部分js、css文件体积较大导致页面卡顿&#xff0c;于是使用webpack插件compression-webpack-plugin开启gzip压缩 前端配置vue.config.js 先通过npm下载compression-webpack-plugin包&#xff0c;npm i compression-webpack-plug…

服务器基础知识(IP地址与自动化技术的使用)

目录 ip地址是什么&#xff1f; 如何查看ip地址 Windows的命令提示符 图形化版本&#xff1a; 自动化技术的应用与意义 ip地址是什么&#xff1f; IP地址的主要作用是**为互联网上的每个网络和每台主机分配一个逻辑地址**。 它由32位二进制数字组成&#xff0c;通常分为四…

微信小程序(十七)自定义组件生命周期(根据状态栏自适配)

注释很详细&#xff0c;直接上代码 上一篇 新增内容&#xff1a; 1.获取手机状态栏的高度 2.验证attached可以修改数据 3.动态绑定样式数值 源码&#xff1a; myNav.js Component({lifetimes:{//相当于vue的created,因为无法更新数据被打入冷宫created(){},//相当于vue的mount…

Docker数据卷挂载(以容器化Mysql为例)

数据卷 数据卷是一个虚拟目录&#xff0c;是容器内目录与****之间映射的桥梁 在执行docker run命令时&#xff0c;使用**-v 本地目录&#xff1a;容器目录**可以完成本地目录挂载 eg.Mysql容器的数据挂载 1.在根目录root下创建目录mysql及三个子目录&#xff1a; cd ~ pwd m…

2982. 找出出现至少三次的最长特殊子字符串 II

字典树思路 用字典树搞一下就好了&#xff0c;比如aaaaa &#xff1a; a存5次 aa 4次以此类推&#xff5e; 字典树板子复习&#xff1a;P8306 【模板】字典树 这里这个清空方式 很好 因为很多时候memset T #include<iostream> #include<cstring> using namesp…

TensorFlow2实战-系列教程2:神经网络分类任务

&#x1f9e1;&#x1f49b;&#x1f49a;TensorFlow2实战-系列教程 总目录 有任何问题欢迎在下面留言 本篇文章的代码运行界面均在Jupyter Notebook中进行 本篇文章配套的代码资源已经上传 1、Mnist数据集 下载mnist数据集&#xff1a; %matplotlib inline from pathlib imp…

蓝桥杯省赛无忧 编程13 肖恩的投球游戏

#include <iostream> #include <vector> using namespace std; int main() {int n, q;cin >> n >> q;vector<int> a(n 1);vector<int> diff(n 2, 0); // 初始化差分数组// 读取初始球数&#xff0c;构建差分数组for (int i 1; i < …

Redis 击穿、穿透、雪崩产生原因解决思路

大家都知道&#xff0c;计算机的瓶颈之一就是IO&#xff0c;为了解决内存与磁盘速度不匹配的问题&#xff0c;产生了缓存&#xff0c;将一些热点数据放在内存中&#xff0c;随用随取&#xff0c;降低连接到数据库的请求链接,避免数据库挂掉。需要注意的是&#xff0c;无论是击穿…