Browse Source

完善校准功能

master
LiLongLong 4 months ago
parent
commit
f61aa8f09c
  1. 1
      src/assets/icon_down.svg
  2. 1
      src/assets/icon_left.svg
  3. 1
      src/assets/icon_leftR.svg
  4. 1
      src/assets/icon_right.svg
  5. 1
      src/assets/icon_rightR.svg
  6. 1
      src/assets/icon_up.svg
  7. 139
      src/components/konva/MeasurementCanvas.tsx
  8. 240
      src/pages/Measure.tsx
  9. 12
      src/pages/MeasureSave.tsx
  10. 0
      src/pages/measure.scss
  11. 1
      src/services/apiTypes.ts
  12. 8
      src/store/features/measureSlice.ts
  13. 8
      src/utils/bridge.ts
  14. 4434
      src/utils/measureData.ts

1
src/assets/icon_down.svg

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" width="50" height="50" viewBox="0 0 50 50"><defs><filter id="master_svg0_58_9800" filterUnits="objectBoundingBox" color-interpolation-filters="sRGB" x="-0.09090909090909091" y="-0.06818181818181818" width="1.1818181818181819" height="1.1818181818181819"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/><feOffset dy="1" dx="0"/><feGaussianBlur stdDeviation="1"/><feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.15000000596046448 0"/><feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow"/><feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow" result="shape"/></filter><clipPath id="master_svg1_58_9798"><rect x="9" y="52" width="32" height="32" rx="0"/></clipPath></defs><g transform="matrix(1,0,0,-1,0,92)"><g transform="matrix(1,0,0,-1,0,180)" filter="url(#master_svg0_58_9800)"><ellipse cx="25" cy="112" rx="22" ry="22" fill="#FFFFFF" fill-opacity="1"/></g><g clip-path="url(#master_svg1_58_9798)"><g><path d="M21.7247371875,67.9623234375L21.7247371875,81.1185234375C21.7247371875,81.5717234375,22.0934871875,81.9404234375,22.5466171875,81.9404234375L27.3966171875,81.9404234375C27.8497371875,81.9404234375,28.218517187499998,81.5717234375,28.218517187499998,81.1185234375L28.218517187499998,67.9623234375L31.5341171875,67.9623234375C31.8528171875,67.9623234375,32.0497171875,67.6154234375,31.890317187500003,67.3435234375L25.3591171875,56.2029294375C25.1997371875,55.9310546375,24.8091171875,55.9310546375,24.6497371875,56.2029294375L18.1153630875,67.3404234375C17.9528631875,67.6154234375,18.1528634875,67.9592234375,18.4716131875,67.9592234375L21.7247371875,67.9592234375L21.7247371875,67.9623234375Z" fill="#5F5F5F" fill-opacity="1" style="mix-blend-mode:passthrough"/></g></g></g></svg>

1
src/assets/icon_left.svg

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" width="50" height="50" viewBox="0 0 50 50"><defs><filter id="master_svg0_58_9802" filterUnits="objectBoundingBox" color-interpolation-filters="sRGB" x="-0.09090909090909091" y="-0.06818181818181818" width="1.1818181818181819" height="1.1818181818181819"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/><feOffset dy="1" dx="0"/><feGaussianBlur stdDeviation="1"/><feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.15000000596046448 0"/><feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow"/><feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow" result="shape"/></filter><clipPath id="master_svg1_58_9803"><rect x="10" y="83" width="30" height="30" rx="0"/></clipPath></defs><g transform="matrix(1,0,0,-1,0,92)"><g transform="matrix(1,0,0,-1,0,180)" filter="url(#master_svg0_58_9802)"><ellipse cx="25" cy="112" rx="22" ry="22" fill="#FFFFFF" fill-opacity="1"/></g><g transform="matrix(1,0,0,-1,0,166)" clip-path="url(#master_svg1_58_9803)"><g transform="matrix(0,-1,1,0,-93,117)"><path d="M15.66712,116.9633L15.66712,130.1195C15.66712,130.5727,16.03587,130.9414,16.489,130.9414L21.339,130.9414C21.79212,130.9414,22.160899999999998,130.5727,22.160899999999998,130.1195L22.160899999999998,116.9633L25.4765,116.9633C25.7952,116.9633,25.9921,116.6164,25.832700000000003,116.3445L19.3015,105.203906C19.14212,104.9320312,18.7515,104.9320312,18.59212,105.203906L12.0577459,116.3414C11.895246,116.6164,12.0952463,116.9602,12.413996,116.9602L15.66712,116.9602L15.66712,116.9633Z" fill="#5F5F5F" fill-opacity="1" style="mix-blend-mode:passthrough"/></g></g></g></svg>

