Merge branch 'dunhuang-dev' of http://192.168.34.160:8023/admin/zhgdyun into dunhuang-cs
This commit is contained in:
commit
678f084a34
@ -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);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
|
||||
<style scoped lang="less">
|
||||
.gauge-canvas {
|
||||
margin-bottom: 28px;
|
||||
position: absolute;
|
||||
position: absolute
|
||||
}
|
||||
#content {
|
||||
margin: auto;
|
||||
|
||||
@ -111,7 +111,6 @@ import {
|
||||
setHikPtzControlApi,
|
||||
} from "@/assets/js/api/equipmentCenter/cameraList";
|
||||
import { Message } from "element-ui";
|
||||
import { active } from "sortablejs";
|
||||
|
||||
const props = defineProps({
|
||||
value: {
|
||||
@ -168,9 +167,16 @@ const controlInfo = ref({
|
||||
});
|
||||
const videoInfo = ref({});
|
||||
|
||||
// 常量定义
|
||||
const DEVICE_TYPE_BALL = 2; // 球机设备类型
|
||||
const VIDEO_TYPE_ISC = 3; // ISC视频类型
|
||||
const SUCCESS_CODE = 200; // 成功状态码
|
||||
const GAP_ANGLE = 90; // 仪表盘缺口角度
|
||||
const INDICATOR_COLOR = "#2758C0"; // 指示器颜色
|
||||
|
||||
// 方法定义
|
||||
const initCanvas = () => {
|
||||
// canvas.value = canvas.value;
|
||||
if (!canvas.value) return;
|
||||
ctx.value = canvas.value.getContext("2d");
|
||||
|
||||
const dpr = window.devicePixelRatio || 1;
|
||||
@ -233,18 +239,18 @@ const drawGauge = () => {
|
||||
indicatorWidth,
|
||||
indicatorHeight
|
||||
);
|
||||
ctx.value.fillStyle = "#2758C0";
|
||||
ctx.value.fillStyle = INDICATOR_COLOR;
|
||||
ctx.value.fill();
|
||||
|
||||
ctx.value.restore();
|
||||
};
|
||||
const controlVideoMouse = (opType, action) => {
|
||||
if (videoInfo.value.deviceType != 2) {
|
||||
if (videoInfo.value.deviceType !== DEVICE_TYPE_BALL) {
|
||||
Message.warning("该设备不是球机,不支持此操作");
|
||||
return false;
|
||||
}
|
||||
controlInfo.value.opType = opType;
|
||||
if (action == 0) {
|
||||
if (action === 0) {
|
||||
controlInfo.value.videoFlag = true;
|
||||
}
|
||||
const json = {
|
||||
@ -256,36 +262,41 @@ const controlVideoMouse = (opType, action) => {
|
||||
};
|
||||
setHikPtzControlApi(json)
|
||||
.then((res) => {
|
||||
if (res.code == 200) {
|
||||
// Message.success("控制成功");
|
||||
if (res.code === SUCCESS_CODE) {
|
||||
// 控制成功,静默处理
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("视频控制失败:", error);
|
||||
Message.error("视频控制失败,请稍后重试");
|
||||
})
|
||||
.finally(() => {
|
||||
if (action == 1) {
|
||||
if (action === 1) {
|
||||
controlInfo.value.videoFlag = false;
|
||||
}
|
||||
});
|
||||
};
|
||||
const controlVideoFn = (pan, tilt, zoom, opType) => {
|
||||
controlInfo.value.opType = opType;
|
||||
if (controlInfo.value.videoFlag) {
|
||||
Message.warning("不要重复点击");
|
||||
return;
|
||||
}
|
||||
if (videoInfo.value.deviceType != 2) {
|
||||
Message.warning("该设备不是球机,不支持此操作");
|
||||
return false;
|
||||
}
|
||||
switch (videoInfo.value.videoType) {
|
||||
case 3:
|
||||
controlInfo.value.videoFlag = true;
|
||||
controlVideoFn_isc(opType);
|
||||
break;
|
||||
default:
|
||||
Message.warning("暂不支持");
|
||||
break;
|
||||
}
|
||||
};
|
||||
// 如果将来需要使用,保留此方法
|
||||
// const controlVideoFn = (opType) => {
|
||||
// controlInfo.value.opType = opType;
|
||||
// if (controlInfo.value.videoFlag) {
|
||||
// Message.warning("不要重复点击");
|
||||
// return;
|
||||
// }
|
||||
// if (videoInfo.value.deviceType !== DEVICE_TYPE_BALL) {
|
||||
// Message.warning("该设备不是球机,不支持此操作");
|
||||
// return false;
|
||||
// }
|
||||
// switch (videoInfo.value.videoType) {
|
||||
// case VIDEO_TYPE_ISC:
|
||||
// controlInfo.value.videoFlag = true;
|
||||
// controlVideoFn_isc(opType);
|
||||
// break;
|
||||
// default:
|
||||
// Message.warning("暂不支持");
|
||||
// break;
|
||||
// }
|
||||
// };
|
||||
|
||||
const controlVideoFn_isc = (opType) => {
|
||||
const json = {
|
||||
@ -296,10 +307,14 @@ const controlVideoFn_isc = (opType) => {
|
||||
};
|
||||
getHikPtzControlApi(json)
|
||||
.then((res) => {
|
||||
if (res.code == 200) {
|
||||
if (res.code === SUCCESS_CODE) {
|
||||
Message.success("控制成功");
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("ISC视频控制失败:", error);
|
||||
Message.error("视频控制失败,请稍后重试");
|
||||
})
|
||||
.finally(() => {
|
||||
controlInfo.value.videoFlag = false;
|
||||
});
|
||||
@ -316,25 +331,83 @@ const startDrag = (e) => {
|
||||
};
|
||||
|
||||
const handleDrag = (e) => {
|
||||
if (!isDragging.value) return;
|
||||
if (!isDragging.value || !canvas.value) return;
|
||||
|
||||
const rect = canvas.value.getBoundingClientRect();
|
||||
const clientX = e.clientX || e.touches[0].clientX;
|
||||
const clientY = e.clientY || e.touches[0].clientY;
|
||||
// 安全地获取触摸或鼠标坐标
|
||||
const clientX = e.clientX ?? (e.touches?.[0]?.clientX);
|
||||
const clientY = e.clientY ?? (e.touches?.[0]?.clientY);
|
||||
|
||||
if (clientX === undefined || clientY === undefined) return;
|
||||
|
||||
const centerX = rect.left + rect.width / 2;
|
||||
const centerY = rect.top + rect.height / 2;
|
||||
const x = clientX - centerX;
|
||||
const y = clientY - centerY;
|
||||
|
||||
let angle = Math.atan2(y, x) * (180 / Math.PI);
|
||||
angle = (angle + 180) % 360;
|
||||
|
||||
const gapAngle = 90;
|
||||
const usableAngle = 360 - gapAngle;
|
||||
let value = Math.round((angle * 100) / usableAngle);
|
||||
if (angle > usableAngle) value = 100;
|
||||
currentValue.value = Math.min(100, Math.max(0, value));
|
||||
// 计算鼠标相对于圆心的角度(弧度)
|
||||
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);
|
||||
currentValue.value = Math.min(props.max, Math.max(props.min, value));
|
||||
|
||||
drawGauge();
|
||||
emit("input", currentValue.value);
|
||||
@ -346,6 +419,7 @@ const handleDrag = (e) => {
|
||||
};
|
||||
|
||||
const stopDrag = () => {
|
||||
if (!isDragging.value) return;
|
||||
isDragging.value = false;
|
||||
document.removeEventListener("mousemove", handleDrag);
|
||||
document.removeEventListener("mouseup", stopDrag);
|
||||
@ -355,6 +429,7 @@ const stopDrag = () => {
|
||||
|
||||
// 生命周期
|
||||
onMounted(() => {
|
||||
if (!canvas.value) return;
|
||||
initCanvas();
|
||||
drawGauge();
|
||||
canvas.value.addEventListener("mousedown", startDrag);
|
||||
@ -362,8 +437,16 @@ onMounted(() => {
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
canvas.value.removeEventListener("mousedown", startDrag);
|
||||
canvas.value.removeEventListener("touchstart", startDrag);
|
||||
// 确保停止拖拽状态
|
||||
if (isDragging.value) {
|
||||
stopDrag();
|
||||
}
|
||||
// 清理canvas事件监听器
|
||||
if (canvas.value) {
|
||||
canvas.value.removeEventListener("mousedown", startDrag);
|
||||
canvas.value.removeEventListener("touchstart", startDrag);
|
||||
}
|
||||
// 清理document事件监听器(防止内存泄漏)
|
||||
document.removeEventListener("mousemove", handleDrag);
|
||||
document.removeEventListener("mouseup", stopDrag);
|
||||
document.removeEventListener("touchmove", handleDrag);
|
||||
@ -392,10 +475,7 @@ watch(
|
||||
margin-bottom: 28px;
|
||||
position: absolute;
|
||||
}
|
||||
#content {
|
||||
margin: auto;
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
.draggable-gauge-container {
|
||||
position: relative;
|
||||
user-select: none;
|
||||
@ -408,73 +488,6 @@ watch(
|
||||
}
|
||||
}
|
||||
|
||||
// Element UI 风格仪表盘样式
|
||||
.gauge-wrapper {
|
||||
width: 260px;
|
||||
height: 260px;
|
||||
position: absolute;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-bottom: 30px;
|
||||
z-index: 5;
|
||||
}
|
||||
|
||||
.gauge-container {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
.gauge-svg {
|
||||
transform: rotate(-135deg); // 旋转SVG使圆弧从正确位置开始
|
||||
}
|
||||
|
||||
.gauge-background {
|
||||
opacity: 0.2;
|
||||
}
|
||||
|
||||
.gauge-progress {
|
||||
stroke-linecap: round; // 圆角端点
|
||||
}
|
||||
|
||||
.gauge-indicator {
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
|
||||
// &:hover {
|
||||
// transform: scale(1.1);
|
||||
// }
|
||||
|
||||
// &:active {
|
||||
// transform: scale(0.95);
|
||||
// }
|
||||
}
|
||||
|
||||
.gauge-value-display {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
text-align: center;
|
||||
pointer-events: none;
|
||||
z-index: 4;
|
||||
|
||||
.value-text {
|
||||
font-weight: bold;
|
||||
line-height: 1;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.title-text {
|
||||
opacity: 0.8;
|
||||
line-height: 1;
|
||||
}
|
||||
}
|
||||
.container-bottom {
|
||||
margin-top: 6px;
|
||||
width: 183px;
|
||||
@ -490,10 +503,7 @@ watch(
|
||||
background-size: 100% 100%;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
.video-icon2_1 {
|
||||
height: 13px;
|
||||
background-image: url("~@/assets/images/videoSurveillance/video-icon2_1.png");
|
||||
}
|
||||
// .video-icon2_1 未使用,已移除
|
||||
}
|
||||
.container-box {
|
||||
width: 159px;
|
||||
|
||||
@ -984,6 +984,7 @@ export default {
|
||||
...row,
|
||||
projectSn: this.$store.state.projectSn,
|
||||
isEnabled: row.isEnabled == 1 ? 0 : 1,
|
||||
devSn: this.currentGroupInfo.devSn,
|
||||
}).then((res) => {
|
||||
if (res.code == 200) {
|
||||
this.$message.success(this.$t("message.projectInfo.saveSuccess"));
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user