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); const videoRect = screenToVideoCoordinates(x, y, w, h);
if (videoRect && rectangles.length < maxRectangles) { if (videoRect && rectangles.length < maxRectangles) {
// 4idkey
const ts = Date.now().toString();
const shortId = ts.slice(-4);
// //
const templateParams = selectedTemplate const templateParams = selectedTemplate
? { ? {
// //
name: `target${rectangles.length + 1}` || selectedTemplate.name, name:
`target${rectangles.length + 1}` || selectedTemplate.name,
radius: selectedTemplate.physicalRadius || 40.0, radius: selectedTemplate.physicalRadius || 40.0,
isReferencePoint: selectedTemplate.isBaseline || false, isReferencePoint: selectedTemplate.isBaseline || false,
gradientThreshold: gradientThreshold:
@ -309,8 +313,8 @@ const CameraView = ({
}; };
const newRect = { const newRect = {
id: Date.now(), // 使ID id: shortId,
key: Date.now().toString(), key: shortId,
...videoRect, ...videoRect,
...templateParams, ...templateParams,
// //
@ -735,8 +739,13 @@ const CameraView = ({
const targets = {}; const targets = {};
// console.log(rectangles, ""); // console.log(rectangles, "");
rectangles.forEach((rect, index) => { rectangles.forEach((rect) => {
targets[index.toString()] = { // 使ID
const targetKey = rect.id
? rect.id.toString()
: Date.now().toString();
targets[targetKey] = {
info: { info: {
rectangle_area: { rectangle_area: {
x: rect.x, x: rect.x,
@ -751,8 +760,8 @@ const CameraView = ({
anchor: rect.anchorThreshold || 80, anchor: rect.anchorThreshold || 80,
}, },
radius: rect.radius || 40.0, radius: rect.radius || 40.0,
id: index, id: rect.id || targetKey, // 使ID
desc: rect.name || `${index}_target`, desc: rect.name || `target_${targetKey}`,
base: rect.isReferencePoint || false, base: rect.isReferencePoint || false,
}, },
perspective: [ perspective: [
@ -1167,35 +1176,43 @@ const CameraView = ({
{/* 保存按钮 */} {/* 保存按钮 */}
<button <button
onClick={handleSave} onClick={handleSave}
disabled={isSaving} disabled={isSaving || rectangles.length === 0}
style={{ style={{
position: "absolute", position: "absolute",
top: "10px", top: "10px",
right: "10px", right: "10px",
backgroundColor: isSaving backgroundColor:
? "rgba(128, 128, 128, 0.7)" isSaving || rectangles.length === 0
: "rgba(0, 100, 0, 0.7)", ? "rgba(128, 128, 128, 0.7)"
: "rgba(0, 100, 0, 0.7)",
color: "white", color: "white",
border: "1px solid rgba(255, 255, 255, 0.3)", border: "1px solid rgba(255, 255, 255, 0.3)",
borderRadius: "4px", borderRadius: "4px",
padding: "6px 12px", padding: "6px 12px",
fontSize: "12px", fontSize: "12px",
cursor: isSaving ? "not-allowed" : "pointer", cursor:
isSaving || rectangles.length === 0
? "not-allowed"
: "pointer",
zIndex: 10, zIndex: 10,
transition: "all 0.2s", transition: "all 0.2s",
}} }}
onMouseEnter={(e) => { onMouseEnter={(e) => {
if (!isSaving) { if (!isSaving && rectangles.length > 0) {
e.target.style.backgroundColor = "rgba(0, 150, 0, 0.8)"; e.target.style.backgroundColor = "rgba(0, 150, 0, 0.8)";
} }
}} }}
onMouseLeave={(e) => { onMouseLeave={(e) => {
if (!isSaving) { if (!isSaving && rectangles.length > 0) {
e.target.style.backgroundColor = "rgba(0, 100, 0, 0.7)"; e.target.style.backgroundColor = "rgba(0, 100, 0, 0.7)";
} }
}} }}
> >
{isSaving ? "保存中..." : "保存"} {isSaving
? "保存中..."
: rectangles.length === 0
? "无标靶"
: "保存"}
</button> </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 = { const colorMap = {
target1: "#52c41a", // 绿 target1: "#52c41a", // 绿
target2: "#faad14", // target2: "#faad14", //
@ -52,7 +52,7 @@ const RealtimeCharts = ({ tableData, lastUpdateTime }) => {
target10: "#1890ff", // target10: "#1890ff", //
}; };
return colorMap[deviceId] || "#1890ff"; return colorMap[deviceDesc] || "#1890ff";
}; };
// - 25 // - 25
@ -118,6 +118,18 @@ const RealtimeCharts = ({ tableData, lastUpdateTime }) => {
// //
const { deviceIds } = sampleData(tableData); 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(() => { useEffect(() => {
if (deviceIds.length > 0) { if (deviceIds.length > 0) {
const initialVisible = {}; const initialVisible = {};
@ -203,11 +215,12 @@ const RealtimeCharts = ({ tableData, lastUpdateTime }) => {
: null; : null;
}); });
const deviceDesc = deviceDescMap[deviceId] || deviceId;
return { return {
label: deviceId, label: deviceId,
data: data, data: data,
borderColor: getDeviceColor(deviceId), borderColor: getDeviceColor(deviceDesc),
backgroundColor: getDeviceColor(deviceId) + "20", backgroundColor: getDeviceColor(deviceDesc) + "20",
borderWidth: 2, borderWidth: 2,
pointRadius: 3, pointRadius: 3,
pointHoverRadius: 5, pointHoverRadius: 5,
@ -245,11 +258,12 @@ const RealtimeCharts = ({ tableData, lastUpdateTime }) => {
: null; : null;
}); });
const deviceDesc = deviceDescMap[deviceId] || deviceId;
return { return {
label: deviceId, label: deviceId,
data: data, data: data,
borderColor: getDeviceColor(deviceId), borderColor: getDeviceColor(deviceDesc),
backgroundColor: getDeviceColor(deviceId) + "20", backgroundColor: getDeviceColor(deviceDesc) + "20",
borderWidth: 2, borderWidth: 2,
pointRadius: 3, pointRadius: 3,
pointHoverRadius: 5, pointHoverRadius: 5,
@ -266,8 +280,8 @@ const RealtimeCharts = ({ tableData, lastUpdateTime }) => {
} }
}, [tableData, visibleTargets]); }, [tableData, visibleTargets]);
// Chart.js // Chart.js - 访 deviceDescMap
const chartOptions = { const chartOptions = useMemo(() => ({
responsive: true, responsive: true,
maintainAspectRatio: false, maintainAspectRatio: false,
interaction: { interaction: {
@ -282,6 +296,17 @@ const RealtimeCharts = ({ tableData, lastUpdateTime }) => {
filter: function (tooltipItem) { filter: function (tooltipItem) {
return tooltipItem.parsed.y !== null; 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: { scales: {
@ -323,10 +348,10 @@ const RealtimeCharts = ({ tableData, lastUpdateTime }) => {
tension: 0, tension: 0,
}, },
}, },
}; }), [deviceDescMap]);
// X // X
const xChartOptions = { const xChartOptions = useMemo(() => ({
...chartOptions, ...chartOptions,
plugins: { plugins: {
...chartOptions.plugins, ...chartOptions.plugins,
@ -348,10 +373,10 @@ const RealtimeCharts = ({ tableData, lastUpdateTime }) => {
}, },
}, },
}, },
}; }), [chartOptions]);
// Y // Y
const yChartOptions = { const yChartOptions = useMemo(() => ({
...chartOptions, ...chartOptions,
plugins: { plugins: {
...chartOptions.plugins, ...chartOptions.plugins,
@ -373,7 +398,7 @@ const RealtimeCharts = ({ tableData, lastUpdateTime }) => {
}, },
}, },
}, },
}; }), [chartOptions]);
// //
if (!tableData || tableData.length === 0) { if (!tableData || tableData.length === 0) {
@ -451,19 +476,19 @@ const RealtimeCharts = ({ tableData, lastUpdateTime }) => {
> >
全部隐藏 全部隐藏
</Button> </Button>
{deviceIds.map(deviceId => ( {deviceIds.map((deviceId,index) => (
<Button <Button
key={deviceId} key={deviceId}
size="small" size="small"
type={visibleTargets[deviceId] ? "primary" : "default"} type={visibleTargets[deviceId] ? "primary" : "default"}
style={{ style={{
borderColor: getDeviceColor(deviceId), borderColor: getDeviceColor(deviceDescMap[deviceId] || deviceId),
color: visibleTargets[deviceId] ? "#fff" : getDeviceColor(deviceId), color: visibleTargets[deviceId] ? "#fff" : getDeviceColor(deviceDescMap[deviceId] || deviceId),
backgroundColor: visibleTargets[deviceId] ? getDeviceColor(deviceId) : "#fff" backgroundColor: visibleTargets[deviceId] ? getDeviceColor(deviceDescMap[deviceId] || deviceId) : "#fff"
}} }}
onClick={() => toggleTargetVisibility(deviceId)} onClick={() => toggleTargetVisibility(deviceId)}
> >
{deviceId} {deviceDescMap[deviceId] || deviceId}
</Button> </Button>
))} ))}
</Space> </Space>

7
client/src/sections/wuyuanbiaoba/components/RealtimeDataTable.jsx

@ -5,7 +5,12 @@ const { Title } = Typography;
const RealtimeDataTable = ({ realtimeData }) => { const RealtimeDataTable = ({ realtimeData }) => {
const tableColumns = [ const tableColumns = [
{ title: "设备编号", dataIndex: "deviceId", key: "deviceId" }, {
title: "设备编号",
dataIndex: "deviceId",
key: "deviceId",
render: (deviceId, record) => record.desc || deviceId
},
{ {
title: "X值(mm)", title: "X值(mm)",
dataIndex: "xValue", dataIndex: "xValue",

15
client/src/sections/wuyuanbiaoba/container/index.jsx

@ -74,7 +74,8 @@ const WuyuanbiaobaContent = () => {
return data.data.map((item) => ({ return data.data.map((item) => ({
key: item.pos, key: item.pos,
deviceId: `target${Number(item.pos) + 1}`, deviceId: item.pos, // 使 pos deviceId
desc: item.desc, // desc
xValue: item.x, xValue: item.x,
yValue: item.y, yValue: item.y,
updateTime: data.time || new Date().toLocaleString(), updateTime: data.time || new Date().toLocaleString(),
@ -372,18 +373,6 @@ const WuyuanbiaobaContent = () => {
<Title level={3} style={{ color: "#333", margin: 0 }}> <Title level={3} style={{ color: "#333", margin: 0 }}>
视觉位移计配置工具 视觉位移计配置工具
</Title> </Title>
<div
style={{
display: "flex",
alignItems: "center",
cursor: "pointer",
color: "#1890ff",
fontSize: "24px",
marginLeft: "16px",
}}
>
<EyeOutlined />
</div>
</div> </div>
{/* 中间区域 - 固定视口剩余高度 */} {/* 中间区域 - 固定视口剩余高度 */}

67
client/src/sections/wuyuanbiaoba/hooks/useTargetStorage.js

@ -129,9 +129,12 @@ export const useTargetStorage = () => {
const buildTargetsPayload = useCallback((targetsArray) => { const buildTargetsPayload = useCallback((targetsArray) => {
const targets = {}; const targets = {};
targetsArray.forEach((target, index) => { targetsArray.forEach((target) => {
// 使用标靶的原始ID作为键,而不是数组索引
const targetKey = target.id || target.key || Date.now().toString();
const rectangleArea = target.rectangleArea || {}; const rectangleArea = target.rectangleArea || {};
targets[index.toString()] = {
targets[targetKey] = {
info: { info: {
rectangle_area: { rectangle_area: {
x: rectangleArea.x || 0, x: rectangleArea.x || 0,
@ -147,7 +150,7 @@ export const useTargetStorage = () => {
}, },
radius: parseFloat(target.radius) || 40.0, radius: parseFloat(target.radius) || 40.0,
id: target.id || target.key, id: target.id || target.key,
desc: target.name || `${target.id || target.key}_target`, desc: target.name || `target_${targetKey}`,
base: target.isReferencePoint || false, base: target.isReferencePoint || false,
}, },
perspective: target.perspective || [ perspective: target.perspective || [
@ -251,28 +254,38 @@ export const useTargetStorage = () => {
} }
try { try {
// 先更新本地状态 // 先计算更新后的数据,不依赖状态更新
let updatedTargets = [];
setTargets(prev => { setTargets(prev => {
console.log(prev, '更新标靶前的状态'); console.log('更新标靶前的状态:', prev);
updatedTargets = prev.map(target => const updatedTargets = prev.map(target =>
target.key === targetInfo.key || target.id === targetInfo.id target.key === targetInfo.key || target.id === targetInfo.id
? { ...target, ...targetInfo } ? { ...target, ...targetInfo }
: target : target
); );
console.log(updatedTargets, '更新标靶后的状态'); console.log('更新标靶后的状态:', updatedTargets);
console.log('更新的标靶匹配:', prev.map(t => ({ console.log('更新的标靶匹配:', prev.map(t => ({
id: t.id, id: t.id,
key: t.key, key: t.key,
match: t.key === targetInfo.key || t.id === targetInfo.id, match: t.key === targetInfo.key || t.id === targetInfo.id,
isUpdated: 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; return updatedTargets;
}); });
// 使用通用函数构建payload并发送 return true;
const outputData = buildTargetsPayload(updatedTargets);
return sendTargetsData(outputData, '更新', targetInfo.key || targetInfo.id);
} catch (error) { } catch (error) {
console.error('更新标靶失败:', error); console.error('更新标靶失败:', error);
@ -290,19 +303,33 @@ export const useTargetStorage = () => {
} }
try { try {
// 先更新本地状态 // 先计算删除后的数据,不依赖状态更新
let updatedTargets = [];
setTargets(prev => { setTargets(prev => {
console.log(prev, '删除标靶前的状态'); console.log('删除标靶前的状态:', prev);
updatedTargets = prev.filter(target => target.key !== targetKey && target.id !== targetKey); const updatedTargets = prev.filter(target => target.key !== targetKey && target.id !== targetKey);
console.log(updatedTargets, '删除标靶后的状态'); console.log('删除标靶后的状态:', updatedTargets);
console.log('删除的标靶ID匹配:', prev.map(t => ({ id: t.id, key: t.key, match: t.key === targetKey || t.id === targetKey }))); 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; return updatedTargets;
}); });
// 使用通用函数构建payload并发送 return true;
const outputData = buildTargetsPayload(updatedTargets);
return sendTargetsData(outputData, '删除', targetKey);
} catch (error) { } catch (error) {
console.error('删除标靶失败:', error); console.error('删除标靶失败:', error);

Loading…
Cancel
Save