1
src/assets/icon_leftR.svg

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" width="50" height="50" viewBox="0 0 50 50"><defs><filter id="master_svg0_58_9791" filterUnits="objectBoundingBox" color-interpolation-filters="sRGB" x="-0.09090909090909091" y="-0.06818181818181818" width="1.1818181818181819" height="1.1818181818181819"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/><feOffset dy="1" dx="0"/><feGaussianBlur stdDeviation="1"/><feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.15000000596046448 0"/><feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow"/><feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow" result="shape"/></filter><clipPath id="master_svg1_58_9789"><rect x="11" y="82.00011444091797" width="29" height="28" rx="0"/></clipPath><clipPath id="master_svg2_58_9685"><rect x="12" y="85.00011444091797" width="28" height="22" rx="0"/></clipPath></defs><g transform="matrix(1,0,0,-1,0,92)"><g transform="matrix(1,0,0,-1,0,180)" filter="url(#master_svg0_58_9791)"><ellipse cx="25" cy="112" rx="22" ry="22" fill="#FFFFFF" fill-opacity="1"/></g><g transform="matrix(1,0,0,-1,0,164.00022888183594)" clip-path="url(#master_svg1_58_9789)"><g clip-path="url(#master_svg2_58_9685)"><g><path d="M28.397199999999998,87.98627444091797L24.035980000000002,87.98627444091797L25.1113,86.91002444091797C25.5445,86.47652444091797,25.5445,85.75902544091797,25.1113,85.32553444091796C24.67265,84.89164144091797,23.96686,84.89164144091797,23.52817,85.32553444091796L21.03394,87.82184444091797C20.824849999999998,88.03112444091796,20.66056,88.41976444091797,20.66056,88.71872444091797C20.66056,89.01768444091798,20.824849999999998,89.40633444091797,21.03394,89.61560444091796L23.52817,92.11192444091797C23.752209999999998,92.33614444091796,24.035980000000002,92.44077444091796,24.319760000000002,92.44077444091796C24.60353,92.44077444091796,24.88731,92.33614444091796,25.1113,92.11192444091797C25.5445,91.67842444091796,25.5445,90.96092444091796,25.1113,90.52743444091797L24.81263,90.22847444091796L28.412100000000002,90.22847444091796C31.9144,90.23669444091797,34.7515,93.07617444091797,34.759699999999995,96.58141444091797C34.759699999999995,97.19421444091797,35.2675,97.70251444091797,35.8798,97.70251444091797C36.4922,97.70251444091797,37,97.19421444091797,37,96.58141444091797C36.9851,91.84285444091798,33.1466,87.98627444091797,28.397199999999998,87.98627444091797ZM31.2648,93.59177444091797L16.56823,93.59177444091797C15.701969,93.59177444091797,15,94.29432444091796,15,95.16131444091796L15,105.43061444091796C15,106.29751444091796,15.701969,107.00011444091797,16.56823,107.00011444091797L31.2648,107.00011444091797C32.131,107.00011444091797,32.833,106.29751444091796,32.833,105.43061444091796L32.833,95.16131444091796C32.847899999999996,94.29432444091796,32.131,93.59177444091797,31.2648,93.59177444091797ZM30.607599999999998,104.75791444091797L17.24033,104.75791444091797L17.24033,95.83401444091797L30.607599999999998,95.83401444091797L30.607599999999998,104.75791444091797Z" fill="#5F5F5F" fill-opacity="1" style="mix-blend-mode:passthrough"/></g></g></g></g></svg>

