LiLongLong 5 months ago
parent
commit
ca0c0b245e
  1. 38
      src/mock/recordList.ts
  2. 64
      src/pages/measure/components/MeasureAction.tsx
  3. 6
      src/pages/measure/components/MeasureDetail.tsx
  4. 23
      src/pages/measure/components/graph/MarkLayer.tsx
  5. 6
      src/pages/measure/components/graph/ResultLayer.tsx
  6. 28
      src/services/httpRequest.ts
  7. 19
      src/services/measure/analysis.ts
  8. 24
      src/services/measure/type.ts
  9. 46
      src/services/wsTypes.ts

38
src/mock/recordList.ts

@ -0,0 +1,38 @@
export const detailList = [{
"id": 2703,//数据主键id
"createTime": "2025-03-03 17:05:34",//数据创建时间
"updateTime": "2025-03-03 17:05:34",//数据修改时间
"uuid": "650fc0a3-8bb2-4223-973e-72846fd31b82",//数据uuid
"operatorName": "张三",//操作员姓名
"trackShapeCode": "001",//轨型code
"verificationMethodCode": "aaa",//核校方式code
"name": "京沪铁路",//测量名称
"lineName": "河北段",//线路名称
"location": "100米处",//位置
"direction": "左"///方向
},{
"id": 2704,//数据主键id
"createTime": "2025-03-03 17:05:34",//数据创建时间
"updateTime": "2025-03-03 17:05:34",//数据修改时间
"uuid": "650fc0a3-8bb2-4223-973e-72846fd31b83",//数据uuid
"operatorName": "李四",//操作员姓名
"trackShapeCode": "002",//轨型code
"verificationMethodCode": "bbb",//核校方式code
"name": "京昆铁路",//测量名称
"lineName": "河北段",//线路名称
"location": "5100米处",//位置
"direction": "右"///方向
}]
export const bluetoothList = [{
name:'Kdkow_1',
id:'1',
},{
name:'llwoa_2',
id:'2',
}]
export const bluetoothInfo = {
type: 'kxy0812',
power: '78%',
}

64
src/pages/measure/components/MeasureAction.tsx

