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.

1210 lines
31 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="">
<!-- 水平刻度尺 -->
<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', left: mouseHorizontal + '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', top: mouseVertical + '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" />
</div>
<WellGroupDataHandle :showDialog="showDialog" v-model="showDialog" class="wellGroupData" />
<!-- </ClientOnly> -->
</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 { useBtnClickStore } from "~/stores/btnClickStore";
const config = useRuntimeConfig();
const props = defineProps({
autoResize: {
type: Boolean,
default: true, // 是否自动调整画布大小
},
});
const { message, status, send } = useWebSocket();
const inputMessage = ref(""); // 消息内容
import { Edit, CopyDocument, View } from "@element-plus/icons-vue";
// 标尺间隔
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);
// 水平主刻度
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.round(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.round(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 });
const isLoading = ref(false);
// 选中的图件元素ID
let selectedElementId = "";
// 选中的图件元素
let selectedElement = 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);
const mouseHorizontal = ref(0);
const mouseVertical = ref(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 btnKey = ref("");
const btnIndex = ref(0);
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"]);
const isView = ref(false);
let mainCtx = null;
// 获取共享数据
watch(
btnKey,
(newVal) => {
if (!newVal) return;
onBtnClick(newVal.key);
},
{ immediate: true }
);
watchEffect(() => {
try {
if (!message.value) return; // 如果没有新消息,直接返回
const json = JSON.parse(message.value);
console.info("接收的数据:", json);
const evtType = json.type;
switch (evtType) {
case "Pong":
case "connected":
console.log("WebSocket 连接成功!");
break;
case "NewToken":
if (isView.value) {
localStorage.setItem("viewerToken", json.drawerToken);
} else {
localStorage.setItem("drawerToken", json.drawerToken);
}
// // 通知父页面 图件 已经打开
// const message = {
// action: "fileOpend",
// token: json.drawerToken,
// file: json.userID,
// };
// sendMsgToParent(message);
break;
case "Redraw":
if (isView.value) {
localStorage.setItem("viewerToken", json.drawerToken);
} else {
localStorage.setItem("drawerToken", json.drawerToken);
}
imgPath.value = json.data + "?_=" + Math.random();
redrawCanvas(imgPath.value);
break;
case "OpenError":
if (isView.value) {
localStorage.removeItem("viewerToken");
} else {
localStorage.removeItem("drawerToken");
}
break;
case "SelectHandle":
if (json.data !== -2) {
handleIndex.value = json.data;
}
break;
case "DragElement":
let imgContent = json.data;
if (!(imgContent && imgContent.Data)) {
return;
}
imgPath.value = imgContent.Data + "?_=" + Math.random();
console.log("拖动图件:", imgPath.value);
imgVisible.value = true;
nextTick(() => {
let imgTmp = image.value;
imgTmp.src = `${config.public.imgUrl}/${imgPath.value}`;
});
// redrawCanvas(imgPath.value);
break;
case "ElementProperty":
let wellGroupProp = json.data;
selectedElement = wellGroupProp.ElementData;
selectedElementId = wellGroupProp.ElementID;
emitter.emit("WellGroupData", {
propData: selectedElement,
id: selectedElementId,
});
break;
case "MapRangeReal":
console.log("MapRangeReal", queryDrawRuler.value);
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);
drawRulers();
queryDrawRuler.value = false;
}
isLoading.value = true;
break;
case "center":
break;
// case "ReloadLayer":
// let layerData = json.data;
// emitter.emit("ReloadLayer", layerData);
// break;
}
} finally {
}
});
const handleWellContextMenu = (e) => {
// 阻止默认的右键菜单
// event.preventDefault();
if (selectedElementId === "") {
console.warn("没有选中任何井组");
return;
}
contextMenu.value = {
visible: true,
x: e.offsetX,
y: e.offsetY,
};
console.log("右键菜单事件:", contextMenu.value);
// 阻止事件冒泡,避免触发 mouseup 事件
// event.stopPropagation();
};
// 关闭右键菜单
const closeContextMenu = () => {
contextMenu.value = {
visible: false,
x: 0,
y: 0,
};
};
const handleMenuAction = (action) => {
switch (action) {
// 应用井组参数
case "wellGroupClone":
send("WellGroupClone", { elementID: selectedElementId });
break;
case "wellGroupData":
showDialog.value = true;
console.log("显示井组数据对话框", showDialog.value);
break;
// 取消操作
case "cancel":
break;
// 编辑操作
case "edit":
// if (window.parent && window.parent.postMessage) {
// /* console.log('编辑井组:', this.selectedElementId, this.selectedElement); */
// window.parent.postMessage({action:'editWellGroup',data:this.selectedElement,dataid:this.selectedElementId},'*');
// }
break;
default:
console.warn("未知操作:", action);
}
// 隐藏右键菜单
closeContextMenu();
};
// 鼠标按下
const canvasMouseDown = (event) => {
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 });
// imgPosition.x = 0;
// imgPosition.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:
mouseStartX.value = event.clientX - rect.left;
mouseStartY.value = event.clientY - rect.top;
send("SelectMouseDown", { x: mouseStartX.value, y: mouseStartY.value });
break;
}
closeContextMenu();
};
// 鼠标按键抬起
const canvasMouseUp = (event) => {
const rect = canvasRef.value.getBoundingClientRect();
let endX = 0;
let endY = 0;
console.log("canvasMouseUp", drawerToolType.value);
switch (drawerToolType.value) {
case canvasToolType.ITEM_VIEW_PAN:
endX = event.clientX - rect.left;
endY = event.clientY - rect.top;
// 必须释放,否则图件显示不出来
cancelAnimationFrame(frameId);
send("ViewPan", {
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;
send("ViewWindow", {
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) {
// 拖动结束
// dragImageVisible.value = false;
send("DragElement", { x: endX, y: endY });
} else {
// 图元选择
send("SelectMouseUp", { x: endX, y: endY, keyCode: pressedKeyCode.value });
}
break;
}
mouseDown.value = false;
};
// 鼠标移动
const canvasMouseMove = (event) => {
mouseHorizontal.value = event.offsetX + rulerHeight;
mouseVertical.value = event.offsetY + rulerHeight;
if (!mouseDown.value) {
if (drawerToolType.value == canvasToolType.ITEM_SELECT) {
send("MouseMove", {
x: event.offsetX,
y: event.offsetY,
handleIndex: handleIndex.value,
});
}
return;
}
let offsetX = event.offsetX - lastX;
let offsetY = event.offsetY - lastY;
const rect = canvasRef.value.getBoundingClientRect();
switch (drawerToolType.value) {
case canvasToolType.ITEM_VIEW_PAN:
if (isDragFirst.value) {
const dataURL = canvasRef.value.toDataURL("image/png");
image.value.src = dataURL;
Object.assign(imgPosition, {
x: imgPosition.x + offsetX,
y: imgPosition.y + offsetY,
});
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;
rubberbandRectangle.top = dragStartY;
rubberbandRectangle.width = endX - dragStartX;
rubberbandRectangle.height = endY - dragStartY;
event.preventDefault();
break;
case canvasToolType.ITEM_SELECT:
offsetX = event.offsetX - lastX;
offsetY = event.offsetY - lastY;
Object.assign(imgPosition, {
x: imgPosition.x + offsetX,
y: imgPosition.y + offsetY,
});
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 redrawCanvas = async (data) => {
if (data.length === 0) return;
try {
console.log("图片路径:", data);
const response = await fetch(`${config.public.imgUrl}/${data}`, {
cache: "no-store",
});
if (!response.ok) {
throw new Error("Network response was not ok");
}
const { pixelWidth, pixelHeight } = getDrawingAreaSize();
viewSize.width = pixelWidth;
viewSize.height = pixelHeight;
const blob = await response.blob();
imgData = await createImageBitmap(blob);
mainCtx.clearRect(0, 0, viewSize.width, viewSize.height);
mainCtx.drawImage(imgData, 0, 0);
imgVisible.value = false;
queryDrawRuler.value = true;
// var rect = getDrawingAreaSize();
send("QueryRangeScreen2Real", {
startX: 0,
endX: viewSize.width,
startY: 0,
endY: viewSize.height,
});
} 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;
console.log("drawerToolType", drawerToolType.value);
};
const toolSelect = () => {
drawerToolType.value = canvasToolType.ITEM_SELECT;
};
// 放大
const drawZoomIn = () => {
send("ZoomIn", { width: viewSize.width, height: viewSize.height });
};
// 缩小
const drawZoomOut = () => {
send("ZoomOut", { width: viewSize.width, height: viewSize.height });
};
// 刷新
const drawRefresh = () => {
redrawCanvas(imgPath.value);
// send("Refresh", { width: viewSize.width, height: viewSize.height });
};
// 居中
const drawCenter = () => {
send("Center", { width: viewSize.width, height: viewSize.height });
};
// 全部
const viewAll = () => {
send("ViewAll", { 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("showLayers");
// 发送消息到API
// console.log("发送获取图层消息");
// send("GetLayers");
};
// 按键
const handleKeyDown = (event) => {
pressedKeyCode.value = event.keyCode;
};
const handleKeyUp = (event) => {
pressedKeyCode.value = null;
};
// 发送消息到父组件(被嵌入时用)
// const sendMsgToParent = (message) => {
// if (window.parent && window.parent.postMessage) {
// console.info(message);
// window.parent.postMessage(message, "*");
// }
// };
const handleSend = () => {
if (inputMessage.value.trim()) {
send(inputMessage.value);
inputMessage.value = ""; // 清空消息
}
};
// 打开文件
const openFile = (fileUrl, token) => {
if (!fileUrl) return;
// const drawerToken = localStorage.getItem("drawerToken");
// if (drawerToken !== null && drawerToken.length > 0) {
// // 关闭已经打开的文件
// // closeDrawer(drawerToken);
// }
if (!canvasRef.value) return;
// 获取画布大小
let rect = getDrawingAreaSize();
console.log("画布大小:", rect);
send("OpenFile", {
file: fileUrl,
token: token,
width: rect.pixelWidth,
height: rect.pixelHeight,
});
console.log("打开文件:", fileUrl);
};
const onBtnClick = (btnKey) => {
switch (btnKey) {
case "default":
// 处理默认按钮点击
toolDefault();
// 测试打开文件
// let drawerToken = generateGUID();
// openFile(`${dataDir}/RESULT-2025-05-19092336.kev`, drawerToken);
break;
case "zoom_in":
// 处理放大按钮点击
drawZoomIn();
break;
case "zoom_out":
// 处理缩小按钮点击
drawZoomOut();
break;
case "refresh":
// 处理刷新按钮点击
drawRefresh();
break;
case "center":
// 处理居中按钮点击
drawCenter();
break;
case "part":
// 处理局部按钮点击
toolViewWindow();
break;
case "all":
// 处理全部按钮点击
viewAll();
break;
case "move":
// 处理移动按钮点击
toolViewMove();
break;
case "choose":
// 处理选择按钮点击
toolSelect();
break;
case "save":
// 处理保存按钮点击
send("SaveFile", "");
break;
case "layer":
// 处理图层按钮点击
showLayers();
break;
default:
console.warn(`未定义的按钮:${btn.key}`);
}
};
// 安全获取 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;
// 获取容器尺寸
const { pixelWidth, pixelHeight } = getDrawingAreaSize();
viewSize.width = pixelWidth;
viewSize.height = pixelHeight;
// 设置画布大小
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");
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.width, viewSize.height);
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;
// 主刻度
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();
console.log("X:", x);
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;
// console.log("主: value=", value, ";Y=", y);
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;
// console.log("次: value=", value, ";Y=", y);
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;
// console.log("最小: value=", value, ";Y=", y);
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 = () => {
if (!canvasRef.value) return;
// const canvas = canvasRef.value;
// // 保存当前画布内容
// const imageData = mainCtx.getImageData(0, 0, canvas.width, canvas.height);
// initCanvasSize();
// // 恢复画布内容
// mainCtx.putImageData(imageData, 0, 0);
redrawCanvas(imgPath.value);
};
// 生命周期钩子
onMounted(() => {
emitter.on("btnClick", (key, index) => {
btnKey.value = key;
btnIndex.value = index;
});
emitter.on("evtType", (data) => {
console.log("evtType得到的数据", data);
const json = JSON.parse(data.data);
switch (data.type) {
case "Redraw":
if (isView.value) {
localStorage.setItem("viewerToken", json.drawerToken);
}
else {
localStorage.setItem("drawerToken", json.drawerToken);
}
imgPath.value = json.data + "?_=" + Math.random();
redrawCanvas(imgPath.value);
break;
case "openFile":
console.log("收到参数设置中的打开文件:", json.fileName);
let drawerToken = json.drawerToken || generateGUID();
localStorage.setItem("drawerToken", drawerToken);
openFile(`${dataDir}/${json.fileName}`, drawerToken);
break;
case "calcRedraw":
send("Redraw", { width: viewSize.width, height: viewSize.height });
// redrawCanvas(imgPath.value);
break;
case "viewFile":
isView.value = true;
openFile(`${dataDir}/${json.fileUrl}`, json.viewerToken);
break;
case "viewAll":
viewAll();
break;
case "clearCanvas":
if (!mainCtx) return;
mainCtx.clearRect(0, 0, viewSize.width, viewSize.height);
break;
}
});
image.value = new Image();
if (canvasRef.value) {
initCanvasSize();
window.addEventListener("resize", handleResize);
} else {
console.error("Canvas element not found");
}
// 全局监听键盘事件
window.addEventListener("keydown", handleKeyDown);
window.addEventListener("keyup", handleKeyUp);
});
onBeforeUnmount(() => {
emitter.off("btnClick");
emitter.off("evtType");
});
onUnmounted(() => {
window.removeEventListener("resize", handleResize);
// 全局监听键盘事件
window.removeEventListener("keydown", handleKeyDown);
window.removeEventListener("keyup", handleKeyUp);
});
</script>
<style scoped>
.canvas-container {
width: 100%;
height: 100%;
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;
/* 确保画布是块级元素 */
background: #fff;
border: 0;
position: absolute;
z-index: 1;
top: 20px;
/* 避开水平标尺 */
left: 20px;
/* 避开垂直标尺 */
width: calc(100vw - 20px);
height: calc(100vh - 20px);
resize: both;
}
/*自定义右键菜单样式*/
.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>