1
src/assets/icon_right.svg

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" width="50" height="50" viewBox="0 0 50 50"><defs><filter id="master_svg0_58_9793" filterUnits="objectBoundingBox" color-interpolation-filters="sRGB" x="-0.09090909090909091" y="-0.06818181818181818" width="1.1818181818181819" height="1.1818181818181819"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/><feOffset dy="1" dx="0"/><feGaussianBlur stdDeviation="1"/><feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.15000000596046448 0"/><feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow"/><feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow" result="shape"/></filter></defs><g transform="matrix(1,0,0,-1,0,92)"><g transform="matrix(1,0,0,-1,0,180)" filter="url(#master_svg0_58_9793)"><ellipse cx="25" cy="112" rx="22" ry="22" fill="#FFFFFF" fill-opacity="1"/></g><g transform="matrix(0,1,-1,0,99.05224609375,23.16943359375)"><path d="M41.60852625,73.07413984375L41.60852625,86.23033984375C41.60852625,86.68353984375,41.97727625,87.05223984375,42.43040625,87.05223984375L47.28040625,87.05223984375C47.73352625,87.05223984375,48.10230625,86.68353984375,48.10230625,86.23033984375L48.10230625,73.07413984375L51.41790625,73.07413984375C51.73660625,73.07413984375,51.93350625,72.72723984375,51.77410625,72.45533984375L45.24290625,61.31474584375C45.08352625,61.04287104375,44.69290625,61.04287104375,44.53352625,61.31474584375L37.99915215,72.45223984375C37.83665225,72.72723984375,38.03665255,73.07103984375,38.35540225,73.07103984375L41.60852625,73.07103984375L41.60852625,73.07413984375Z" fill="#5F5F5F" fill-opacity="1" style="mix-blend-mode:passthrough"/></g></g></svg>

1
src/assets/icon_rightR.svg

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" width="50" height="50" viewBox="0 0 50 50"><defs><filter id="master_svg0_62_9683" filterUnits="objectBoundingBox" color-interpolation-filters="sRGB" x="-0.09090909090909091" y="-0.06818181818181818" width="1.1818181818181819" height="1.1818181818181819"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/><feOffset dy="1" dx="0"/><feGaussianBlur stdDeviation="1"/><feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.15000000596046448 0"/><feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow"/><feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow" result="shape"/></filter></defs><g><g filter="url(#master_svg0_62_9683)"><ellipse cx="25" cy="24" rx="22" ry="22" fill="#FFFFFF" fill-opacity="1"/></g><g transform="matrix(-1,0,0,1,74,0)"><path d="M50.3972,15.98616L46.03598,15.98616L47.1113,14.90991C47.5445,14.47641,47.5445,13.758911,47.1113,13.32542C46.672650000000004,12.891527,45.96686,12.891527,45.52817,13.32542L43.03394,15.82173C42.82485,16.031010000000002,42.660560000000004,16.41965,42.660560000000004,16.718609999999998C42.660560000000004,17.01757,42.82485,17.40622,43.03394,17.61549L45.52817,20.11181C45.75221,20.33603,46.03598,20.44066,46.31976,20.44066C46.60353,20.44066,46.88731,20.33603,47.1113,20.11181C47.5445,19.67831,47.5445,18.960810000000002,47.1113,18.52732L46.81263,18.228360000000002L50.4121,18.228360000000002C53.9144,18.23658,56.7515,21.07606,56.759699999999995,24.5813C56.759699999999995,25.1941,57.2675,25.7024,57.8798,25.7024C58.4922,25.7024,59,25.1941,59,24.5813C58.9851,19.84274,55.1466,15.98616,50.3972,15.98616ZM53.2648,21.591659999999997L38.56823,21.591659999999997C37.701969,21.591659999999997,37,22.29421,37,23.1612L37,33.430499999999995C37,34.297399999999996,37.701969,35,38.56823,35L53.2648,35C54.131,35,54.833,34.297399999999996,54.833,33.430499999999995L54.833,23.1612C54.847899999999996,22.29421,54.131,21.591659999999997,53.2648,21.591659999999997ZM52.6076,32.7578L39.24033,32.7578L39.24033,23.8339L52.6076,23.8339L52.6076,32.7578Z" fill="#5F5F5F" fill-opacity="1" style="mix-blend-mode:passthrough"/></g></g></svg>

