15 changed files with 452 additions and 29 deletions
-
4.prettierrc.js
-
BINpublic/audio/alert_left.mp3
-
BINpublic/audio/begin_left.mp3
-
BINpublic/audio/begin_right.mp3
-
BINpublic/audio/end_left.mp3
-
BINpublic/audio/end_right.mp3
-
29src/App.tsx
-
28src/components/StepItem.tsx
-
9src/index.tsx
-
101src/pages/Measure.tsx
-
146src/services/wsTypes.ts
-
14src/store/index.ts
-
49src/store/measureSlice.ts
-
95src/utils/bridge.ts
-
6src/utils/hooks.ts
@ -0,0 +1,146 @@ |
|||
// 开始、停止绘制
|
|||
export type TaskState = { |
|||
messageType: 'EVENT'; |
|||
data: |
|||
| 'START_RECORD_SIG' |
|||
// | "END_RECORD_SIG"
|
|||
// | "FINISHED"
|
|||
| 'START_RECORD_LEFT' |
|||
| 'FINISH_RECORD_RIGHT' |
|||
| 'FINISH_RECORD' |
|||
| 'FINISH_RECORD_LEFT' |
|||
// | "END_RECORD_SIG"
|
|||
| 'END_RECORD' |
|||
| 'START_RECORD_RIGHT' |
|||
| 'WRONG_SIDE'; |
|||
// data: {
|
|||
// event: "START_RECORD_SIG" | "END_RECORD_SIG" | "FINISHED" | "START_RECORD_LEFT" | "FINISH_RECORD_RIGHT" | "FINISH_RECORD" | "FINISH_RECORD_LEFT" | "END_RECORD_SIG" | "END_RECORD" | "START_RECORD_RIGHT";
|
|||
// };
|
|||
path: '/api/measurement-task/event'; |
|||
}; |
|||
|
|||
// 连续上报坐标点
|
|||
export type TrackRecordSig = { |
|||
messageType: 'STATE'; |
|||
data: { |
|||
x: number; |
|||
y: number; |
|||
}; |
|||
path: '/api/measurement-task/point-report'; |
|||
}; |
|||
|
|||
export const defaultContext: ContextMessage['data'] = { |
|||
loginFlag: true, |
|||
loginUser: { |
|||
id: 3, //数据主键id
|
|||
account: 'test001', //用户账户
|
|||
nickname: '测试账户001', //用户昵称
|
|||
userRole: 'User', //用户角色,可用值:User,Admin,Dev
|
|||
isBuiltInUser: false, //是否内置用户(内置用户不可删除)
|
|||
}, |
|||
}; |
|||
|
|||
// 上下文状态
|
|||
export type ContextMessage = { |
|||
messageType: 'DeviceContext'; |
|||
data: { |
|||
loginFlag: Boolean; |
|||
loginUser: Partial<{ |
|||
id: number; |
|||
account: string; |
|||
nickname: string; |
|||
password: string; |
|||
userRole: 'Admin' | 'User' | 'Dev'; |
|||
isBuiltInUser: boolean; |
|||
}>; |
|||
}; |
|||
path: '/api/deviceContext'; |
|||
}; |
|||
|
|||
export type loginUser = Partial<{ |
|||
id: 3; //数据主键id
|
|||
account: 'test001'; //用户账户
|
|||
nickname: '测试账户001'; //用户昵称
|
|||
userRole: 'User'; //用户角色,可用值:User,Admin,Dev
|
|||
isBuiltInUser: false; //是否内置用户(内置用户不可删除)
|
|||
}>; |
|||
|
|||
export const taskStatusDescMap: { [k in MeasureState['data']['taskStatus']]: string } = { |
|||
IDLE: '空闲', |
|||
MEASURING: '测量中', |
|||
WAITING_FOR_MEASURING: '等待测量', |
|||
FINISHED: '测量完成', |
|||
START_RECORD_LEFT: '', |
|||
FINISH_RECORD_RIGHT: '', |
|||
FINISH_RECORD: '', |
|||
FINISH_RECORD_LEFT: '', |
|||
END_RECORD_SIG: '', |
|||
END_RECORD: '', |
|||
START_RECORD_RIGHT: '', |
|||
}; |
|||
// 测量任务状态
|
|||
export type MeasureState = { |
|||
messageType: 'STATE'; |
|||
data: { |
|||
taskStatus: |
|||
| 'IDLE' |
|||
| 'MEASURING' |
|||
| 'WAITING_FOR_MEASURING' |
|||
| 'FINISHED' |
|||
| 'START_RECORD_LEFT' |
|||
| 'FINISH_RECORD_RIGHT' |
|||
| 'FINISH_RECORD' |
|||
| 'FINISH_RECORD_LEFT' |
|||
| 'END_RECORD_SIG' |
|||
| 'END_RECORD' |
|||
| 'START_RECORD_RIGHT'; |
|||
measureSideCnt: 0 | 1 | 2; //已测量数量,0,1,2 最多两边(左边和右边)
|
|||
isMeasuringLeftEnd: boolean; //测量左侧完成
|
|||
isMeasuringRightEnd: boolean; //测量右侧完成
|
|||
motionlessSigFlag: boolean; //滑轮质心是否静止
|
|||
inStartMeasuringPos: boolean; //是否在允许开始测量的位置
|
|||
isWrongSide: boolean; //测量方向是错误的
|
|||
// profileRecordDescription: null; //用户填写的新测量信息
|
|||
}; |
|||
path: '/api/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 ChannelMessage = { |
|||
messageType: 'STATE'; |
|||
data: { |
|||
isConnect: boolean; |
|||
connectPort: string; |
|||
sn: string; |
|||
descriptivePortName: string; |
|||
}; |
|||
path: '/api/subdevice/uartchanel/get-channel-state'; |
|||
}; |
|||
|
|||
export type DeviceStatus = { |
|||
messageType: 'STATE'; |
|||
data: { |
|||
isConnected: boolean; //是否链接
|
|||
power: number; //电量
|
|||
inclinatorX: number; //x轴倾斜
|
|||
inclinatorY: number; //y轴倾斜
|
|||
temperature: number; //温度
|
|||
}; |
|||
path: '/api/profiler-state/get-state'; |
|||
}; |
|||
|
|||
export type Datagram = |
|||
| TrackRecordSig |
|||
| TaskState |
|||
| ContextMessage |
|||
| MeasureState |
|||
| ChannelMessage |
|||
| DeviceStatus; |
@ -0,0 +1,14 @@ |
|||
import { configureStore } from '@reduxjs/toolkit'; |
|||
import measureSlice from './measureSlice'; |
|||
// configureStore创建一个redux数据
|
|||
const store = configureStore({ |
|||
// 合并多个Slice
|
|||
reducer: { |
|||
measure: measureSlice, |
|||
}, |
|||
}); |
|||
|
|||
export default store; |
|||
|
|||
export type RootState = ReturnType<typeof store.getState>; |
|||
export type AppDispatch = typeof store.dispatch; |
@ -0,0 +1,49 @@ |
|||
import { createSlice, PayloadAction } from '@reduxjs/toolkit'; |
|||
import { TaskState, TrackRecordSig } from '../services/wsTypes'; |
|||
|
|||
export interface MeasureState { |
|||
taskState?: TaskState['data']; |
|||
// leftFinished: boolean;
|
|||
leftPoints: TrackRecordSig['data'][]; |
|||
rightPoints: TrackRecordSig['data'][]; |
|||
} |
|||
const initialState: MeasureState = { |
|||
taskState: undefined, |
|||
// leftFinished: false,
|
|||
leftPoints: [], |
|||
rightPoints: [], |
|||
}; |
|||
|
|||
function isLeftFinished(state: MeasureState) { |
|||
if ( |
|||
state.taskState === 'FINISH_RECORD_LEFT' || |
|||
state.taskState === 'START_RECORD_RIGHT' || |
|||
state.taskState === 'FINISH_RECORD_RIGHT' |
|||
) |
|||
return true; |
|||
return false; |
|||
} |
|||
|
|||
export const measureSlice = createSlice({ |
|||
name: 'measure', |
|||
initialState, |
|||
reducers: { |
|||
updateTaskState: (state, action: PayloadAction<TaskState['data']>) => { |
|||
state.taskState = action.payload; |
|||
if (action.payload === 'START_RECORD_SIG' || action.payload === 'WRONG_SIDE') { |
|||
state.leftPoints = []; |
|||
state.rightPoints = []; |
|||
} |
|||
}, |
|||
addNewPoint: (state, action: PayloadAction<TrackRecordSig['data']>) => { |
|||
if (isLeftFinished(state)) { |
|||
state.rightPoints.push(action.payload); |
|||
} else { |
|||
state.leftPoints.push(action.payload); |
|||
} |
|||
}, |
|||
}, |
|||
}); |
|||
|
|||
export const { updateTaskState, addNewPoint } = measureSlice.actions; |
|||
export default measureSlice.reducer; |
@ -0,0 +1,95 @@ |
|||
import { Subject } from 'rxjs'; |
|||
|
|||
declare global { |
|||
interface Window { |
|||
WebViewJavascriptBridge: { |
|||
callHandler: ( |
|||
name: string, |
|||
args: Record<string, any>, |
|||
callback: (res: string) => void |
|||
) => void; |
|||
registerHandler: ( |
|||
name: string, |
|||
func: (data: string, callback: (res: string) => void) => void |
|||
) => void; |
|||
}; |
|||
WVJBCallbacks: Array<(bridge: typeof window.WebViewJavascriptBridge) => void>; |
|||
} |
|||
} |
|||
|
|||
export function setupWebViewJavascriptBridge( |
|||
callback: (bridge: typeof window.WebViewJavascriptBridge) => void |
|||
) { |
|||
if (window.WebViewJavascriptBridge) { |
|||
return callback(window.WebViewJavascriptBridge); |
|||
} |
|||
|
|||
if (/android/i.test(navigator.userAgent)) { |
|||
document.addEventListener( |
|||
'WebViewJavascriptBridgeReady', |
|||
function () { |
|||
callback(window.WebViewJavascriptBridge); |
|||
}, |
|||
false |
|||
); |
|||
} else { |
|||
if (window.WVJBCallbacks) { |
|||
return window.WVJBCallbacks.push(callback); |
|||
} |
|||
window.WVJBCallbacks = [callback]; |
|||
var WVJBIframe = document.createElement('iframe'); |
|||
WVJBIframe.style.display = 'none'; |
|||
WVJBIframe.src = 'https://__bridge_loaded__'; |
|||
document.documentElement.appendChild(WVJBIframe); |
|||
setTimeout(function () { |
|||
document.documentElement.removeChild(WVJBIframe); |
|||
}, 0); |
|||
} |
|||
} |
|||
|
|||
type ShowModelParam = Partial<{ |
|||
title: string; // 非必填,但 title 和 content 至少要填一个
|
|||
content: string; // 非必填,但 title 和 content 至少要填一个
|
|||
contentAlignCenter: boolean; // 内容文本水平居中? 默认 false, 居左
|
|||
showCancel: boolean; // 默认 true
|
|||
cancelText: string; //默认 '取消'
|
|||
// cancelColor: string; // 默认'#333333'
|
|||
confirmText: string; // 默认 '确定'
|
|||
// confirmColor: string; // 默认 APP主题色
|
|||
}>; |
|||
|
|||
// 是否运行在 原生APP 的 WebView 中
|
|||
const appWebview = navigator.userAgent.includes('iFlyTop-mobile'); |
|||
|
|||
const bridgeSub = new Subject<{ func: string; data: Record<string, any> | any[] }>(); |
|||
export const bridgeOb = bridgeSub.asObservable(); |
|||
|
|||
export function registerBridgeFunc() { |
|||
const jsFuncs = ['funcInJs']; |
|||
jsFuncs.forEach((funcName) => { |
|||
window.WebViewJavascriptBridge.registerHandler(funcName, (data, callback) => { |
|||
bridgeSub.next({ func: funcName, data: JSON.parse(data) }); |
|||
callback(JSON.stringify({ success: true })); |
|||
}); |
|||
}); |
|||
} |
|||
|
|||
export default class Bridge { |
|||
static register(name: string, func: (data: string, callback: (res: string) => void) => void) { |
|||
if (appWebview) { |
|||
window.WebViewJavascriptBridge.registerHandler(name, func); |
|||
} |
|||
} |
|||
|
|||
static showModal(param: ShowModelParam) { |
|||
if (appWebview) { |
|||
return new Promise<'confirm' | 'cancel'>((resolve) => { |
|||
window.WebViewJavascriptBridge.callHandler('showModal', param, (res) => { |
|||
resolve(res as 'confirm' | 'cancel'); |
|||
}); |
|||
}); |
|||
} else { |
|||
return Promise.resolve('confirm' as const); |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,6 @@ |
|||
import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux' |
|||
import type { RootState, AppDispatch } from '../store' |
|||
|
|||
// 在整个应用程序中使用,而不是简单的 `useDispatch` 和 `useSelector`
|
|||
export const useAppDispatch: () => AppDispatch = useDispatch |
|||
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector |
Write
Preview
Loading…
Cancel
Save
Reference in new issue