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.
733 lines
24 KiB
733 lines
24 KiB
import React, { useState, useEffect, useRef, useCallback, useMemo } from "react";
|
|
import { Button, Checkbox, CheckboxProps, Drawer, Input, InputNumber, message, Select, Spin, Switch } from "antd";
|
|
import { useNavigate } from "react-router";
|
|
import {
|
|
fetchAnalysisReport,
|
|
getAlignPointsByRailSize,
|
|
getReport,
|
|
startMeasurement,
|
|
stopMeasurement,
|
|
} from "../../../services/measure/analysis";
|
|
import { getBaseRecordPointSetByCode, gx_list } from "../../../services/track/trackShape"
|
|
import { createWebSocket, sharedWsUrl } from "../../../services/socket";
|
|
import { switchMeasureAfterSave } from "../../../store/features/contextSlice";
|
|
import measureState, { updateGxState, updateMeasureData } from "../../../store/measure/measureState";
|
|
import { AnalysisReport, trackItem } from "../../../services/measure/type";
|
|
import { MeasureState, ResultRecordData, TaskState, TrackRecordSig } from "../../../services/wsTypes";
|
|
import { useAppDispatch, useAppSelector } from "../../../utils/hooks";
|
|
import Gr_round from "../../../assets/green_round.svg";
|
|
import Bl_round from "../../../assets/blue_round.svg";
|
|
import icon_left from "../../../assets/icon_left.svg";
|
|
import icon_right from "../../../assets/icon_right.svg";
|
|
import icon_up from "../../../assets/icon_up.svg";
|
|
import icon_down from "../../../assets/icon_down.svg";
|
|
import icon_leftR from "../../../assets/icon_leftR.svg";
|
|
import icon_rightR from "../../../assets/icon_rightR.svg";
|
|
import MeasurementCanvas, { AnalysisData, BenchmarkShape, MeasurementCanvasRef, Point } from "./konva/MeasurementCanvas";
|
|
import "./MeasureAction.scss";
|
|
import { GX_CODE } from "../../../constant";
|
|
// 创建 websocket 客户端
|
|
const wsClient = createWebSocket(sharedWsUrl);
|
|
export default function MeasureAction() {
|
|
const dispatch = useAppDispatch();
|
|
const navigate = useNavigate();
|
|
const deviceInfo = useAppSelector(store => store.context.device);
|
|
const measureState = useAppSelector((store) => store.measureState);
|
|
const [railSize, setRailSize] = useState<string>(GX_CODE)
|
|
const STEP_COLOR_GREEN = "green";
|
|
const STEP_COLOR_BLUE = "blue";
|
|
const STEP_COLOR_GREY = "grey";
|
|
/** ----------------------- 引用 ----------------------- **/
|
|
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);
|
|
// showMark的备份,记录showMark最近一次的值
|
|
const [angleMarkBackup, setAngleMarkBackup] = useState(true);
|
|
const afterSave = useAppSelector(store => store.context.newMeasureAfterSave);
|
|
const [startBtnText, setStartBtnText] = useState("开始测量");
|
|
const [measurementFinished, setMeasurementFinished] = useState(false);
|
|
|
|
// 【分析】之后,会得到分析报告
|
|
const [analysisReport, setAnalysisReport] = useState<AnalysisReport | null>(null);
|
|
|
|
// 初始状态列表
|
|
const initialStatusList = useMemo(
|
|
() => [
|
|
{ name: "等待测量", background: "#ececec", isReady: false, color: STEP_COLOR_GREY },
|
|
{ name: "正在进行测量", background: "#ececec", isReady: false, color: STEP_COLOR_GREY },
|
|
{ name: "一侧测量完成", background: "#ececec", isReady: false, color: STEP_COLOR_GREY },
|
|
{ name: "等待测量另一侧", background: "#ececec", isReady: false, color: STEP_COLOR_GREY },
|
|
{ name: "正在进行测量", background: "#ececec", isReady: false, color: STEP_COLOR_GREY },
|
|
{ name: "测量已完成", background: "#ececec", isReady: false, color: STEP_COLOR_GREY },
|
|
],
|
|
[]
|
|
);
|
|
const [statusList, setStatusList] = useState(initialStatusList);
|
|
// 打开抽屉(显示分析结果)
|
|
const [openDrawer, setOpenDrawer] = useState(false);
|
|
|
|
/** ----------------------- 事件处理函数 ----------------------- */
|
|
// 切换保存后自动开始新测量
|
|
const onAfterSaveChange: CheckboxProps["onChange"] = e => {
|
|
dispatch(switchMeasureAfterSave(e.target.checked));
|
|
};
|
|
|
|
useEffect(()=>{
|
|
setRailSize(measureState.gxCode || GX_CODE)
|
|
}, [measureState])
|
|
|
|
|
|
// 分析按钮点击事件
|
|
const onAnalysisBtnClick = () => {
|
|
// if (analysisReport) {
|
|
// setOpenDrawer(true);
|
|
// return;
|
|
// }
|
|
let params = {
|
|
code:railSize,
|
|
pointList:[]
|
|
}
|
|
|
|
if(showCalibration){//校准分析时,添加校准后的坐标点
|
|
params.pointList = calibrationData
|
|
}
|
|
|
|
// getReport(params).then(res=> {
|
|
// console.log('res---', res)
|
|
// })
|
|
getReport(params).then(res => {
|
|
if (res.success) {
|
|
const report: AnalysisReport = res.data;
|
|
console.log(report);
|
|
// 更新 canvas 的分析数据
|
|
if (report && report.angleAnalysisList) {
|
|
// 先过滤掉 distance 为 null 的数据
|
|
const validItems = report.angleAnalysisList.filter(item => item.distance !== null);
|
|
const analysisData: AnalysisData[] = validItems.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: { 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);
|
|
setOpenDrawer(true);
|
|
} else {
|
|
message.error("分析报告请求失败: " + res.data.info);
|
|
}
|
|
});
|
|
};
|
|
|
|
const [startLoading, setStartLoading] = useState(false)
|
|
// 开始/重新测量按钮点击事件
|
|
const onStart = useCallback(() => {
|
|
// if (!deviceInfo.isConnected) {
|
|
// message.error("请先连接设备");
|
|
// return;
|
|
// }
|
|
setStartLoading(true)
|
|
setAudioList([])
|
|
// if(deviceInfo.power < 20){
|
|
// message.error('电量低于20%,请充电后再测量!')
|
|
// return
|
|
// }
|
|
// 重置测量相关状态
|
|
setMeasurementFinished(false);
|
|
setAnalysisReport(null);
|
|
setshowCalibration(false)//校准线
|
|
dispatch(updateMeasureData([]))
|
|
isLeftFinished.current = false;
|
|
leftPoints.current = [];
|
|
rightPoints.current = [];
|
|
canvasRef.current?.clearShapes();
|
|
canvasRef.current?.resetCanvas();
|
|
startMeasurement().then(res => {
|
|
setTimeout(()=>{
|
|
setStartLoading(false)
|
|
}, 1000)
|
|
if (res.status !== 0) {
|
|
message.error(res.data.info);
|
|
} else {
|
|
let list = [...initialStatusList]
|
|
list.forEach((item, index) => {
|
|
if(index === 0){
|
|
item.color = STEP_COLOR_BLUE;
|
|
}else{
|
|
item.color = STEP_COLOR_GREY;
|
|
}
|
|
})
|
|
setStatusList(list);
|
|
message.success("已通知设备开始测量");
|
|
setStartBtnText("重新测量");
|
|
const audioReady = new Audio("/audio/ready.mp3");
|
|
// 播放音频
|
|
audioReady
|
|
.play()
|
|
.then(() => {
|
|
console.log("音频开始播放 已准备好");
|
|
})
|
|
.catch(err => {
|
|
console.error("播放音频时出错:", err);
|
|
});
|
|
}
|
|
});
|
|
}, [initialStatusList, startBtnText, deviceInfo]);
|
|
|
|
//停止测量
|
|
const onStop = () => {
|
|
stopMeasurement().then(res=>{
|
|
if (res.status !== 0) {
|
|
}else{
|
|
message.error('已停止测量')
|
|
}
|
|
}).catch(e=>{
|
|
message.error('调用出错')
|
|
})
|
|
}
|
|
|
|
// 保存按钮点击事件
|
|
const onSaveBtnClick = () => {
|
|
dispatch(updateMeasureData(newMeasureData))
|
|
navigate('/measure/config')
|
|
//将校准的数据存入store
|
|
};
|
|
|
|
//校准
|
|
const [showCalibration, setshowCalibration] = useState(false)
|
|
|
|
const [caloading, setCaLoading] = useState(false)
|
|
const [loadingText, setLoadingText] = useState('正在校准...')
|
|
const [calibrationData, setCalibrationData] = useState([])
|
|
const onCalibrationBtnClick = () => {
|
|
setCaLoading(true)
|
|
setLoadingText('正在校准...')
|
|
//获取校准数据
|
|
getAlignPointsByRailSize({railSize:railSize}).then(res => {
|
|
if(res.success){
|
|
setshowCalibration(true)
|
|
setCalibrationData(res.data)
|
|
canvasRef.current?.setMeasurementCalibrationData(res.data)
|
|
}else{
|
|
message.error('校准失败!')
|
|
}
|
|
setCaLoading(false)
|
|
setLoadingText('')
|
|
}).catch(e=>{
|
|
setCaLoading(false)
|
|
message.error('校准失败!')
|
|
})
|
|
}
|
|
|
|
// 辅助函数:渲染状态项的图标
|
|
const renderStatusIcon = (item: (typeof initialStatusList)[0]) => {
|
|
if (item.color === STEP_COLOR_GREEN) {
|
|
return <img src={Gr_round} alt="green" />;
|
|
} else if (item.color === STEP_COLOR_BLUE) {
|
|
return <img src={Bl_round} alt="blue" />;
|
|
} else {
|
|
return (
|
|
<div
|
|
style={{
|
|
width: "22px",
|
|
height: "22px",
|
|
background: "#c0c0c0",
|
|
borderRadius: "50%",
|
|
marginTop: "10px",
|
|
}}
|
|
/>
|
|
);
|
|
}
|
|
};
|
|
|
|
const [audioList, setAudioList] = useState<HTMLAudioElement[]>([])
|
|
const pauseAudio = () => {
|
|
audioList.forEach(audio => {
|
|
audio.pause()
|
|
})
|
|
}
|
|
const [status, setStatus] = useState(0)
|
|
/** ----------------------- WebSocket 消息处理 ----------------------- **/
|
|
useEffect(() => {
|
|
// 处理任务状态消息
|
|
const handleStateMessage = (state: MeasureState["data"]) => {};
|
|
// 处理事件消息
|
|
const handleEventMessage = (type: TaskState["data"]) => {
|
|
/**
|
|
* IDLE: 空闲
|
|
* START_MEASURE: 开始测量
|
|
* WAITING_FOR_RECORD_THE_1ST_SIDE: 等待测量
|
|
* RECORD_THE_FIRST_SIDE: 测量第一条曲线
|
|
* RECORD_THE_1ST_SIDE_FINISHED: 第一条曲线测量完成
|
|
* WAITING_FOR_RECORD_THE_2ND_SIDE: 等待测量第二条曲线
|
|
* RECORD_THE_2ND_SIDE: 测量第二线曲线
|
|
* FINISHED: 第二条曲线测量完成
|
|
*/
|
|
setStatusList(prev => {
|
|
const updated = [...prev];
|
|
switch (type) {
|
|
case 'WAITING_FOR_RECORD_THE_1ST_SIDE':
|
|
setStatus(1);
|
|
break;
|
|
case 'WAITING_FOR_RECORD_THE_2ND_SIDE':
|
|
setStatus(2)
|
|
break;
|
|
case 'START_RECORD_SIG':
|
|
updated[0].color = STEP_COLOR_GREEN;
|
|
updated[1].color = STEP_COLOR_BLUE;
|
|
break;
|
|
case 'START_RECORD_LEFT':
|
|
case 'START_RECORD_RIGHT':
|
|
if (status === 1) {
|
|
updated[0].color = STEP_COLOR_GREEN;
|
|
updated[1].color = STEP_COLOR_BLUE;
|
|
} else if (status === 2) {
|
|
updated[3].color = STEP_COLOR_GREEN;
|
|
updated[4].color = STEP_COLOR_BLUE;
|
|
}
|
|
const audio1 = new Audio("/audio/measuring.mp3");
|
|
// 播放音频
|
|
audio1
|
|
.play()
|
|
.then(() => {
|
|
console.log("音频开始播放");
|
|
})
|
|
.catch(err => {
|
|
console.error("播放音频时出错:", err);
|
|
});
|
|
setAudioList([...audioList, audio1])
|
|
break;
|
|
case "FINISH_RECORD_LEFT":
|
|
case "FINISH_RECORD_RIGHT":
|
|
pauseAudio()
|
|
if (status === 1) {
|
|
updated[1].color = STEP_COLOR_GREEN;
|
|
updated[2].color = STEP_COLOR_GREEN;
|
|
updated[3].color = STEP_COLOR_BLUE;
|
|
const line1Audio = new Audio("/audio/side_end.mp3");
|
|
// 播放音频
|
|
line1Audio
|
|
.play()
|
|
.then(() => {
|
|
console.log("音频开始播放");
|
|
})
|
|
.catch(err => {
|
|
console.error("播放音频时出错:", err);
|
|
});
|
|
setAudioList([...audioList, line1Audio])
|
|
}else if(status === 2) {
|
|
updated[3].color = STEP_COLOR_GREEN;
|
|
updated[4].color = STEP_COLOR_BLUE;
|
|
const audio2 = new Audio("/audio/measure_end.mp3");
|
|
// 播放音频
|
|
audio2
|
|
.play()
|
|
.then(() => {
|
|
console.log("音频开始播放");
|
|
})
|
|
.catch(err => {
|
|
console.error("播放音频时出错:", err);
|
|
});
|
|
setAudioList([...audioList, audio2])
|
|
setMeasurementFinished(true);
|
|
}
|
|
updated[1].color = STEP_COLOR_GREEN;
|
|
updated[2].color = STEP_COLOR_GREEN;
|
|
updated[3].color = STEP_COLOR_BLUE;
|
|
isLeftFinished.current = true;
|
|
|
|
|
|
break;
|
|
case "FINISH_RECORD":
|
|
updated.forEach(item => {
|
|
item.color = STEP_COLOR_GREEN
|
|
})
|
|
const audioFinish= new Audio("/audio/measure_end.mp3");//measure_end
|
|
// 播放音频
|
|
audioFinish
|
|
.play()
|
|
.then(() => {
|
|
console.log("音频开始播放");
|
|
})
|
|
.catch(err => {
|
|
console.error("播放音频时出错:", err);
|
|
});
|
|
setCaLoading(true)
|
|
setLoadingText('正在处理测量数据...')
|
|
setMeasurementFinished(true);
|
|
break;
|
|
case "WRONG_SIDE":
|
|
const audio5 = new Audio("/audio/alert_left.mp3");
|
|
// 播放音频
|
|
audio5
|
|
.play()
|
|
.then(() => {
|
|
console.log("音频开始播放");
|
|
})
|
|
.catch(err => {
|
|
console.error("播放音频时出错:", err);
|
|
});
|
|
// 把状态全部置灰
|
|
updated.forEach(u => (u.color = STEP_COLOR_GREY));
|
|
// 调用停止测量
|
|
onStop();
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return updated;
|
|
});
|
|
};
|
|
|
|
// 处理点数据消息
|
|
const handlePointReport = (pointData: TrackRecordSig["data"]) => {
|
|
if (!isLeftFinished.current) {
|
|
leftPoints.current.push(pointData);
|
|
canvasRef.current?.setMeasurementDataLeft([...leftPoints.current]);
|
|
} else {
|
|
rightPoints.current.push(pointData);
|
|
canvasRef.current?.setMeasurementDataRight([...rightPoints.current]);
|
|
}
|
|
};
|
|
|
|
// 处理测量后的数据
|
|
const handleMeasureResult = (pointData: ResultRecordData["data"]) => {
|
|
canvasRef.current?.setMeasurementDataLeft([...pointData.outline1]);
|
|
canvasRef.current?.setMeasurementDataRight([...pointData.outline2]);
|
|
}
|
|
|
|
const subscription = wsClient.dataOb.subscribe(data => {
|
|
if (data.path === "/api/measurement-task/get-task-state") {
|
|
handleStateMessage(data.data);
|
|
} else if (data.path === "/api/measurement-task/event") {
|
|
handleEventMessage(data.data);
|
|
} else if (data.path === "/api/measurement-task/point-report") {
|
|
handlePointReport(data.data);
|
|
} else if (data.path === "/api/measurement-task/measure-finished") {
|
|
setCaLoading(false)
|
|
handleMeasureResult(data.data)
|
|
}
|
|
});
|
|
wsClient.connect();
|
|
return () => subscription.unsubscribe();
|
|
}, [onStart]);
|
|
|
|
/** ----------------------- 页面加载获取基础图形数据 -------基线---------------- **/
|
|
useEffect(() => {
|
|
queryBasePoints(railSize)
|
|
//获取轨型
|
|
getTrackDataList()
|
|
}, []);
|
|
|
|
//获取测量基线
|
|
const queryBasePoints = (gxCode:string) => {
|
|
getBaseRecordPointSetByCode(gxCode).then(res => {
|
|
if (res.success) {
|
|
const benchmarkShapes = JSON.parse(res.data.points) as BenchmarkShape[];
|
|
if (canvasRef.current) {
|
|
console.log("解析后的基础图形数据:", benchmarkShapes);
|
|
canvasRef.current.setBenchmarkData(benchmarkShapes);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
/********************************轨型数据************************************* */
|
|
const [trackList, setTrackList] = useState<trackItem[]>([])
|
|
const [defaultTrackValue, setDefaultTrackValue] = useState<string>()
|
|
const getTrackDataList = () => {
|
|
gx_list().then(res => {
|
|
if(res.data && res.data.length){
|
|
let resData:trackItem[] = res.data;
|
|
let list:trackItem[] = []
|
|
resData.map(item => {
|
|
if(item.points){
|
|
list.push(item)
|
|
}
|
|
})
|
|
setTrackList(list)
|
|
}
|
|
})
|
|
}
|
|
|
|
useEffect(()=>{
|
|
if(trackList && trackList.length){
|
|
setDefaultTrackValue(trackList[0].code)
|
|
}
|
|
},[trackList])
|
|
|
|
|
|
//当前选择的轨型 默认"GX-60"
|
|
|
|
const onTrackChange = (value: string) => {
|
|
setRailSize(value)
|
|
queryBasePoints(value)
|
|
//缓存至STORE
|
|
dispatch(updateGxState(value))
|
|
}
|
|
|
|
//上下移动
|
|
const onMoveLine = (type:string) => {
|
|
let list = canvasRef.current?.getMeasurementCalibrationData()
|
|
if(list && list.length){
|
|
list.forEach(item => {
|
|
if(type === 'up'){//向上移动,原数据减y X轴不动
|
|
item.y = item.y - distance/1000;
|
|
}
|
|
if(type === 'down'){//向上移动,原数据加y X轴不动
|
|
item.y = item.y + distance/1000;
|
|
}
|
|
if(type === 'left'){//向左移动,原数据减x Y轴不动
|
|
item.x = item.x - distance/1000;
|
|
}
|
|
if(type === 'right'){//向右移动,原数据加x Y轴不动
|
|
item.x = item.x + distance/1000;
|
|
}
|
|
})
|
|
canvasRef.current?.setMeasurementCalibrationData(list)
|
|
setNewMeasureData(list)
|
|
}
|
|
}
|
|
|
|
//旋转
|
|
let [measurementRotation, setMeasurementRotation] = useState<number>(0)
|
|
let [newMeasureData, setNewMeasureData] = useState<Point[]>()
|
|
let [angle, setAngle] = useState<number>(1);//角度单位 分
|
|
let [distance, setDistance] = useState<number>(10)
|
|
const onRotationLine = (type:string) => {
|
|
let mrValue = 0
|
|
if(type === 'left'){//逆时针
|
|
mrValue = measurementRotation - (angle/60) * Math.PI / 180;
|
|
}
|
|
if(type === 'right'){//顺时针
|
|
mrValue = measurementRotation + (angle/60) * Math.PI / 180;
|
|
}
|
|
let list = canvasRef.current?.getMeasurementCalibrationData()
|
|
if(list && list.length){
|
|
list.forEach((item, index) => {
|
|
let cloneItem = rotatePoint(item, mrValue)
|
|
item.x = cloneItem.x
|
|
item.y = cloneItem.y
|
|
})
|
|
canvasRef.current?.setMeasurementCalibrationData(list)
|
|
setNewMeasureData(list)
|
|
}
|
|
}
|
|
|
|
const rotatePoint = (pt:{x:number;y:number}, angle:number) => {
|
|
const item = {
|
|
x: pt.x * Math.cos(angle) - pt.y * Math.sin(angle),
|
|
y: pt.x * Math.sin(angle) + pt.y * Math.cos(angle)
|
|
};
|
|
return item
|
|
}
|
|
|
|
/** ----------------------- 渲染 ----------------------- **/
|
|
return (
|
|
<>
|
|
<div className="flex h-full px-6">
|
|
{/* 左侧区域:包含开关区域和测量画布 */}
|
|
<div className="">
|
|
|
|
<div className="relative flex gap-4 items-center py-3">
|
|
<Select
|
|
className="w-[150px]"
|
|
placeholder="请选择轨型"
|
|
key={defaultTrackValue}
|
|
defaultValue={defaultTrackValue}
|
|
onChange={onTrackChange}
|
|
options={trackList.map((item) => ({
|
|
label: item.name,
|
|
value: item.code,
|
|
}))}
|
|
></Select>
|
|
{/* <div
|
|
className="absolute text-primary border border-primary rounded px-4 py-[2px] font-medium cursor-pointer hover:text-primary/[0.8]"
|
|
onClick={() => navigate("../config", { replace: true })}>
|
|
返回
|
|
</div> */}
|
|
<section className="ml-auto flex gap-4 items-center">
|
|
{/* 参考线开关 */}
|
|
<div className="flex gap-2 items-center">
|
|
<Switch defaultChecked size="small" onChange={checked => setShowGrid(checked)} />
|
|
<span>参考线</span>
|
|
</div>
|
|
{/* 标准线开关 */}
|
|
<div className="flex gap-2 items-center">
|
|
<Switch
|
|
size="small"
|
|
checked={showStandard}
|
|
onChange={checked => {
|
|
setShowStandard(checked);
|
|
if (!checked) {
|
|
setAngleMarkBackup(showMark);
|
|
setShowMark(false);
|
|
} else {
|
|
setShowMark(angleMarkBackup);
|
|
}
|
|
}}
|
|
/>
|
|
<span>标准线</span>
|
|
</div>
|
|
{/* 角度线开关,仅在点击分析按钮后显示 */}
|
|
{analysisReport && (
|
|
<div className="flex gap-2 items-center">
|
|
<Switch
|
|
size="small"
|
|
checked={showMark}
|
|
disabled={!showStandard}
|
|
onChange={checked => {
|
|
setShowMark(checked);
|
|
setAngleMarkBackup(checked);
|
|
}}
|
|
/>
|
|
<span>角度线</span>
|
|
</div>
|
|
)}
|
|
</section>
|
|
</div>
|
|
<Spin spinning={caloading} tip={ loadingText }>
|
|
<div className="relative">
|
|
<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={showGrid}
|
|
showCalibration={showCalibration}
|
|
ref={canvasRef}
|
|
/>
|
|
</div>
|
|
{showCalibration &&
|
|
<div className="flex justify-center h-[50px]">
|
|
<div className="mt-[12px]">
|
|
移动距离:<InputNumber defaultValue={distance} placeholder="微米" onChange={(value) => value !== null && setDistance(Number(value))}></InputNumber>
|
|
<span>微米</span>
|
|
</div>
|
|
<img width={40} src={icon_left} onClick={()=>(onMoveLine("left"))} className="text-[20px] ml-[20px]" alt="左移"/>
|
|
<img width={40} src={icon_right} onClick={()=>(onMoveLine("right"))} className="text-[20px] ml-[20px]" alt="右移"/>
|
|
<img width={40} src={icon_up} onClick={()=>(onMoveLine("up"))} className="text-[20px] ml-[20px]" alt="上移"/>
|
|
<img width={40} src={icon_down} onClick={()=>(onMoveLine("down"))} className="text-[20px] ml-[20px]" alt="下移"/>
|
|
<img width={40} src={icon_leftR} onClick={()=>(onRotationLine("left"))} className="text-[20px] ml-[20px]" alt="逆时针旋转"/>
|
|
<img width={40} src={icon_rightR} onClick={()=>(onRotationLine("right"))} className="text-[20px] ml-[20px]" alt="顺时针旋转"/>
|
|
<div className="mt-[12px] ml-[20px]">
|
|
旋转角度:<InputNumber placeholder="请输入角度" defaultValue={angle} onChange={(value) => value !== null && setAngle(Number(value))}></InputNumber>
|
|
<span>分</span>
|
|
</div>
|
|
</div>
|
|
}
|
|
</Spin>
|
|
</div>
|
|
{/* 右侧区域:根据 showAnalysisTable 状态显示测量步骤或分析表格 */}
|
|
<div className="min-w-[300px] flex-auto py-6 flex flex-col items-center">
|
|
<div>
|
|
<h1 className="font-medium text-xl text-center">测量步骤</h1>
|
|
<div className="w-[13rem] mt-5">
|
|
{statusList.map((item, index) => (
|
|
<div
|
|
key={index}
|
|
style={{ background: item.background, borderRadius: "20px" }}
|
|
className="mt-[10px] h-[40px]">
|
|
<div style={{ display: "flex", lineHeight: "40px" }} className="pl-[1rem]">
|
|
{renderStatusIcon(item)}
|
|
<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} loading={startLoading}>
|
|
{startBtnText}
|
|
</Button>
|
|
<Button
|
|
style={{ width: 200 }}
|
|
size="large"
|
|
type="primary"
|
|
onClick={onAnalysisBtnClick}
|
|
disabled={!measurementFinished}>
|
|
分析
|
|
</Button>
|
|
<Button
|
|
style={{ width: 200 }}
|
|
size="large"
|
|
type="primary"
|
|
onClick={onSaveBtnClick}
|
|
disabled={!measurementFinished}
|
|
>
|
|
保存
|
|
</Button>
|
|
<Button
|
|
style={{ width: 200 }}
|
|
size="large"
|
|
type="primary"
|
|
onClick={onCalibrationBtnClick}
|
|
disabled={!measurementFinished}
|
|
>
|
|
校准
|
|
</Button>
|
|
<Checkbox checked={afterSave} onChange={onAfterSaveChange}>
|
|
保存后自动开始新测量
|
|
</Checkbox>
|
|
</section>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<Drawer title="分析结果" onClose={() => setOpenDrawer(false)} open={openDrawer}>
|
|
{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>
|
|
))}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
{/* <div className="mt-5 flex justify-center">
|
|
<Button style={{ minWidth: 200 }} size="large" type="primary" onClick={onExport}>
|
|
导出
|
|
</Button>
|
|
</div> */}
|
|
</>
|
|
)}
|
|
</Drawer>
|
|
</>
|
|
);
|
|
}
|