From 3156cc2db4de0e83fc5bfd85b624c717be83bef6 Mon Sep 17 00:00:00 2001 From: qinjian Date: Thu, 21 Aug 2025 14:52:10 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=9B=B4=E6=96=B0=E6=A0=87=E9=9D=B6?= =?UTF-8?q?=E7=AE=A1=E7=90=86=E9=80=BB=E8=BE=91=EF=BC=8C=E4=BD=BF=E7=94=A8?= =?UTF-8?q?=E5=8E=9F=E5=A7=8BID=E4=BD=9C=E4=B8=BA=E9=94=AE=EF=BC=8C?= =?UTF-8?q?=E4=BC=98=E5=8C=96=E5=AE=9E=E6=97=B6=E5=9B=BE=E8=A1=A8=E5=92=8C?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E8=A1=A8=E6=98=BE=E7=A4=BA=EF=BC=8C=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=E8=AE=BE=E5=A4=87=E6=8F=8F=E8=BF=B0=E6=98=A0=E5=B0=84?= =?UTF-8?q?=20fix:=20-=20=E5=88=A0=E9=99=A4=E5=8D=95=E4=B8=AA=E6=A0=87?= =?UTF-8?q?=E9=9D=B6=E4=BC=9A=E5=AF=BC=E8=87=B4=E6=95=B0=E6=8D=AE=E6=B8=85?= =?UTF-8?q?=E7=A9=BA=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../wuyuanbiaoba/components/CameraView.jsx | 47 ++++++++----- .../components/RealtimeCharts.jsx | 61 ++++++++++++----- .../components/RealtimeDataTable.jsx | 7 +- .../sections/wuyuanbiaoba/container/index.jsx | 15 +---- .../wuyuanbiaoba/hooks/useTargetStorage.js | 67 +++++++++++++------ 5 files changed, 130 insertions(+), 67 deletions(-) diff --git a/client/src/sections/wuyuanbiaoba/components/CameraView.jsx b/client/src/sections/wuyuanbiaoba/components/CameraView.jsx index d85589a..e0c653f 100644 --- a/client/src/sections/wuyuanbiaoba/components/CameraView.jsx +++ b/client/src/sections/wuyuanbiaoba/components/CameraView.jsx @@ -282,11 +282,15 @@ const CameraView = ({ const videoRect = screenToVideoCoordinates(x, y, w, h); if (videoRect && rectangles.length < maxRectangles) { + // 只取时间戳后4位作为id和key + const ts = Date.now().toString(); + const shortId = ts.slice(-4); // 根据选中的模板预设参数 const templateParams = selectedTemplate ? { // 从模板中获取参数 - name: `target${rectangles.length + 1}` || selectedTemplate.name, + name: + `target${rectangles.length + 1}` || selectedTemplate.name, radius: selectedTemplate.physicalRadius || 40.0, isReferencePoint: selectedTemplate.isBaseline || false, gradientThreshold: @@ -309,8 +313,8 @@ const CameraView = ({ }; const newRect = { - id: Date.now(), // 使用时间戳作为唯一ID - key: Date.now().toString(), + id: shortId, + key: shortId, ...videoRect, ...templateParams, // 保存矩形区域信息用于服务端 @@ -735,8 +739,13 @@ const CameraView = ({ const targets = {}; // console.log(rectangles, "当前矩形数据"); - rectangles.forEach((rect, index) => { - targets[index.toString()] = { + rectangles.forEach((rect) => { + // 使用标靶的原始ID,而不是数组索引 + const targetKey = rect.id + ? rect.id.toString() + : Date.now().toString(); + + targets[targetKey] = { info: { rectangle_area: { x: rect.x, @@ -751,8 +760,8 @@ const CameraView = ({ anchor: rect.anchorThreshold || 80, }, radius: rect.radius || 40.0, - id: index, - desc: rect.name || `${index}_target`, + id: rect.id || targetKey, // 使用原始ID + desc: rect.name || `target_${targetKey}`, base: rect.isReferencePoint || false, }, perspective: [ @@ -1167,35 +1176,43 @@ const CameraView = ({ {/* 保存按钮 */} {/* 信息显示 */} diff --git a/client/src/sections/wuyuanbiaoba/components/RealtimeCharts.jsx b/client/src/sections/wuyuanbiaoba/components/RealtimeCharts.jsx index 9215667..b1a5b16 100644 --- a/client/src/sections/wuyuanbiaoba/components/RealtimeCharts.jsx +++ b/client/src/sections/wuyuanbiaoba/components/RealtimeCharts.jsx @@ -38,7 +38,7 @@ const RealtimeCharts = ({ tableData, lastUpdateTime }) => { } // 固定的设备颜色映射,确保颜色一致性 - const getDeviceColor = (deviceId) => { + const getDeviceColor = (deviceDesc) => { const colorMap = { target1: "#52c41a", // 绿色 target2: "#faad14", // 橙色 @@ -52,7 +52,7 @@ const RealtimeCharts = ({ tableData, lastUpdateTime }) => { target10: "#1890ff", // 蓝色 }; - return colorMap[deviceId] || "#1890ff"; + return colorMap[deviceDesc] || "#1890ff"; }; // 数据采样函数 - 每秒采样一次,最多保留25个数据点 @@ -118,6 +118,18 @@ const RealtimeCharts = ({ tableData, lastUpdateTime }) => { // 初始化可见目标状态 const { deviceIds } = sampleData(tableData); + + // 获取 deviceId 到 desc 的映射 + const deviceDescMap = useMemo(() => { + const map = {}; + tableData.forEach(item => { + if (item.deviceId && item.desc) { + map[item.deviceId] = item.desc; + } + }); + return map; + }, [tableData]); + useEffect(() => { if (deviceIds.length > 0) { const initialVisible = {}; @@ -203,11 +215,12 @@ const RealtimeCharts = ({ tableData, lastUpdateTime }) => { : null; }); + const deviceDesc = deviceDescMap[deviceId] || deviceId; return { label: deviceId, data: data, - borderColor: getDeviceColor(deviceId), - backgroundColor: getDeviceColor(deviceId) + "20", + borderColor: getDeviceColor(deviceDesc), + backgroundColor: getDeviceColor(deviceDesc) + "20", borderWidth: 2, pointRadius: 3, pointHoverRadius: 5, @@ -245,11 +258,12 @@ const RealtimeCharts = ({ tableData, lastUpdateTime }) => { : null; }); + const deviceDesc = deviceDescMap[deviceId] || deviceId; return { label: deviceId, data: data, - borderColor: getDeviceColor(deviceId), - backgroundColor: getDeviceColor(deviceId) + "20", + borderColor: getDeviceColor(deviceDesc), + backgroundColor: getDeviceColor(deviceDesc) + "20", borderWidth: 2, pointRadius: 3, pointHoverRadius: 5, @@ -266,8 +280,8 @@ const RealtimeCharts = ({ tableData, lastUpdateTime }) => { } }, [tableData, visibleTargets]); - // Chart.js配置选项 - const chartOptions = { + // Chart.js配置选项 - 移到组件内部以访问 deviceDescMap + const chartOptions = useMemo(() => ({ responsive: true, maintainAspectRatio: false, interaction: { @@ -282,6 +296,17 @@ const RealtimeCharts = ({ tableData, lastUpdateTime }) => { filter: function (tooltipItem) { return tooltipItem.parsed.y !== null; }, + callbacks: { + title: function(context) { + return context[0].label; // 显示时间 + }, + label: function(context) { + const deviceId = context.dataset.label; + const deviceDesc = deviceDescMap[deviceId] || deviceId; + const value = context.parsed.y; + return `${deviceDesc}: ${value}`; + } + } }, }, scales: { @@ -323,10 +348,10 @@ const RealtimeCharts = ({ tableData, lastUpdateTime }) => { tension: 0, }, }, - }; + }), [deviceDescMap]); // X轴图表配置 - const xChartOptions = { + const xChartOptions = useMemo(() => ({ ...chartOptions, plugins: { ...chartOptions.plugins, @@ -348,10 +373,10 @@ const RealtimeCharts = ({ tableData, lastUpdateTime }) => { }, }, }, - }; + }), [chartOptions]); // Y轴图表配置 - const yChartOptions = { + const yChartOptions = useMemo(() => ({ ...chartOptions, plugins: { ...chartOptions.plugins, @@ -373,7 +398,7 @@ const RealtimeCharts = ({ tableData, lastUpdateTime }) => { }, }, }, - }; + }), [chartOptions]); // 如果没有数据,显示空状态 if (!tableData || tableData.length === 0) { @@ -451,19 +476,19 @@ const RealtimeCharts = ({ tableData, lastUpdateTime }) => { > 全部隐藏 - {deviceIds.map(deviceId => ( + {deviceIds.map((deviceId,index) => ( ))} diff --git a/client/src/sections/wuyuanbiaoba/components/RealtimeDataTable.jsx b/client/src/sections/wuyuanbiaoba/components/RealtimeDataTable.jsx index 2881194..b4a6771 100644 --- a/client/src/sections/wuyuanbiaoba/components/RealtimeDataTable.jsx +++ b/client/src/sections/wuyuanbiaoba/components/RealtimeDataTable.jsx @@ -5,7 +5,12 @@ const { Title } = Typography; const RealtimeDataTable = ({ realtimeData }) => { const tableColumns = [ - { title: "设备编号", dataIndex: "deviceId", key: "deviceId" }, + { + title: "设备编号", + dataIndex: "deviceId", + key: "deviceId", + render: (deviceId, record) => record.desc || deviceId + }, { title: "X值(mm)", dataIndex: "xValue", diff --git a/client/src/sections/wuyuanbiaoba/container/index.jsx b/client/src/sections/wuyuanbiaoba/container/index.jsx index ba3de9c..4d22d9a 100644 --- a/client/src/sections/wuyuanbiaoba/container/index.jsx +++ b/client/src/sections/wuyuanbiaoba/container/index.jsx @@ -74,7 +74,8 @@ const WuyuanbiaobaContent = () => { return data.data.map((item) => ({ key: item.pos, - deviceId: `target${Number(item.pos) + 1}`, + deviceId: item.pos, // 使用 pos 作为 deviceId + desc: item.desc, // 添加 desc 字段 xValue: item.x, yValue: item.y, updateTime: data.time || new Date().toLocaleString(), @@ -372,18 +373,6 @@ const WuyuanbiaobaContent = () => { 视觉位移计配置工具 -
- -
{/* 中间区域 - 固定视口剩余高度 */} diff --git a/client/src/sections/wuyuanbiaoba/hooks/useTargetStorage.js b/client/src/sections/wuyuanbiaoba/hooks/useTargetStorage.js index cb967b2..eefc8f0 100644 --- a/client/src/sections/wuyuanbiaoba/hooks/useTargetStorage.js +++ b/client/src/sections/wuyuanbiaoba/hooks/useTargetStorage.js @@ -129,9 +129,12 @@ export const useTargetStorage = () => { const buildTargetsPayload = useCallback((targetsArray) => { const targets = {}; - targetsArray.forEach((target, index) => { + targetsArray.forEach((target) => { + // 使用标靶的原始ID作为键,而不是数组索引 + const targetKey = target.id || target.key || Date.now().toString(); const rectangleArea = target.rectangleArea || {}; - targets[index.toString()] = { + + targets[targetKey] = { info: { rectangle_area: { x: rectangleArea.x || 0, @@ -147,7 +150,7 @@ export const useTargetStorage = () => { }, radius: parseFloat(target.radius) || 40.0, id: target.id || target.key, - desc: target.name || `${target.id || target.key}_target`, + desc: target.name || `target_${targetKey}`, base: target.isReferencePoint || false, }, perspective: target.perspective || [ @@ -251,28 +254,38 @@ export const useTargetStorage = () => { } try { - // 先更新本地状态 - let updatedTargets = []; + // 先计算更新后的数据,不依赖状态更新 setTargets(prev => { - console.log(prev, '更新标靶前的状态'); - updatedTargets = prev.map(target => + console.log('更新标靶前的状态:', prev); + const updatedTargets = prev.map(target => target.key === targetInfo.key || target.id === targetInfo.id ? { ...target, ...targetInfo } : target ); - console.log(updatedTargets, '更新标靶后的状态'); + console.log('更新标靶后的状态:', updatedTargets); console.log('更新的标靶匹配:', prev.map(t => ({ id: t.id, key: t.key, match: t.key === targetInfo.key || t.id === targetInfo.id, isUpdated: t.key === targetInfo.key || t.id === targetInfo.id ? '是' : '否' }))); + + // 立即构建并发送数据,使用计算出的 updatedTargets + console.log('准备构建payload,使用的数据:', updatedTargets); + const outputData = buildTargetsPayload(updatedTargets); + console.log('构建的payload:', outputData); + + // 检查payload是否为空 + if (!outputData.values.targets || Object.keys(outputData.values.targets).length === 0) { + console.warn('警告:构建的payload为空!这会导致所有标靶被清除'); + } + + sendTargetsData(outputData, '更新', targetInfo.key || targetInfo.id); + return updatedTargets; }); - // 使用通用函数构建payload并发送 - const outputData = buildTargetsPayload(updatedTargets); - return sendTargetsData(outputData, '更新', targetInfo.key || targetInfo.id); + return true; } catch (error) { console.error('更新标靶失败:', error); @@ -290,19 +303,33 @@ export const useTargetStorage = () => { } try { - // 先更新本地状态 - let updatedTargets = []; + // 先计算删除后的数据,不依赖状态更新 setTargets(prev => { - console.log(prev, '删除标靶前的状态'); - updatedTargets = prev.filter(target => target.key !== targetKey && target.id !== targetKey); - console.log(updatedTargets, '删除标靶后的状态'); - console.log('删除的标靶ID匹配:', prev.map(t => ({ id: t.id, key: t.key, match: t.key === targetKey || t.id === targetKey }))); + console.log('删除标靶前的状态:', prev); + const updatedTargets = prev.filter(target => target.key !== targetKey && target.id !== targetKey); + console.log('删除标靶后的状态:', updatedTargets); + console.log('删除的标靶ID匹配:', prev.map(t => ({ + id: t.id, + key: t.key, + match: t.key === targetKey || t.id === targetKey + }))); + + // 立即构建并发送数据,使用计算出的 updatedTargets + console.log('准备构建payload,使用的数据:', updatedTargets); + const outputData = buildTargetsPayload(updatedTargets); + console.log('构建的payload:', outputData); + + // 检查payload是否为空 + if (!outputData.values.targets || Object.keys(outputData.values.targets).length === 0) { + console.warn('警告:构建的payload为空!这会导致所有标靶被清除'); + } + + sendTargetsData(outputData, '删除', targetKey); + return updatedTargets; }); - // 使用通用函数构建payload并发送 - const outputData = buildTargetsPayload(updatedTargets); - return sendTargetsData(outputData, '删除', targetKey); + return true; } catch (error) { console.error('删除标靶失败:', error);