Browse Source

feat: 更新标靶管理逻辑,使用原始ID作为键,优化实时图表和数据表显示,添加设备描述映射

fix:
- 删除单个标靶会导致数据清空的问题
master
qinjian 3 days ago
parent
commit
3156cc2db4
  1. 47
      client/src/sections/wuyuanbiaoba/components/CameraView.jsx
  2. 61
      client/src/sections/wuyuanbiaoba/components/RealtimeCharts.jsx
  3. 7
      client/src/sections/wuyuanbiaoba/components/RealtimeDataTable.jsx
  4. 15
      client/src/sections/wuyuanbiaoba/container/index.jsx
  5. 67
      client/src/sections/wuyuanbiaoba/hooks/useTargetStorage.js

47
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) {
// 4idkey
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 = ({
{/* 保存按钮 */}
<button
onClick={handleSave}
disabled={isSaving}
disabled={isSaving || rectangles.length === 0}
style={{
position: "absolute",
top: "10px",
right: "10px",
backgroundColor: isSaving
? "rgba(128, 128, 128, 0.7)"
: "rgba(0, 100, 0, 0.7)",
backgroundColor:
isSaving || rectangles.length === 0
? "rgba(128, 128, 128, 0.7)"
: "rgba(0, 100, 0, 0.7)",
color: "white",
border: "1px solid rgba(255, 255, 255, 0.3)",
borderRadius: "4px",
padding: "6px 12px",
fontSize: "12px",
cursor: isSaving ? "not-allowed" : "pointer",
cursor:
isSaving || rectangles.length === 0
? "not-allowed"
: "pointer",
zIndex: 10,
transition: "all 0.2s",
}}
onMouseEnter={(e) => {
if (!isSaving) {
if (!isSaving && rectangles.length > 0) {
e.target.style.backgroundColor = "rgba(0, 150, 0, 0.8)";
}
}}
onMouseLeave={(e) => {
if (!isSaving) {
if (!isSaving && rectangles.length > 0) {
e.target.style.backgroundColor = "rgba(0, 100, 0, 0.7)";
}
}}
>
{isSaving ? "保存中..." : "保存"}
{isSaving
? "保存中..."
: rectangles.length === 0
? "无标靶"
: "保存"}
</button>
{/* 信息显示 */}

61
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 }) => {
>
全部隐藏
</Button>
{deviceIds.map(deviceId => (
{deviceIds.map((deviceId,index) => (
<Button
key={deviceId}
size="small"
type={visibleTargets[deviceId] ? "primary" : "default"}
style={{
borderColor: getDeviceColor(deviceId),
color: visibleTargets[deviceId] ? "#fff" : getDeviceColor(deviceId),
backgroundColor: visibleTargets[deviceId] ? getDeviceColor(deviceId) : "#fff"
borderColor: getDeviceColor(deviceDescMap[deviceId] || deviceId),
color: visibleTargets[deviceId] ? "#fff" : getDeviceColor(deviceDescMap[deviceId] || deviceId),
backgroundColor: visibleTargets[deviceId] ? getDeviceColor(deviceDescMap[deviceId] || deviceId) : "#fff"
}}
onClick={() => toggleTargetVisibility(deviceId)}
>
{deviceId}
{deviceDescMap[deviceId] || deviceId}
</Button>
))}
</Space>

7
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",

15
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 = () => {
<Title level={3} style={{ color: "#333", margin: 0 }}>
视觉位移计配置工具
</Title>
<div
style={{
display: "flex",
alignItems: "center",
cursor: "pointer",
color: "#1890ff",
fontSize: "24px",
marginLeft: "16px",
}}
>
<EyeOutlined />
</div>
</div>
{/* 中间区域 - 固定视口剩余高度 */}

67
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);

Loading…
Cancel
Save