You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
484 lines
14 KiB
484 lines
14 KiB
import React, { useState, useEffect } from "react";
|
|
import { Tabs, Typography } from "antd";
|
|
import { EyeOutlined } from "@ant-design/icons";
|
|
import {
|
|
CameraView,
|
|
TargetList,
|
|
TemplateList,
|
|
RealtimeCharts,
|
|
RealtimeDataTable,
|
|
TemplateModal,
|
|
TargetDetailModal,
|
|
} from "../components";
|
|
import {
|
|
WebSocketProvider,
|
|
useWebSocket,
|
|
useWebSocketSubscription,
|
|
} from "../actions/websocket.jsx";
|
|
import { useTemplateStorage } from "../hooks/useTemplateStorage.js";
|
|
import { useTargetStorage } from "../hooks/useTargetStorage.js";
|
|
|
|
const { Title } = Typography;
|
|
|
|
// 内部组件,使用WebSocket hook
|
|
const WuyuanbiaobaContent = () => {
|
|
const { isConnected, sendMessage } = useWebSocket();
|
|
|
|
// 订阅实时数据
|
|
const realtimeDataSubscription = useWebSocketSubscription("dev", "data");
|
|
|
|
const {
|
|
templates: tempListData,
|
|
loading: templatesLoading,
|
|
addTemplate,
|
|
updateTemplate,
|
|
deleteTemplate,
|
|
getTemplateByKey,
|
|
} = useTemplateStorage();
|
|
|
|
const {
|
|
targets: targetListData,
|
|
loading: targetsLoading,
|
|
addTarget,
|
|
updateTarget,
|
|
deleteTarget,
|
|
refreshTargets,
|
|
} = useTargetStorage();
|
|
const [selectedTemplate, setSelectedTemplate] = useState(null);
|
|
const [tableData, setTableData] = useState([]);
|
|
const [realtimeData, setRealtimeData] = useState([]);
|
|
const [lastUpdateTime, setLastUpdateTime] = useState(new Date());
|
|
|
|
// 数据采样相关状态
|
|
const [lastSampleTime, setLastSampleTime] = useState(0);
|
|
|
|
// 模板相关状态
|
|
const [templateModalVisible, setTemplateModalVisible] = useState(false);
|
|
const [templateModalMode, setTemplateModalMode] = useState("add"); // 'add' | 'edit'
|
|
const [currentEditTemplate, setCurrentEditTemplate] = useState(null);
|
|
|
|
// 标靶详情模态框相关状态
|
|
const [targetDetailModalVisible, setTargetDetailModalVisible] =
|
|
useState(false);
|
|
const [targetDetailModalMode, setTargetDetailModalMode] = useState("edit"); // 'edit'
|
|
const [currentEditTarget, setCurrentEditTarget] = useState(null);
|
|
|
|
// 添加选中标靶的状态
|
|
const [selectedTargetId, setSelectedTargetId] = useState(null);
|
|
|
|
// 处理实时数据并转换为表格格式
|
|
const processRealtimeData = (data) => {
|
|
if (!data || !data.data || !Array.isArray(data.data)) {
|
|
return [];
|
|
}
|
|
|
|
return data.data.map((item) => ({
|
|
key: item.pos,
|
|
deviceId: `target${Number(item.pos) + 1}`,
|
|
xValue: item.x,
|
|
yValue: item.y,
|
|
updateTime: data.time || new Date().toLocaleString(),
|
|
}));
|
|
};
|
|
|
|
// 初始化数据
|
|
useEffect(() => {
|
|
// 初始化空的实时数据表格
|
|
setRealtimeData([]);
|
|
setTableData([]);
|
|
console.log("数据已初始化,等待实时数据...", import.meta.env.MODE);
|
|
}, []);
|
|
|
|
// 模板数据加载完成后,默认选中内置模板
|
|
useEffect(() => {
|
|
if (!templatesLoading && tempListData.length > 0 && !selectedTemplate) {
|
|
// 查找内置模板
|
|
const builtinTemplate = tempListData.find(
|
|
(template) => template.key === "builtin_1"
|
|
);
|
|
if (builtinTemplate) {
|
|
setSelectedTemplate("builtin_1");
|
|
console.log("默认选中内置模板:", builtinTemplate.name);
|
|
} else {
|
|
// 如果没有内置模板,选择第一个模板
|
|
setSelectedTemplate(tempListData[0].key);
|
|
console.log("默认选中第一个模板:", tempListData[0].name);
|
|
}
|
|
}
|
|
}, [templatesLoading, tempListData, selectedTemplate]);
|
|
|
|
// WebSocket连接成功后的处理
|
|
useEffect(() => {
|
|
if (isConnected) {
|
|
console.log("WebSocket已连接,等待实时数据...");
|
|
} else {
|
|
console.log("WebSocket未连接");
|
|
}
|
|
}, [isConnected]);
|
|
|
|
// 调试实时数据订阅状态
|
|
useEffect(() => {
|
|
// console.log('实时数据订阅状态:', {
|
|
// hasData: !!realtimeDataSubscription.latest,
|
|
// dataCount: realtimeDataSubscription.data?.length || 0,
|
|
// latestTimestamp: realtimeDataSubscription.latest?.timestamp,
|
|
// });
|
|
}, [realtimeDataSubscription]);
|
|
|
|
// 处理实时数据更新
|
|
useEffect(() => {
|
|
if (
|
|
realtimeDataSubscription.latest &&
|
|
realtimeDataSubscription.latest.values
|
|
) {
|
|
const newRealtimeData = processRealtimeData(
|
|
realtimeDataSubscription.latest.values
|
|
);
|
|
|
|
if (newRealtimeData.length > 0) {
|
|
const currentTime = Date.now();
|
|
const currentSecond = Math.floor(currentTime / 1000);
|
|
|
|
// 每秒采样一次数据用于图表和表格显示
|
|
if (currentSecond > lastSampleTime) {
|
|
setLastSampleTime(currentSecond);
|
|
|
|
// 更新采样后的历史数据(用于图表显示)
|
|
setTableData((prevData) => {
|
|
const updatedData = [
|
|
...prevData,
|
|
...newRealtimeData.map((point) => ({
|
|
...point,
|
|
key: `${currentTime}_${point.key}`,
|
|
timestamp: currentTime,
|
|
updateTime: new Date(currentTime).toLocaleString(),
|
|
})),
|
|
];
|
|
|
|
// 只保留最近25个数据点(25秒)
|
|
return updatedData.slice(-75); // 3个设备 * 25个时间点
|
|
});
|
|
|
|
// 更新实时数据表格(使用最新的采样数据)
|
|
setRealtimeData(newRealtimeData.map((point) => ({
|
|
...point,
|
|
key: `realtime_${point.key}`,
|
|
updateTime: new Date(currentTime).toLocaleString(),
|
|
})));
|
|
}
|
|
|
|
setLastUpdateTime(new Date());
|
|
}
|
|
}
|
|
}, [realtimeDataSubscription.latest, lastSampleTime]);
|
|
|
|
// 编辑标靶的处理函数
|
|
const handleEditTarget = (target) => {
|
|
setCurrentEditTarget(target);
|
|
setTargetDetailModalMode("edit");
|
|
setTargetDetailModalVisible(true);
|
|
console.log("编辑标靶:", target);
|
|
};
|
|
|
|
// 选中标靶的处理函数
|
|
const handleSelectTarget = (target) => {
|
|
setSelectedTargetId(target.id);
|
|
console.log("选中标靶:", target);
|
|
};
|
|
|
|
// 清除选中标靶的处理函数
|
|
const handleClearSelection = () => {
|
|
setSelectedTargetId(null);
|
|
console.log("清除标靶选中状态");
|
|
};
|
|
|
|
// 处理矩形框点击事件
|
|
const handleRectangleClick = (targetData) => {
|
|
console.log("矩形框被点击,打开标靶详情:", targetData);
|
|
setCurrentEditTarget(targetData);
|
|
setTargetDetailModalMode("edit");
|
|
setTargetDetailModalVisible(true);
|
|
};
|
|
|
|
// 选择模板的处理函数
|
|
const handleTemplateSelect = (templateKey) => {
|
|
setSelectedTemplate(templateKey);
|
|
console.log("选中模板:", templateKey);
|
|
};
|
|
|
|
// 添加新模板的处理函数
|
|
const handleAddTemplate = () => {
|
|
setTemplateModalMode("add");
|
|
setCurrentEditTemplate(null);
|
|
setTemplateModalVisible(true);
|
|
};
|
|
|
|
// 编辑模板的处理函数
|
|
const handleEditTemplate = (template) => {
|
|
setTemplateModalMode("edit");
|
|
setCurrentEditTemplate(template);
|
|
setTemplateModalVisible(true);
|
|
};
|
|
|
|
// 模态框确认处理函数
|
|
const handleTemplateModalOk = (templateInfo) => {
|
|
console.log(templateInfo, "templateInfo");
|
|
|
|
let success = false;
|
|
if (templateModalMode === "add") {
|
|
success = addTemplate(templateInfo);
|
|
} else {
|
|
success = updateTemplate(templateInfo);
|
|
}
|
|
|
|
if (success) {
|
|
setTemplateModalVisible(false);
|
|
} else {
|
|
console.error("模板操作失败");
|
|
// 可以添加用户提示
|
|
}
|
|
};
|
|
|
|
// 模态框取消处理函数
|
|
const handleTemplateModalCancel = () => {
|
|
setTemplateModalVisible(false);
|
|
setCurrentEditTemplate(null);
|
|
};
|
|
|
|
// 删除模板处理函数
|
|
const handleDeleteTemplate = (template) => {
|
|
const success = deleteTemplate(template.key);
|
|
|
|
if (success) {
|
|
setTemplateModalVisible(false);
|
|
|
|
// 如果删除的是当前选中的模板,则清空选中状态
|
|
if (selectedTemplate === template.key) {
|
|
setSelectedTemplate(null);
|
|
}
|
|
} else {
|
|
console.error("删除模板失败");
|
|
// 可以添加用户提示
|
|
}
|
|
};
|
|
|
|
// 标靶详情模态框确认处理函数
|
|
const handleTargetDetailModalOk = (targetInfo) => {
|
|
const success = updateTarget(targetInfo);
|
|
|
|
if (success) {
|
|
setTargetDetailModalVisible(false);
|
|
setCurrentEditTarget(null);
|
|
console.log("更新标靶信息:", targetInfo);
|
|
} else {
|
|
console.error("更新标靶失败");
|
|
// 可以添加用户提示
|
|
}
|
|
};
|
|
|
|
// 标靶详情模态框取消处理函数
|
|
const handleTargetDetailModalCancel = () => {
|
|
setTargetDetailModalVisible(false);
|
|
setCurrentEditTarget(null);
|
|
};
|
|
|
|
// 删除标靶处理函数
|
|
const handleDeleteTarget = (targetKey) => {
|
|
console.log("开始删除标靶:", targetKey);
|
|
const success = deleteTarget(targetKey);
|
|
|
|
if (success) {
|
|
setTargetDetailModalVisible(false);
|
|
setCurrentEditTarget(null);
|
|
// 如果删除的标靶是当前选中的,清除选中状态
|
|
if (selectedTargetId === targetKey) {
|
|
setSelectedTargetId(null);
|
|
}
|
|
console.log("删除标靶成功:", targetKey);
|
|
|
|
// 强制刷新标靶数据,确保UI立即更新
|
|
setTimeout(() => {
|
|
refreshTargets();
|
|
}, 500);
|
|
} else {
|
|
console.error("删除标靶失败");
|
|
// 可以添加用户提示
|
|
}
|
|
};
|
|
// 标靶-一键清零
|
|
const onClickClearAll = () => {
|
|
console.log("一键清零操作");
|
|
sendMessage(
|
|
JSON.stringify({
|
|
_from: "setup",
|
|
cmd: "clearZero",
|
|
values: {},
|
|
})
|
|
);
|
|
};
|
|
|
|
// Tabs items 配置
|
|
const tabItems = [
|
|
{
|
|
key: "target-list",
|
|
label: "标靶列表",
|
|
children: (
|
|
<TargetList
|
|
targetListData={targetListData}
|
|
selectedTargetId={selectedTargetId}
|
|
onEditTarget={handleEditTarget}
|
|
onSelectTarget={handleSelectTarget}
|
|
onClickClearAll={onClickClearAll}
|
|
/>
|
|
),
|
|
},
|
|
{
|
|
key: "temp-list",
|
|
label: "模板列表",
|
|
children: (
|
|
<TemplateList
|
|
tempListData={tempListData}
|
|
selectedTemplate={selectedTemplate}
|
|
onTemplateSelect={handleTemplateSelect}
|
|
onAddTemplate={handleAddTemplate}
|
|
onEditTemplate={handleEditTemplate}
|
|
/>
|
|
),
|
|
},
|
|
];
|
|
|
|
return (
|
|
<div
|
|
style={{
|
|
minHeight: "100vh",
|
|
display: "flex",
|
|
flexDirection: "column",
|
|
backgroundColor: "#f0f2f5",
|
|
}}
|
|
>
|
|
{/* Header 区域 */}
|
|
<div
|
|
style={{
|
|
height: "60px",
|
|
backgroundColor: "white",
|
|
color: "#333",
|
|
display: "flex",
|
|
alignItems: "center",
|
|
padding: "0 24px",
|
|
flexShrink: 0,
|
|
borderBottom: "1px solid #d9d9d9",
|
|
}}
|
|
>
|
|
<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>
|
|
|
|
{/* 中间区域 - 固定视口剩余高度 */}
|
|
<div
|
|
style={{
|
|
height: "calc(100vh - 60px)",
|
|
display: "flex",
|
|
margin: "16px",
|
|
}}
|
|
>
|
|
{/* Camera 区域 */}
|
|
<CameraView
|
|
selectedTargetId={selectedTargetId}
|
|
onClearSelection={handleClearSelection}
|
|
onRectangleClick={handleRectangleClick}
|
|
selectedTemplate={
|
|
selectedTemplate ? getTemplateByKey(selectedTemplate) : null
|
|
}
|
|
/>
|
|
|
|
{/* 右侧 Target List / Temp List 区域 */}
|
|
<div
|
|
style={{
|
|
flex: 1,
|
|
backgroundColor: "white",
|
|
display: "flex",
|
|
flexDirection: "column",
|
|
}}
|
|
>
|
|
<Tabs
|
|
defaultActiveKey="target-list"
|
|
items={tabItems}
|
|
style={{
|
|
height: "100%",
|
|
display: "flex",
|
|
flexDirection: "column",
|
|
}}
|
|
centered
|
|
size="large"
|
|
tabBarGutter={64}
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
{/* 底部区域 - 在视口下方,需要滚动查看 */}
|
|
<div
|
|
style={{
|
|
margin: "16px",
|
|
marginTop: 0,
|
|
minHeight: "600px",
|
|
display: "flex",
|
|
backgroundColor: "white",
|
|
}}
|
|
>
|
|
{/* Charts 区域 */}
|
|
<RealtimeCharts
|
|
tableData={tableData}
|
|
lastUpdateTime={lastUpdateTime}
|
|
/>
|
|
|
|
{/* Table 区域 - 使用采样数据显示 */}
|
|
<RealtimeDataTable realtimeData={realtimeData} />
|
|
</div>
|
|
|
|
{/* 模板编辑模态框 */}
|
|
<TemplateModal
|
|
visible={templateModalVisible}
|
|
mode={templateModalMode}
|
|
templateData={currentEditTemplate}
|
|
tempListData={tempListData}
|
|
onOk={handleTemplateModalOk}
|
|
onCancel={handleTemplateModalCancel}
|
|
onDelete={handleDeleteTemplate}
|
|
/>
|
|
|
|
{/* 标靶详情模态框 */}
|
|
<TargetDetailModal
|
|
visible={targetDetailModalVisible}
|
|
mode={targetDetailModalMode}
|
|
targetData={currentEditTarget}
|
|
onOk={handleTargetDetailModalOk}
|
|
onCancel={handleTargetDetailModalCancel}
|
|
onDelete={handleDeleteTarget}
|
|
/>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
// 主组件,使用WebSocket Provider包装
|
|
const Wuyuanbiaoba = () => {
|
|
return (
|
|
<WebSocketProvider>
|
|
<WuyuanbiaobaContent />
|
|
</WebSocketProvider>
|
|
);
|
|
};
|
|
|
|
export default Wuyuanbiaoba;
|
|
|