import React, { useState, useEffect } from "react"; import { Tabs, Typography } from "antd"; import ExcelJS from "exceljs"; 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"; import { useRef } from "react"; const { Title } = Typography; // 内部组件,使用WebSocket hook const WuyuanbiaobaContent = () => { const { isConnected, sendMessage } = useWebSocket(); // 订阅实时数据 const realtimeDataSubscription = useWebSocketSubscription("dev", "data"); const realtimeBufferRef = useRef([]); const exportCountRef = useRef(200); const MAX_BUFFER_SIZE = 1000; const setExportCount = (count) => { exportCountRef.current = count; }; // 数据缓冲区数据维护函数 const pushToRealtimeBuffer = (newDataGroup) => { // newDataGroup: { time: "2025-09-29 01:40:57.091", data: [...] } const now = Date.now(); const group = { ...newDataGroup, _bufferTimestamp: now, }; // 合并到缓冲区 realtimeBufferRef.current = [...realtimeBufferRef.current, group]; // 只保留最大MAX_BUFFER_SIZE条数据 if (realtimeBufferRef.current.length > MAX_BUFFER_SIZE) { realtimeBufferRef.current = realtimeBufferRef.current.slice( -MAX_BUFFER_SIZE ); } }; 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: item.pos, // 使用 pos 作为 deviceId desc: item.desc, // 添加 desc 字段 xValue: item.x, yValue: item.y, updateTime: data.time, })); }; // 初始化数据 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个数据点 return updatedData.slice(-75); // 3个设备 * 25个时间点 }); // 更新实时数据表格(使用最新的采样数据) setRealtimeData( newRealtimeData.map((point) => ({ ...point, key: `realtime_${point.key}`, updateTime: new Date(currentTime).toLocaleString(), })) ); } //维护两分钟的缓冲区 pushToRealtimeBuffer(realtimeDataSubscription.latest.values); setLastUpdateTime(new Date()); } } }, [realtimeDataSubscription.latest]); const dataExport = async () => { const count = exportCountRef.current || 200; const buf = realtimeBufferRef.current; if (buf.length === 0) { console.warn("没有数据可导出"); return; } // 只取最新的 count 条 const dataToExport = buf.slice(-count); // 收集所有出现过的desc,保持顺序且唯一 const descSet = []; dataToExport.forEach((group) => { if (Array.isArray(group.data)) { group.data.forEach((point) => { if (point && !descSet.includes(point.desc)) { descSet.push(point.desc); } }); } }); // 构造表头 const columns = [{ header: "采集时间", key: "time", width: 24 }]; descSet.forEach((desc) => { columns.push( { header: `标靶${desc}X`, key: `x_${desc}`, width: 12 }, { header: `标靶${desc}Y`, key: `y_${desc}`, width: 12 } ); }); // 创建工作簿和工作表 const workbook = new ExcelJS.Workbook(); const worksheet = workbook.addWorksheet("两分钟内数据"); worksheet.columns = columns; // 填充数据 dataToExport.forEach((group) => { const row = { time: group.time }; // 先清空所有desc对应的列 descSet.forEach((desc) => { row[`x_${desc}`] = ""; row[`y_${desc}`] = ""; }); // 填充本组数据 group.data.forEach((point) => { if (point && descSet.includes(point.desc)) { row[`x_${point.desc}`] = point.x; row[`y_${point.desc}`] = point.y; } }); worksheet.addRow(row); }); // 设置表头样式 worksheet.getRow(1).font = { bold: true, color: { argb: "FF1F497D" } }; worksheet.getRow(1).alignment = { vertical: "middle", horizontal: "center", }; // 设置边框样式 worksheet.eachRow((row, rowNumber) => { row.eachCell((cell) => { cell.border = { top: { style: "thin" }, left: { style: "thin" }, bottom: { style: "thin" }, right: { style: "thin" }, }; cell.alignment = { vertical: "middle", horizontal: "center" }; }); }); // 导出为文件 const now = new Date(); const pad = (n) => n.toString().padStart(2, "0"); const localFileName = `${now.getFullYear()}-${pad( now.getMonth() + 1 )}-${pad(now.getDate())}-${pad(now.getHours())}-${pad( now.getMinutes() )}-${pad(now.getSeconds())}.xlsx`; const buffer = await workbook.xlsx.writeBuffer(); const blob = new Blob([buffer], { type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", }); const url = window.URL.createObjectURL(blob); const a = document.createElement("a"); a.href = url; a.download = localFileName; document.body.appendChild(a); a.click(); document.body.removeChild(a); window.URL.revokeObjectURL(url); }; // 编辑标靶的处理函数 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: ( ), }, { key: "temp-list", label: "模板列表", children: ( ), }, ]; return (
{/* Header 区域 */}
视觉位移计配置工具
{/* 中间区域 - 固定视口剩余高度 */}
{/* Camera 区域 */} {/* 右侧 Target List / Temp List 区域 */}
{/* 底部区域 - 在视口下方,需要滚动查看 */}
{/* Charts 区域 */} {/* Table 区域 - 使用采样数据显示 */}
{/* 模板编辑模态框 */} {/* 标靶详情模态框 */}
); }; // 主组件,使用WebSocket Provider包装 const Wuyuanbiaoba = () => { return ( ); }; export default Wuyuanbiaoba;