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

5 months ago
5 months ago
5 months ago
4 months ago
4 months ago
5 months ago
4 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
3 months ago
5 months ago
3 months ago
5 months ago
4 months ago
5 months ago
3 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
4 months ago
5 months ago
4 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
3 months ago
3 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
4 months ago
4 months ago
5 months ago
4 months ago
4 months ago
4 months ago
4 months ago
4 months ago
4 months ago
4 months ago
4 months ago
4 months ago
4 months ago
4 months ago
5 months ago
4 months ago
5 months ago
4 months ago
5 months ago
5 months ago
5 months ago
4 months ago
4 months ago
5 months ago
5 months ago
5 months ago
3 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
  1. import React, { useState, useEffect, useRef, useCallback, useMemo } from "react";
  2. import { Button, Checkbox, CheckboxProps, Drawer, Input, InputNumber, message, Select, Spin, Switch } from "antd";
  3. import { useNavigate } from "react-router";
  4. import {
  5. fetchAnalysisReport,
  6. getAlignPointsByRailSize,
  7. getReport,
  8. startMeasurement,
  9. stopMeasurement,
  10. } from "../../../services/measure/analysis";
  11. import { getBaseRecordPointSetByCode, gx_list } from "../../../services/track/trackShape"
  12. import { createWebSocket, sharedWsUrl } from "../../../services/socket";
  13. import { switchMeasureAfterSave } from "../../../store/features/contextSlice";
  14. import measureState, { updateGxState, updateMeasureData } from "../../../store/measure/measureState";
  15. import { AnalysisReport, trackItem } from "../../../services/measure/type";
  16. import { MeasureState, ResultRecordData, TaskState, TrackRecordSig } from "../../../services/wsTypes";
  17. import { useAppDispatch, useAppSelector } from "../../../utils/hooks";
  18. import Gr_round from "../../../assets/green_round.svg";
  19. import Bl_round from "../../../assets/blue_round.svg";
  20. import icon_left from "../../../assets/icon_left.svg";
  21. import icon_right from "../../../assets/icon_right.svg";
  22. import icon_up from "../../../assets/icon_up.svg";
  23. import icon_down from "../../../assets/icon_down.svg";
  24. import icon_leftR from "../../../assets/icon_leftR.svg";
  25. import icon_rightR from "../../../assets/icon_rightR.svg";
  26. import MeasurementCanvas, { AnalysisData, BenchmarkShape, MeasurementCanvasRef, Point } from "./konva/MeasurementCanvas";
  27. import "./MeasureAction.scss";
  28. import { GX_CODE } from "../../../constant";
  29. // 创建 websocket 客户端
  30. const wsClient = createWebSocket(sharedWsUrl);
  31. export default function MeasureAction() {
  32. const dispatch = useAppDispatch();
  33. const navigate = useNavigate();
  34. const deviceInfo = useAppSelector(store => store.context.device);
  35. const measureState = useAppSelector((store) => store.measureState);
  36. const [railSize, setRailSize] = useState<string>(GX_CODE)
  37. const STEP_COLOR_GREEN = "green";
  38. const STEP_COLOR_BLUE = "blue";
  39. const STEP_COLOR_GREY = "grey";
  40. /** ----------------------- 引用 ----------------------- **/
  41. const canvasRef = useRef<MeasurementCanvasRef>(null);
  42. const leftPoints = useRef<{ x: number; y: number }[]>([]);
  43. const rightPoints = useRef<{ x: number; y: number }[]>([]);
  44. const isLeftFinished = useRef(false);
  45. /** ----------------------- 状态 ----------------------- **/
  46. const [showGrid, setShowGrid] = useState(true);
  47. const [showStandard, setShowStandard] = useState(true);
  48. const [showMark, setShowMark] = useState(true);
  49. // showMark的备份,记录showMark最近一次的值
  50. const [angleMarkBackup, setAngleMarkBackup] = useState(true);
  51. const afterSave = useAppSelector(store => store.context.newMeasureAfterSave);
  52. const [startBtnText, setStartBtnText] = useState("开始测量");
  53. const [measurementFinished, setMeasurementFinished] = useState(false);
  54. // 【分析】之后,会得到分析报告
  55. const [analysisReport, setAnalysisReport] = useState<AnalysisReport | null>(null);
  56. // 初始状态列表
  57. const initialStatusList = useMemo(
  58. () => [
  59. { name: "等待测量", background: "#ececec", isReady: false, color: STEP_COLOR_GREY },
  60. { name: "正在进行测量", background: "#ececec", isReady: false, color: STEP_COLOR_GREY },
  61. { name: "一侧测量完成", background: "#ececec", isReady: false, color: STEP_COLOR_GREY },
  62. { name: "等待测量另一侧", background: "#ececec", isReady: false, color: STEP_COLOR_GREY },
  63. { name: "正在进行测量", background: "#ececec", isReady: false, color: STEP_COLOR_GREY },
  64. { name: "测量已完成", background: "#ececec", isReady: false, color: STEP_COLOR_GREY },
  65. ],
  66. []
  67. );
  68. const [statusList, setStatusList] = useState(initialStatusList);
  69. // 打开抽屉(显示分析结果)
  70. const [openDrawer, setOpenDrawer] = useState(false);
  71. /** ----------------------- 事件处理函数 ----------------------- */
  72. // 切换保存后自动开始新测量
  73. const onAfterSaveChange: CheckboxProps["onChange"] = e => {
  74. dispatch(switchMeasureAfterSave(e.target.checked));
  75. };
  76. useEffect(()=>{
  77. setRailSize(measureState.gxCode || GX_CODE)
  78. }, [measureState])
  79. // 分析按钮点击事件
  80. const onAnalysisBtnClick = () => {
  81. // if (analysisReport) {
  82. // setOpenDrawer(true);
  83. // return;
  84. // }
  85. let params = {
  86. code:railSize,
  87. pointList:[]
  88. }
  89. if(showCalibration){//校准分析时,添加校准后的坐标点
  90. params.pointList = calibrationData
  91. }
  92. // getReport(params).then(res=> {
  93. // console.log('res---', res)
  94. // })
  95. getReport(params).then(res => {
  96. if (res.success) {
  97. const report: AnalysisReport = res.data;
  98. console.log(report);
  99. // 更新 canvas 的分析数据
  100. if (report && report.angleAnalysisList) {
  101. // 先过滤掉 distance 为 null 的数据
  102. const validItems = report.angleAnalysisList.filter(item => item.distance !== null);
  103. const analysisData: AnalysisData[] = validItems.map(item => ({
  104. pointA: { x: parseFloat(item.pointA.x), y: parseFloat(item.pointA.y) },
  105. pointB: { x: parseFloat(item.pointB.x), y: parseFloat(item.pointB.y) },
  106. base: { x: parseFloat(item.pointA.x), y: parseFloat(item.pointA.y) },
  107. measure: { x: parseFloat(item.pointB.x), y: parseFloat(item.pointB.y) },
  108. distance: parseFloat(item.distance),
  109. describe: item.describe,
  110. }));
  111. canvasRef.current?.setAnalysisData(analysisData);
  112. }
  113. setAnalysisReport(report);
  114. setOpenDrawer(true);
  115. } else {
  116. message.error("分析报告请求失败: " + res.data.info);
  117. }
  118. });
  119. };
  120. const [startLoading, setStartLoading] = useState(false)
  121. // 开始/重新测量按钮点击事件
  122. const onStart = useCallback(() => {
  123. // if (!deviceInfo.isConnected) {
  124. // message.error("请先连接设备");
  125. // return;
  126. // }
  127. setStartLoading(true)
  128. setAudioList([])
  129. // if(deviceInfo.power < 20){
  130. // message.error('电量低于20%,请充电后再测量!')
  131. // return
  132. // }
  133. // 重置测量相关状态
  134. setMeasurementFinished(false);
  135. setAnalysisReport(null);
  136. setshowCalibration(false)//校准线
  137. dispatch(updateMeasureData([]))
  138. isLeftFinished.current = false;
  139. leftPoints.current = [];
  140. rightPoints.current = [];
  141. canvasRef.current?.clearShapes();
  142. canvasRef.current?.resetCanvas();
  143. startMeasurement().then(res => {
  144. setTimeout(()=>{
  145. setStartLoading(false)
  146. }, 1000)
  147. if (res.status !== 0) {
  148. message.error(res.data.info);
  149. } else {
  150. let list = [...initialStatusList]
  151. list.forEach((item, index) => {
  152. if(index === 0){
  153. item.color = STEP_COLOR_BLUE;
  154. }else{
  155. item.color = STEP_COLOR_GREY;
  156. }
  157. })
  158. setStatusList(list);
  159. message.success("已通知设备开始测量");
  160. setStartBtnText("重新测量");
  161. const audioReady = new Audio("/audio/ready.mp3");
  162. // 播放音频
  163. audioReady
  164. .play()
  165. .then(() => {
  166. console.log("音频开始播放 已准备好");
  167. })
  168. .catch(err => {
  169. console.error("播放音频时出错:", err);
  170. });
  171. }
  172. });
  173. }, [initialStatusList, startBtnText, deviceInfo]);
  174. //停止测量
  175. const onStop = () => {
  176. stopMeasurement().then(res=>{
  177. if (res.status !== 0) {
  178. }else{
  179. message.error('已停止测量')
  180. }
  181. }).catch(e=>{
  182. message.error('调用出错')
  183. })
  184. }
  185. // 保存按钮点击事件
  186. const onSaveBtnClick = () => {
  187. dispatch(updateMeasureData(newMeasureData))
  188. navigate('/measure/config')
  189. //将校准的数据存入store
  190. };
  191. //校准
  192. const [showCalibration, setshowCalibration] = useState(false)
  193. const [caloading, setCaLoading] = useState(false)
  194. const [loadingText, setLoadingText] = useState('正在校准...')
  195. const [calibrationData, setCalibrationData] = useState([])
  196. const onCalibrationBtnClick = () => {
  197. setCaLoading(true)
  198. setLoadingText('正在校准...')
  199. //获取校准数据
  200. getAlignPointsByRailSize({railSize:railSize}).then(res => {
  201. if(res.success){
  202. setshowCalibration(true)
  203. setCalibrationData(res.data)
  204. canvasRef.current?.setMeasurementCalibrationData(res.data)
  205. }else{
  206. message.error('校准失败!')
  207. }
  208. setCaLoading(false)
  209. setLoadingText('')
  210. }).catch(e=>{
  211. setCaLoading(false)
  212. message.error('校准失败!')
  213. })
  214. }
  215. // 辅助函数:渲染状态项的图标
  216. const renderStatusIcon = (item: (typeof initialStatusList)[0]) => {
  217. if (item.color === STEP_COLOR_GREEN) {
  218. return <img src={Gr_round} alt="green" />;
  219. } else if (item.color === STEP_COLOR_BLUE) {
  220. return <img src={Bl_round} alt="blue" />;
  221. } else {
  222. return (
  223. <div
  224. style={{
  225. width: "22px",
  226. height: "22px",
  227. background: "#c0c0c0",
  228. borderRadius: "50%",
  229. marginTop: "10px",
  230. }}
  231. />
  232. );
  233. }
  234. };
  235. const [audioList, setAudioList] = useState<HTMLAudioElement[]>([])
  236. const pauseAudio = () => {
  237. audioList.forEach(audio => {
  238. audio.pause()
  239. })
  240. }
  241. const [status, setStatus] = useState(0)
  242. /** ----------------------- WebSocket 消息处理 ----------------------- **/
  243. useEffect(() => {
  244. // 处理任务状态消息
  245. const handleStateMessage = (state: MeasureState["data"]) => {};
  246. // 处理事件消息
  247. const handleEventMessage = (type: TaskState["data"]) => {
  248. /**
  249. * IDLE
  250. * START_MEASURE: 开始测量
  251. * WAITING_FOR_RECORD_THE_1ST_SIDE: 等待测量
  252. * RECORD_THE_FIRST_SIDE: 测量第一条曲线
  253. * RECORD_THE_1ST_SIDE_FINISHED: 第一条曲线测量完成
  254. * WAITING_FOR_RECORD_THE_2ND_SIDE: 等待测量第二条曲线
  255. * RECORD_THE_2ND_SIDE: 测量第二线曲线
  256. * FINISHED: 第二条曲线测量完成
  257. */
  258. setStatusList(prev => {
  259. const updated = [...prev];
  260. switch (type) {
  261. case 'WAITING_FOR_RECORD_THE_1ST_SIDE':
  262. setStatus(1);
  263. break;
  264. case 'WAITING_FOR_RECORD_THE_2ND_SIDE':
  265. setStatus(2)
  266. break;
  267. case 'START_RECORD_SIG':
  268. updated[0].color = STEP_COLOR_GREEN;
  269. updated[1].color = STEP_COLOR_BLUE;
  270. break;
  271. case 'START_RECORD_LEFT':
  272. case 'START_RECORD_RIGHT':
  273. if (status === 1) {
  274. updated[0].color = STEP_COLOR_GREEN;
  275. updated[1].color = STEP_COLOR_BLUE;
  276. } else if (status === 2) {
  277. updated[3].color = STEP_COLOR_GREEN;
  278. updated[4].color = STEP_COLOR_BLUE;
  279. }
  280. const audio1 = new Audio("/audio/measuring.mp3");
  281. // 播放音频
  282. audio1
  283. .play()
  284. .then(() => {
  285. console.log("音频开始播放");
  286. })
  287. .catch(err => {
  288. console.error("播放音频时出错:", err);
  289. });
  290. setAudioList([...audioList, audio1])
  291. break;
  292. case "FINISH_RECORD_LEFT":
  293. case "FINISH_RECORD_RIGHT":
  294. pauseAudio()
  295. if (status === 1) {
  296. updated[1].color = STEP_COLOR_GREEN;
  297. updated[2].color = STEP_COLOR_GREEN;
  298. updated[3].color = STEP_COLOR_BLUE;
  299. const line1Audio = new Audio("/audio/side_end.mp3");
  300. // 播放音频
  301. line1Audio
  302. .play()
  303. .then(() => {
  304. console.log("音频开始播放");
  305. })
  306. .catch(err => {
  307. console.error("播放音频时出错:", err);
  308. });
  309. setAudioList([...audioList, line1Audio])
  310. }else if(status === 2) {
  311. updated[3].color = STEP_COLOR_GREEN;
  312. updated[4].color = STEP_COLOR_BLUE;
  313. const audio2 = new Audio("/audio/measure_end.mp3");
  314. // 播放音频
  315. audio2
  316. .play()
  317. .then(() => {
  318. console.log("音频开始播放");
  319. })
  320. .catch(err => {
  321. console.error("播放音频时出错:", err);
  322. });
  323. setAudioList([...audioList, audio2])
  324. setMeasurementFinished(true);
  325. }
  326. updated[1].color = STEP_COLOR_GREEN;
  327. updated[2].color = STEP_COLOR_GREEN;
  328. updated[3].color = STEP_COLOR_BLUE;
  329. isLeftFinished.current = true;
  330. break;
  331. case "FINISH_RECORD":
  332. updated.forEach(item => {
  333. item.color = STEP_COLOR_GREEN
  334. })
  335. const audioFinish= new Audio("/audio/measure_end.mp3");//measure_end
  336. // 播放音频
  337. audioFinish
  338. .play()
  339. .then(() => {
  340. console.log("音频开始播放");
  341. })
  342. .catch(err => {
  343. console.error("播放音频时出错:", err);
  344. });
  345. setCaLoading(true)
  346. setLoadingText('正在处理测量数据...')
  347. setMeasurementFinished(true);
  348. break;
  349. case "WRONG_SIDE":
  350. const audio5 = new Audio("/audio/alert_left.mp3");
  351. // 播放音频
  352. audio5
  353. .play()
  354. .then(() => {
  355. console.log("音频开始播放");
  356. })
  357. .catch(err => {
  358. console.error("播放音频时出错:", err);
  359. });
  360. // 把状态全部置灰
  361. updated.forEach(u => (u.color = STEP_COLOR_GREY));
  362. // 调用停止测量
  363. onStop();
  364. break;
  365. default:
  366. break;
  367. }
  368. return updated;
  369. });
  370. };
  371. // 处理点数据消息
  372. const handlePointReport = (pointData: TrackRecordSig["data"]) => {
  373. if (!isLeftFinished.current) {
  374. leftPoints.current.push(pointData);
  375. canvasRef.current?.setMeasurementDataLeft([...leftPoints.current]);
  376. } else {
  377. rightPoints.current.push(pointData);
  378. canvasRef.current?.setMeasurementDataRight([...rightPoints.current]);
  379. }
  380. };
  381. // 处理测量后的数据
  382. const handleMeasureResult = (pointData: ResultRecordData["data"]) => {
  383. canvasRef.current?.setMeasurementDataLeft([...pointData.outline1]);
  384. canvasRef.current?.setMeasurementDataRight([...pointData.outline2]);
  385. }
  386. const subscription = wsClient.dataOb.subscribe(data => {
  387. if (data.path === "/api/measurement-task/get-task-state") {
  388. handleStateMessage(data.data);
  389. } else if (data.path === "/api/measurement-task/event") {
  390. handleEventMessage(data.data);
  391. } else if (data.path === "/api/measurement-task/point-report") {
  392. handlePointReport(data.data);
  393. } else if (data.path === "/api/measurement-task/measure-finished") {
  394. setCaLoading(false)
  395. handleMeasureResult(data.data)
  396. }
  397. });
  398. wsClient.connect();
  399. return () => subscription.unsubscribe();
  400. }, [onStart]);
  401. /** ----------------------- 页面加载获取基础图形数据 -------基线---------------- **/
  402. useEffect(() => {
  403. queryBasePoints(railSize)
  404. //获取轨型
  405. getTrackDataList()
  406. }, []);
  407. //获取测量基线
  408. const queryBasePoints = (gxCode:string) => {
  409. getBaseRecordPointSetByCode(gxCode).then(res => {
  410. if (res.success) {
  411. const benchmarkShapes = JSON.parse(res.data.points) as BenchmarkShape[];
  412. if (canvasRef.current) {
  413. console.log("解析后的基础图形数据:", benchmarkShapes);
  414. canvasRef.current.setBenchmarkData(benchmarkShapes);
  415. }
  416. }
  417. });
  418. }
  419. /********************************轨型数据************************************* */
  420. const [trackList, setTrackList] = useState<trackItem[]>([])
  421. const [defaultTrackValue, setDefaultTrackValue] = useState<string>()
  422. const getTrackDataList = () => {
  423. gx_list().then(res => {
  424. if(res.data && res.data.length){
  425. let resData:trackItem[] = res.data;
  426. let list:trackItem[] = []
  427. resData.map(item => {
  428. if(item.points){
  429. list.push(item)
  430. }
  431. })
  432. setTrackList(list)
  433. }
  434. })
  435. }
  436. useEffect(()=>{
  437. if(trackList && trackList.length){
  438. setDefaultTrackValue(trackList[0].code)
  439. }
  440. },[trackList])
  441. //当前选择的轨型 默认"GX-60"
  442. const onTrackChange = (value: string) => {
  443. setRailSize(value)
  444. queryBasePoints(value)
  445. //缓存至STORE
  446. dispatch(updateGxState(value))
  447. }
  448. //上下移动
  449. const onMoveLine = (type:string) => {
  450. let list = canvasRef.current?.getMeasurementCalibrationData()
  451. if(list && list.length){
  452. list.forEach(item => {
  453. if(type === 'up'){//向上移动,原数据减y X轴不动
  454. item.y = item.y - distance/1000;
  455. }
  456. if(type === 'down'){//向上移动,原数据加y X轴不动
  457. item.y = item.y + distance/1000;
  458. }
  459. if(type === 'left'){//向左移动,原数据减x Y轴不动
  460. item.x = item.x - distance/1000;
  461. }
  462. if(type === 'right'){//向右移动,原数据加x Y轴不动
  463. item.x = item.x + distance/1000;
  464. }
  465. })
  466. canvasRef.current?.setMeasurementCalibrationData(list)
  467. setNewMeasureData(list)
  468. }
  469. }
  470. //旋转
  471. let [measurementRotation, setMeasurementRotation] = useState<number>(0)
  472. let [newMeasureData, setNewMeasureData] = useState<Point[]>()
  473. let [angle, setAngle] = useState<number>(1);//角度单位 分
  474. let [distance, setDistance] = useState<number>(10)
  475. const onRotationLine = (type:string) => {
  476. let mrValue = 0
  477. if(type === 'left'){//逆时针
  478. mrValue = measurementRotation - (angle/60) * Math.PI / 180;
  479. }
  480. if(type === 'right'){//顺时针
  481. mrValue = measurementRotation + (angle/60) * Math.PI / 180;
  482. }
  483. let list = canvasRef.current?.getMeasurementCalibrationData()
  484. if(list && list.length){
  485. list.forEach((item, index) => {
  486. let cloneItem = rotatePoint(item, mrValue)
  487. item.x = cloneItem.x
  488. item.y = cloneItem.y
  489. })
  490. canvasRef.current?.setMeasurementCalibrationData(list)
  491. setNewMeasureData(list)
  492. }
  493. }
  494. const rotatePoint = (pt:{x:number;y:number}, angle:number) => {
  495. const item = {
  496. x: pt.x * Math.cos(angle) - pt.y * Math.sin(angle),
  497. y: pt.x * Math.sin(angle) + pt.y * Math.cos(angle)
  498. };
  499. return item
  500. }
  501. /** ----------------------- 渲染 ----------------------- **/
  502. return (
  503. <>
  504. <div className="flex h-full px-6">
  505. {/* 左侧区域:包含开关区域和测量画布 */}
  506. <div className="">
  507. <div className="relative flex gap-4 items-center py-3">
  508. <Select
  509. className="w-[150px]"
  510. placeholder="请选择轨型"
  511. key={defaultTrackValue}
  512. defaultValue={defaultTrackValue}
  513. onChange={onTrackChange}
  514. options={trackList.map((item) => ({
  515. label: item.name,
  516. value: item.code,
  517. }))}
  518. ></Select>
  519. {/* <div
  520. className="absolute text-primary border border-primary rounded px-4 py-[2px] font-medium cursor-pointer hover:text-primary/[0.8]"
  521. onClick={() => navigate("../config", { replace: true })}>
  522. </div> */}
  523. <section className="ml-auto flex gap-4 items-center">
  524. {/* 参考线开关 */}
  525. <div className="flex gap-2 items-center">
  526. <Switch defaultChecked size="small" onChange={checked => setShowGrid(checked)} />
  527. <span>线</span>
  528. </div>
  529. {/* 标准线开关 */}
  530. <div className="flex gap-2 items-center">
  531. <Switch
  532. size="small"
  533. checked={showStandard}
  534. onChange={checked => {
  535. setShowStandard(checked);
  536. if (!checked) {
  537. setAngleMarkBackup(showMark);
  538. setShowMark(false);
  539. } else {
  540. setShowMark(angleMarkBackup);
  541. }
  542. }}
  543. />
  544. <span>线</span>
  545. </div>
  546. {/* 角度线开关,仅在点击分析按钮后显示 */}
  547. {analysisReport && (
  548. <div className="flex gap-2 items-center">
  549. <Switch
  550. size="small"
  551. checked={showMark}
  552. disabled={!showStandard}
  553. onChange={checked => {
  554. setShowMark(checked);
  555. setAngleMarkBackup(checked);
  556. }}
  557. />
  558. <span>线</span>
  559. </div>
  560. )}
  561. </section>
  562. </div>
  563. <Spin spinning={caloading} tip={ loadingText }>
  564. <div className="relative">
  565. <MeasurementCanvas
  566. width={800}
  567. height={600}
  568. logicalExtent={{ minX: -50, maxX: 50, minY: -20, maxY: 60 }}
  569. gridStep={1}
  570. origin={{ x: 0, y: 20 }}
  571. pixelPerMm={8}
  572. maxZoom={10}
  573. showGrid={showGrid}
  574. showBenchmark={showStandard}
  575. showAnalysis={showMark}
  576. showScale={false}
  577. scaleInterval={1}
  578. showCoordinates={showGrid}
  579. showCalibration={showCalibration}
  580. ref={canvasRef}
  581. />
  582. </div>
  583. {showCalibration &&
  584. <div className="flex justify-center h-[50px]">
  585. <div className="mt-[12px]">
  586. <InputNumber defaultValue={distance} placeholder="微米" onChange={(value) => value !== null && setDistance(Number(value))}></InputNumber>
  587. <span></span>
  588. </div>
  589. <img width={40} src={icon_left} onClick={()=>(onMoveLine("left"))} className="text-[20px] ml-[20px]" alt="左移"/>
  590. <img width={40} src={icon_right} onClick={()=>(onMoveLine("right"))} className="text-[20px] ml-[20px]" alt="右移"/>
  591. <img width={40} src={icon_up} onClick={()=>(onMoveLine("up"))} className="text-[20px] ml-[20px]" alt="上移"/>
  592. <img width={40} src={icon_down} onClick={()=>(onMoveLine("down"))} className="text-[20px] ml-[20px]" alt="下移"/>
  593. <img width={40} src={icon_leftR} onClick={()=>(onRotationLine("left"))} className="text-[20px] ml-[20px]" alt="逆时针旋转"/>
  594. <img width={40} src={icon_rightR} onClick={()=>(onRotationLine("right"))} className="text-[20px] ml-[20px]" alt="顺时针旋转"/>
  595. <div className="mt-[12px] ml-[20px]">
  596. <InputNumber placeholder="请输入角度" defaultValue={angle} onChange={(value) => value !== null && setAngle(Number(value))}></InputNumber>
  597. <span></span>
  598. </div>
  599. </div>
  600. }
  601. </Spin>
  602. </div>
  603. {/* 右侧区域:根据 showAnalysisTable 状态显示测量步骤或分析表格 */}
  604. <div className="min-w-[300px] flex-auto py-6 flex flex-col items-center">
  605. <div>
  606. <h1 className="font-medium text-xl text-center"></h1>
  607. <div className="w-[13rem] mt-5">
  608. {statusList.map((item, index) => (
  609. <div
  610. key={index}
  611. style={{ background: item.background, borderRadius: "20px" }}
  612. className="mt-[10px] h-[40px]">
  613. <div style={{ display: "flex", lineHeight: "40px" }} className="pl-[1rem]">
  614. {renderStatusIcon(item)}
  615. <div className="pl-[5px]">{item.name}</div>
  616. </div>
  617. </div>
  618. ))}
  619. </div>
  620. <section className="flex flex-col items-center gap-4 mt-6 border-t border-[#D8D8D8] py-4">
  621. <Button style={{ width: 200 }} size="large" type="primary" onClick={onStart} loading={startLoading}>
  622. {startBtnText}
  623. </Button>
  624. <Button
  625. style={{ width: 200 }}
  626. size="large"
  627. type="primary"
  628. onClick={onAnalysisBtnClick}
  629. disabled={!measurementFinished}>
  630. </Button>
  631. <Button
  632. style={{ width: 200 }}
  633. size="large"
  634. type="primary"
  635. onClick={onSaveBtnClick}
  636. disabled={!measurementFinished}
  637. >
  638. </Button>
  639. <Button
  640. style={{ width: 200 }}
  641. size="large"
  642. type="primary"
  643. onClick={onCalibrationBtnClick}
  644. disabled={!measurementFinished}
  645. >
  646. </Button>
  647. <Checkbox checked={afterSave} onChange={onAfterSaveChange}>
  648. </Checkbox>
  649. </section>
  650. </div>
  651. </div>
  652. </div>
  653. <Drawer title="分析结果" onClose={() => setOpenDrawer(false)} open={openDrawer}>
  654. {analysisReport && (
  655. <>
  656. <div className="analysis-table">
  657. <table
  658. style={{
  659. width: "100%",
  660. borderCollapse: "collapse",
  661. border: "1px solid #ccc",
  662. textAlign: "center",
  663. }}>
  664. <tbody>
  665. <tr style={{ height: "40px", fontSize: "18px", color: "#9E9E9E" }}>
  666. <td style={{ padding: "8px", border: "1px solid #ccc" }}>W1垂直磨耗</td>
  667. <td style={{ padding: "8px", border: "1px solid #ccc" }}>{analysisReport.w1}</td>
  668. </tr>
  669. <tr style={{ height: "40px", fontSize: "18px", color: "#9E9E9E" }}>
  670. <td style={{ padding: "8px", border: "1px solid #ccc" }}></td>
  671. <td style={{ padding: "8px", border: "1px solid #ccc" }}>
  672. {analysisReport.railHeadWidth}
  673. </td>
  674. </tr>
  675. {analysisReport.angleAnalysisList.map((item, index) => (
  676. <tr key={index} style={{ height: "40px", fontSize: "18px", color: "#9E9E9E" }}>
  677. <td style={{ padding: "8px", border: "1px solid #ccc" }}>{item.describe}</td>
  678. <td style={{ padding: "8px", border: "1px solid #ccc" }}>{item.distance}</td>
  679. </tr>
  680. ))}
  681. </tbody>
  682. </table>
  683. </div>
  684. {/* <div className="mt-5 flex justify-center">
  685. <Button style={{ minWidth: 200 }} size="large" type="primary" onClick={onExport}>
  686. </Button>
  687. </div> */}
  688. </>
  689. )}
  690. </Drawer>
  691. </>
  692. );
  693. }