|
|
import StepItem from '../components/StepItem'; import { useNavigate } from 'react-router-dom'; import CustomNavBar from '../components/CustomNavBar'; import MeasurementCanvas, { BenchmarkShape, MeasurementCanvasRef, Point, } from '../components/konva/MeasurementCanvas'; import { useEffect, useRef, useState } from 'react';
import RailTypeBtn from '../components/RailTypeBtn'; import { Cascader, Dialog, Mask, Picker, SpinLoading, Toast } from 'antd-mobile'; import { useAppDispatch, useAppSelector } from '../utils/hooks'; import { updateMeasureData, updateTaskState, updateMeasureStatus, updateMeasureFinish, updateShowCalibration } from '../store/features/measureSlice'; import Bridge from '../utils/bridge'; import { selectLabeledKtjOrgs, updateRailPoints } from '../store/features/baseData'; import { updateOrg } from '../store/features/contextSlice'; import { selectOrgTextArr } from '../store'; 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'; export default function Measure() { const navigate = useNavigate(); const dispatch = useAppDispatch(); const labeledKtjOrgs = useAppSelector(selectLabeledKtjOrgs); const orgTextArr = useAppSelector(selectOrgTextArr);
const measureState = useAppSelector((state) => state.measure); const contextState = useAppSelector((state) => state.context); const baseState = useAppSelector((state) => state.baseData); const [railPickerVisible, setRailPickerVisible] = useState(false); const [railId, setRailId] = useState<(number | string | null)[]>([]); const canvasRef = useRef<MeasurementCanvasRef>(null); const [railSize, setRailSize] = useState<number | string | null>(); const iconWidth = 40; const [showStandard, setShowStandard] = useState(false);
// 默认选中第一个轨型
useEffect(() => { if (baseState.railTypes.length > 0) { let railData = baseState.railTypes[0]; setRailId([railData.id]); setRailSize(railData.code); } }, [baseState.railTypes]);
function drawRailBaseLine(points: string) { const benchmarkShapes = JSON.parse(points) as BenchmarkShape[]; if (canvasRef.current) { canvasRef.current.setBenchmarkData(benchmarkShapes); } }
// useEffect(() => {
// // setTimeout(() => {
// // setShowStandard(false)
// // setMeasurementCanvasKey(measurementCanvasKey + 1)
// // }, 1000)
//
// return () =>{
// console.log(111)
// setMeasurementCanvasKey(measurementCanvasKey + 1)
// }
// }, []);
// 检查轨型有没有坐标,如果有,绘制轨型基准线,如果没,拉取再绘制其线
useEffect(() => { if (railId.length > 0) { const r = baseState.railTypes.find((rail) => rail.id === railId[0]); if (!r) return; if (!!r.points) { drawRailBaseLine(r.points); return; } Bridge.getTrackPoint({ code: r.code }).then((res) => { if (res.success) { dispatch(updateRailPoints(res.data)); drawRailBaseLine(res.data.points!); console.log(r.points) } else { Toast.show(res.message); } }); } }, [baseState.railTypes, dispatch, railId, measureState.measureFinishData]);
useEffect(() => { if (canvasRef.current && measureState.measureFinishData.length) { setTimeout(() => { setShowStandard(true) }, 100); canvasRef.current?.setMeasurementCalibrationData(measureState.measureFinishData); setShowMeasureFinish(true) } }, [baseState.railTypes, measureState.measureFinishData, railId]);
// 绘制测量坐标线
useEffect(() => { if (canvasRef.current) { canvasRef.current.setMeasurementDataLeft(measureState.leftPoints); } }, [measureState.leftPoints]);
useEffect(() => { if (canvasRef.current) { canvasRef.current.setMeasurementDataRight(measureState.rightPoints); } }, [measureState.rightPoints]);
// 左右两测量完成,转换后的测量线(两线合一线)
// useEffect(() => {
// const points = baseState.railTypes.find((r) => r.id === railId[0])
// console.log(1111, points)
//
// if (canvasRef.current && measureState.measureFinishData.length) {
// setTimeout(() => {
// const points = baseState.railTypes.find((r) => r.code === railId[0])?.points
// drawRailBaseLine(points || '[]')
// setShowStandard(true)
// }, 100);
// canvasRef.current?.setMeasurementCalibrationData(measureState.measureFinishData);
// // setShowCalibration(true);
// setShowMeasureFinish(true)
// setLoading(false)
//
//
// }
// }, [measureState.measureFinishData])
useEffect(() => { if(measureState.measureStatus === 'FINISH_RECORD'){ setLoading(true) Bridge.record().then((res) => { if (res.success) { dispatch(updateMeasureFinish(res.data)); } else { dispatch(updateMeasureFinish([])); setState({ left_ready: 'none', right_ready: 'none', left_begin: 'none', right_begin: 'none', left_end: 'none', right_end: 'none', }); Toast.show(res.message); } }).finally(() => { setLoading(false) }) } }, [dispatch, measureState.measureStatus])
useEffect(() => { return () => { dispatch(updateMeasureStatus('')) } }, [dispatch])
const onSaveClick = () => { if (!contextState.currOrgCode || !orgTextArr) { Dialog.alert({ content: '请选择铁路局/工务段/线路', onConfirm: () => { onOrgBarClick(); }, }); return; } dispatch(updateMeasureData(newMeasureData)); navigate('/measure/save'); };
const [loading, setLoading] = useState(false); const [showMeasureFinish, setShowMeasureFinish] = useState(false); const onCalibrationBtnClick = () => { setLoading(true); Bridge.alignPoints({ railSize: railSize || 'GX-60' }) .then((res) => { if (res.success) { // setShowCalibration(true);
dispatch(updateShowCalibration(true)) canvasRef.current?.setMeasurementCalibrationData(res.data); dispatch(updateMeasureFinish(res.data)) } else { } setLoading(false); }) .catch((e) => { setLoading(false); Toast.show({ content: <span>服务器异常</span>, position: 'top', }); }); };
const [initStart, setInitStart] = useState(false);
const [measurementCanvasKey, setMeasurementCanvasKey] = useState(0);
const [state, setState] = useState({ left_ready: 'none', right_ready: 'none', left_begin: 'none', right_begin: 'none', left_end: 'none', right_end: 'none', });
const onStartClick = () => { setMeasurementCanvasKey(measurementCanvasKey+1) // setShowCalibration(false);
dispatch(updateShowCalibration(false)) setShowMeasureFinish(false) dispatch(updateMeasureData([])); if (!contextState.device.connected) { Dialog.alert({ content: '蓝牙未连接,请先连接蓝牙', onConfirm: () => { navigate('/home/bluetooth'); }, }); return; } if (baseState.ktjOrgs.length === 0) { Dialog.alert({ content: '请在基础数据同步完成后重试', onConfirm: () => { navigate('/home/mine'); }, }); return; } if (!contextState.currOrgCode || !orgTextArr) { Dialog.alert({ content: '请选择铁路局/工务段/线路', onConfirm: () => { onOrgBarClick(); }, }); return; } // if (contextState.device.power < 20) {
// Toast.show("电量低于20%,请充电后测量");
// return;
// }
Bridge.startMeasure().then((res) => { if (res.success) { dispatch(updateTaskState('START_RECORD_SIG')); } else { Toast.show(res.message); } setInitStart(true); setShowStandard(false) }); };
const onOrgBarClick = async () => { if (baseState.ktjOrgs.length === 0) { Dialog.alert({ content: '请在基础数据同步完成后重试', onConfirm: () => { navigate('/home/mine'); }, }); return; } const value = await Cascader.prompt({ options: labeledKtjOrgs, placeholder: '请选择', }); console.log(value) // Toast.show(value ? `你选择了 ${value.join(' - ')}` : '你没有进行选择');
if (value?.length) { dispatch(updateOrg(value as string[])); }else { Toast.show('请选择组织机构'); } };
const [status, setStatus] = useState(0) useEffect(() => { console.log(measureState.taskState) switch (measureState.taskState) { case 'WAITING_FOR_RECORD_THE_1ST_SIDE': setStatus(1); setState({ left_ready: 'ongoing', right_ready: 'none', left_begin: 'none', right_begin: 'none', left_end: 'none', right_end: 'none', }); break; case 'WAITING_FOR_RECORD_THE_2ND_SIDE': setStatus(2) break; case 'START_RECORD_SIG': setState({ left_ready: 'ongoing', right_ready: 'none', left_begin: 'none', right_begin: 'none', left_end: 'none', right_end: 'none', }); break; case 'START_RECORD_LEFT': case 'START_RECORD_RIGHT': if (status === 1) { setState({ left_ready: 'done', right_ready: 'none', left_begin: 'ongoing', right_begin: 'none', left_end: 'none', right_end: 'none', }); } else if (status === 2) { setState({ left_ready: 'done', right_ready: 'done', left_begin: 'done', right_begin: 'ongoing', left_end: 'done', right_end: 'none', }); } break; case 'FINISH_RECORD_LEFT': case 'FINISH_RECORD_RIGHT': if (status === 1) { setState({ left_ready: 'done', right_ready: 'none', left_begin: 'done', right_begin: 'none', left_end: 'done', right_end: 'none', }); } else if (status === 2) { setState({ left_ready: 'done', right_ready: 'done', left_begin: 'done', right_begin: 'done', left_end: 'done', right_end: 'done', }); } break; case 'FINISH_RECORD': setState({ left_ready: 'done', right_ready: 'done', left_begin: 'done', right_begin: 'done', left_end: 'done', right_end: 'done', }); // 测量完成,显示基线
break; default: setState({ left_ready: 'none', right_ready: 'none', left_begin: 'none', right_begin: 'none', left_end: 'none', right_end: 'none', }); } }, [measureState.taskState, status]);
function railName() { return baseState.railTypes.find((r) => r.id === railId[0])?.name || ''; }
function onRailSizeChange(ids: (number | string | null)[]) { if (ids && ids.length) { setRailId(ids); let id = ids[0]; const codes = baseState.railTypes.map((item) => { if (item.id === id) { return item.code; } return item.code; }); if (codes && codes.length) { setRailSize(codes[0]); } } } const [style, setStyle] = useState({ left: 'none', right: 'none', up: 'none', down: 'none', left_rotation:'none', right_rotation:'none', }); //上下移动
let timerRef = useRef<any>(null); const handlePressStart = (type: string) => { setStyle({...style, [type]: 'active'}) console.log('你进行了长按操作!'); if (timerRef.current) { return } timerRef.current = setInterval(() => { console.log('111!'); onHandleMove(type); }, 100); console.log(timerRef.current) };
const handlePressEnd = () => { setStyle({ left: 'none', right: 'none', up: 'none', down: 'none', left_rotation:'none', right_rotation:'none', }) clearInterval(timerRef.current); timerRef.current = null; };
const onMoveLine = (type: string) => { console.log('这是点击'); onHandleMove(type); };
const onHandleMove = (type: string) => { let list = canvasRef.current?.getMeasurementCalibrationData(); console.log(list) if (list && list.length) { const updatedList = list.map((item) => { const mutableItem = { ...item }; // 创建副本
if (type === 'up') { mutableItem.y -= distance / 1000; } else if (type === 'down') { mutableItem.y += distance / 1000; } else if (type === 'left') { mutableItem.x -= distance / 1000; } else if (type === 'right') { mutableItem.x += distance / 1000; } return mutableItem; });
canvasRef.current?.setMeasurementCalibrationData(updatedList); dispatch(updateMeasureFinish(list)); setNewMeasureData(updatedList); } };
const handleRotationPressStart = (type: string) => { setStyle({...style, [type === 'left' ? 'left_rotation' : 'right_rotation']: 'active'}) timerRef.current = setInterval(() => { onRotationLine(type); }, 500); };
//旋转
let [measurementRotation] = useState<number>(0); let [newMeasureData, setNewMeasureData] = useState<Point[]>(); let [angle] = useState<number>(5); //角度单位 分
let [distance] = useState<number>(100); 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) { const updatedList = list.map((item) => { const mutableItem = { ...item }; // 创建副本
let cloneItem = rotatePoint(mutableItem, mrValue); mutableItem.x = cloneItem.x; mutableItem.y = cloneItem.y; return mutableItem; });
canvasRef.current?.setMeasurementCalibrationData(updatedList); dispatch(updateMeasureFinish(list)); setNewMeasureData(updatedList); } };
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; };
const handleContextMenu = (e: any) => { e.preventDefault(); };
return ( <> <div className="relative pt-[--navBarHeight]"> <div className="absolute top-0 w-full z-10"> <CustomNavBar title={'测量'}></CustomNavBar> </div>
<main className="home-page-content overflow-x-hidden overflow-y-auto"> <div className="relative h-0 p-0 pb-[70%]"> {/**正在校准时的loading */} {/*loading*/} {loading && ( <Mask opacity='thick' className="h-[100vh] flex justify-center items-center"> <div style={{ margin: '45%', display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center' }}> <SpinLoading color="#5c92b4" /> <span className="whitespace-nowrap mt-5 text-[#5c92b4]" style={{fontSize: '16px'}}>请稍候...</span> </div> </Mask> )}
{/**测量区 */} <div className="absolute left-0 right-0 top-0 bottom-0 bg-title"> <MeasurementCanvas width={window.innerWidth} height={window.innerWidth * 0.7} logicalExtent={{ minX: -45, maxX: 45, minY: -18, maxY: 52 }} gridStep={3} origin={{ x: 0, y: 20 }} pixelPerMm={window.innerWidth / 90} maxZoom={8} showGrid={true} showBenchmark={showStandard} showAnalysis={false} showScale={false} scaleInterval={1} showCoordinates={false} showCalibration={measureState.showCalibration || showMeasureFinish} ref={canvasRef} key={measurementCanvasKey} />
{/**选择轨型区 */} {railId.length > 0 && ( <div className="absolute left-1 bottom-1"> <RailTypeBtn text={railName()} onClick={() => setRailPickerVisible(true)} /> </div> )} </div> </div>
{/**局段线区 */} <section className="h-10 bg-[#e3e8f5] flex justify-between items-center px-4" onClick={onOrgBarClick} > <p className="text-text" style={{ color: contextState.currOrgCode ? '#333' : 'red' }}> {contextState.currOrgCode && orgTextArr ? orgTextArr.join('/') : '点击此处选择铁路局和工务段'} </p> <span className="text-primary underline">修改</span> </section> {/**手动校准区 */} {(measureState.showCalibration && measureState.measureFinishData.length>0) && ( <section className="h-10 bg-[#e3e8f5] flex justify-between items-center px-4"> <img width={iconWidth} src={icon_left} onClick={() => onMoveLine('left')} onTouchStart={() => handlePressStart('left')} onTouchEnd={handlePressEnd} onTouchCancel={handlePressEnd} onContextMenu={handleContextMenu} className={ style.left === 'active' ? 'text-[20px] ml-[5px] icon-button active-icon-button' : 'text-[20px] ml-[5px] icon-button' } alt="左移" />
<img width={iconWidth} src={icon_right} onClick={() => onMoveLine('right')} onTouchStart={() => handlePressStart('right')} onTouchEnd={handlePressEnd} onTouchCancel={handlePressEnd} onContextMenu={handleContextMenu} className={ style.right === 'active' ? 'text-[20px] ml-[5px] icon-button active-icon-button' : 'text-[20px] ml-[5px] icon-button' } alt="右移" />
<img width={iconWidth} src={icon_up} onClick={() => onMoveLine('up')} onTouchStart={() => handlePressStart('up')} onTouchEnd={handlePressEnd} onTouchCancel={handlePressEnd} onContextMenu={handleContextMenu} className={ style.up === 'active' ? 'text-[20px] ml-[5px] icon-button active-icon-button' : 'text-[20px] ml-[5px] icon-button' } alt="上移" />
<img width={iconWidth} src={icon_down} onClick={() => onMoveLine('down')} onTouchStart={() => handlePressStart('down')} onTouchEnd={handlePressEnd} onTouchCancel={handlePressEnd} onContextMenu={handleContextMenu} className={ style.down === 'active' ? 'text-[20px] ml-[5px] icon-button active-icon-button' : 'text-[20px] ml-[5px] icon-button' } alt="下移" />
<img width={iconWidth} src={icon_leftR} onClick={() => onRotationLine('left')} onTouchStart={() => handleRotationPressStart('left')} onTouchEnd={handlePressEnd} onTouchCancel={handlePressEnd} onContextMenu={handleContextMenu} className={ style.left_rotation === 'active' ? 'text-[20px] ml-[5px] icon-button active-icon-button' : 'text-[20px] ml-[5px] icon-button' } alt="逆时针旋转" />
<img width={iconWidth} src={icon_rightR} onClick={() => onRotationLine('right')} onTouchStart={() => handleRotationPressStart('right')} onTouchEnd={handlePressEnd} onTouchCancel={handlePressEnd} onContextMenu={handleContextMenu} className={ style.right_rotation === 'active' ? 'text-[20px] ml-[5px] icon-button active-icon-button' : 'text-[20px] ml-[5px] icon-button' } alt="顺时针旋转" /> </section> )}
{/**按钮操作区 */} <section className="flex items-center gap-4 px-4 my-4"> <button className="btn-contained rounded-md text-sm h-10 flex-1" onClick={onStartClick}> {!initStart || (measureState.leftPoints.length > 0 && measureState.rightPoints.length > 0) ? '开始测量' : '重新测量'} </button> <button className="btn-contained rounded-md text-sm h-10 flex-1" disabled={!measureState.measureFinishData.length} onClick={onSaveClick} > 保存 </button> <button className="btn-contained rounded-md text-sm h-10 flex-1" disabled={!measureState.measureFinishData.length} onClick={onCalibrationBtnClick} > 校准 </button> </section>
{/**测量状态区 */} <section className="grid grid-cols-2 gap-[10px]"> <StepItem state={state.left_ready} text={'等待测量'} /> <StepItem state={state.right_ready} text={'等待测量另一侧'} /> <StepItem state={state.left_begin} text={'正在进行测量'} /> <StepItem state={state.right_begin} text={'正在进行测量'} /> <StepItem state={state.left_end} text={'一侧测量完成'} /> <StepItem state={state.right_end} text={'测量已完成'} /> </section> </main> </div> <Picker columns={[baseState.railTypes.map((t) => ({ label: t.name, value: t.id }))]} visible={railPickerVisible} onClose={() => { setRailPickerVisible(false); }} value={railId} onConfirm={(v) => { onRailSizeChange(v); }} /> </> ); }
|