diff --git a/src/assets/icons/svg/cgq.svg b/src/assets/icons/svg/cgq.svg new file mode 100644 index 0000000..f651888 --- /dev/null +++ b/src/assets/icons/svg/cgq.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/fan.svg b/src/assets/icons/svg/fan.svg new file mode 100644 index 0000000..fc2be17 --- /dev/null +++ b/src/assets/icons/svg/fan.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/gjdj.svg b/src/assets/icons/svg/gjdj.svg new file mode 100644 index 0000000..7fdf89a --- /dev/null +++ b/src/assets/icons/svg/gjdj.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/pump.svg b/src/assets/icons/svg/pump.svg new file mode 100644 index 0000000..ed8c917 --- /dev/null +++ b/src/assets/icons/svg/pump.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/router/index.js b/src/router/index.js index dbbdc00..7f30e90 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -76,8 +76,8 @@ export const constantRoutes = [ path: 'index', component: () => import('@/views/index'), name: 'Index', - meta: { title: '工作台', icon: 'index', affix: true } - // meta: { title: '首页', icon: '@/assets/treeIcons/index.png', affix: true } + meta: { title: '工作台', icon: 'index' } + // meta: { title: '首页', icon: '@/assets/treeIcons/index.png', affix: true } affix 是否常驻 }, ] }, @@ -90,7 +90,7 @@ export const constantRoutes = [ path: 'plan', component: () => import('@/views/plan'), name: 'Plan', - meta: { title: '平面图', icon: 'plan', affix: true } + meta: { title: '平面图', icon: 'plan' } }, ] @@ -234,6 +234,71 @@ export const constantRoutes = [ }, ] }, + { + name: "linkConfig", + alwaysShow: true, + component: Layout, + hidden: false, + name: "linkConfig", + path: "/linkConfig", + redirect: "noRedirect", + meta: { + icon: "link", + link: null, + noCache: false, + title: "联动配置管理", + }, + children: [ + { + component: () => import('@/views/linkConfig/alarmConfig'), + hidden: false, + name: "alarmConfig", + path: "alarmConfig", + meta: { + icon: "gjdj", + link: null, + noCache: false, + title: "报警等级配置", + }, + }, + { + component: () => import('@/views/linkConfig/fanLinkConfig'), + hidden: false, + name: "fanLinkConfig", + path: "fanLinkConfig", + meta: { + icon: "fan", + link: null, + noCache: false, + title: "风机联动阈值配置", + }, + }, + { + component: () => import('@/views/linkConfig/pumpLinkConfig'), + hidden: false, + name: "pumpLinkConfig", + path: "pumpLinkConfig", + meta: { + icon: "pump", + link: null, + noCache: false, + title: "水泵联动阈值配置", + }, + }, + { + component: () => import('@/views/linkConfig/sensorConfig'), + hidden: false, + name: "sensorConfig", + path: "sensorConfig", + meta: { + icon: "cgq", + link: null, + noCache: false, + title: "传感器报警阈值", + }, + }, + ] + }, { name: "Equipment", diff --git a/src/views/linkConfig/alarmConfig.vue b/src/views/linkConfig/alarmConfig.vue new file mode 100644 index 0000000..203db83 --- /dev/null +++ b/src/views/linkConfig/alarmConfig.vue @@ -0,0 +1,222 @@ + + + + + + + diff --git a/src/views/linkConfig/fanLinkConfig.vue b/src/views/linkConfig/fanLinkConfig.vue new file mode 100644 index 0000000..16ff658 --- /dev/null +++ b/src/views/linkConfig/fanLinkConfig.vue @@ -0,0 +1,1222 @@ + + + + + + \ No newline at end of file diff --git a/src/views/linkConfig/pumpLinkConfig.vue b/src/views/linkConfig/pumpLinkConfig.vue new file mode 100644 index 0000000..32d1225 --- /dev/null +++ b/src/views/linkConfig/pumpLinkConfig.vue @@ -0,0 +1,830 @@ + + + + + \ No newline at end of file diff --git a/src/views/linkConfig/sensorConfig.vue b/src/views/linkConfig/sensorConfig.vue new file mode 100644 index 0000000..db75c2c --- /dev/null +++ b/src/views/linkConfig/sensorConfig.vue @@ -0,0 +1,356 @@ + + + + + diff --git a/src/views/plan.vue b/src/views/plan.vue index 39cab86..4796a93 100644 --- a/src/views/plan.vue +++ b/src/views/plan.vue @@ -70,10 +70,7 @@
- +
+
+
+
@@ -158,6 +158,7 @@ import { import typesDictionary from "../utils/equipmentType"; //设备类型字典 import videoEle from "./components/videoEle.vue"; import tableEle from "./components/tableEle.vue"; +import * as echarts from "echarts"; const tableModelValue = ref(false); // 新增:底层背景Canvas引用 @@ -248,7 +249,7 @@ const MOUSE_CURRENT_Y = ref(0); const activeMarker = ref({ target: "" }); const Shrink = ref(false); -const SELECT_ACTION_TYPE = ref(''); +const SELECT_ACTION_TYPE = ref(""); //背景图片/标记图片等(保持不变) const backgroundImage = new URL("../assets/gl.png", import.meta.url).href; @@ -276,7 +277,10 @@ const icons = { }; icons.baiye.src = new URL("../assets/icon/baiye.png", import.meta.url).href; -icons.dianbiao.src = new URL("../assets/icon/dianbiao.png", import.meta.url).href; +icons.dianbiao.src = new URL( + "../assets/icon/dianbiao.png", + import.meta.url +).href; icons.hongwai.src = new URL("../assets/icon/hongwai.png", import.meta.url).href; icons.jiawan.src = new URL("../assets/icon/jiawan.png", import.meta.url).href; icons.jingai.src = new URL("../assets/icon/jingai.png", import.meta.url).href; @@ -303,6 +307,170 @@ const updateTableModelValue = (value) => { tableModelValue.value = value; }; +// 图表引用和实例 +const chartRef = ref(null); +let chartInstance = null; + +// 状态变量 +const darkMode = ref(true); +const chartData = ref({ + xAxis: [ + "1月", + "2月", + "3月", + "4月", + "5月", + "6月", + "7月", + "8月", + "9月", + "10月", + "11月", + "12月", + ], + series: [ + { + name: "产品A", + data: [120, 132, 101, 134, 90, 230, 210, 230, 180, 230, 210, 250], + color: "#42b983", // 柔和的绿色 + }, + { + name: "产品B", + data: [220, 182, 191, 234, 290, 330, 310, 330, 380, 330, 310, 350], + color: "#3498db", // 柔和的蓝色 + }, + { + name: "产品C", + data: [150, 232, 201, 154, 190, 330, 410, 330, 380, 430, 410, 450], + color: "#f39c12", // 柔和的橙色 + }, + ], +}); + +// 初始化图表 +const initChart = () => { + // 销毁已有实例 + if (chartInstance) { + chartInstance.dispose(); + } + + // 创建新实例 + chartInstance = echarts.init(chartRef.value); + + // 设置图表选项 + const option = { + backgroundColor: darkMode.value + ? "rgba(30, 30, 30, 0.7)" + : "rgba(255, 255, 255, 0.7)", + tooltip: { + trigger: "axis", + backgroundColor: darkMode.value + ? "rgba(40, 40, 40, 0.9)" + : "rgba(255, 255, 255, 0.9)", + borderColor: darkMode.value ? "#555" : "#ddd", + textStyle: { + color: darkMode.value ? "#fff" : "#333", + }, + padding: 10, + borderRadius: 6, + }, + legend: { + data: chartData.value.series.map((item) => item.name), + top: 10, + textStyle: { + color: darkMode.value ? "#eee" : "#555", + }, + }, + grid: { + left: "3%", + right: "4%", + bottom: "3%", + containLabel: true, + }, + xAxis: { + type: "category", + boundaryGap: false, + data: chartData.value.xAxis, + axisLine: { + lineStyle: { + color: darkMode.value ? "#555" : "#ddd", + }, + }, + axisLabel: { + color: darkMode.value ? "#bbb" : "#666", + }, + splitLine: { + show: true, + lineStyle: { + color: darkMode.value + ? "rgba(255, 255, 255, 0.05)" + : "rgba(0, 0, 0, 0.05)", + }, + }, + }, + yAxis: { + type: "value", + axisLine: { + lineStyle: { + color: darkMode.value ? "#555" : "#ddd", + }, + }, + axisLabel: { + color: darkMode.value ? "#bbb" : "#666", + }, + splitLine: { + show: true, + lineStyle: { + color: darkMode.value + ? "rgba(255, 255, 255, 0.05)" + : "rgba(0, 0, 0, 0.05)", + }, + }, + }, + series: chartData.value.series.map((item) => ({ + name: item.name, + type: "line", + data: item.data, + symbol: "circle", + symbolSize: 6, + emphasis: { + symbolSize: 8, + }, + lineStyle: { + width: 2, + color: item.color, + }, + itemStyle: { + color: item.color, + borderWidth: 2, + borderColor: darkMode.value ? "#333" : "#fff", + }, + areaStyle: { + color: { + type: "linear", + x: 0, + y: 0, + x2: 0, + y2: 1, + colorStops: [ + { + offset: 0, + color: item.color + "80", // 透明度80% + }, + { + offset: 1, + color: item.color + "00", // 透明度0% + }, + ], + }, + }, + })), + }; + + // 设置选项 + chartInstance.setOption(option); +}; + const pointsToPartiData = (data) => { let _partiData = {}; let areaId = 0; @@ -327,7 +495,7 @@ const handleTabbar = (e) => { d.isSelected = false; } }); - if(e.isSelected) { + if (e.isSelected) { SELECT_ACTION_TYPE.value = e.value; } else { SELECT_ACTION_TYPE.value = ""; @@ -401,15 +569,15 @@ const drawBackground = () => { // 初始化Canvas(修改:同时初始化两层Canvas) const initCanvas = () => { - nextTick(()=> { + nextTick(() => { // 初始化上层主Canvas ctx.value = canvasRef.value.getContext("2d"); // 初始化底层背景Canvas bgCtx.value = bgCanvasRef.value.getContext("2d"); - + // 加载背景图(加载完成后绘制底层背景) loadImage(); - }) + }); }; const getIcon = (id) => { @@ -444,18 +612,18 @@ const resizeCanvas = () => { // 同时设置两层Canvas的大小(保持一致) const clientWidth = canvasRef.value.clientWidth; const clientHeight = canvasRef.value.clientHeight; - + canvasRef.value.width = clientWidth; canvasRef.value.height = clientHeight; bgCanvasRef.value.width = clientWidth; bgCanvasRef.value.height = clientHeight; - + canvasWidthNum.value = clientWidth; canvasHeightNum.value = clientHeight; - + BL.value = clientHeight / StandardC; getCurrentArea(); - + // 窗口 resize 时需要重新绘制背景 drawBackground(); // 同时重绘上层内容 @@ -481,7 +649,14 @@ const getCurrentArea = () => { }; const isCGQ = (type) => { - if (type == 26 || type == 30 || type == 31 || type == 32 || type == 34 || type == 35) { + if ( + type == 26 || + type == 30 || + type == 31 || + type == 32 || + type == 34 || + type == 35 + ) { return true; } return false; @@ -516,7 +691,7 @@ const drawTooltip = () => { const canvasY = activeMarker.value.y * BL.value * scale.value + offsetY.value; let x = canvasX - tooltipWidth / 2; - let y = canvasY - tooltipHeight - (20 * scale.value); + let y = canvasY - tooltipHeight - 20 * scale.value; const canvasWidth = canvasRef.value.width; const canvasHeight = canvasRef.value.height; @@ -536,7 +711,13 @@ const drawTooltip = () => { ctxTooltip.lineTo(x + tooltipWidth - radius, y); ctxTooltip.arcTo(x + tooltipWidth, y, x + tooltipWidth, y + radius, radius); ctxTooltip.lineTo(x + tooltipWidth, y + tooltipHeight - radius); - ctxTooltip.arcTo(x + tooltipWidth, y + tooltipHeight, x + tooltipWidth - radius, y + tooltipHeight, radius); + ctxTooltip.arcTo( + x + tooltipWidth, + y + tooltipHeight, + x + tooltipWidth - radius, + y + tooltipHeight, + radius + ); ctxTooltip.lineTo(x + radius, y + tooltipHeight); ctxTooltip.arcTo(x, y + tooltipHeight, x, y + tooltipHeight - radius, radius); ctxTooltip.lineTo(x, y + radius); @@ -580,7 +761,7 @@ const draw = (alpha = 1) => { const canvasHeight = canvasRef.value.height; BL.value = canvasHeight / StandardC; - + // 清除上层画布(只清除动态内容) ctx.value.clearRect(0, 0, canvasWidth, canvasHeight); @@ -613,8 +794,11 @@ const draw = (alpha = 1) => { for (const key in points) { let _points = points[key]; _points.forEach((point) => { - if(SELECT_ACTION_TYPE.value && point.TypeId !== SELECT_ACTION_TYPE.value) { - return + if ( + SELECT_ACTION_TYPE.value && + point.TypeId !== SELECT_ACTION_TYPE.value + ) { + return; } let status = getIconStatus(point.TypeId); @@ -655,19 +839,78 @@ const draw = (alpha = 1) => { ); } - // 添加文字传感器数值 - if(point.TypeId == 26) { + // 辅助函数:绘制圆角矩形 + function roundedRect(ctx, x, y, width, height, radius, isStroke = false) { + ctx.beginPath(); + ctx.moveTo(x + radius, y); + ctx.lineTo(x + width - radius, y); + ctx.arcTo(x + width, y, x + width, y + radius, radius); + ctx.lineTo(x + width, y + height - radius); + ctx.arcTo( + x + width, + y + height, + x + width - radius, + y + height, + radius + ); + ctx.lineTo(x + radius, y + height); + ctx.arcTo(x, y + height, x, y + height - radius, radius); + ctx.lineTo(x, y + radius); + ctx.arcTo(x, y, x + radius, y, radius); + ctx.closePath(); + + if (isStroke) { + ctx.stroke(); + } else { + ctx.fill(); + } + } + + // 添加文字传感器数值(带圆角边框和半透明背景) + if (point.TypeId == 26) { + // 设置字体样式 + ctx.value.font = `10px Arial`; + const textColor = "#FFF"; // 白色字体 + const borderColor = "rgba(100, 180, 255, 0.9)"; // 浅蓝色边框 + const bgColor = "rgba(100, 180, 255, 0.4)"; // 同色系半透明背景 + + let _value = point.Data[0].Value; // 传感器数值value - ctx.value.font = `10px Arial`; // 设置字体和大小 - ctx.value.fillStyle = "#FFF"; // 设置文字颜色 // 计算文字位置(图标右侧10px处,垂直居中对齐) + const textX = point.x * BL.value; + const textY = point.y * BL.value + 20; // 垂直居中调整 - let _value = point.Data[0].Value; //传感器数值value + // 测量文本宽度,用于计算背景框大小 + const textMetrics = ctx.value.measureText(_value); + const padding = 3; // 文字周围的内边距 + const borderRadius = 3; // 圆角半径 - const textX = point.x * BL.value + (iSize * BL.value) / scale.value / 2 + 1; - const textY = point.y * BL.value + 15; // +5是为了视觉上垂直居中 - ctx.value.fillText(_value, textX, textY); // 绘制文字 + // 计算背景框的位置和大小 + const bgX = textX - padding - textMetrics.width / 2; + const bgY = textY - 10; // 基于10px字体的位置调整 + const bgWidth = textMetrics.width + padding * 2; + const bgHeight = 14; // 适合10px字体的高度 + // 绘制圆角背景 + ctx.value.fillStyle = bgColor; + roundedRect(ctx.value, bgX, bgY + 10, bgWidth, bgHeight, borderRadius); + + // 绘制圆角边框 + ctx.value.strokeStyle = borderColor; + ctx.value.lineWidth = 1; + roundedRect( + ctx.value, + bgX, + bgY + 10, + bgWidth, + bgHeight, + borderRadius, + true + ); + + // 绘制文字(确保在最上层) + ctx.value.fillStyle = textColor; + ctx.value.fillText(_value, textX - textMetrics.width / 2, textY + 10); } if (point.target == "device" && point.IsOpen) { @@ -682,7 +925,11 @@ const draw = (alpha = 1) => { ctx.value.strokeStyle = window.customConfigUrl.faultColor; ctx.value.lineWidth = (4 / scale.value) * BL.value; ctx.value.stroke(); - } else if (point.target == "device" && !isCGQ(point.TypeId) && point.TypeId != 66) { + } else if ( + point.target == "device" && + !isCGQ(point.TypeId) && + point.TypeId != 66 + ) { ctx.value.strokeStyle = window.customConfigUrl.closeColor; ctx.value.lineWidth = (4 / scale.value) * BL.value; ctx.value.stroke(); @@ -716,7 +963,7 @@ const animate = () => { let mouseMoveTimeout = null; const MOUSE_MOVE_DELAY = 16; const onMouseMove = (e) => { - if(!BL.value) return; + if (!BL.value) return; if (dragging.value === null) { if (mouseMoveTimeout) { cancelAnimationFrame(mouseMoveTimeout); @@ -763,7 +1010,8 @@ const onMouseMove = (e) => { activeReferenceLine.value = closestLine; if (closestLine !== null) { const easingFactor = 0.2; - currentPoint.y = currentPoint.y + (closestLine - currentPoint.y) * easingFactor; + currentPoint.y = + currentPoint.y + (closestLine - currentPoint.y) * easingFactor; if (Math.abs(currentPoint.y - closestLine) < 10) { currentPoint.y = closestLine; } @@ -934,7 +1182,10 @@ const handleWheel = (e) => { const zoomIntensity = 0.1; const wheel = e.deltaY < 0 ? 1 : -1; - const newScale = Math.max(0.75, Math.min(2, scale.value + wheel * zoomIntensity)); + const newScale = Math.max( + 0.75, + Math.min(2, scale.value + wheel * zoomIntensity) + ); scalePercent.value = Math.round(newScale * 100); @@ -942,7 +1193,7 @@ const handleWheel = (e) => { offsetY.value = mouseY - imgY * newScale; scale.value = newScale; - + // 缩放时需要重新绘制背景 drawBackground(); // 同时重绘上层内容 @@ -960,7 +1211,9 @@ const handleCanvasClick = (e) => { const originalY = (y - offsetY.value) / scale.value; console.log( - `点击坐标: (${(originalX / BL.value).toFixed(1)}, ${(originalY / BL.value).toFixed(1)})` + `点击坐标: (${(originalX / BL.value).toFixed(1)}, ${( + originalY / BL.value + ).toFixed(1)})` ); let clickedPoint = false; @@ -1008,7 +1261,7 @@ const resetView = () => { selectedPointId.value = null; progress.value = 0; selectAreaId.value = 1; - + // 重置背景 drawBackground(); // 重置上层内容 @@ -1137,6 +1390,7 @@ const upDialogZindex = (e) => { // 初始化 onMounted(() => { getJson(); + initChart(); window.addEventListener("resize", resizeCanvas); initCanvas(); // animate(); // 根据原代码注释,按需启用 @@ -1282,7 +1536,8 @@ body { } /* 新增:两层Canvas样式,确保完全重叠 */ -.bg-canvas, .main-canvas { +.bg-canvas, +.main-canvas { display: block; width: 100%; height: 100%; @@ -1383,9 +1638,8 @@ button:active { background: rgba(255, 255, 255, 0.1); backdrop-filter: blur(4px); border-radius: 4px; - margin-top: 15px; position: absolute; - bottom: 30px; + top: 30px; left: 50%; transform: translateX(-50%); box-shadow: 0 4px 10px rgba(255, 255, 255, 0.1); @@ -1407,6 +1661,24 @@ button:active { width: 100%; } +.charts-content { + width: 800px; + height: 240px; + background: rgba(255, 255, 255, 0.1); + backdrop-filter: blur(4px); + border-radius: 4px; + position: absolute; + bottom: 30px; + left: 50%; + transform: translateX(-50%); + box-shadow: 0 4px 10px rgba(255, 255, 255, 0.1); + display: flex; + .line-chart { + width: 100%; + height: 100%; + } +} + /deep/ .el-slider__button { height: 10px !important; width: 10px !important; diff --git a/src/views/system/log/index.vue b/src/views/system/log/index.vue index 8dacc4d..8e01b11 100644 --- a/src/views/system/log/index.vue +++ b/src/views/system/log/index.vue @@ -55,6 +55,9 @@ 查询 + + 导出 +