1
src/assets/icon_up.svg

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" width="50" height="50" viewBox="0 0 50 50"><defs><filter id="master_svg0_58_9785" filterUnits="objectBoundingBox" color-interpolation-filters="sRGB" x="-0.09090909090909091" y="-0.06818181818181818" width="1.1818181818181819" height="1.1818181818181819"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/><feOffset dy="1" dx="0"/><feGaussianBlur stdDeviation="1"/><feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.15000000596046448 0"/><feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow"/><feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow" result="shape"/></filter><clipPath id="master_svg1_58_9786"><rect x="9" y="84" width="32" height="32" rx="0"/></clipPath></defs><g transform="matrix(1,0,0,-1,0,92)"><g transform="matrix(1,0,0,-1,0,180)" filter="url(#master_svg0_58_9785)"><ellipse cx="25" cy="112" rx="22" ry="22" fill="#FFFFFF" fill-opacity="1"/></g><g transform="matrix(1,0,0,-1,0,168)" clip-path="url(#master_svg1_58_9786)"><g><path d="M21.7247371875,99.9623234375L21.7247371875,113.1185234375C21.7247371875,113.5717234375,22.0934871875,113.9404234375,22.5466171875,113.9404234375L27.3966171875,113.9404234375C27.8497371875,113.9404234375,28.218517187499998,113.5717234375,28.218517187499998,113.1185234375L28.218517187499998,99.9623234375L31.5341171875,99.9623234375C31.8528171875,99.9623234375,32.0497171875,99.6154234375,31.890317187500003,99.3435234375L25.3591171875,88.2029294375C25.1997371875,87.9310546375,24.8091171875,87.9310546375,24.6497371875,88.2029294375L18.1153630875,99.3404234375C17.9528631875,99.6154234375,18.1528634875,99.9592234375,18.4716131875,99.9592234375L21.7247371875,99.9592234375L21.7247371875,99.9623234375Z" fill="#5F5F5F" fill-opacity="1" style="mix-blend-mode:passthrough"/></g></g></g></svg>

139
src/components/konva/MeasurementCanvas.tsx

