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

1 month ago
<!--
绘制图件的组件
add by RYG
-->
<template>
<!-- <ClientOnly> -->
<!-- 绘图区域 -->
1 month ago
<div class="canvas-container" ref="containerRef" @wheel.prevent="">
1 month ago
<!-- 水平刻度尺 -->
<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>
<!-- 水平刻度指示 -->
1 month ago
<div ref="rulerHorizontalIndicator" width="1px"
:style="{ height: rulerHeight + 'px', left: mouseHorizontal + 'px' }" style="
1 month ago
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>
<!-- 垂直刻度指示 -->
1 month ago
<div width="1px" :style="{ width: rulerHeight + 'px', top: mouseVertical + 'px' }" style="
1 month ago
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" />
1 month ago
<!-- </ClientOnly> -->
1 month ago
</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";
1 month ago
// import { useBtnClickStore } from "~/stores/btnClickStore";
1 month ago
const config = useRuntimeConfig();
const props = defineProps({
autoResize: {
type: Boolean,
default: true, // 是否自动调整画布大小
},
});
1 month ago
const { message, status, send } = useWebSocket();
const inputMessage = ref(""); // 消息内容
1 month ago
1 month ago
import { Edit, CopyDocument, View } from "@element-plus/icons-vue";
1 month ago
// 标尺间隔
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);
// 垂直标尺的范围
1 month ago
const yRange = computed(() => vRulerSize.startY - vRulerSize.endY);
1 month ago
// 水平主刻度
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(
1 month ago
() => Math.round(hRulerSize.startX / hMainStep.value) * hMainStep.value
1 month ago
);
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(
1 month ago
() => Math.round(vRulerSize.endY / vMainStep.value) * vMainStep.value
1 month ago
);
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 });
1 month ago
const isLoading = ref(false);
1 month ago
// 选中的图件元素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);
1 month ago
const mouseHorizontal = ref(0);
const mouseVertical = ref(0);
1 month ago
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();
1 month ago
const btnKey = ref("");
const btnIndex = ref(0);
1 month ago
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;
1 month ago
const emit = defineEmits(["resize", "init"]);
1 month ago
const isView = ref(false);
let mainCtx = null;
1 month ago
// 获取共享数据
watch(
btnKey,
(newVal) => {
if (!newVal) return;
onBtnClick(newVal.key);
},
{ immediate: true }
);
watchEffect(() => {
1 month ago
try {
1 month ago
if (!message.value) return; // 如果没有新消息,直接返回
const json = JSON.parse(message.value);
console.info("接收的数据:", json);
1 month ago
const evtType = json.type;
switch (evtType) {
1 month ago
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);
1 month ago
break;
1 month ago
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");
}
1 month ago
break;
1 month ago
case "SelectHandle":
1 month ago
if (json.data !== -2) {
handleIndex.value = json.data;
}
break;
1 month ago
case "DragElement":
1 month ago
let imgContent = json.data;
1 month ago
if (!(imgContent && imgContent.Data)) {
1 month ago
return;
}
imgPath.value = imgContent.Data + "?_=" + Math.random();
1 month ago
console.log("拖动图件:", imgPath.value);
1 month ago
imgVisible.value = true;
1 month ago
nextTick(() => {
let imgTmp = image.value;
imgTmp.src = `${config.public.imgUrl}/${imgPath.value}`;
});
// redrawCanvas(imgPath.value);
1 month ago
break;
1 month ago
case "ElementProperty":
1 month ago
let wellGroupProp = json.data;
selectedElement = wellGroupProp.ElementData;
selectedElementId = wellGroupProp.ElementID;
1 month ago
emitter.emit("WellGroupData", {
propData: selectedElement,
id: selectedElementId,
});
1 month ago
break;
1 month ago
case "MapRangeReal":
console.log("MapRangeReal", queryDrawRuler.value);
1 month ago
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;
}
1 month ago
isLoading.value = true;
1 month ago
break;
case "center":
break;
1 month ago
// case "ReloadLayer":
// let layerData = json.data;
// emitter.emit("ReloadLayer", layerData);
// break;
1 month ago
}
} finally {
}
});
const handleWellContextMenu = (e) => {
// 阻止默认的右键菜单
// event.preventDefault();
if (selectedElementId === "") {
console.warn("没有选中任何井组");
return;
}
contextMenu.value = {
visible: true,
x: e.offsetX,
y: e.offsetY,
};
1 month ago
console.log("右键菜单事件:", contextMenu.value);
1 month ago
// 阻止事件冒泡,避免触发 mouseup 事件
// event.stopPropagation();
};
// 关闭右键菜单
const closeContextMenu = () => {
contextMenu.value = {
visible: false,
x: 0,
y: 0,
};
};
const handleMenuAction = (action) => {
switch (action) {
// 应用井组参数
case "wellGroupClone":
1 month ago
send("WellGroupClone", { elementID: selectedElementId });
1 month ago
break;
case "wellGroupData":
showDialog.value = true;
1 month ago
console.log("显示井组数据对话框", showDialog.value);
1 month ago
break;
// 取消操作
case "cancel":
break;
// 编辑操作
case "edit":
1 month ago
// if (window.parent && window.parent.postMessage) {
// /* console.log('编辑井组:', this.selectedElementId, this.selectedElement); */
// window.parent.postMessage({action:'editWellGroup',data:this.selectedElement,dataid:this.selectedElementId},'*');
// }
1 month ago
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 });
1 month ago
// imgPosition.x = 0;
// imgPosition.y = 0;
1 month ago
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;
1 month ago
send("SelectMouseDown", { x: mouseStartX.value, y: mouseStartY.value });
1 month ago
break;
}
closeContextMenu();
};
// 鼠标按键抬起
const canvasMouseUp = (event) => {
const rect = canvasRef.value.getBoundingClientRect();
let endX = 0;
let endY = 0;
1 month ago
console.log("canvasMouseUp", drawerToolType.value);
1 month ago
switch (drawerToolType.value) {
case canvasToolType.ITEM_VIEW_PAN:
endX = event.clientX - rect.left;
endY = event.clientY - rect.top;
// 必须释放,否则图件显示不出来
cancelAnimationFrame(frameId);
1 month ago
send("ViewPan", {
startX: mouseStartX.value + rulerHeight,
startY: mouseStartY.value + rulerHeight,
1 month ago
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;
1 month ago
send("ViewWindow", {
1 month ago
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) {
1 month ago
// 拖动结束
// dragImageVisible.value = false;
send("DragElement", { x: endX, y: endY });
1 month ago
} else {
// 图元选择
1 month ago
send("SelectMouseUp", { x: endX, y: endY, keyCode: pressedKeyCode.value });
1 month ago
}
break;
}
mouseDown.value = false;
};
// 鼠标移动
const canvasMouseMove = (event) => {
1 month ago
mouseHorizontal.value = event.offsetX + rulerHeight;
mouseVertical.value = event.offsetY + rulerHeight;
1 month ago
if (!mouseDown.value) {
1 month ago
if (drawerToolType.value == canvasToolType.ITEM_SELECT) {
send("MouseMove", {
1 month ago
x: event.offsetX,
y: event.offsetY,
handleIndex: handleIndex.value,
});
}
return;
}
1 month ago
let offsetX = event.offsetX - lastX;
let offsetY = event.offsetY - lastY;
1 month ago
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, {
1 month ago
x: imgPosition.x + offsetX,
y: imgPosition.y + offsetY,
1 month ago
});
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;
1 month ago
1 month ago
if (endX < dragStartX) {
dragStartX = endX;
endX = mouseStartX.value;
}
if (endY < dragStartY) {
dragStartY = endY;
endY = mouseStartY.value;
}
1 month ago
rubberbandRectangle.left = dragStartX;
rubberbandRectangle.top = dragStartY;
1 month ago
rubberbandRectangle.width = endX - dragStartX;
rubberbandRectangle.height = endY - dragStartY;
event.preventDefault();
break;
case canvasToolType.ITEM_SELECT:
1 month ago
offsetX = event.offsetX - lastX;
offsetY = event.offsetY - lastY;
1 month ago
Object.assign(imgPosition, {
1 month ago
x: imgPosition.x + offsetX,
y: imgPosition.y + offsetY,
});
1 month ago
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 {
1 month ago
console.log("图片路径:", data);
1 month ago
const response = await fetch(`${config.public.imgUrl}/${data}`, {
cache: "no-store",
});
if (!response.ok) {
1 month ago
throw new Error("Network response was not ok");
1 month ago
}
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;
1 month ago
queryDrawRuler.value = true;
// var rect = getDrawingAreaSize();
send("QueryRangeScreen2Real", {
1 month ago
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;
1 month ago
console.log("drawerToolType", drawerToolType.value);
1 month ago
};
const toolSelect = () => {
drawerToolType.value = canvasToolType.ITEM_SELECT;
};
// 放大
const drawZoomIn = () => {
1 month ago
send("ZoomIn", { width: viewSize.width, height: viewSize.height });
1 month ago
};
// 缩小
const drawZoomOut = () => {
1 month ago
send("ZoomOut", { width: viewSize.width, height: viewSize.height });
1 month ago
};
// 刷新
const drawRefresh = () => {
redrawCanvas(imgPath.value);
// send("Refresh", { width: viewSize.width, height: viewSize.height });
};
// 居中
const drawCenter = () => {
1 month ago
send("Center", { width: viewSize.width, height: viewSize.height });
1 month ago
};
// 全部
const viewAll = () => {
1 month ago
send("ViewAll", { width: viewSize.width, height: viewSize.height });
1 month ago
};
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 = () => {
1 month ago
emitter.emit("showLayers");
// 发送消息到API
// console.log("发送获取图层消息");
// send("GetLayers");
1 month ago
};
// 按键
const handleKeyDown = (event) => {
pressedKeyCode.value = event.keyCode;
};
const handleKeyUp = (event) => {
pressedKeyCode.value = null;
};
1 month ago
// 发送消息到父组件(被嵌入时用)
// 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 = ""; // 清空消息
}
};
1 month ago
// 打开文件
const openFile = (fileUrl, token) => {
if (!fileUrl) return;
1 month ago
// const drawerToken = localStorage.getItem("drawerToken");
// if (drawerToken !== null && drawerToken.length > 0) {
// // 关闭已经打开的文件
// // closeDrawer(drawerToken);
// }
1 month ago
if (!canvasRef.value) return;
// 获取画布大小
let rect = getDrawingAreaSize();
1 month ago
console.log("画布大小:", rect);
send("OpenFile", {
1 month ago
file: fileUrl,
token: token,
width: rect.pixelWidth,
height: rect.pixelHeight,
});
1 month ago
console.log("打开文件:", fileUrl);
1 month ago
};
1 month ago
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();
1 month ago
1 month ago
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}`);
}
};
1 month ago
// 安全获取 devicePixelRatio
const getDevicePixelRatio = () => {
return (typeof window !== "undefined" && window.devicePixelRatio) || 1;
};
// 设备像素比
1 month ago
1 month ago
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;
1 month ago
console.log("窗口尺寸:", viewSize.width, viewSize.height);
1 month ago
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();
1 month ago
console.log("X:", x);
1 month ago
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";
// 计算像素比例
1 month ago
const pxPerUnit = height / yRange.value;
1 month ago
// 主刻度
for (
let value = vRulerStart.value;
value >= vRulerEnd.value;
value -= vMainStep.value
) {
const y = (((vRulerStart.value - value) / yRange.value) * height) / scale.value;
1 month ago
// console.log("主: value=", value, ";Y=", y);
1 month ago
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;
1 month ago
// console.log("次: value=", value, ";Y=", y);
1 month ago
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;
1 month ago
// console.log("最小: value=", value, ";Y=", y);
1 month ago
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;
};
1 month ago
const handleResize = () => {
1 month ago
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(() => {
1 month ago
emitter.on("btnClick", (key, index) => {
btnKey.value = key;
btnIndex.value = index;
1 month ago
});
1 month ago
emitter.on("evtType", (data) => {
console.log("evtType得到的数据", data);
1 month ago
const json = JSON.parse(data.data);
switch (data.type) {
1 month ago
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);
1 month ago
let drawerToken = json.drawerToken || generateGUID();
1 month ago
localStorage.setItem("drawerToken", drawerToken);
1 month ago
openFile(`${dataDir}/${json.fileName}`, drawerToken);
break;
case "calcRedraw":
1 month ago
send("Redraw", { width: viewSize.width, height: viewSize.height });
// redrawCanvas(imgPath.value);
1 month ago
break;
case "viewFile":
isView.value = true;
openFile(`${dataDir}/${json.fileUrl}`, json.viewerToken);
break;
1 month ago
case "viewAll":
1 month ago
viewAll();
break;
1 month ago
case "clearCanvas":
if (!mainCtx) return;
mainCtx.clearRect(0, 0, viewSize.width, viewSize.height);
1 month ago
break;
}
});
1 month ago
image.value = new Image();
if (canvasRef.value) {
initCanvasSize();
window.addEventListener("resize", handleResize);
} else {
console.error("Canvas element not found");
}
1 month ago
1 month ago
// 全局监听键盘事件
window.addEventListener("keydown", handleKeyDown);
window.addEventListener("keyup", handleKeyUp);
1 month ago
});
onBeforeUnmount(() => {
1 month ago
emitter.off("btnClick");
emitter.off("evtType");
});
1 month ago
1 month ago
onUnmounted(() => {
1 month ago
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;
1 month ago
position: absolute;
1 month ago
z-index: 1;
top: 20px;
/* 避开水平标尺 */
left: 20px;
/* 避开垂直标尺 */
1 month ago
width: calc(100vw - 20px);
height: calc(100vh - 20px);
1 month ago
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>