|
|
import React from 'react'; import { Button, Checkbox, CheckboxProps, message, Switch } from "antd"; import { useEffect, useRef, useState } from "react"; import { useNavigate } from "react-router"; import { fetchAnalysisReport, getBaseRecordPointSetByCode, saveMeasurement, startMeasurement, } from "../../../services/measure/analysis"; import { createWebSocket, sharedWsUrl } from "../../../services/socket"; import { switchMeasureAfterSave } from "../../../store/features/contextSlice"; import { AnalysisReport } from "../../../services/measure/type"; import { useAppDispatch, useAppSelector } from "../../../utils/hooks"; import Gr_round from '../../../assets/green_round.svg'; import Bl_round from '../../../assets/blue_round.svg'; import MeasurementCanvas, { AnalysisData, BenchmarkShape, MeasurementCanvasRef, } from "./konva/MeasurementCanvas";
const wsClient = createWebSocket(sharedWsUrl);
export default function MeasureAction() { const dispatch = useAppDispatch(); const navigate = useNavigate();
// MeasurementCanvas 的 ref
const canvasRef = useRef<MeasurementCanvasRef>(null);
// 用于累计点数据
const leftPoints = useRef<{ x: number; y: number }[]>([]); const rightPoints = useRef<{ x: number; y: number }[]>([]); // 标志左侧数据是否结束
const isLeftFinished = useRef(false);
const [showGrid, setShowGrid] = useState(true); const [showStandard, setShowStandard] = useState(true); const [showMark, setShowMark] = useState(true); // 用于保存角度线的备份状态,当标准线关闭时记住原先角度线是否开启
const [angleMarkBackup, setAngleMarkBackup] = useState(true); const afterSave = useAppSelector((store) => store.context.newMeasureAfterSave);
// 初始按钮文本为“开始测量”
const [startBtnText, setStartBtnText] = useState("开始测量"); // 测量是否完成的状态
const [measurementFinished, setMeasurementFinished] = useState(false); // 本次测量周期内按钮是否已点击过(只能点击一次)
const [analysisClicked, setAnalysisClicked] = useState(false); const [saveClicked, setSaveClicked] = useState(false);
// 新增:保存接口返回的分析报告数据和是否显示分析表格(右侧区域切换)
const [analysisReport, setAnalysisReport] = useState<AnalysisReport | null>(null); const [showAnalysisTable, setShowAnalysisTable] = useState(false);
const initialStatusList = [ { statusCode: "START_RECORD_LEFT", name: "请移动到顶部,停顿2秒", background: "#ececec", isReady: false, color: "h", }, { statusCode: "START_RECORD_LEFT", name: "开始测量左侧", background: "#ececec", isReady: false, color: "h", }, { statusCode: "START_RECORD_LEFT", name: "左侧测量完成", background: "#ececec", isReady: false, color: "h", }, { statusCode: "START_RECORD_LEFT", name: "请移动到顶部,停顿2秒", background: "#ececec", isReady: false, color: "h", }, { statusCode: "START_RECORD_LEFT", name: "开始测量右侧", background: "#ececec", isReady: false, color: "h", }, { statusCode: "START_RECORD_LEFT", name: "右侧测量完成", background: "#ececec", isReady: false, color: "h", }, ]; const [statusList, setStatusList] = useState(initialStatusList);
const onAfterSaveChange: CheckboxProps["onChange"] = (e) => { dispatch(switchMeasureAfterSave(e.target.checked)); };
const onAnalysisBtnClick = () => { // 分析按钮只允许点击一次
setAnalysisClicked(true); fetchAnalysisReport("6001").then((res) => { if (res.success) { const report: AnalysisReport = res.data; console.log(res.data); // 更新 canvas 分析数据(如有需要)
if (report && report.angleAnalysisList) { const analysisData: AnalysisData[] = report.angleAnalysisList.map((item) => ({ pointA: { x: parseFloat(item.pointA.x), y: parseFloat(item.pointA.y) }, pointB: { x: parseFloat(item.pointB.x), y: parseFloat(item.pointB.y) }, // 默认将 base 与 measure 分别设置为 pointA 与 pointB
base: { x: parseFloat(item.pointA.x), y: parseFloat(item.pointA.y) }, measure: { x: parseFloat(item.pointB.x), y: parseFloat(item.pointB.y) }, distance: parseFloat(item.distance), describe: item.describe, })); canvasRef.current?.setAnalysisData(analysisData); } // 保存返回数据,并显示分析表格(右侧区域切换)
setAnalysisReport(report); setShowAnalysisTable(true); } else { message.error("分析报告请求失败: " + res.data.info); } }); };
const onStart = () => { // 如果按钮文本为“新测量”,则直接跳转到新测量页面
if (startBtnText === "新测量") { navigate("../newMeasure"); return; } // 进入测量流程时恢复右侧区域为测量步骤
setShowAnalysisTable(false); setMeasurementFinished(false); setAnalysisClicked(false); setSaveClicked(false); isLeftFinished.current = false; leftPoints.current = []; rightPoints.current = []; // 清空绘制的图形,并重置缩放/偏移
canvasRef.current?.clearShapes(); canvasRef.current?.resetCanvas(); // 如果按钮原来为“重新测量”,则重置状态列表
if (startBtnText === "重新测量") { setStatusList(initialStatusList); } startMeasurement().then((res) => { if (res.status !== 0) { message.error(res.data.info); } else { const newStatusList = [...initialStatusList]; newStatusList[0].color = "b"; setStatusList(newStatusList); message.success("已通知设备开始测量"); // 测量启动成功后,按钮文本变为“重新测量”
setStartBtnText("重新测量"); } }); };
const onSaveBtnClick = () => { // 保存按钮只允许点击一次
setSaveClicked(true); saveMeasurement().then((res) => { if (res.status !== 0) { message.error(res.data.info); } else { message.success("保存成功"); if (afterSave) { // 勾选了保存后自动开始新测量则直接跳转
navigate("../config"); } else { // 否则修改按钮文本为“新测量”
setStartBtnText("新测量"); } } }); };
useEffect(() => { const subscription = wsClient.dataOb.subscribe((data) => { // 处理状态变化事件
if (data.messageType === "EVENT" && data.path === "/measurement-task/event") { if (data.data === "START_RECORD_LEFT") { statusList[0].color = "g"; statusList[1].color = "b"; } else if (data.data === "FINISH_RECORD_LEFT") { statusList[1].color = "g"; statusList[2].color = "g"; statusList[3].color = "b"; // 左侧测量结束后,切换到右侧数据累计
isLeftFinished.current = true; } else if (data.data === "START_RECORD_RIGHT") { statusList[3].color = "g"; statusList[4].color = "b"; } else if (data.data === "FINISH_RECORD_RIGHT") { statusList[4].color = "g"; statusList[5].color = "g"; // 接收到 FINISH_RECORD_RIGHT 后认为测量完成
setMeasurementFinished(true); } setStatusList([...statusList]); }
if (data.messageType === "STATE" && (data as any).path === "/measurement-task/point-report") { const pointData = ((data as unknown) as { data: { x: number; y: number } }).data; console.log("pointData ====" + pointData.x + "," + pointData.y); if (!isLeftFinished.current) { leftPoints.current.push(pointData); canvasRef.current?.setMeasurementDataLeft([...leftPoints.current]); } else { rightPoints.current.push(pointData); canvasRef.current?.setMeasurementDataRight([...rightPoints.current]); } } }); wsClient.connect(); return () => subscription.unsubscribe(); }, [statusList]);
// 页面加载时获取基础图形数据,并传入 MeasurementCanvas
useEffect(() => { getBaseRecordPointSetByCode("6001").then((res) => { if (res.success) { const benchmarkShapes = JSON.parse(res.data.points) as BenchmarkShape[]; if (canvasRef.current) { console.log("解析后的基础图形数据:", benchmarkShapes); canvasRef.current.setBenchmarkData(benchmarkShapes); } } }); }, []);
type StatusCodeData = { statusCode: string; name: string; background: string; isReady: boolean; color: string; };
const onHandleChangeStatus = (item: StatusCodeData) => { let backgroundColor = ""; if (item.statusCode === "START_RECORD_LEFT") { backgroundColor = item.background; } return backgroundColor; };
const onHandleIcon = (item: StatusCodeData, index: number) => { if (item.color === "g") { return <img src={Gr_round} alt="" />; } else if (item.color === "b") { return <img src={Bl_round} alt="" />; } else if (item.color === "h") { return ( <div style={{ width: "22px", height: "22px", background: "#c0c0c0", borderRadius: "50%", marginTop: "10px", }} ></div> ); } };
return ( <div className="flex h-full"> {/* 左侧区域:包含开关区域和测量画布 */} <div className="flex-none"> <div className="flex gap-4 items-center px-6 pt-5"> <div className="flex gap-2 items-center"> <Switch defaultChecked onChange={(checked) => setShowGrid(checked)} /> <span>参考线</span> </div> <div className="flex gap-2 items-center"> <Switch checked={showStandard} onChange={(checked) => { setShowStandard(checked); if (!checked) { // 关闭标准线时,备份当前角度线状态,并关闭角度线
setAngleMarkBackup(showMark); setShowMark(false); } else { // 打开标准线时,恢复角度线之前的状态
setShowMark(angleMarkBackup); } }} /> <span>标准线</span> </div> <div className="flex gap-2 items-center"> <Switch checked={showMark} disabled={!showStandard} onChange={(checked) => { setShowMark(checked); // 当标准线处于开启状态时,允许修改角度线状态,并更新备份状态
setAngleMarkBackup(checked); }} /> <span>角度线</span> </div> </div> <div className="relative m-2"> <MeasurementCanvas width={800} height={600} logicalExtent={{ minX: -50, maxX: 50, minY: -20, maxY: 60 }} gridStep={1} origin={{ x: 0, y: 20 }} pixelPerMm={8} maxZoom={10} showGrid={showGrid} showBenchmark={showStandard} showAnalysis={showMark} showScale={false} scaleInterval={1} showCoordinates={true} ref={canvasRef} /> </div> </div> {/* 右侧区域:根据 showAnalysisTable 状态决定显示测量步骤区域还是分析表格 */} <div className="w-[300px] flex-none py-6"> {showAnalysisTable && analysisReport ? ( <div className="analysis-table"> <table style={{ width: "100%", borderCollapse: "collapse", border: "1px solid #ccc", textAlign: "center", }} > <tbody> <tr style={{ height: "40px", fontSize: "18px", color: "#9E9E9E" }}> <td style={{ padding: "8px", border: "1px solid #ccc" }}>W1垂直磨耗</td> <td style={{ padding: "8px", border: "1px solid #ccc" }}>{analysisReport.w1}</td> </tr> <tr style={{ height: "40px", fontSize: "18px", color: "#9E9E9E" }}> <td style={{ padding: "8px", border: "1px solid #ccc" }}>轨头宽度</td> <td style={{ padding: "8px", border: "1px solid #ccc" }}>{analysisReport.railHeadWidth}</td> </tr> {analysisReport.angleAnalysisList.map((item, index) => ( <tr key={index} style={{ height: "40px", fontSize: "18px", color: "#9E9E9E" }}> <td style={{ padding: "8px", border: "1px solid #ccc" }}>{item.describe}</td> <td style={{ padding: "8px", border: "1px solid #ccc" }}>{item.distance}</td> </tr> ))} <tr style={{ height: "40px", fontSize: "18px", color: "#9E9E9E" }}> <td colSpan={2} style={{ textAlign: "center", padding: "8px", border: "1px solid #ccc" }} > <Button style={{ width: 200 }} size="large" type="primary" onClick={() => navigate("../config")}> 返回 </Button> </td> </tr> </tbody> </table> </div> ) : ( <div> <h1 className="font-medium text-xl text-center">测量步骤</h1> <div className="ml-[45px] w-[13rem] mt-5"> {statusList.map((item, index) => { return ( <div key={index} style={{ background: onHandleChangeStatus(item), borderRadius: "20px" }} className="mt-[10px] h-[40px]" > <div style={{ display: "flex", lineHeight: "40px" }} className="pl-[1rem]"> {onHandleIcon(item, index)} <div className="pl-[5px]">{item.name}</div> </div> </div> ); })} </div> <section className="flex flex-col items-center gap-4 mt-6 border-t border-[#D8D8D8] py-4"> <Button style={{ width: 200 }} size="large" type="primary" onClick={onStart}> {startBtnText} </Button> <Button style={{ width: 200 }} size="large" type="primary" onClick={onAnalysisBtnClick} disabled={!measurementFinished || analysisClicked} > 分析 </Button> <Button style={{ width: 200 }} size="large" type="primary" onClick={onSaveBtnClick} disabled={!measurementFinished || saveClicked} > 保存 </Button> <Checkbox checked={afterSave} onChange={onAfterSaveChange}> 保存后自动开始新测量 </Checkbox> </section> </div> )} </div> </div> ); }
|