@ -6,7 +6,6 @@ import React, {
useEffect,
} from "react";
import { Stage, Layer, Line, Shape, Text } from "react-konva";
import "./MeasurementCanvas.scss";
// 数据类型定义
export interface Point {
@ -63,11 +62,13 @@ export interface MeasurementCanvasProps {
initialBenchmarkData?: BenchmarkShape[];
initialMeasurementDataLeft?: Point[];
initialMeasurementDataRight?: Point[];
initMeasurementCalibrationData?: Point[];
initialAnalysisData?: AnalysisData[];
// 控制是否显示标准线(benchmark shapes)
showBenchmark?: boolean;
// 控制是否显示分析线
showAnalysis?: boolean;
showCalibration?: boolean;
}
export interface MeasurementCanvasRef {
@ -76,6 +77,9 @@ export interface MeasurementCanvasRef {
setBenchmarkData: (data: BenchmarkShape[]) => void;
setMeasurementDataLeft: (data: Point[]) => void;
setMeasurementDataRight: (data: Point[]) => void;
setMeasurementCalibrationData: (data: Point[]) => void;
getMeasurementCalibrationData: () => Point[];
getMeasuerProp: () => {scale:number, offset:{x:number, y:number}};
setMeasurementData: (data: Point[]) => void;
setAnalysisData: (data: AnalysisData[]) => void;
redraw: () => void;
@ -96,8 +100,8 @@ const MeasurementCanvas = forwardRef<MeasurementCanvasRef, MeasurementCanvasProp
logicalExtent = { minX: -100, maxX: 100, minY: -100, maxY: 100 },
gridStep = 1,
showGrid = true,
// showScale = false,
// scaleInterval = 10,
showScale = false,
scaleInterval = 10,
showCoordinates = false,
coordinateInterval = 1,
pixelPerMm = 10,
@ -107,9 +111,11 @@ const MeasurementCanvas = forwardRef<MeasurementCanvasRef, MeasurementCanvasProp
initialBenchmarkData = [],
initialMeasurementDataLeft = [],
initialMeasurementDataRight = [],
initMeasurementCalibrationData = [],
initialAnalysisData = [],
showBenchmark = true,
showAnalysis = true,
showCalibration = false
} = props;
// Stage 物理中心(像素)
@ -142,6 +148,7 @@ const MeasurementCanvas = forwardRef<MeasurementCanvasRef, MeasurementCanvasProp
const rightPointsRef = useRef<Point[]>([...initialMeasurementDataRight]);
const [measurementDataLeft, setMeasurementDataLeftState] = useState<Point[]>(initialMeasurementDataLeft);
const [measurementDataRight, setMeasurementDataRightState] = useState<Point[]>(initialMeasurementDataRight);
const [measurementCalibrationData, setMeasurementCalibrationDataState] = useState<Point[]>(initMeasurementCalibrationData);
const [measurementData, setMeasurementDataState] = useState<Point[]>([]);
const refreshInterval = 50;
const refreshTimer = useRef<number | null>(null);
@ -191,6 +198,20 @@ const MeasurementCanvas = forwardRef<MeasurementCanvasRef, MeasurementCanvasProp
redraw: () => {
setScale((prev) => prev);
},
setMeasurementCalibrationData: (data: Point[])=>{
setMeasurementCalibrationDataState(data)
},
getMeasurementCalibrationData: ()=>{
return measurementCalibrationData
},
getMeasuerProp: ()=>{
return {
scale,
offset,
origin
}
},
}));
const stageRef = useRef<any>(null);
@ -361,12 +382,12 @@ const MeasurementCanvas = forwardRef<MeasurementCanvasRef, MeasurementCanvasProp
const xAxisStart = transform({ x: logicalExtent.minX, y: 0 });
const xAxisEnd = transform({ x: logicalExtent.maxX, y: 0 });
lines.push(
<Line key="x-axis" points={[xAxisStart.x, xAxisStart.y, xAxisEnd.x, xAxisEnd.y]} stroke="#ccc" strokeWidth={1} />
<Line key="x-axis" points={[xAxisStart.x, xAxisStart.y, xAxisEnd.x, xAxisEnd.y]} stroke="gray" strokeWidth={2} />
);
const yAxisStart = transform({ x: 0, y: logicalExtent.minY });
const yAxisEnd = transform({ x: 0, y: logicalExtent.maxY });
lines.push(
<Line key="y-axis" points={[yAxisStart.x, yAxisStart.y, yAxisEnd.x, yAxisEnd.y]} stroke="#ccc" strokeWidth={1} />
<Line key="y-axis" points={[yAxisStart.x, yAxisStart.y, yAxisEnd.x, yAxisEnd.y]} stroke="gray" strokeWidth={2} />
);
return lines;
};
@ -392,7 +413,7 @@ const MeasurementCanvas = forwardRef<MeasurementCanvasRef, MeasurementCanvasProp
};
const renderBenchmarkShapes = () => {
return benchmarkData.map((shape, idx) => {
let bk = benchmarkData.map((shape, idx) => {
if (shape.type === "line") {
const p1 = transform(shape.start);
const p2 = transform(shape.end);
@ -460,6 +481,7 @@ const MeasurementCanvas = forwardRef<MeasurementCanvasRef, MeasurementCanvasProp
}
return null;
});
return bk;
};
const renderMeasurementCurveLeft = () => {
@ -482,9 +504,10 @@ const MeasurementCanvas = forwardRef<MeasurementCanvasRef, MeasurementCanvasProp
);
};
const renderMeasurementCurveRight = () => {
if (measurementDataRight.length === 0) return null;
const pts = measurementDataRight
//校准线
const renderMeasurementCalibration = () => {
if (!measurementCalibrationData || measurementCalibrationData.length === 0) return null;
const pts = measurementCalibrationData
.map((pt) => {
const p = transform(pt);
return [p.x, p.y];
@ -500,6 +523,24 @@ const MeasurementCanvas = forwardRef<MeasurementCanvasRef, MeasurementCanvasProp
lineJoin="round"
/>
);
}
const renderMeasurementCurveRight = () => {
if (measurementDataRight.length === 0) return null;
const pts = measurementDataRight.map((pt) => {
const p = transform(pt);
return [p.x, p.y];
}).flat();
return (
<Line
points={pts}
stroke="red"
strokeWidth={2}
tension={1}
lineCap="round"
lineJoin="round"
/>
);
};
const renderMeasurementCurve = () => {
@ -534,29 +575,67 @@ const MeasurementCanvas = forwardRef<MeasurementCanvasRef, MeasurementCanvasProp
);
});
};
return (
<Stage
ref={stageRef}
width={width}
height={height}
onPointerDown={handlePointerDown}
onPointerMove={handlePointerMove}
onPointerUp={handlePointerUp}
onPointerCancel={handlePointerCancel}
onWheel={handleWheel}
style={{ background: "#fff" }}
<div
style={{
width,
height,
border: "1px solid #ccc",
position: "relative",
touchAction: "none",
}}
>
<Layer>
{showGrid && renderGridAndAxes()}
{showBenchmark && renderBenchmarkShapes()}
{renderMeasurementCurveLeft()}
{renderMeasurementCurveRight()}
{renderMeasurementCurve()}
{showAnalysis && renderAnalysis()}
</Layer>
{showCoordinates && <Layer>{renderCoordinates()}</Layer>}
</Stage>
<Stage
ref={stageRef}
width={width}
height={height}
onPointerDown={handlePointerDown}
onPointerMove={handlePointerMove}
onPointerUp={handlePointerUp}
onPointerCancel={handlePointerCancel}
onWheel={handleWheel}
style={{ background: "#fff" }}
>
<Layer>
{showGrid && renderGridAndAxes()}
{/**基线层 */}
{showBenchmark && renderBenchmarkShapes()}
{/**左线层 校准时不显示*/}
{!showCalibration && renderMeasurementCurveLeft()}
{/**右线层 */}
{!showCalibration && renderMeasurementCurveRight()}
{/**校准层 */}
{showCalibration && renderMeasurementCalibration()}
{renderMeasurementCurve()}
{showAnalysis && renderAnalysis()}
</Layer>
{showCoordinates && <Layer>{renderCoordinates()}</Layer>}
</Stage>
{showScale && (
<div
style={{
position: "absolute",
bottom: 10,
left: 10,
background: "rgba(255,255,255,0.8)",
padding: "2px 4px",
border: "1px solid #ccc",
fontSize: 12,
pointerEvents: "none",
}}
>
<div
style={{
width: gridStep * scaleInterval * scale,
borderBottom: "2px solid black",
marginBottom: 2,
}}
></div>
<div>{`${gridStep * scaleInterval} mm`}</div>
</div>
)}
</div>
);
}
);

