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.

735 lines
23 KiB

5 months ago
5 months ago
2 months ago
2 months ago
2 months ago
3 months ago
5 months ago
3 months ago
2 months ago
2 months ago
5 months ago
3 months ago
3 months ago
5 months ago
2 months ago
5 months ago
4 months ago
4 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
4 months ago
2 months ago
3 months ago
4 months ago
2 months ago
2 months ago
3 months ago
2 months ago
3 months ago
2 months ago
3 months ago
2 months ago
3 months ago
2 months ago
3 months ago
3 months ago
4 months ago
2 months ago
3 months ago
3 months ago
2 months ago
4 months ago
2 months ago
2 months ago
2 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
2 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
2 months ago
2 months ago
3 months ago
2 months ago
2 months ago
3 months ago
2 months ago
3 months ago
2 months ago
2 months ago
2 months ago
3 months ago
2 months ago
3 months ago
3 months ago
2 months ago
3 months ago
2 months ago
3 months ago
2 months ago
3 months ago
2 months ago
3 months ago
2 months ago
3 months ago
3 months ago
2 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
2 months ago
3 months ago
2 months ago
3 months ago
3 months ago
3 months ago
3 months ago
5 months ago
2 months ago
2 months ago
3 months ago
2 months ago
3 months ago
5 months ago
2 months ago
2 months ago
3 months ago
3 months ago
2 months ago
3 months ago
2 months ago
3 months ago
2 months ago
3 months ago
2 months ago
3 months ago
2 months ago
3 months ago
2 months ago
3 months ago
2 months ago
3 months ago
2 months ago
3 months ago
2 months ago
3 months ago
2 months ago
3 months ago
2 months ago
3 months ago
2 months ago
3 months ago
2 months ago
3 months ago
3 months ago
3 months ago
2 months ago
2 months ago
3 months ago
3 months ago
5 months ago
  1. import StepItem from '../components/StepItem';
  2. import { useNavigate } from 'react-router-dom';
  3. import CustomNavBar from '../components/CustomNavBar';
  4. import MeasurementCanvas, {
  5. BenchmarkShape,
  6. MeasurementCanvasRef,
  7. Point,
  8. } from '../components/konva/MeasurementCanvas';
  9. import { useEffect, useRef, useState } from 'react';
  10. import RailTypeBtn from '../components/RailTypeBtn';
  11. import { Cascader, Dialog, Mask, Picker, SpinLoading, Toast } from 'antd-mobile';
  12. import { useAppDispatch, useAppSelector } from '../utils/hooks';
  13. import {
  14. updateMeasureData,
  15. updateTaskState,
  16. updateMeasureStatus,
  17. updateMeasureFinish,
  18. updateShowCalibration
  19. } from '../store/features/measureSlice';
  20. import Bridge from '../utils/bridge';
  21. import { selectLabeledKtjOrgs, updateRailPoints } from '../store/features/baseData';
  22. import { updateOrg } from '../store/features/contextSlice';
  23. import { selectOrgTextArr } from '../store';
  24. import icon_left from '../assets/icon_left.svg';
  25. import icon_right from '../assets/icon_right.svg';
  26. import icon_up from '../assets/icon_up.svg';
  27. import icon_down from '../assets/icon_down.svg';
  28. import icon_leftR from '../assets/icon_leftR.svg';
  29. import icon_rightR from '../assets/icon_rightR.svg';
  30. export default function Measure() {
  31. const navigate = useNavigate();
  32. const dispatch = useAppDispatch();
  33. const labeledKtjOrgs = useAppSelector(selectLabeledKtjOrgs);
  34. const orgTextArr = useAppSelector(selectOrgTextArr);
  35. const measureState = useAppSelector((state) => state.measure);
  36. const contextState = useAppSelector((state) => state.context);
  37. const baseState = useAppSelector((state) => state.baseData);
  38. const [railPickerVisible, setRailPickerVisible] = useState(false);
  39. const [railId, setRailId] = useState<(number | string | null)[]>([]);
  40. const canvasRef = useRef<MeasurementCanvasRef>(null);
  41. const [railSize, setRailSize] = useState<number | string | null>();
  42. const iconWidth = 40;
  43. const [showStandard, setShowStandard] = useState(false);
  44. // 默认选中第一个轨型
  45. useEffect(() => {
  46. if (baseState.railTypes.length > 0) {
  47. let railData = baseState.railTypes[0];
  48. setRailId([railData.id]);
  49. setRailSize(railData.code);
  50. }
  51. }, [baseState.railTypes]);
  52. function drawRailBaseLine(points: string) {
  53. const benchmarkShapes = JSON.parse(points) as BenchmarkShape[];
  54. if (canvasRef.current) {
  55. canvasRef.current.setBenchmarkData(benchmarkShapes);
  56. }
  57. }
  58. // useEffect(() => {
  59. // // setTimeout(() => {
  60. // // setShowStandard(false)
  61. // // setMeasurementCanvasKey(measurementCanvasKey + 1)
  62. // // }, 1000)
  63. //
  64. // return () =>{
  65. // console.log(111)
  66. // setMeasurementCanvasKey(measurementCanvasKey + 1)
  67. // }
  68. // }, []);
  69. // 检查轨型有没有坐标,如果有,绘制轨型基准线,如果没,拉取再绘制其线
  70. useEffect(() => {
  71. if (railId.length > 0) {
  72. const r = baseState.railTypes.find((rail) => rail.id === railId[0]);
  73. if (!r) return;
  74. if (!!r.points) {
  75. drawRailBaseLine(r.points);
  76. return;
  77. }
  78. Bridge.getTrackPoint({ code: r.code }).then((res) => {
  79. if (res.success) {
  80. dispatch(updateRailPoints(res.data));
  81. drawRailBaseLine(res.data.points!);
  82. console.log(r.points)
  83. } else {
  84. Toast.show(res.message);
  85. }
  86. });
  87. }
  88. }, [baseState.railTypes, dispatch, railId, measureState.measureFinishData]);
  89. useEffect(() => {
  90. if (canvasRef.current && measureState.measureFinishData.length) {
  91. setTimeout(() => {
  92. setShowStandard(true)
  93. }, 100);
  94. canvasRef.current?.setMeasurementCalibrationData(measureState.measureFinishData);
  95. setShowMeasureFinish(true)
  96. }
  97. }, [baseState.railTypes, measureState.measureFinishData, railId]);
  98. // 绘制测量坐标线
  99. useEffect(() => {
  100. if (canvasRef.current) {
  101. canvasRef.current.setMeasurementDataLeft(measureState.leftPoints);
  102. }
  103. }, [measureState.leftPoints]);
  104. useEffect(() => {
  105. if (canvasRef.current) {
  106. canvasRef.current.setMeasurementDataRight(measureState.rightPoints);
  107. }
  108. }, [measureState.rightPoints]);
  109. // 左右两测量完成,转换后的测量线(两线合一线)
  110. // useEffect(() => {
  111. // const points = baseState.railTypes.find((r) => r.id === railId[0])
  112. // console.log(1111, points)
  113. //
  114. // if (canvasRef.current && measureState.measureFinishData.length) {
  115. // setTimeout(() => {
  116. // const points = baseState.railTypes.find((r) => r.code === railId[0])?.points
  117. // drawRailBaseLine(points || '[]')
  118. // setShowStandard(true)
  119. // }, 100);
  120. // canvasRef.current?.setMeasurementCalibrationData(measureState.measureFinishData);
  121. // // setShowCalibration(true);
  122. // setShowMeasureFinish(true)
  123. // setLoading(false)
  124. //
  125. //
  126. // }
  127. // }, [measureState.measureFinishData])
  128. useEffect(() => {
  129. if(measureState.measureStatus === 'FINISH_RECORD'){
  130. setLoading(true)
  131. Bridge.record().then((res) => {
  132. if (res.success) {
  133. dispatch(updateMeasureFinish(res.data));
  134. } else {
  135. dispatch(updateMeasureFinish([]));
  136. setState({
  137. left_ready: 'none',
  138. right_ready: 'none',
  139. left_begin: 'none',
  140. right_begin: 'none',
  141. left_end: 'none',
  142. right_end: 'none',
  143. });
  144. Toast.show(res.message);
  145. }
  146. }).finally(() => {
  147. setLoading(false)
  148. })
  149. }
  150. }, [dispatch, measureState.measureStatus])
  151. useEffect(() => {
  152. return () => {
  153. dispatch(updateMeasureStatus(''))
  154. }
  155. }, [dispatch])
  156. const onSaveClick = () => {
  157. if (!contextState.currOrgCode || !orgTextArr) {
  158. Dialog.alert({
  159. content: '请选择铁路局/工务段/线路',
  160. onConfirm: () => {
  161. onOrgBarClick();
  162. },
  163. });
  164. return;
  165. }
  166. dispatch(updateMeasureData(newMeasureData));
  167. navigate('/measure/save');
  168. };
  169. const [loading, setLoading] = useState(false);
  170. const [showMeasureFinish, setShowMeasureFinish] = useState(false);
  171. const onCalibrationBtnClick = () => {
  172. setLoading(true);
  173. Bridge.alignPoints({ railSize: railSize || 'GX-60' })
  174. .then((res) => {
  175. if (res.success) {
  176. // setShowCalibration(true);
  177. dispatch(updateShowCalibration(true))
  178. canvasRef.current?.setMeasurementCalibrationData(res.data);
  179. dispatch(updateMeasureFinish(res.data))
  180. } else {
  181. }
  182. setLoading(false);
  183. })
  184. .catch((e) => {
  185. setLoading(false);
  186. Toast.show({
  187. content: <span></span>,
  188. position: 'top',
  189. });
  190. });
  191. };
  192. const [initStart, setInitStart] = useState(false);
  193. const [measurementCanvasKey, setMeasurementCanvasKey] = useState(0);
  194. const [state, setState] = useState({
  195. left_ready: 'none',
  196. right_ready: 'none',
  197. left_begin: 'none',
  198. right_begin: 'none',
  199. left_end: 'none',
  200. right_end: 'none',
  201. });
  202. const onStartClick = () => {
  203. setMeasurementCanvasKey(measurementCanvasKey+1)
  204. // setShowCalibration(false);
  205. dispatch(updateShowCalibration(false))
  206. setShowMeasureFinish(false)
  207. dispatch(updateMeasureData([]));
  208. if (!contextState.device.connected) {
  209. Dialog.alert({
  210. content: '蓝牙未连接,请先连接蓝牙',
  211. onConfirm: () => {
  212. navigate('/home/bluetooth');
  213. },
  214. });
  215. return;
  216. }
  217. if (baseState.ktjOrgs.length === 0) {
  218. Dialog.alert({
  219. content: '请在基础数据同步完成后重试',
  220. onConfirm: () => {
  221. navigate('/home/mine');
  222. },
  223. });
  224. return;
  225. }
  226. if (!contextState.currOrgCode || !orgTextArr) {
  227. Dialog.alert({
  228. content: '请选择铁路局/工务段/线路',
  229. onConfirm: () => {
  230. onOrgBarClick();
  231. },
  232. });
  233. return;
  234. }
  235. // if (contextState.device.power < 20) {
  236. // Toast.show("电量低于20%,请充电后测量");
  237. // return;
  238. // }
  239. Bridge.startMeasure().then((res) => {
  240. if (res.success) {
  241. dispatch(updateTaskState('START_RECORD_SIG'));
  242. } else {
  243. Toast.show(res.message);
  244. }
  245. setInitStart(true);
  246. setShowStandard(false)
  247. });
  248. };
  249. const onOrgBarClick = async () => {
  250. if (baseState.ktjOrgs.length === 0) {
  251. Dialog.alert({
  252. content: '请在基础数据同步完成后重试',
  253. onConfirm: () => {
  254. navigate('/home/mine');
  255. },
  256. });
  257. return;
  258. }
  259. const value = await Cascader.prompt({
  260. options: labeledKtjOrgs,
  261. placeholder: '请选择',
  262. });
  263. console.log(value)
  264. // Toast.show(value ? `你选择了 ${value.join(' - ')}` : '你没有进行选择');
  265. if (value?.length) {
  266. dispatch(updateOrg(value as string[]));
  267. }else {
  268. Toast.show('请选择组织机构');
  269. }
  270. };
  271. const [status, setStatus] = useState(0)
  272. useEffect(() => {
  273. console.log(measureState.taskState)
  274. switch (measureState.taskState) {
  275. case 'WAITING_FOR_RECORD_THE_1ST_SIDE':
  276. setStatus(1);
  277. setState({
  278. left_ready: 'ongoing',
  279. right_ready: 'none',
  280. left_begin: 'none',
  281. right_begin: 'none',
  282. left_end: 'none',
  283. right_end: 'none',
  284. });
  285. break;
  286. case 'WAITING_FOR_RECORD_THE_2ND_SIDE':
  287. setStatus(2)
  288. break;
  289. case 'START_RECORD_SIG':
  290. setState({
  291. left_ready: 'ongoing',
  292. right_ready: 'none',
  293. left_begin: 'none',
  294. right_begin: 'none',
  295. left_end: 'none',
  296. right_end: 'none',
  297. });
  298. break;
  299. case 'START_RECORD_LEFT':
  300. case 'START_RECORD_RIGHT':
  301. if (status === 1) {
  302. setState({
  303. left_ready: 'done',
  304. right_ready: 'none',
  305. left_begin: 'ongoing',
  306. right_begin: 'none',
  307. left_end: 'none',
  308. right_end: 'none',
  309. });
  310. } else if (status === 2) {
  311. setState({
  312. left_ready: 'done',
  313. right_ready: 'done',
  314. left_begin: 'done',
  315. right_begin: 'ongoing',
  316. left_end: 'done',
  317. right_end: 'none',
  318. });
  319. }
  320. break;
  321. case 'FINISH_RECORD_LEFT':
  322. case 'FINISH_RECORD_RIGHT':
  323. if (status === 1) {
  324. setState({
  325. left_ready: 'done',
  326. right_ready: 'none',
  327. left_begin: 'done',
  328. right_begin: 'none',
  329. left_end: 'done',
  330. right_end: 'none',
  331. });
  332. } else if (status === 2) {
  333. setState({
  334. left_ready: 'done',
  335. right_ready: 'done',
  336. left_begin: 'done',
  337. right_begin: 'done',
  338. left_end: 'done',
  339. right_end: 'done',
  340. });
  341. }
  342. break;
  343. case 'FINISH_RECORD':
  344. setState({
  345. left_ready: 'done',
  346. right_ready: 'done',
  347. left_begin: 'done',
  348. right_begin: 'done',
  349. left_end: 'done',
  350. right_end: 'done',
  351. });
  352. // 测量完成,显示基线
  353. break;
  354. default:
  355. setState({
  356. left_ready: 'none',
  357. right_ready: 'none',
  358. left_begin: 'none',
  359. right_begin: 'none',
  360. left_end: 'none',
  361. right_end: 'none',
  362. });
  363. }
  364. }, [measureState.taskState, status]);
  365. function railName() {
  366. return baseState.railTypes.find((r) => r.id === railId[0])?.name || '';
  367. }
  368. function onRailSizeChange(ids: (number | string | null)[]) {
  369. if (ids && ids.length) {
  370. setRailId(ids);
  371. let id = ids[0];
  372. const codes = baseState.railTypes.map((item) => {
  373. if (item.id === id) {
  374. return item.code;
  375. }
  376. return item.code;
  377. });
  378. if (codes && codes.length) {
  379. setRailSize(codes[0]);
  380. }
  381. }
  382. }
  383. const [style, setStyle] = useState({
  384. left: 'none',
  385. right: 'none',
  386. up: 'none',
  387. down: 'none',
  388. left_rotation:'none',
  389. right_rotation:'none',
  390. });
  391. //上下移动
  392. let timerRef = useRef<any>(null);
  393. const handlePressStart = (type: string) => {
  394. setStyle({...style, [type]: 'active'})
  395. console.log('你进行了长按操作!');
  396. if (timerRef.current) {
  397. return
  398. }
  399. timerRef.current = setInterval(() => {
  400. console.log('111!');
  401. onHandleMove(type);
  402. }, 100);
  403. console.log(timerRef.current)
  404. };
  405. const handlePressEnd = () => {
  406. setStyle({
  407. left: 'none',
  408. right: 'none',
  409. up: 'none',
  410. down: 'none',
  411. left_rotation:'none',
  412. right_rotation:'none',
  413. })
  414. clearInterval(timerRef.current);
  415. timerRef.current = null;
  416. };
  417. const onMoveLine = (type: string) => {
  418. console.log('这是点击');
  419. onHandleMove(type);
  420. };
  421. const onHandleMove = (type: string) => {
  422. let list = canvasRef.current?.getMeasurementCalibrationData();
  423. console.log(list)
  424. if (list && list.length) {
  425. const updatedList = list.map((item) => {
  426. const mutableItem = { ...item }; // 创建副本
  427. if (type === 'up') {
  428. mutableItem.y -= distance / 1000;
  429. } else if (type === 'down') {
  430. mutableItem.y += distance / 1000;
  431. } else if (type === 'left') {
  432. mutableItem.x -= distance / 1000;
  433. } else if (type === 'right') {
  434. mutableItem.x += distance / 1000;
  435. }
  436. return mutableItem;
  437. });
  438. canvasRef.current?.setMeasurementCalibrationData(updatedList);
  439. dispatch(updateMeasureFinish(list));
  440. setNewMeasureData(updatedList);
  441. }
  442. };
  443. const handleRotationPressStart = (type: string) => {
  444. setStyle({...style, [type === 'left' ? 'left_rotation' : 'right_rotation']: 'active'})
  445. timerRef.current = setInterval(() => {
  446. onRotationLine(type);
  447. }, 500);
  448. };
  449. //旋转
  450. let [measurementRotation] = useState<number>(0);
  451. let [newMeasureData, setNewMeasureData] = useState<Point[]>();
  452. let [angle] = useState<number>(5); //角度单位 分
  453. let [distance] = useState<number>(100);
  454. const onRotationLine = (type: string) => {
  455. let mrValue = 0;
  456. if (type === 'left') {
  457. //逆时针
  458. mrValue = measurementRotation - ((angle / 60) * Math.PI) / 180;
  459. }
  460. if (type === 'right') {
  461. //顺时针
  462. mrValue = measurementRotation + ((angle / 60) * Math.PI) / 180;
  463. }
  464. let list = canvasRef.current?.getMeasurementCalibrationData();
  465. if (list && list.length) {
  466. const updatedList = list.map((item) => {
  467. const mutableItem = { ...item }; // 创建副本
  468. let cloneItem = rotatePoint(mutableItem, mrValue);
  469. mutableItem.x = cloneItem.x;
  470. mutableItem.y = cloneItem.y;
  471. return mutableItem;
  472. });
  473. canvasRef.current?.setMeasurementCalibrationData(updatedList);
  474. dispatch(updateMeasureFinish(list));
  475. setNewMeasureData(updatedList);
  476. }
  477. };
  478. const rotatePoint = (pt: { x: number; y: number }, angle: number) => {
  479. const item = {
  480. x: pt.x * Math.cos(angle) - pt.y * Math.sin(angle),
  481. y: pt.x * Math.sin(angle) + pt.y * Math.cos(angle),
  482. };
  483. return item;
  484. };
  485. const handleContextMenu = (e: any) => {
  486. e.preventDefault();
  487. };
  488. return (
  489. <>
  490. <div className="relative pt-[--navBarHeight]">
  491. <div className="absolute top-0 w-full z-10">
  492. <CustomNavBar title={'测量'}></CustomNavBar>
  493. </div>
  494. <main className="home-page-content overflow-x-hidden overflow-y-auto">
  495. <div className="relative h-0 p-0 pb-[70%]">
  496. {/**正在校准时的loading */}
  497. {/*loading*/}
  498. {loading && (
  499. <Mask opacity='thick' className="h-[100vh] flex justify-center items-center">
  500. <div style={{ margin: '45%', display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center' }}>
  501. <SpinLoading color="#5c92b4" />
  502. <span className="whitespace-nowrap mt-5 text-[#5c92b4]" style={{fontSize: '16px'}}>...</span>
  503. </div>
  504. </Mask>
  505. )}
  506. {/**测量区 */}
  507. <div className="absolute left-0 right-0 top-0 bottom-0 bg-title">
  508. <MeasurementCanvas
  509. width={window.innerWidth}
  510. height={window.innerWidth * 0.7}
  511. logicalExtent={{ minX: -45, maxX: 45, minY: -18, maxY: 52 }}
  512. gridStep={3}
  513. origin={{ x: 0, y: 20 }}
  514. pixelPerMm={window.innerWidth / 90}
  515. maxZoom={8}
  516. showGrid={true}
  517. showBenchmark={showStandard}
  518. showAnalysis={false}
  519. showScale={false}
  520. scaleInterval={1}
  521. showCoordinates={false}
  522. showCalibration={measureState.showCalibration || showMeasureFinish}
  523. ref={canvasRef}
  524. key={measurementCanvasKey}
  525. />
  526. {/**选择轨型区 */}
  527. {railId.length > 0 && (
  528. <div className="absolute left-1 bottom-1">
  529. <RailTypeBtn text={railName()} onClick={() => setRailPickerVisible(true)} />
  530. </div>
  531. )}
  532. </div>
  533. </div>
  534. {/**局段线区 */}
  535. <section
  536. className="h-10 bg-[#e3e8f5] flex justify-between items-center px-4"
  537. onClick={onOrgBarClick}
  538. >
  539. <p className="text-text" style={{ color: contextState.currOrgCode ? '#333' : 'red' }}>
  540. {contextState.currOrgCode && orgTextArr ? orgTextArr.join('/') : '点击此处选择铁路局和工务段'}
  541. </p>
  542. <span className="text-primary underline"></span>
  543. </section>
  544. {/**手动校准区 */}
  545. {(measureState.showCalibration && measureState.measureFinishData.length>0) && (
  546. <section className="h-10 bg-[#e3e8f5] flex justify-between items-center px-4">
  547. <img
  548. width={iconWidth}
  549. src={icon_left}
  550. onClick={() => onMoveLine('left')}
  551. onTouchStart={() => handlePressStart('left')}
  552. onTouchEnd={handlePressEnd}
  553. onTouchCancel={handlePressEnd}
  554. onContextMenu={handleContextMenu}
  555. className={
  556. style.left === 'active'
  557. ? 'text-[20px] ml-[5px] icon-button active-icon-button'
  558. : 'text-[20px] ml-[5px] icon-button'
  559. }
  560. alt="左移"
  561. />
  562. <img
  563. width={iconWidth}
  564. src={icon_right}
  565. onClick={() => onMoveLine('right')}
  566. onTouchStart={() => handlePressStart('right')}
  567. onTouchEnd={handlePressEnd}
  568. onTouchCancel={handlePressEnd}
  569. onContextMenu={handleContextMenu}
  570. className={
  571. style.right === 'active'
  572. ? 'text-[20px] ml-[5px] icon-button active-icon-button'
  573. : 'text-[20px] ml-[5px] icon-button'
  574. }
  575. alt="右移"
  576. />
  577. <img
  578. width={iconWidth}
  579. src={icon_up}
  580. onClick={() => onMoveLine('up')}
  581. onTouchStart={() => handlePressStart('up')}
  582. onTouchEnd={handlePressEnd}
  583. onTouchCancel={handlePressEnd}
  584. onContextMenu={handleContextMenu}
  585. className={
  586. style.up === 'active'
  587. ? 'text-[20px] ml-[5px] icon-button active-icon-button'
  588. : 'text-[20px] ml-[5px] icon-button'
  589. }
  590. alt="上移"
  591. />
  592. <img
  593. width={iconWidth}
  594. src={icon_down}
  595. onClick={() => onMoveLine('down')}
  596. onTouchStart={() => handlePressStart('down')}
  597. onTouchEnd={handlePressEnd}
  598. onTouchCancel={handlePressEnd}
  599. onContextMenu={handleContextMenu}
  600. className={
  601. style.down === 'active'
  602. ? 'text-[20px] ml-[5px] icon-button active-icon-button'
  603. : 'text-[20px] ml-[5px] icon-button'
  604. }
  605. alt="下移"
  606. />
  607. <img
  608. width={iconWidth}
  609. src={icon_leftR}
  610. onClick={() => onRotationLine('left')}
  611. onTouchStart={() => handleRotationPressStart('left')}
  612. onTouchEnd={handlePressEnd}
  613. onTouchCancel={handlePressEnd}
  614. onContextMenu={handleContextMenu}
  615. className={
  616. style.left_rotation === 'active'
  617. ? 'text-[20px] ml-[5px] icon-button active-icon-button'
  618. : 'text-[20px] ml-[5px] icon-button'
  619. }
  620. alt="逆时针旋转"
  621. />
  622. <img
  623. width={iconWidth}
  624. src={icon_rightR}
  625. onClick={() => onRotationLine('right')}
  626. onTouchStart={() => handleRotationPressStart('right')}
  627. onTouchEnd={handlePressEnd}
  628. onTouchCancel={handlePressEnd}
  629. onContextMenu={handleContextMenu}
  630. className={
  631. style.right_rotation === 'active'
  632. ? 'text-[20px] ml-[5px] icon-button active-icon-button'
  633. : 'text-[20px] ml-[5px] icon-button'
  634. }
  635. alt="顺时针旋转"
  636. />
  637. </section>
  638. )}
  639. {/**按钮操作区 */}
  640. <section className="flex items-center gap-4 px-4 my-4">
  641. <button className="btn-contained rounded-md text-sm h-10 flex-1" onClick={onStartClick}>
  642. {!initStart ||
  643. (measureState.leftPoints.length > 0 && measureState.rightPoints.length > 0)
  644. ? '开始测量'
  645. : '重新测量'}
  646. </button>
  647. <button
  648. className="btn-contained rounded-md text-sm h-10 flex-1"
  649. disabled={!measureState.measureFinishData.length}
  650. onClick={onSaveClick}
  651. >
  652. </button>
  653. <button
  654. className="btn-contained rounded-md text-sm h-10 flex-1"
  655. disabled={!measureState.measureFinishData.length}
  656. onClick={onCalibrationBtnClick}
  657. >
  658. </button>
  659. </section>
  660. {/**测量状态区 */}
  661. <section className="grid grid-cols-2 gap-[10px]">
  662. <StepItem state={state.left_ready} text={'等待测量'} />
  663. <StepItem state={state.right_ready} text={'等待测量另一侧'} />
  664. <StepItem state={state.left_begin} text={'正在进行测量'} />
  665. <StepItem state={state.right_begin} text={'正在进行测量'} />
  666. <StepItem state={state.left_end} text={'一侧测量完成'} />
  667. <StepItem state={state.right_end} text={'测量已完成'} />
  668. </section>
  669. </main>
  670. </div>
  671. <Picker
  672. columns={[baseState.railTypes.map((t) => ({ label: t.name, value: t.id }))]}
  673. visible={railPickerVisible}
  674. onClose={() => {
  675. setRailPickerVisible(false);
  676. }}
  677. value={railId}
  678. onConfirm={(v) => {
  679. onRailSizeChange(v);
  680. }}
  681. />
  682. </>
  683. );
  684. }