diff --git a/src/components/DraggableGauge.vue b/src/components/DraggableGauge.vue index c128c5dc..614c5eb9 100644 --- a/src/components/DraggableGauge.vue +++ b/src/components/DraggableGauge.vue @@ -113,12 +113,13 @@ import { getHikPtzControlApi, setHikPtzControlApi, } from "@/assets/js/api/equipmentCenter/cameraList"; +import { Message } from "element-ui"; export default { - name: "DraggableGauge", + name: 'DraggableGauge', props: { value: { type: Number, - default: 50, + default: 10, }, size: { type: Number, @@ -160,7 +161,6 @@ export default { data() { return { currentValue: this.value, - canvas: null, ctx: null, isDragging: false, controlInfo: { @@ -168,55 +168,25 @@ export default { opType: "", }, videoInfo: {}, + // 常量定义 + DEVICE_TYPE_BALL: 2, // 球机设备类型 + VIDEO_TYPE_ISC: 3, // ISC视频类型 + SUCCESS_CODE: 200, // 成功状态码 + GAP_ANGLE: 90, // 仪表盘缺口角度 + INDICATOR_COLOR: "#2758C0", // 指示器颜色 }; }, - mounted() { - this.initCanvas(); - this.drawGauge(); - this.addEventListeners(); - }, - beforeDestroy() { - this.removeEventListeners(); - }, methods: { - controlVideoMouse(opType, action) { - if (this.videoInfo.deviceType != 2) { - this.$message.warning("该设备不是球机,不支持此操作"); - return false; - } - this.controlInfo.opType = opType; - if (action == 0) { - this.controlInfo.videoFlag = true; - } - const json = { - itemId: this.videoInfo.itemId, - opType: opType, - action: action, - opSize: this.currentValue, - opCode: 1, - }; - setHikPtzControlApi(json) - .then((res) => { - if (res.code == 200) { - // Message.success("控制成功"); - } - }) - .finally(() => { - if (action == 1) { - this.controlInfo.videoFlag = false; - } - }); - }, initCanvas() { - this.canvas = this.$refs.canvas; - this.ctx = this.canvas.getContext("2d"); + const canvas = this.$refs.canvas; + if (!canvas) return; + this.ctx = canvas.getContext("2d"); - // 设置canvas尺寸 const dpr = window.devicePixelRatio || 1; - this.canvas.width = this.size * dpr; - this.canvas.height = this.size * dpr; - this.canvas.style.width = `${this.size}px`; - this.canvas.style.height = `${this.size}px`; + canvas.width = this.size * dpr; + canvas.height = this.size * dpr; + canvas.style.width = `${this.size}px`; + canvas.style.height = `${this.size}px`; this.ctx.scale(dpr, dpr); }, drawGauge() { @@ -225,15 +195,13 @@ export default { const center = this.size / 2; const radius = center - this.lineWidth / 2; const startAngle = Math.PI / 2 + Math.PI / 4; - const endAngle = - startAngle + ((2 * Math.PI - Math.PI / 2) * this.currentValue) / 100; + const endAngle = startAngle + ((2 * Math.PI - Math.PI / 2) * this.currentValue) / 100; - // 清除画布 this.ctx.clearRect(0, 0, this.size, this.size); - // 绘制背景圆(虚线) + // 绘制背景圆 this.ctx.beginPath(); - this.ctx.setLineDash([5, 3]); // 设置虚线样式:5px实线,3px空白 + this.ctx.setLineDash([5, 3]); this.ctx.arc( center, center, @@ -246,25 +214,15 @@ export default { this.ctx.lineWidth = this.lineWidth; this.ctx.stroke(); - // 绘制进度圆(虚线) + // 绘制进度圆 this.ctx.beginPath(); - this.ctx.setLineDash([5, 3]); // 设置虚线样式:5px实线,3px空白 + this.ctx.setLineDash([5, 3]); this.ctx.arc(center, center, radius, startAngle, endAngle); this.ctx.strokeStyle = this.progressColor; this.ctx.lineWidth = this.lineWidth; this.ctx.stroke(); - // // 绘制指示器 - // const indicatorRadius = this.lineWidth * 1.5; - // const indicatorX = center + radius * Math.cos(endAngle); - // const indicatorY = center + radius * Math.sin(endAngle); - - // this.ctx.beginPath(); - // this.ctx.setLineDash([]); // 重置为实线 - // this.ctx.arc(indicatorX, indicatorY, indicatorRadius, 0, 2 * Math.PI); - // this.ctx.fillStyle = this.progressColor; - // this.ctx.fill(); - // 绘制指示器(长方形) + // 绘制指示器 const indicatorWidth = this.lineWidth; const indicatorHeight = this.lineWidth * 3; const indicatorX = center + radius * Math.cos(endAngle); @@ -272,7 +230,7 @@ export default { this.ctx.save(); this.ctx.translate(indicatorX, indicatorY); - this.ctx.rotate(endAngle + Math.PI / 2); // 使长方形朝向切线方向 + this.ctx.rotate(endAngle + Math.PI / 2); this.ctx.beginPath(); this.ctx.setLineDash([]); @@ -282,22 +240,63 @@ export default { indicatorWidth, indicatorHeight ); - this.ctx.fillStyle = "#2758C0"; + this.ctx.fillStyle = this.INDICATOR_COLOR; this.ctx.fill(); this.ctx.restore(); }, - addEventListeners() { - this.canvas.addEventListener("mousedown", this.startDrag); - this.canvas.addEventListener("touchstart", this.startDrag); + controlVideoMouse(opType, action) { + if (this.videoInfo.deviceType !== this.DEVICE_TYPE_BALL) { + Message.warning("该设备不是球机,不支持此操作"); + return false; + } + this.controlInfo.opType = opType; + if (action === 0) { + this.controlInfo.videoFlag = true; + } + const json = { + itemId: this.videoInfo.itemId, + opType: opType, + action: action, + opSize: this.currentValue, + opCode: 1, + }; + setHikPtzControlApi(json) + .then((res) => { + if (res.code === this.SUCCESS_CODE) { + // 控制成功,静默处理 + } + }) + .catch((error) => { + console.error("视频控制失败:", error); + Message.error("视频控制失败,请稍后重试"); + }) + .finally(() => { + if (action === 1) { + this.controlInfo.videoFlag = false; + } + }); }, - removeEventListeners() { - this.canvas.removeEventListener("mousedown", this.startDrag); - this.canvas.removeEventListener("touchstart", this.startDrag); - document.removeEventListener("mousemove", this.handleDrag); - document.removeEventListener("mouseup", this.stopDrag); - document.removeEventListener("touchmove", this.handleDrag); - document.removeEventListener("touchend", this.stopDrag); + controlVideoFn_isc(opType) { + const json = { + itemId: this.videoInfo.itemId, + opType: opType, + opSize: this.currentValue, + opCode: 1, + }; + getHikPtzControlApi(json) + .then((res) => { + if (res.code === this.SUCCESS_CODE) { + Message.success("控制成功"); + } + }) + .catch((error) => { + console.error("ISC视频控制失败:", error); + Message.error("视频控制失败,请稍后重试"); + }) + .finally(() => { + this.controlInfo.videoFlag = false; + }); }, startDrag(e) { this.isDragging = true; @@ -305,93 +304,103 @@ export default { document.addEventListener("mouseup", this.stopDrag); document.addEventListener("touchmove", this.handleDrag); document.addEventListener("touchend", this.stopDrag); - this.handleDrag(e); // 立即更新位置 + this.handleDrag(e); }, handleDrag(e) { - if (!this.isDragging) return; + if (!this.isDragging || !this.$refs.canvas) return; - const rect = this.canvas.getBoundingClientRect(); - const clientX = e.clientX || e.touches[0].clientX; - const clientY = e.clientY || e.touches[0].clientY; + const rect = this.$refs.canvas.getBoundingClientRect(); + // 安全地获取触摸或鼠标坐标 + const clientX = e.clientX !== undefined ? e.clientX : (e.touches && e.touches[0] && e.touches[0].clientX); + const clientY = e.clientY !== undefined ? e.clientY : (e.touches && e.touches[0] && e.touches[0].clientY); + + if (clientX === undefined || clientY === undefined) return; - // 计算相对于canvas中心的坐标 const centerX = rect.left + rect.width / 2; const centerY = rect.top + rect.height / 2; const x = clientX - centerX; const y = clientY - centerY; - // 计算角度 (0-360度),从底部开始计算 - let angle = Math.atan2(y, x) * (180 / Math.PI); - angle = (angle + 180) % 360; // 调整为从底部开始 + // 计算鼠标相对于圆心的角度(弧度) + let angle = Math.atan2(y, x); + + // 仪表盘起始角度:Math.PI / 2 + Math.PI / 4 = 135° (从右上角开始) + const startAngle = Math.PI / 2 + Math.PI / 4; + // 仪表盘结束角度:135° + 270° = 405° = 45° (跨越0°) + const endAngle = startAngle + (2 * Math.PI - Math.PI / 2); + // 仪表盘有效角度范围:270° (360° - 90°缺口) + const usableAngle = 2 * Math.PI - Math.PI / 2; + + // 将角度转换为 0-2π 范围 + if (angle < 0) { + angle += 2 * Math.PI; + } + + // 计算相对于起始角度的偏移角度 + let relativeAngle; + + // 计算结束角度(在0-2π范围内) + const endAngleNormalized = endAngle % (2 * Math.PI); // 45° = π/4 + + // 判断角度是否在有效范围内 + // 情况1:角度在 startAngle 到 2π 之间(135° 到 360°) + if (angle >= startAngle && angle <= 2 * Math.PI) { + relativeAngle = angle - startAngle; + // 如果超出有效范围,限制在边界 + if (relativeAngle > usableAngle) { + relativeAngle = usableAngle; + } + } + // 情况2:角度在 0 到 endAngleNormalized 之间(0° 到 45°),即跨越0°的情况 + else if (angle >= 0 && angle <= endAngleNormalized) { + // 角度跨越了0°,需要加上2π再计算 + relativeAngle = (angle + 2 * Math.PI) - startAngle; + // 确保不超过有效范围 + if (relativeAngle > usableAngle) { + relativeAngle = usableAngle; + } + // 确保不小于0(防止计算错误) + if (relativeAngle < 0) { + relativeAngle = 0; + } + } + // 情况3:角度在无效范围内(45° 到 135°之间,即缺口部分) + else { + // 如果角度在缺口范围内,限制到最近的边界 + // 判断更接近起始还是结束 + const distToStart = Math.abs(angle - startAngle); + const distToEnd = Math.min( + Math.abs(angle - endAngleNormalized), + Math.abs(angle + 2 * Math.PI - endAngleNormalized) + ); + + if (distToStart < distToEnd) { + relativeAngle = 0; // 限制到起始位置(0%) + } else { + relativeAngle = usableAngle; // 限制到结束位置(100%) + } + } + + // 将角度转换为 0-100 的值 + const value = Math.round((relativeAngle / usableAngle) * 100); + this.currentValue = Math.min(this.max, Math.max(this.min, value)); - // 转换为0-100的值,考虑90度缺口部分 - const gapAngle = 90; // 缺口角度 - const usableAngle = 360 - gapAngle; - let value = Math.round((angle * 100) / usableAngle); - if (angle > usableAngle) value = 100; // 超过可用角度时设为最大值 - this.currentValue = Math.min(100, Math.max(0, value)); - - // 立即重绘指示器位置 this.drawGauge(); - this.$emit("input", this.currentValue); this.$emit("change", this.currentValue); - if (e.cancelable && e.type.includes("touch")) { + if (e.cancelable && e.type && e.type.includes("touch")) { e.preventDefault(); } }, stopDrag() { + if (!this.isDragging) return; this.isDragging = false; document.removeEventListener("mousemove", this.handleDrag); document.removeEventListener("mouseup", this.stopDrag); document.removeEventListener("touchmove", this.handleDrag); document.removeEventListener("touchend", this.stopDrag); - }, - controlVideoFn(pan, tilt, zoom, opType) { - this.controlInfo.opType = opType; - if (this.controlInfo.videoFlag) { - this.$message.warning("不要重复点击"); - return; - } - if (this.videoInfo.deviceType != 2) { - this.$message.warning("该设备不是球机,不支持此操作"); - return false; - } - switch (this.videoInfo.videoType) { - case 3: - this.controlInfo.videoFlag = true; - this.controlVideoFn_isc(opType); - break; - // case 4: - // this.controlInfo.videoFlag = true; - // this.controlVideoFn_dh(pan, tilt, zoom) - // break; - default: - this.$message.warning("暂不支持"); - break; - } - }, - controlVideoFn_isc(opType) { - let json = { - // cameraId: this.videoInfo.deviceSerial, - itemId: this.videoInfo.itemId, - opType: opType, - opSize: this.currentValue, - opCode: 1, - }; - getHikPtzControlApi({ - ...json, - }) - .then((res) => { - if (res.code == 200) { - this.$message.success("控制成功"); - } - }) - .finally(() => { - this.controlInfo.videoFlag = false; - }); - }, + } }, watch: { value(newVal) { @@ -403,16 +412,40 @@ export default { this.videoInfo = newVal; }, deep: true, - immediate: true, - }, + immediate: true + } }, + mounted() { + if (!this.$refs.canvas) return; + this.initCanvas(); + this.drawGauge(); + this.$refs.canvas.addEventListener("mousedown", this.startDrag); + this.$refs.canvas.addEventListener("touchstart", this.startDrag); + }, + beforeDestroy() { + // 确保停止拖拽状态 + if (this.isDragging) { + this.stopDrag(); + } + // 清理canvas事件监听器 + if (this.$refs.canvas) { + this.$refs.canvas.removeEventListener("mousedown", this.startDrag); + this.$refs.canvas.removeEventListener("touchstart", this.startDrag); + } + // 清理document事件监听器(防止内存泄漏) + document.removeEventListener("mousemove", this.handleDrag); + document.removeEventListener("mouseup", this.stopDrag); + document.removeEventListener("touchmove", this.handleDrag); + document.removeEventListener("touchend", this.stopDrag); + } }; +