You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1319 lines
33 KiB
Vue

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<!--
绘制图件的组件
add by RYG
-->
<template>
<!-- <ClientOnly> -->
<!-- 绘图区域 -->
<div class="canvas-container" ref="containerRef" @wheel.prevent="" v-bind="$attrs">
<!-- -->
<canvas id="ruler-horizontal" ref="hRulerRef" :width="viewSize.width * 2 + 'px'" :height="rulerHeight * 2 + 'px'"
:style="{
width: viewSize.width + 'px',
height: rulerHeight + 'px',
left: rulerHeight + 'px',
}" class="ruler horizontal">
</canvas>
<!-- 水平刻度指示 -->
<div ref="rulerHorizontalIndicator" width="1px" :style="{ height: rulerHeight + 'px' }" style="
position: absolute;
top: 0px;
width: 1px;
background-color: #3e8e41;
z-index: 99;
box-sizing: border-box;
"></div>
<!-- 垂直刻度尺 -->
<canvas id="ruler-vertical" ref="vRulerRef" :width="rulerHeight * 2 + 'px'" :height="viewSize.height * 2 + 'px'"
:style="{
height: viewSize.height + 'px',
width: rulerHeight + 'px',
top: rulerHeight + 'px',
}" class="ruler vertical">
</canvas>
<!-- 垂直刻度指示 -->
<div width="1px" :style="{ width: rulerHeight + 'px' }" style="
position: absolute;
left: 0px;
height: 1px;
background-color: #3e8e41;
z-index: 99;
box-sizing: border-box;
"></div>
<div v-if="rubberVisible" ref="rubberbandDiv" :style="{
position: 'absolute',
left: rubberbandRectangle.left + 'px',
top: rubberbandRectangle.top + 'px',
width: rubberbandRectangle.width + 'px',
height: rubberbandRectangle.height + 'px',
}" style="
pointer-events: none;
border: 2px dashed rgb(81, 153, 212);
cursor: crosshair;
opacity: 0.5;
display: inline;
z-index: 999;
"></div>
<canvas ref="canvasRef" @mousedown="canvasMouseDown" @mouseup="canvasMouseUp" @mousemove="canvasMouseMove"
@wheel="canvasMouseWheel" @contextmenu.prevent="handleWellContextMenu" :style="{ cursor: GetCursor() }"
class="main-canvas"></canvas>
<div class="context-menu" :class="{ visible: contextMenu.visible }"
:style="{ top: contextMenu.y + 'px', left: contextMenu.x + 'px' }" v-if="contextMenu.visible">
<div class="menu-item" @click="handleMenuAction('edit')">
<el-icon>
<Edit />
</el-icon>编辑
</div>
<!-- <hr class="divider" /> -->
<div class="menu-item" @click="handleMenuAction('wellGroupClone')">
<el-icon>
<CopyDocument />
</el-icon>应用井组参数到其它井组
</div>
<div class="menu-item" @click="handleMenuAction('wellGroupData')">
<el-icon>
<View />
</el-icon>
</div>
</div>
<img v-if="imgVisible" ref="image" :style="{
position: 'absolute',
left: imgPosition.x + 'px',
top: imgPosition.y + 'px',
}" style="pointer-events: none; z-index: 8" />
<!-- <img v-if="imgVisible" ref="image" :style="{
transform: `translate(${imgPosition.x}px, ${imgPosition.y}px)`
}" style="z-index: 50" /> -->
</div>
<LoadingDialog v-model="loading" ref="loadingRef" :timeout="10000" message="正在加载图件……" @timeout="onTimeout" />
<WellGroupDataHandle :showDialog="showDialog" v-model="showDialog" class="wellGroupData" />
<WellGroupEditDialog v-model="editVisible" ref="editRef" :parentToken="currentToken" v-if="wellGroupData"
:wellGroupData="wellGroupData" />
</template>
<script setup>
import { ref, onMounted, onUnmounted, watch } from "vue";
import { emitter } from "~/utils/eventBus";
import { canvasToolType, MouseIcons } from "~/enums/common.enum";
import { generateGUID } from "~/utils/common";
import { Edit, CopyDocument, View } from "@element-plus/icons-vue";
import { COMMAND, EMIT_COMMAND } from '~/utils/commandTypes'
import { LRUCache } from 'lru-cache';
import { useWebSocket } from '@/composables/useWebSocket'
const { $toastMessage } = useNuxtApp();
const config = useRuntimeConfig();
const EXPIRE_TIME = 5 * 60 * 1000;// 5 分钟
const cache = new LRUCache({
max: 1000, // 最多 1000 个条目
ttl: EXPIRE_TIME // 5 分钟过期
});
const loading = ref(false);
const loadingRef = ref(null);
const props = defineProps({
autoResize: {
type: Boolean,
default: true, // 是否自动调整画布大小
},
// 页面的 图件存储的 token 的 key
tokenKey: {
type: String,
default: 'drawerToken'
}
});
const currentTabTokenKey = computed(() => props.tokenKey);
const editVisible = ref(false);
const editRef = ref(null);
const cmdIds = ref([]);
// 接收到的消息中的token
const receivedToken = ref('');
const currentToken = ref(computed(() => receivedToken.value || localStorage.getItem(currentTabTokenKey.value)));
const { onMessage, status, messages, send } = useWebSocket();
// 标尺间隔
let ruler_metrics = [
1,
2,
5,
10,
25,
50,
100,
250,
500,
1000,
2500,
5000,
10000,
25000,
50000,
100000,
];
// 水平标尺的起止值
const hRulerSize = reactive({ startX: 0, endX: 0 });
// 垂直标尺的起止值
const vRulerSize = reactive({ startY: 0, endY: 0 });
// 水平标尺的范围
const xRange = computed(() => hRulerSize.endX - hRulerSize.startX);
// 垂直标尺的范围
const yRange = computed(() => vRulerSize.startY - vRulerSize.endY - 45);
// 水平主刻度
const hMainStep = computed(() =>
calculateInterval(hRulerSize.endX, hRulerSize.startX, viewSize.width, false)
);
// 水平中刻度
const hMiddleStep = computed(() => hMainStep.value / 2);
// 水平最小刻度
const hMinStep = computed(() => hMainStep.value / 10);
// 水平刻度取整数值
const hRulerStart = computed(
() => Math.floor(hRulerSize.startX / hMainStep.value) * hMainStep.value
);
const hRulerEnd = computed(
() => Math.round(hRulerSize.endX / hMainStep.value) * hMainStep.value
);
// 垂直主刻度
const vMainStep = computed(() =>
calculateInterval(vRulerSize.startY, vRulerSize.endY, viewSize.height, true)
);
// 垂直中刻度
const vMiddleStep = computed(() => vMainStep.value / 2);
// 垂直最小刻度
const vMinStep = computed(() => vMainStep.value / 10);
// 垂直刻度取整数值
const vRulerStart = computed(
() => Math.round(vRulerSize.startY / vMainStep.value) * vMainStep.value
);
const vRulerEnd = computed(
() => Math.floor(vRulerSize.endY / vMainStep.value) * vMainStep.value
);
const imgPath = ref(""); // 图片路径
const imgVisible = ref(false);
let imgData = null;
const queryDrawRuler = ref(false);
const imgPosition = reactive({ x: 0, y: 0 });
const viewSize = reactive({ width: 1024, height: 768 });
// 选中的图件元素ID
let selectedElementId = "";
// 选中的图件元素
let selectedElement = null;
const wellGroupData = ref(null);
// 右键菜单状态
const contextMenu = ref({
visible: false,
x: 0,
y: 0,
});
// const dragImageVisible = ref(false);
// 标尺高度/宽度
let rulerHeight = 20;
const mouseStartX = ref(0);
const mouseStartY = ref(0);
let imgX = 0;
let imgY = 0;
let lastX;
let lastY;
const isDragFirst = ref(true);
const mouseDown = ref(false);
// 橡皮筋选框
const rubberVisible = ref(false);
const rubberbandDiv = ref(null);
const rubberbandRectangle = reactive({ left: 10, top: 10, width: 100, height: 100 });
const pressedKeyCode = ref(null);
const drawerToolType = ref(canvasToolType.ITEM_DEFAULT);
const handleIndex = ref(-1);
// 缩放比例
const scale = ref(1);
// const store = useBtnClickStore();
const containerRef = ref(null);
const canvasRef = ref(null);
const hRulerRef = ref(null);
const vRulerRef = ref(null);
const image = ref(null);
const dpr = ref(1); // 设备像素比
let frameId = 0;
const showDialog = ref(false);
const dataDir = config.public.dataDir;
const emit = defineEmits(["resize", "init", "openFile"]);
const isView = ref(false);
let mainCtx = null;
const unsubscribe = onMessage((data) => {
try {
if (!data) return; // 如果没有新消息,直接返回
// console.log("收到消息:", data);
const json = JSON.parse(data);
const evtType = json.type;
if (evtType !== COMMAND.SELECT_HANDLE) {
console.log("接收到DrawServer的消息", json)
console.log("判断:", json.cmdID, currentTabTokenKey.value);
}
if (json.cmdID && json.cmdID !== currentTabTokenKey.value) {
return;
}
const msgId = `${evtType}-${json.dateTime}`;
if (cache.has(msgId) && (evtType !== COMMAND.SELECT_HANDLE && evtType !== COMMAND.DRAG_ELEMENT)) {
return;
}
cache.set(msgId, true);
// 只有第一次打开文件时才存储token
if (Object.hasOwn(json, "drawerToken") && json.drawerToken && evtType === COMMAND.NEW_TOKEN) {
receivedToken.value = json.drawerToken;
localStorage.setItem(currentTabTokenKey.value, json.drawerToken);
return;
}
switch (evtType) {
case COMMAND.REDRAW:
refreshImg(json);
break;
case COMMAND.OPEN_ERROR:
localStorage.removeItem(currentTabTokenKey.value);
closeLoading();
break;
case COMMAND.SELECT_HANDLE:
if (json.data !== -2) {
handleIndex.value = json.data;
}
break;
case COMMAND.DRAG_ELEMENT:
let imgContent = json.data;
if (!imgContent && imgContent.Data) {
return;
}
imgPath.value = imgContent.Data + "?_=" + Math.random();
let imgTmp = image.value;
imgTmp.src = `${config.public.imgUrl}/${imgPath.value}`;
imgX = json.data.ImgLeft;
imgY = json.data.ImgTop;
Object.assign(imgPosition, {
x: imgX,
y: imgY
})
imgVisible.value = true;
break;
case COMMAND.ELEMENT_PROPERTY:
console.log("当前KEY", currentTabTokenKey.value, "\t cmdID", json.cmdID)
if (currentTabTokenKey.value !== json.cmdID) {
return;
}
let wellGroupProp = json.data;
wellGroupData.value = json.data;
selectedElement = wellGroupProp.ElementData;
selectedElementId = wellGroupProp.ElementID;
if (selectedElement) {
emitter.emit(EMIT_COMMAND.WELL_GROUP_DATA, {
propData: selectedElement,
id: selectedElementId,
});
}
break;
case COMMAND.MAP_RANGE_REAL:
if (queryDrawRuler.value) {
let imgRect = json.data;
hRulerSize.startX = Math.round(imgRect.left);
hRulerSize.endX = Math.round(imgRect.right);
vRulerSize.startY = Math.round(imgRect.top);
vRulerSize.endY = Math.round(imgRect.bottom);
console.log("标尺属性:", hRulerSize, vRulerSize);
drawRulers();
queryDrawRuler.value = false;
}
closeLoading();
break;
case "center":
break;
case COMMAND.SAVE:
if (json.data) {
$toastMessage.success("保存成功!");
sendCmd(COMMAND.REDRAW, { width: viewSize.width, height: viewSize.height });
} else {
$toastMessage.error("保存失败!");
}
break;
case COMMAND.MERGE_FILE:
loading.value = true;
sendCmd(COMMAND.REDRAW, { width: viewSize.width, height: viewSize.height });
break;
}
} finally {
}
});
const handleWellContextMenu = (e) => {
// 阻止默认的右键菜单
// event.preventDefault();
if (selectedElementId === "") {
console.warn("没有选中任何井组");
return;
}
contextMenu.value = {
visible: true,
x: e.offsetX,
y: e.offsetY,
};
// 阻止事件冒泡,避免触发 mouseup 事件
// event.stopPropagation();
};
// 关闭右键菜单
const closeContextMenu = () => {
contextMenu.value = {
visible: false,
x: 0,
y: 0,
};
};
const handleMenuAction = (action) => {
switch (action) {
// 应用井组参数
case "wellGroupClone":
sendCmd(COMMAND.WELL_GROUP_CLONE, { elementID: selectedElementId });
break;
case "wellGroupData":
showDialog.value = true;
break;
// 取消操作
case "cancel":
break;
// 编辑操作
case "edit":
editVisible.value = true;
break;
default:
console.warn("未知操作:", action);
}
// 隐藏右键菜单
closeContextMenu();
};
// 鼠标按下
const canvasMouseDown = (event) => {
if (event.button !== 0) {
return;
}
mouseDown.value = true;
lastX = event.offsetX;
lastY = event.offsetY;
const rect = canvasRef.value.getBoundingClientRect();
switch (drawerToolType.value) {
case canvasToolType.ITEM_VIEW_PAN:
isDragFirst.value = true;
imgVisible.value = true;
Object.assign(imgPosition, { x: 0, y: 0 });
mouseStartX.value = event.clientX - rect.left;
mouseStartY.value = event.clientY - rect.top;
break;
case canvasToolType.ITEM_VIEW_WINDOW:
event.preventDefault();
rubberVisible.value = true;
mouseStartX.value = event.clientX - rect.left;
mouseStartY.value = event.clientY - rect.top;
rubberbandRectangle.left = mouseStartX.value;
rubberbandRectangle.top = mouseStartY.value;
rubberbandRectangle.width = 1;
rubberbandRectangle.height = 1;
break;
case canvasToolType.ITEM_SELECT:
imgVisible.value = true;
mouseStartX.value = event.clientX - rect.left;
mouseStartY.value = event.clientY - rect.top;
sendCmd(COMMAND.SELECT_MOUSE_DOWN, { x: mouseStartX.value, y: mouseStartY.value });
break;
}
closeContextMenu();
};
// 鼠标按键抬起
const canvasMouseUp = (event) => {
if (event.button !== 0) {
return;
}
const rect = canvasRef.value.getBoundingClientRect();
let endX = 0;
let endY = 0;
switch (drawerToolType.value) {
case canvasToolType.ITEM_VIEW_PAN:
endX = event.clientX - rect.left;
endY = event.clientY - rect.top;
// 必须释放,否则图件显示不出来
cancelAnimationFrame(frameId);
sendCmd(COMMAND.VIEW_PAN, {
startX: mouseStartX.value - rulerHeight,
startY: mouseStartY.value - rulerHeight,
endX: endX,
endY: endY,
width: viewSize.width,
height: viewSize.height,
});
break;
case canvasToolType.ITEM_VIEW_WINDOW:
rubberVisible.value = false;
endX = event.clientX - rect.left;
endY = event.clientY - rect.top;
sendCmd(COMMAND.VIEW_WINDOW, {
startX: mouseStartX.value,
startY: mouseStartY.value,
endX: endX,
endY: endY,
width: viewSize.width,
height: viewSize.height,
});
break;
case canvasToolType.ITEM_SELECT:
endX = event.offsetX;
endY = event.offsetY;
if (handleIndex.value == 5) {
sendCmd(COMMAND.DRAG_ELEMENT, { x: endX, y: endY });
} else {
// 图元选择
sendCmd(COMMAND.SELECT_MOUSE_UP, { x: endX, y: endY, keyCode: pressedKeyCode.value });
}
break;
}
mouseDown.value = false;
};
// 鼠标移动
const canvasMouseMove = (event) => {
if (!mouseDown.value) {
if (drawerToolType.value === canvasToolType.ITEM_SELECT) {
sendCmd(COMMAND.MOUSE_MOVE, {
x: event.offsetX,
y: event.offsetY,
handleIndex: handleIndex.value,
});
}
return;
}
const rect = canvasRef.value.getBoundingClientRect();
switch (drawerToolType.value) {
case canvasToolType.ITEM_VIEW_PAN:
let offsetX = event.offsetX - lastX;
let offsetY = event.offsetY - lastY;
if (isDragFirst.value) {
const dataURL = canvasRef.value.toDataURL("image/png");
image.value.src = dataURL;
Object.assign(imgPosition, {
x: imgPosition.x + rulerHeight,
y: imgPosition.y + rulerHeight,
});
draw();
isDragFirst.value = false;
} else {
Object.assign(imgPosition, {
x: imgPosition.x + offsetX,
y: imgPosition.y + offsetY,
});
}
lastX = event.offsetX;
lastY = event.offsetY;
break;
case canvasToolType.ITEM_VIEW_WINDOW:
let endX = event.clientX - rect.left;
let endY = event.clientY - rect.top;
let dragStartX = mouseStartX.value;
let dragStartY = mouseStartY.value;
if (endX < dragStartX) {
dragStartX = endX;
endX = mouseStartX.value;
}
if (endY < dragStartY) {
dragStartY = endY;
endY = mouseStartY.value;
}
rubberbandRectangle.left = dragStartX + rulerHeight;
rubberbandRectangle.top = dragStartY + rulerHeight;
rubberbandRectangle.width = endX - dragStartX;
rubberbandRectangle.height = endY - dragStartY;
event.preventDefault();
break;
case canvasToolType.ITEM_SELECT:
let moveX = event.clientX - rect.left - mouseStartX.value;
let moveY = event.clientY - rect.top - mouseStartY.value;
// console.log("拖动:", moveX, moveY);
Object.assign(imgPosition, {
x: imgX + moveX,
y: imgY + moveY
})
lastX = event.offsetX;
lastY = event.offsetY;
break;
}
};
const draw = () => {
if (!mainCtx) return;
mainCtx.clearRect(0, 0, viewSize.width, viewSize.height);
frameId = requestAnimationFrame(draw);
};
const canvasMouseWheel = (event) => {
scale.value = scale.value + 1;
};
const refreshImg = (data) => {
console.log("refreshImg", data);
imgPath.value = data.data + "?_=" + Math.random();
redrawCanvas(imgPath.value);
closeLoading();
if (currentTabTokenKey.value !== data.cmdID) {
return;
}
emitter.emit(EMIT_COMMAND.REFRESH_LAYER);
}
// 绘制图片
const redrawCanvas = async (data) => {
if (data.length === 0) return;
try {
const response = await fetch(`${config.public.imgUrl}/${data}`, {
cache: "no-store",
});
if (!response.ok) {
$toastMessage.error("Network response was not ok");
return;
}
const { pixelWidth, pixelHeight } = getDrawingAreaSize();
viewSize.width = pixelWidth;
viewSize.height = pixelHeight;
const blob = await response.blob();
imgData = await createImageBitmap(blob);
if (!mainCtx) {
return;
}
mainCtx.clearRect(0, 0, viewSize.width, viewSize.height);
mainCtx.drawImage(imgData, 0, 0);
imgVisible.value = false;
sendCmd(COMMAND.QUERY_RANGE_SCREEN_TO_REAL, {
startX: 0,
endX: viewSize.width,
startY: 0,
endY: viewSize.height,
});
queryDrawRuler.value = true;
} catch (error) {
console.error("Failed to load the image:", error);
}
};
// 默认
const toolDefault = () => {
drawerToolType.value = canvasToolType.ITEM_DEFAULT;
};
const toolViewMove = () => {
drawerToolType.value = canvasToolType.ITEM_VIEW_PAN;
};
const toolViewWindow = () => {
drawerToolType.value = canvasToolType.ITEM_VIEW_WINDOW;
};
const toolSelect = () => {
drawerToolType.value = canvasToolType.ITEM_SELECT;
};
// 放大
const drawZoomIn = () => {
sendCmd(COMMAND.ZOOM_IN, { width: viewSize.width, height: viewSize.height });
};
// 缩小
const drawZoomOut = () => {
sendCmd(COMMAND.ZOOM_OUT, { width: viewSize.width, height: viewSize.height });
};
// 刷新
const drawRefresh = () => {
redrawCanvas(imgPath.value);
// send("Refresh", { width: viewSize.width, height: viewSize.height });
};
// 居中
const drawCenter = () => {
sendCmd(COMMAND.CENTER, { width: viewSize.width, height: viewSize.height });
};
// 全部
const viewAll = () => {
sendCmd(COMMAND.VIEW_ALL, { width: viewSize.width, height: viewSize.height });
};
const GetCursor = () => {
let iconName = "default";
switch (drawerToolType.value) {
case canvasToolType.ITEM_SELECT:
if (handleIndex.value == 5) {
iconName = `url('./assets/move.svg') 24 24 auto`;
} else {
iconName = MouseIcons.pointerSelect;
}
break;
case canvasToolType.ITEM_VIEW_PAN:
iconName = MouseIcons.viewPan;
break;
case canvasToolType.ITEM_VIEW_WINDOW:
iconName = MouseIcons.crosshair;
break;
}
return iconName;
};
const showLayers = () => {
emitter.emit(EMIT_COMMAND.SHOW_LAYERS);
};
// 按键
const handleKeyDown = (event) => {
pressedKeyCode.value = event.keyCode;
};
const handleKeyUp = (event) => {
pressedKeyCode.value = null;
};
// 打开文件
const openFile = (fileUrl, token) => {
if (!fileUrl) return;
if (!canvasRef.value) return;
loading.value = true;
// 获取画布大小
let rect = getDrawingAreaSize();
console.log("当前tokenkey:", currentTabTokenKey.value, token)
if (token) {
localStorage.setItem(currentTabTokenKey.value, token);
}
// console.log("画布尺寸:", viewSize);
sendCmd(COMMAND.OPEN_FILE, {
file: fileUrl,
token: token,
width: rect.pixelWidth,
height: rect.pixelHeight,
});
};
// defineExpose({
// });
function save() {
sendCmd(COMMAND.SAVE_FILE);
}
// 安全获取 devicePixelRatio
const getDevicePixelRatio = () => {
return (typeof window !== "undefined" && window.devicePixelRatio) || 1;
};
// 设备像素比
const getDrawingAreaSize = () => {
if (!containerRef.value) return { width: 0, height: 0 };
const { clientWidth: width, clientHeight: height } = containerRef.value;
dpr.value = getDevicePixelRatio(); // 获取设备像素比
return {
pixelWidth: width * dpr.value, // 实际像素宽度
pixelHeight: height * dpr.value, // 实际像素高度
};
};
// 初始化画布大小
const initCanvasSize = () => {
const canvas = canvasRef.value;
if (!canvas) return;
// console.log("Canvas加载完成")
// 获取容器尺寸
const { pixelWidth, pixelHeight } = getDrawingAreaSize();
viewSize.width = pixelWidth;
viewSize.height = pixelHeight;
// console.log("initCanvasSize:", viewSize)
// 设置画布大小
canvas.width = pixelWidth * dpr.value; // 实际像素宽度
canvas.height = pixelHeight * dpr.value; // 实际像素高度
canvas.style.width = `${pixelWidth}px`;
canvas.style.height = `${pixelHeight}px`;
// 获取2D上下文
mainCtx = canvas.getContext("2d");
if (!mainCtx) {
return;
}
mainCtx.scale(dpr.value, dpr.value); // 缩放上下文以适应设备像素比
// 关闭图像平滑处理
mainCtx.imageSmoothingEnabled = false;
mainCtx.fillStyle = "#FFFFFF";
mainCtx.fillRect(0, 0, canvas.width, canvas.height);
let rect = canvas.getBoundingClientRect();
Object.assign(imgPosition, {
x: rect.left,
y: rect.top,
});
};
// 绘制刻度尺
const drawRulers = () => {
// 获取容器尺寸
const { pixelWidth, pixelHeight } = getDrawingAreaSize();
viewSize.width = pixelWidth;
viewSize.height = pixelHeight;
console.log("窗口尺寸:", viewSize);
drawHRuler();
drawVRuler();
};
// 水平标尺绘制
const drawHRuler = () => {
const width = viewSize.width * 2;
const hRulerCanvas = hRulerRef.value;
const ctx = hRulerCanvas.getContext("2d");
ctx.clearRect(0, 0, width, rulerHeight * 2);
ctx.lineWidth = 1;
ctx.font = "16px Arial";
ctx.textAlign = "left";
ctx.fillStyle = "#333";
// 计算像素比例
const pxPerUnit = width / xRange.value;
console.log("水平标尺属性:", hRulerStart.value, hRulerEnd.value, hMainStep.value);
// 主刻度
for (
let value = hRulerStart.value;
value <= hRulerEnd.value;
value += hMainStep.value
) {
const x = (((value - hRulerStart.value) / xRange.value) * width) / scale.value;
ctx.beginPath();
ctx.moveTo(x, 0);
ctx.lineTo(x, 20);
ctx.stroke();
ctx.fillText(value, x + 5, rulerHeight + 10);
}
// 次刻度
for (
let value = hRulerStart.value;
value <= hRulerEnd.value;
value += hMiddleStep.value
) {
if (value % hMainStep.value === 0) continue;
const x = ((value - hRulerStart.value) / xRange.value) * width;
ctx.beginPath();
ctx.moveTo(x, 0);
ctx.lineTo(x, 15);
ctx.stroke();
}
// 最小刻度
for (let value = hRulerStart.value; value <= hRulerEnd.value; value += hMinStep.value) {
if (value % hMainStep.value === 0 || value % hMiddleStep.value === 0) continue;
const x = ((value - hRulerStart.value) / xRange.value) * width;
ctx.beginPath();
ctx.moveTo(x, 0);
ctx.lineTo(x, 10);
ctx.stroke();
}
};
// 垂直标尺绘制
const drawVRuler = () => {
const height = viewSize.height * 2;
const vRulerCanvas = vRulerRef.value;
const ctx = vRulerCanvas.getContext("2d");
ctx.clearRect(0, 0, rulerHeight * 2, height);
ctx.lineWidth = 1;
ctx.font = "16px Arial";
ctx.textAlign = "left";
ctx.fillStyle = "#333";
// 计算像素比例
// const pxPerUnit = height / yRange.value;
// 主刻度
for (
let value = vRulerStart.value;
value >= vRulerEnd.value;
value -= vMainStep.value
) {
const y = (((vRulerStart.value - value) / yRange.value) * height) / scale.value;
ctx.beginPath();
ctx.moveTo(0, y);
ctx.lineTo(20, y);
ctx.stroke();
// 旋转文字
ctx.save();
ctx.translate(20, y);
ctx.rotate(-Math.PI / 2);
ctx.fillText(value, 10, 10);
ctx.restore();
}
// 次刻度
for (
let value = vRulerStart.value;
value >= vRulerEnd.value;
value -= vMiddleStep.value
) {
if (value % vMainStep.value === 0) continue;
const y = ((vRulerStart.value - value) / yRange.value) * height;
ctx.beginPath();
ctx.moveTo(0, y);
ctx.lineTo(15, y);
ctx.stroke();
}
// 最小刻度
for (let value = vRulerStart.value; value >= vRulerEnd.value; value -= vMinStep.value) {
if (value % vMainStep.value === 0 || value % vMiddleStep.value === 0) continue;
const y = ((vRulerStart.value - value) / yRange.value) * height;
ctx.beginPath();
ctx.moveTo(0, y);
ctx.lineTo(10, y);
ctx.stroke();
}
};
const calculateInterval = (start, end, length, isV) => {
const range = Math.abs(end - start);
// 橫标尺每200像素1个纵标尺每150像素1个
let rulerCount = Math.ceil(isV ? length / 200 : length / 150);
if (rulerCount < 5) {
rulerCount = rulerCount * 2;
}
let magnitude = range / rulerCount;
let step = 0;
for (let i = 0; i < ruler_metrics.length; i++) {
if (magnitude > ruler_metrics[i] && magnitude <= ruler_metrics[i + 1]) {
step = ruler_metrics[i];
break;
}
}
// 按比例换算
let realCount = range / step;
if (isV) {
if (realCount >= 15) {
step = step * 2;
} else if (realCount <= 5) {
step = step / 2;
}
}
if (!isV) {
if (realCount >= 20) {
step = step * 2;
} else if (realCount <= 10) {
step = step / 2;
}
}
return step;
};
const handleResize = debounce(() => {
if (!canvasRef.value) return;
console.log("画布大小改变");
// const canvas = canvasRef.value;
// // 保存当前画布内容
// const imageData = mainCtx.getImageData(0, 0, canvas.width, canvas.height);
// initCanvasSize();
// // 恢复画布内容
// mainCtx.putImageData(imageData, 0, 0);
redrawCanvas(imgPath.value);
// initCanvasSize();
}, 150);
const clearCanvas = () => {
if (!mainCtx) return;
mainCtx.clearRect(0, 0, viewSize.width, viewSize.height);
};
function doOpenFile(item) {
console.log("执行方法:doOpenFile", item)
clearCanvas();
let drawerToken = generateGUID();
openFile(`${dataDir}/${item}`, drawerToken);
}
// 生命周期钩子
onMounted(() => {
nextTick(() => {
image.value = new Image();
initCanvasSize();
window.addEventListener("resize", handleResize);
});
// if (canvasRef.value) {
// const size = getCanvasSize();
// console.log('Size=====', size);
// window.addEventListener("resize", handleResize);
// } else {
// console.error("Canvas element not found");
// }
// 全局监听键盘事件
window.addEventListener("keydown", handleKeyDown);
window.addEventListener("keyup", handleKeyUp);
// emitter.on(EMIT_COMMAND.BUTTON_CLICK, (key, index, tk) => {
// btnKey.value = key;
// btnIndex.value = index;
// btnTk.value = tk;
// });
emitter.on(EMIT_COMMAND.REFRESH_IMG, (data) => {
refreshImg(data);
});
emitter.on(EMIT_COMMAND.EVT_TYPE, (data) => {
const json = JSON.parse(data.data);
const cmdId = data.cmdID;
console.log("接收到 EMIT_COMMAND.EVT_TYPE 的消息:", cmdId, currentTabTokenKey.value, json)
if (cmdId !== currentTabTokenKey.value) {
return;
}
switch (data.type) {
// case COMMAND.REDRAW:
// refreshImg(json);
// break;
case EMIT_COMMAND.OPEN_FILE:
clearCanvas();
let drawerToken = json.drawerToken || generateGUID();
openFile(`${dataDir}/${json.fileName}`, drawerToken);
break;
case "calcRedraw":
sendCmd(COMMAND.REDRAW, { width: viewSize.width, height: viewSize.height });
break;
case "viewFile":
isView.value = true;
openFile(`${dataDir}/${json.fileUrl}`, json.viewerToken);
break;
case EMIT_COMMAND.VIEW_ALL:
viewAll();
break;
case EMIT_COMMAND.CLEAR_CANVAS:
clearCanvas();
break;
// case COMMAND.EDIT_WELL_GROUP:
// if (currentToken.value === STORAGE_KEYS.WELL_GROUP_EDIT_TOKEN) {
// }
// console.log("收到编辑井组回应:", json);
// break;
}
});
emitter.on(EMIT_COMMAND.SOURCE_CHANGED, sourceChanged);
// emitter.on(EMIT_COMMAND.EDIT_REDRAW, (json) => {
// console.log("收到编辑井组回应 CanvasRenderer", json);
// refreshImg(json);
// });
});
const sourceChanged = () => {
localStorage.removeItem(currentTabTokenKey.value);
clearCanvas();
};
onBeforeUnmount(() => {
// emitter.off(EMIT_COMMAND.BUTTON_CLICK);
emitter.off(EMIT_COMMAND.EVT_TYPE);
emitter.off(EMIT_COMMAND.SOURCE_CHANGED);
emitter.off(EMIT_COMMAND.REFRESH_IMG);
emitter.off(EMIT_COMMAND.EDIT_GROUP);
// emitter.off(EMIT_COMMAND.EDIT_REDRAW);
window.removeEventListener("resize", handleResize);
// 全局监听键盘事件
window.removeEventListener("keydown", handleKeyDown);
window.removeEventListener("keyup", handleKeyUp);
});
onUnmounted(() => {
unsubscribe();
});
// 加载数据超时处理
const onTimeout = () => {
$toastMessage.error("加载数据失败,请稍后重试!");
}
// 关闭加载窗口
const closeLoading = () => {
if (loadingRef.value) {
loadingRef.value.close();
} else {
loading.value = false;
}
}
const sendCmd = (type, data = {}) => {
// const cmdid = generateGUID();
// cmdIds.value.push(cmdid);
send(type, data, currentToken.value, currentTabTokenKey.value);
}
const getCanvasSize = () => {
if (canvasRef.value) {
return canvasRef.value.getBoundingClientRect();
// console.log("getCanvasSize:", canvasRef.value.width, canvasRef.value.height);
// return getDrawingAreaSize();
}
return null;
}
// 防抖函数
function debounce(fn, delay) {
let timer = null
return function (...args) {
if (timer) {
clearTimeout(timer)
}
timer = setTimeout(() => {
fn.apply(this, args)
}, delay)
}
}
defineExpose({
// width: computed(() => canvasRef.value?.width),
// height: computed(() => canvasRef.value?.height),
getCanvasSize,
// 处理默认按钮点击
toolDefault,
// 处理放大按钮点击
drawZoomIn,
// 处理缩小按钮点击
drawZoomOut,
// 处理刷新按钮点击
drawRefresh,
// 处理居中按钮点击
drawCenter,
// 处理局部按钮点击
toolViewWindow,
// 处理全部按钮点击
viewAll,
// 处理移动按钮点击
toolViewMove,
// 处理选择按钮点击
toolSelect,
// 处理保存按钮点击
save,
// 处理图层按钮点击
showLayers,
// 打开文件
doOpenFile
})
</script>
<style scoped>
.canvas-container {
width: 100%;
height: 100%;
/* min-width: 800px;
min-height: 550px; */
position: relative;
border: 0;
overflow: hidden;
/* 防止内容溢出 */
background-color: #fff;
}
.ruler {
position: absolute;
z-index: 10;
/* 标尺置于顶层 */
background: rgb(245, 245, 245);
}
.horizontal {
top: 0;
height: 20px;
/* 水平标尺高度 */
}
.vertical {
left: 0;
width: 20px;
/* 垂直标尺宽度 */
}
.main-canvas {
display: block;
width: 100%;
height: 100%;
/* 确保画布是块级元素 */
background: #fff;
border: 0;
/* position: relative; */
z-index: 1;
top: 20px;
/* 避开水平标尺 */
left: 20px;
/* 避开垂直标尺 */
/* width: calc(100vw - 20px);
height: calc(100vh - 20px); */
resize: both;
image-rendering: -webkit-optimize-contrast;
}
/*自定义右键菜单样式*/
.context-menu {
position: absolute;
background: white;
border-radius: 8px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.15);
z-index: 1000;
overflow: hidden;
opacity: 0;
transform: scale(0.95);
transition: all 0.2s ease-out;
min-width: 200px;
}
.context-menu.visible {
opacity: 1;
transform: scale(1);
}
.menu-item {
padding: 8px 15px;
display: flex;
align-items: center;
gap: 12px;
cursor: pointer;
transition: all 0.2s;
border-bottom: 1px solid #f0f4f8;
font-size: 14px;
}
.menu-item:last-child {
border-bottom: none;
}
.menu-item:hover {
background: #f0f7ff;
}
.menu-item .icon {
width: 24px;
height: 24px;
display: flex;
align-items: center;
justify-content: center;
background: #e3f2fd;
border-radius: 6px;
color: #3498db;
}
.menu-item.danger {
color: #e74c3c;
}
.menu-item.danger .icon {
background: #fdecea;
color: #e74c3c;
}
/*分隔线样式*/
.divider {
border: none;
height: 1px;
background: #ccc;
margin: 3px 0;
}
.wellGroupData {
width: calc(100vw - 100px);
height: calc(100vh - 100px);
}
</style>