240
src/pages/Measure.tsx

@ -4,18 +4,24 @@ 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, Picker, Toast } from 'antd-mobile';
import { Cascader, Dialog, Input, Mask, Picker, SpinLoading, Toast } from 'antd-mobile';
import { useAppDispatch, useAppSelector } from '../utils/hooks';
import { updateTaskState } from '../store/features/measureSlice';
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();
@ -27,13 +33,16 @@ export default function Measure() {
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) {
setRailId([baseState.railTypes[0].id]);
let railData = baseState.railTypes[0]
setRailId([railData.id]);
setRailSize(railData.code)
}
}, [baseState.railTypes]);
@ -78,10 +87,32 @@ export default function Measure() {
}, [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=>{
Toast.show({
content: <span></span>,
position: 'top',
})
})
}
const onStartClick = () => {
setshowCalibration(false)
dispatch(updateMeasureData([]))
if (!contextState.device.connected) {
Dialog.alert({
content: '蓝牙未连接,请先连接蓝牙',
@ -185,6 +216,107 @@ export default function Measure() {
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>(60);//角度单位 分
let [distance, setDistance] = useState<number>(1000)
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]">
@ -194,6 +326,17 @@ export default function Measure() {
<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}
@ -209,27 +352,93 @@ export default function Measure() {
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>
<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">
<div className="btn-contained rounded-md text-sm h-10 flex-1" onClick={onStartClick}>
{measureState.leftPoints.length > 0 ? '重新测量' : '开始测量'}
@ -241,8 +450,16 @@ export default function Measure() {
>
</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={'移到顶部停留2秒'} />
<StepItem state={stepState('right_ready')} text={'移到顶部停留2秒'} />
@ -253,16 +470,15 @@ export default function Measure() {
</section>
</main>
</div>
<Picker
columns={[baseState.railTypes.map((t) => ({ ...t, label: t.name, value: t.id }))]}
columns={[baseState.railTypes.map((t) => ({ label: t.name, value: t.id }))]}
visible={railPickerVisible}
onClose={() => {
setRailPickerVisible(false);
}}
value={railId}
onConfirm={(v) => {
setRailId(v);
onRailSizeChange(v)
}}
/>
</>

12
src/pages/MeasureSave.tsx

@ -6,7 +6,7 @@ import { useAppDispatch, useAppSelector } from '../utils/hooks';
import { DATA_SOURCE, LINE_CLASSIFY, UNIT_TYPES, XB_CODES } from '../utils/constant';
import { ExtraDesc, MeasurementDTO } from '../services/apiTypes';
import Bridge from '../utils/bridge';
import { resetState } from '../store/features/measureSlice';
import { resetState, MeasureState } from '../store/features/measureSlice';
import { selectOrgTextArr } from '../store';
export default function MeasureSave() {
@ -16,6 +16,7 @@ export default function MeasureSave() {
const contextState = useAppSelector((state) => state.context);
const baseState = useAppSelector((state) => state.baseData);
const measureState = useAppSelector((state) => state.measure);
const orgTextArr = useAppSelector(selectOrgTextArr);
const [name, setName] = useState('');
@ -111,7 +112,12 @@ export default function MeasureSave() {
mileage: `${mile}+${meter.padStart(3, '0')}`,
radius: '',
extraDesc: JSON.stringify(desc),
alignPoints:[]
};
let measureData = measureState.measureData;
if(measureData && measureData.length){
dto.alignPoints = measureData
}
Bridge.saveMeasure(dto).then((res) => {
if (res.success) {
Toast.show('保存成功');
@ -122,6 +128,10 @@ export default function MeasureSave() {
} else {
Toast.show(res.message);
}
}).catch(e=>{
Toast.show({
content: '操作失败',
});
});
};

0
src/pages/measure.scss

1
src/services/apiTypes.ts

@ -55,6 +55,7 @@ export type MeasurementDTO = {
radius: string;
extraDesc: string; // 额外描述
errorMsg?: string;
alignPoints:[]
};
export type ExtraDesc = {

8
src/store/features/measureSlice.ts

@ -6,12 +6,14 @@ export interface MeasureState {
// leftFinished: boolean;
leftPoints: TrackRecordSig['data'][];
rightPoints: TrackRecordSig['data'][];
measureData:[]
}
const initialState: MeasureState = {
taskState: undefined,
// leftFinished: false,
leftPoints: [],
rightPoints: [],
measureData: []
};
function isLeftFinished(state: MeasureState) {
@ -70,8 +72,12 @@ export const measureSlice = createSlice({
state.leftPoints = [];
state.rightPoints = [];
},
updateMeasureData:(state, { payload })=>{
state.measureData = payload
}
},
});
export const { updateTaskState, addNewPoint, resetState } = measureSlice.actions;
export const { updateTaskState, addNewPoint, resetState, updateMeasureData } = measureSlice.actions;
export default measureSlice.reducer;

8
src/utils/bridge.ts

@ -371,4 +371,12 @@ export default class Bridge {
params: {},
});
}
static alignPoints(params:{railSize:number | string | null}){
return httpRequest<BridgeBaseResult>({
url: 'api/measure/align/points',
method: 'POST',
params,
});
}
}

4434
src/utils/measureData.ts
File diff suppressed because it is too large
View File

Loading…
Cancel
Save