@ -9,6 +9,10 @@ import ResultLayer from "./graph/ResultLayer";
import MarkLayer from "./graph/MarkLayer";
import { useDispatch, useSelector } from "react-redux";
import { switchMeasureAfterSave } from "../../../store/features/contextSlice";
import { AnalyzeAngle } from "../../../services/measure/type";
import { MeasureState, taskStatusDescMap } from "../../../services/wsTypes";
const wsClient = createWebSocket(sharedWsUrl);
export default function MeasureAction() {
const dispatch = useDispatch();
@ -23,19 +27,21 @@ export default function MeasureAction() {
const navigate = useNavigate();
const [angles, setAngles] = useState<AnalyzeAngle[]>([]);
const [taskStatus, setTaskStatus] = useState<MeasureState["data"]["taskStatus"]>("IDLE");
const onAfterSaveChange: CheckboxProps["onChange"] = e => {
dispatch(switchMeasureAfterSave(e.target.checked));
};
const onAnalysisBtnClick = () => {
// navigate("../detail");
analyzeMeasurement().then(res => {
if (res.status === 0) {
// mask
if (res.success) {
setAngles(res.data.angles);
} else {
message.error(res.data.info);
}
})
});
};
const onStart = () => {
@ -61,6 +67,15 @@ export default function MeasureAction() {
});
};
useEffect(() => {
const subscription = wsClient.dataOb.subscribe(data => {
if (data.messageType === "STATE" && data.path === "/measurement-task/get-task-state") {
setTaskStatus(data.data.taskStatus);
}
});
wsClient.connect();
return () => subscription.unsubscribe();
});
return (
<div className="flex h-full ">
<div className="flex-none">
@ -77,10 +92,12 @@ export default function MeasureAction() {
<Switch defaultChecked onChange={checked => setShowResult(checked)} />
<span>线</span>
</div>
<div className="flex gap-2 items-center">
<Switch defaultChecked onChange={checked => setShowMark(checked)} />
<span>线</span>
</div>
{angles.length > 0 && (
<div className="flex gap-2 items-center">
<Switch defaultChecked onChange={checked => setShowMark(checked)} />
<span>线</span>
</div>
)}
</div>
<div className="relative">
<GridLayer
@ -122,25 +139,28 @@ export default function MeasureAction() {
visibility={showResult ? "visible" : "hidden"}
/>
</div>
<div className="absolute top-0">
<MarkLayer
width={840}
height={600}
leftPadding={30}
rightPadding={10}
topPadding={10}
bottomPadding={30}
columns={10}
rows={7}
visibility={showMark ? "visible" : "hidden"}
/>
</div>
{angles.length > 0 && (
<div className="absolute top-0">
<MarkLayer
width={840}
height={600}
leftPadding={30}
rightPadding={10}
topPadding={10}
bottomPadding={30}
columns={10}
rows={7}
visibility={showMark ? "visible" : "hidden"}
angles={angles}
/>
</div>
)}
</div>
</div>
<div className="w-[300px] flex-none py-6">
<h1 className="font-medium text-xl text-center"></h1>
<section className="flex flex-col items-center gap-4 mt-6 border-t border-[#D8D8D8] py-4">
<div></div>
<div>: {taskStatusDescMap[taskStatus]}</div>
<Button style={{ width: 200 }} size="large" type="primary" onClick={onStart}>
</Button>

6
src/pages/measure/components/MeasureDetail.tsx

@ -69,7 +69,11 @@ export default function MeasureDetail() {
const [tableData, setTableData] = useState<DetailTable[]>([])
const getDetailDataList = () => {
getDetailList().then(res => {
setTableData(res.data.list)
if (res.success) {
setTableData(res.data.list)
} else {
setTableData(detailList)
}
}).catch(e=>{
//如果接口异常,则使用mock数据
// setTableData(detailList)

23
src/pages/measure/components/graph/MarkLayer.tsx

@ -1,5 +1,6 @@
import { useCallback, useEffect, useRef } from "react";
import { calculatePointOnCircle, findSymmetricPoint } from "../../../../utils";
import { AnalyzeAngle } from "../../../../services/measure/type";
const marks = [
{ x: 9.949007022412, y: 0.1650166186941, degree: -80 },
@ -17,7 +18,8 @@ export default function MarkLayer(props: {
bottomPadding: number;
columns: number;
rows: number;
visibility: 'hidden' | 'visible';
visibility: "hidden" | "visible";
angles: AnalyzeAngle[];
}) {
const xStartPx = props.leftPadding;
const xEndPx = props.width - props.rightPadding;
@ -29,18 +31,17 @@ export default function MarkLayer(props: {
const unitPx = xStepPx / 10;
const canvasRef = useRef<HTMLCanvasElement | null>(null);
const calcPoints = useCallback(
(arr: typeof marks) => {
return arr.map(p => {
const calcPoints = useCallback(() => {
return props.angles
.map(p => ({ ...p, y: -p.y, degree: -p.degree }))
.map(p => {
const p1 = calculatePointOnCircle(p.x * unitPx, p.y * unitPx, xStepPx / 2, p.degree);
const p2 = findSymmetricPoint(p1.x, p1.y, p.x * unitPx, p.y * unitPx);
// 角度文本,向外偏移
const p3 = calculatePointOnCircle(p.x * unitPx, p.y * unitPx, xStepPx / 2 + 10, p.degree);
return [p1, p2, p3];
});
},
[unitPx, xStepPx]
);
}, [props.angles, unitPx, xStepPx]);
const draw = useCallback(
(ctx: CanvasRenderingContext2D) => {
@ -53,7 +54,7 @@ export default function MarkLayer(props: {
ctx.textAlign = "center";
ctx.font = "normal 14px system";
const lines = calcPoints(marks);
const lines = calcPoints();
for (let idx = 0; idx < lines.length; idx++) {
const line = lines[idx];
ctx.moveTo(line[0].x, line[0].y);
@ -62,14 +63,14 @@ export default function MarkLayer(props: {
ctx.save();
ctx.translate(line[2].x, line[2].y);
ctx.rotate(((marks[idx].degree + 90) * Math.PI) / 180);
ctx.fillText(`${-marks[idx].degree}°`, 0, 0);
ctx.fillText(props.angles[idx].describe, 0, 0);
ctx.restore();
}
ctx.stroke();
// ctx.fillStyle = "skyblue"; // 设置填充颜色
// ctx.fillRect(50, 50, 150, 100); // 绘制一个矩形
},
[calcPoints, xEndPx, xStartPx, yStartPx, yStepPx]
[calcPoints, props.angles, xEndPx, xStartPx, yStartPx, yStepPx]
);
useEffect(() => {
@ -82,7 +83,7 @@ export default function MarkLayer(props: {
}, [draw]);
return (
<div style={{visibility: props.visibility}}>
<div style={{ visibility: props.visibility }}>
<canvas ref={canvasRef} width={props.width} height={props.height}></canvas>
</div>
);

6
src/pages/measure/components/graph/ResultLayer.tsx

@ -1,8 +1,8 @@
import { useCallback, useEffect, useRef, useState } from "react";
import points from "../../../../utils/measure.json";
import { createWebSocket, sharedWsUrl } from "../../../../services/socket";
const wsClient = createWebSocket(sharedWsUrl);
console.log(sharedWsUrl);
const pointArr: { x: number; y: number }[] = [];
@ -54,7 +54,7 @@ export default function ResultLayer(props: {
const ctx = canvas.getContext("2d");
if (!ctx) return;
if (data.data.event === "START_RECORD_SIG") {
if ('event' in data.data && data.data.event === "START_RECORD_SIG") {
// setRtPoints([]);
pointArr.length = 0;
ctx.resetTransform();
@ -66,7 +66,7 @@ export default function ResultLayer(props: {
ctx.translate(xStartPx + xOffset, yStartPx + yOffset);
ctx.beginPath();
}, 0);
} else if (data.data.event === "END_RECORD_SIG") {
} else if ('event' in data.data && data.data.event === "END_RECORD_SIG") {
//
}
} else if (data.path === "/measurement-task/profile-record-ctrl-sig") {

28
src/services/httpRequest.ts

@ -1,9 +1,23 @@
import { Subject } from "rxjs";
export interface BaseResponse<T = any> {
data: T;
export interface FailResponse {
status: number;
success: false;
data: {
info: string;
}
}
export interface SuccessResponse<T = any> {
data: T;
success: true;
status: 0;
}
export type BaseResponse<T = any> = SuccessResponse<T> | FailResponse
// export interface BaseResponse<T = any> {
// data: T;
// status: 0;
// }
type HttpReqParam = {
url: string;
@ -18,6 +32,12 @@ export type ApiException = "invalidToken" | "serverError";
const exceptionSub = new Subject<ApiException>();
export const exceptionOb = exceptionSub.asObservable();
function extHandle(res: BaseResponse) {
return {
...res,
success: res.status === 0,
};
}
export default async function httpRequest<T>({ url, method = "GET", params = {}, encode = "json", headers = {} }: HttpReqParam) {
const token = sessionStorage.getItem("token");
@ -28,7 +48,7 @@ export default async function httpRequest<T>({ url, method = "GET", params = {},
const query = urlEncode(params);
const _url = query ? url + "?" + query : url;
const res = await fetch(_url, { headers });
return res.json() as T;
return res.json().then(res => extHandle(res) as T);
} else {
const body = encode === "json" ? JSON.stringify(params) : urlEncode(params);
const _headers =
@ -36,7 +56,7 @@ export default async function httpRequest<T>({ url, method = "GET", params = {},
? { "Content-Type": "application/json; charset=utf-8", ...headers }
: { "Content-Type": "application/x-www-form-urlencoded; charset=utf-8", ...headers };
const res = await fetch(url, { method, headers: _headers, body });
return res.json() as T;
return res.json().then(res => extHandle(res) as T);
}
}
export function urlEncode(params?: Record<string, any>) {

19
src/services/measure/analysis.ts

@ -1,5 +1,5 @@
import httpRequest, { type BaseResponse } from "../httpRequest";
import type { DetailTable } from "../../services/measure/type";
import type { AnalyzeResult, DetailTable, MeasureRecord } from "../../services/measure/type";
export function startMeasurement() {
return httpRequest<BaseResponse>({
@ -13,8 +13,9 @@ export function stopMeasurement() {
method: "POST",
});
}
export function analyzeMeasurement() {
return httpRequest<BaseResponse>({
return httpRequest<BaseResponse<AnalyzeResult>>({
url: "/measurement-task/analyze-measurement",
method: "POST",
});
@ -27,29 +28,19 @@ export function saveMeasurement() {
}
export function getDetailList() {
return httpRequest<BaseResponse<{list:DetailTable[]}>>({
return httpRequest<BaseResponse<{ list: DetailTable[] }>>({
url: "/measurement-data/list",
method: "POST",
});
}
export function delDetail(params:{ids:string}) {
export function delDetail(params: { ids: string }) {
return httpRequest<BaseResponse>({
url: `/measurement-data/delete/${params.ids}`,
method: "POST",
});
}
export type MeasureRecord = {
operatorName: "张三"; //操作员名称
// trackShapeCode: "code01"; //轨形code
// verificationMethodCode: "code01"; //核校方式code
name: "某某铁路"; ///测量名称
lineName: "河北段"; //线路名称
location: "100米处"; //位置
// direction: "左"; //方向
};
export function createMeasure(params: MeasureRecord) {
return httpRequest<BaseResponse>({
url: "/measurement-task/cache-measurement",

24
src/services/measure/type.ts

@ -7,4 +7,26 @@ export type DetailTable = {
operatorName:string;
name: string;
createTime: string;
}
}
export type MeasureRecord = {
operatorName: "张三"; //操作员名称
// trackShapeCode: "code01"; //轨形code
// verificationMethodCode: "code01"; //核校方式code
name: "某某铁路"; ///测量名称
lineName: "河北段"; //线路名称
location: "100米处"; //位置
// direction: "左"; //方向
};
export type AnalyzeAngle = {
x: number;
y: number;
degree: number;
describe: string;
};
export type AnalyzeResult = {
angles: AnalyzeAngle[];
};

46
src/services/wsTypes.ts

@ -17,10 +17,22 @@ export type TrackRecordSig = {
path: "/measurement-task/profile-record-ctrl-sig";
};
export const defaultContext: ContextMessage["data"] = {
loginFlag: true,
loginUser: {
id: 3, //数据主键id
account: "test001", //用户账户
nickname: "测试账户001", //用户昵称
userRole: "User", //用户角色,可用值:User,Admin,Dev
isBuiltInUser: false, //是否内置用户(内置用户不可删除)
},
newMeasureAfterSave: false,
};
export type ContextMessage = {
messageType: "DeviceContext";
data: {
loginFlag: boolean;
loginFlag: Boolean;
loginUser: Partial<{
id: number;
account: string;
@ -34,4 +46,34 @@ export type ContextMessage = {
path: "/deviceContext";
};
export type Datagram = TrackRecordSig | TaskState | ContextMessage;
export const taskStatusDescMap: { [k in MeasureState["data"]["taskStatus"]]: string } = {
IDLE: "空闲",
MEASURING: "测量中",
WAITING_FOR_MEASURING: "等待测量",
FINISHED: "测量完成",
};
export type MeasureState = {
messageType: "STATE";
data: {
taskStatus: "IDLE" | "MEASURING" | "WAITING_FOR_MEASURING" | "FINISHED";
measureSideCnt: 0 | 1 | 2; //已测量数量,0,1,2 最多两边(左边和右边)
isMeasuringLeftEnd: boolean; //测量左侧完成
isMeasuringRightEnd: boolean; //测量右侧完成
motionlessSigFlag: boolean; //滑轮质心是否静止
inStartMeasuringPos: boolean; //是否在允许开始测量的位置
// profileRecordDescription: null; //用户填写的新测量信息
};
path: "/measurement-task/get-task-state";
};
export const defaultMeasureState = {
taskStatus: "IDLE",
measureSideCnt: 0, //已测量数量,0,1,2 最多两边(左边和右边)
isMeasuringLeftEnd: false, //测量左侧完成
isMeasuringRightEnd: false, //测量右侧完成
motionlessSigFlag: true, //滑轮质心是否静止
inStartMeasuringPos: true, //是否在允许开始测量的位置
};
export type Datagram = TrackRecordSig | TaskState | ContextMessage | MeasureState;
Loading…
Cancel
Save