|
|
import StepItem, { StepName, StepState } 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, Input, Mask, Picker, SpinLoading, Toast } from 'antd-mobile'; import { useAppDispatch, useAppSelector } from '../utils/hooks'; import { updateMeasureData, updateTaskState } 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 = 35;
// 默认选中第一个轨型
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(() => { 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!); } else { Toast.show(res.message); } }); } }, [baseState.railTypes, dispatch, railId]);
// 绘制测量坐标线
useEffect(() => { if (canvasRef.current) { canvasRef.current.setMeasurementDataLeft(measureState.leftPoints); } }, [measureState.leftPoints]);
useEffect(() => { if (canvasRef.current) { canvasRef.current.setMeasurementDataRight(measureState.rightPoints); } }, [measureState.rightPoints]);
const onSaveClick = () => { dispatch(updateMeasureData(newMeasureData)) navigate('/measure/save'); };
const [caloading, setCaloading] = useState(false) const [showCalibration, setshowCalibration] = useState(false) const onCalibrationBtnClick = () => { setCaloading(true) Bridge.alignPoints({railSize:railSize || 'GX-60'}).then(res=>{ if(res.success){ setshowCalibration(true) canvasRef.current?.setMeasurementCalibrationData(res.data) }else{ } setCaloading(false) }).catch(e=>{ setCaloading(false) Toast.show({ content: <span>服务器异常</span>, position: 'top', }) }) }
const onStartClick = () => { setshowCalibration(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) { 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); } openAudio() }); };
const openAudio = () => { const audioReady = new Audio("/audio/ready.mp3"); // 播放音频
audioReady .play() .then(() => { console.log("音频开始播放 已准备好"); }) .catch(err => { console.error("播放音频时出错:", err); }); }
const onOrgBarClick = async () => { if (baseState.ktjOrgs.length === 0) { Dialog.alert({ content: '请在基础数据同步完成后重试', onConfirm: () => { navigate('/home/mine'); }, }); return; } const value = await Cascader.prompt({ options: labeledKtjOrgs, placeholder: '请选择', }); // Toast.show(value ? `你选择了 ${value.join(' - ')}` : '你没有进行选择');
if (value) { dispatch(updateOrg(value as string[])); } }; function stepState(step: StepName): StepState { if (!measureState.taskState) { return 'none'; } switch (measureState.taskState) { case 'START_RECORD_SIG': case 'WRONG_SIDE': if (step === 'left_ready') { return 'ongoing'; } else { return 'none'; } case 'START_RECORD_LEFT': if (step === 'left_ready') { return 'done'; } else if (step === 'left_begin') { return 'ongoing'; } else { return 'none'; } case 'FINISH_RECORD_LEFT': if (step === 'left_ready' || step === 'left_begin' || step === 'left_end') { return 'done'; } else if (step === 'right_ready') { return 'ongoing'; } else { return 'none'; } case 'START_RECORD_RIGHT': if (step === 'right_begin') { return 'ongoing'; } else if (step === 'right_end') { return 'none'; } else { return 'done'; } case 'FINISH_RECORD_RIGHT': { return 'done'; } default: return 'none'; } }
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 } }) if(codes && codes.length){ setRailSize(codes[0]) } } }
//上下移动
const timerRef = useRef<NodeJS.Timeout | null>(null); const handlePressStart = (type:string) => { timerRef.current = setInterval(() => { console.log('你进行了长按操作!'); onHandleMove(type) }, 500); };
const handlePressEnd = () => { if (timerRef.current) { clearInterval(timerRef.current); timerRef.current = null; } };
const onMoveLine = (type:string) => { console.log('这是点击') onHandleMove(type) }
const onHandleMove = (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) } }
const handleRotationPressStart = (type:string) => { timerRef.current = setInterval(() => { onRotationLine(type) }, 500); }
//旋转
let [measurementRotation, setMeasurementRotation] = useState<number>(0) let [newMeasureData, setNewMeasureData] = useState<Point[]>() let [angle, setAngle] = useState<number>(5);//角度单位 分
let [distance, setDistance] = 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){ 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 }
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 */} {caloading && <Mask opacity='thin' className='h-[100vh] flex justify-center items-center'> <div style={{margin:"45%"}}> <SpinLoading color='#5c92b4'/> <div className='w-[100px] mt-[20px] text-[#5c92b4]'>正在校准...</div> </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={true} showAnalysis={false} showScale={false} scaleInterval={1} showCoordinates={false} showCalibration={showCalibration} ref={canvasRef} />
{/**选择轨型区 */} {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.join('/') : '点击此处选择铁路局和工务段'} </p> <span className="text-primary underline">修改</span> </section> {/**手动校准区 */} {showCalibration && <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} onContextMenu={handleContextMenu} className="text-[20px] ml-[5px]" alt="左移"/>
<img width={iconWidth} src={icon_right} onClick={()=>(onMoveLine("right"))} onTouchStart={()=>handlePressStart("right")} onTouchEnd={handlePressEnd} onContextMenu={handleContextMenu} className="text-[20px] ml-[5px]" alt="右移"/>
<img width={iconWidth} src={icon_up} onClick={()=>(onMoveLine("up"))} onTouchStart={()=>handlePressStart("up")} onTouchEnd={handlePressEnd} onContextMenu={handleContextMenu} className="text-[20px] ml-[5px]" alt="上移"/>
<img width={iconWidth} src={icon_down} onClick={()=>(onMoveLine("down"))} onTouchStart={()=>handlePressStart("down")} onTouchEnd={handlePressEnd} onContextMenu={handleContextMenu} className="text-[20px] ml-[5px]" alt="下移"/>
<img width={iconWidth} src={icon_leftR} onClick={()=>(onRotationLine("left"))} onTouchStart={()=>handleRotationPressStart("left")} onTouchEnd={handlePressEnd} onContextMenu={handleContextMenu} className="text-[20px] ml-[5px]" alt="逆时针旋转"/>
<img width={iconWidth} src={icon_rightR} onClick={()=>(onRotationLine("right"))} onTouchStart={()=>handleRotationPressStart("right")} onTouchEnd={handlePressEnd} onContextMenu={handleContextMenu} className="text-[20px] ml-[5px]" 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}> {measureState.leftPoints.length > 0 ? '重新测量' : '开始测量'} </button> <button className="btn-contained rounded-md text-sm h-10 flex-1" disabled={measureState.taskState !== 'FINISH_RECORD_RIGHT'} onClick={onSaveClick} > 保存 </button> <button className="btn-contained rounded-md text-sm h-10 flex-1" disabled={measureState.taskState !== 'FINISH_RECORD_RIGHT'} onClick={onCalibrationBtnClick} > 校准 </button> </section>
{/**测量状态区 */} <section className="grid grid-cols-2 gap-[10px]"> <StepItem state={stepState('left_ready')} text={'等待测量'} /> <StepItem state={stepState('right_ready')} text={'等待测量另一侧'} /> <StepItem state={stepState('left_begin')} text={'正在进行测量'} /> <StepItem state={stepState('right_begin')} text={'正在进行测量'} /> <StepItem state={stepState('left_end')} text={'一侧测量完成'} /> <StepItem state={stepState('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) }} /> </> ); }
|