From 2be29d7952931c418d8990fc205f944358d1fe31 Mon Sep 17 00:00:00 2001 From: zhangjiming Date: Wed, 26 Mar 2025 17:14:21 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0store=E5=92=8Cbridge?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .prettierrc.js | 4 +- public/audio/alert_left.mp3 | Bin 0 -> 8301 bytes public/audio/begin_left.mp3 | Bin 0 -> 8589 bytes public/audio/begin_right.mp3 | Bin 0 -> 7629 bytes public/audio/end_left.mp3 | Bin 0 -> 9453 bytes public/audio/end_right.mp3 | Bin 0 -> 9069 bytes src/App.tsx | 29 ++++++++- src/components/StepItem.tsx | 28 +++++++-- src/index.tsx | 9 ++- src/pages/Measure.tsx | 101 +++++++++++++++++++++++++----- src/services/wsTypes.ts | 146 +++++++++++++++++++++++++++++++++++++++++++ src/store/index.ts | 14 +++++ src/store/measureSlice.ts | 49 +++++++++++++++ src/utils/bridge.ts | 95 ++++++++++++++++++++++++++++ src/utils/hooks.ts | 6 ++ 15 files changed, 452 insertions(+), 29 deletions(-) create mode 100644 public/audio/alert_left.mp3 create mode 100644 public/audio/begin_left.mp3 create mode 100644 public/audio/begin_right.mp3 create mode 100644 public/audio/end_left.mp3 create mode 100644 public/audio/end_right.mp3 create mode 100644 src/services/wsTypes.ts create mode 100644 src/store/index.ts create mode 100644 src/store/measureSlice.ts create mode 100644 src/utils/bridge.ts create mode 100644 src/utils/hooks.ts diff --git a/.prettierrc.js b/.prettierrc.js index adbcd12..83140a2 100644 --- a/.prettierrc.js +++ b/.prettierrc.js @@ -3,8 +3,8 @@ module.exports = { semi: true, // 使用分号 trailingComma: 'es5', // 在对象或数组的最后一个元素后添加逗号 printWidth: 100, // 每行的最大字符数 - tabWidth: 4, // 缩进宽度 - useTabs: true, // 使用制表符缩进 + tabWidth: 2, // 缩进宽度 + useTabs: false, // 使用制表符缩进 jsxSingleQuote: false, // JSX 中使用单引号 bracketSpacing: true, // 对象字面量的括号是否换行 jsxBracketSameLine: false, // JSX 的闭合括号是否在同一行 diff --git a/public/audio/alert_left.mp3 b/public/audio/alert_left.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..f597e87d4436cbd0a01ba006a8b5283685519a0e GIT binary patch literal 8301 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 YWw-pdgZTOLpX2Mlwf+C7{%`yL2dc=q4FCWD literal 0 HcmV?d00001 diff --git a/public/audio/begin_left.mp3 b/public/audio/begin_left.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..084b48e4a05ea28063135570614b7a63b8a2da72 GIT binary patch literal 8589 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%0D2* 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 HZ~uP)AZh{2 literal 0 HcmV?d00001 diff --git a/public/audio/end_left.mp3 b/public/audio/end_left.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..965b171398211a89d75c7f5b514dfacde8911bee GIT binary patch literal 9453 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 literal 0 HcmV?d00001 diff --git a/public/audio/end_right.mp3 b/public/audio/end_right.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..cd7bd183420e1fd7acf483c6513ab6e1984e21db GIT binary patch literal 9069 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 literal 0 HcmV?d00001 diff --git a/src/App.tsx b/src/App.tsx index dc4a854..a5ef158 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useEffect } from 'react'; import { Outlet, useLocation, useNavigate } from 'react-router'; import { SafeArea, TabBar } from 'antd-mobile'; @@ -10,6 +10,10 @@ import icon_3_s from './assets/tabIcon/icon_tab3_s.svg'; import icon_3_u from './assets/tabIcon/icon_tab3_u.svg'; import icon_4_s from './assets/tabIcon/icon_tab4_s.svg'; import icon_4_u from './assets/tabIcon/icon_tab4_u.svg'; +import { bridgeOb } from './utils/bridge'; +import { useAppDispatch } from './utils/hooks'; +import { addNewPoint, updateTaskState } from './store/measureSlice'; +import { TrackRecordSig } from './services/wsTypes'; const BottomBar = () => { const navigate = useNavigate(); @@ -47,8 +51,8 @@ const BottomBar = () => { ]; return ( - setRouteActive(value)}> - {tabs.map(item => ( + setRouteActive(value)}> + {tabs.map((item) => ( @@ -62,6 +66,25 @@ const BottomBar = () => { }; function App() { + const dispatch = useAppDispatch(); + + useEffect(() => { + const subscription = bridgeOb.subscribe(({ func, data }) => { + if (func === '/measurement-task/event') { + if (Array.isArray(data)) return; + dispatch(updateTaskState(data.data)); + // switch (data.data) { + // case "START_RECORD_LEFT": + // break; + // } + } else if (func === '/measurement-task/point-report') { + if (Array.isArray(data)) return; + dispatch(addNewPoint(data as TrackRecordSig['data'])); + } + }); + return () => subscription.unsubscribe(); + }, [dispatch]); + return (
diff --git a/src/components/StepItem.tsx b/src/components/StepItem.tsx index 40fd110..cad53a3 100644 --- a/src/components/StepItem.tsx +++ b/src/components/StepItem.tsx @@ -1,18 +1,34 @@ -export default function StepItem({ state = 0, text }: { state: 0 | 1 | 2; text: string }) { +export type StepName = + | 'left_ready' + | 'left_begin' + | 'left_end' + | 'right_ready' + | 'right_begin' + | 'right_end'; + +export type StepState = 'none' | 'ongoing' | 'done'; + +export default function StepItem({ state = 'none', text }: { state: StepState; text: string }) { return (
+ }`} + >
+ state === 'done' + ? 'bg-[#52C41A]' + : state === 'ongoing' + ? 'bg-[#187ef9]' + : 'bg-[#c0c0c0]' + }`} + >

{text}

diff --git a/src/index.tsx b/src/index.tsx index 5742cd5..3b4748f 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -10,6 +10,9 @@ import { Route, RouterProvider, } from 'react-router-dom'; +import { Provider } from 'react-redux'; +import store from './store/index'; + import Measure from './pages/Measure'; import Setting from './pages/Setting'; import Bluetooth from './pages/Bluetooth'; @@ -22,7 +25,7 @@ const router = createHashRouter( } /> }> - } > + }> }> }> }> @@ -38,7 +41,9 @@ const router = createHashRouter( const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement); root.render( - + + + ); diff --git a/src/pages/Measure.tsx b/src/pages/Measure.tsx index e4b4158..45d83be 100644 --- a/src/pages/Measure.tsx +++ b/src/pages/Measure.tsx @@ -1,4 +1,4 @@ -import StepItem from '../components/StepItem'; +import StepItem, { StepName, StepState } from '../components/StepItem'; import { Link, useNavigate } from 'react-router-dom'; import CustomNavBar from '../components/CustomNavBar'; import MeasurementCanvas, { @@ -10,6 +10,9 @@ import { rail6001, railTypes } from '../utils/constant'; import RailTypeBtn from '../components/RailTypeBtn'; import { Picker } from 'antd-mobile'; +import { bridgeOb } from '../utils/bridge'; +import { useAppDispatch, useAppSelector } from '../utils/hooks'; +import { updateTaskState } from '../store/measureSlice'; declare global { interface Window { @@ -31,6 +34,8 @@ window.bridgeCall = (func, ...args) => { export default function Measure() { const navigate = useNavigate(); + const dispatch = useAppDispatch(); + const measureState = useAppSelector((state) => state.measure); const canvasRef = useRef(null); @@ -38,12 +43,12 @@ export default function Measure() { const [railId, setRailId] = useState<(number | string | null)[]>([1]); const onStartClick = () => { - if (typeof window.ReactNativeWebView !== 'undefined') { - // 发送消息给 React Native - window.ReactNativeWebView.postMessage(JSON.stringify(['add', 2, 3])); - } else { - console.log('当前环境不支持 React Native WebView'); - } + // if (typeof window.ReactNativeWebView !== "undefined") { + // window.ReactNativeWebView.postMessage(JSON.stringify(["add", 2, 3])); + // } else { + // console.log("当前环境不支持 React Native WebView"); + // } + dispatch(updateTaskState('START_RECORD_SIG')); }; const onSaveClick = () => { navigate('/measure/save'); @@ -56,8 +61,72 @@ export default function Measure() { } }, []); + // 播放音频 步骤 + useEffect(() => { + if (measureState.taskState === 'START_RECORD_LEFT') { + const audio1 = new Audio('/audio/begin_left.mp3'); + audio1.play(); + } else if (measureState.taskState === 'FINISH_RECORD_LEFT') { + const audio2 = new Audio('/audio/end_left.mp3'); + audio2.play(); + } else if (measureState.taskState === 'START_RECORD_RIGHT') { + const audio3 = new Audio('/audio/begin_right.mp3'); + audio3.play(); + } else if (measureState.taskState === 'FINISH_RECORD_RIGHT') { + const audio4 = new Audio('/audio/end_right.mp3'); + audio4.play(); + } else if (measureState.taskState === 'WRONG_SIDE') { + const audio5 = new Audio('/audio/alert_left.mp3'); + audio5.play(); + } + }, [measureState.taskState]); + + 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 railTypes.find(r => r.id === railId[0])?.name || ''; + return railTypes.find((r) => r.id === railId[0])?.name || ''; } return ( @@ -109,24 +178,24 @@ export default function Measure() {
- - - - - - + + + + + +
({ ...t, label: t.name, value: t.id }))]} + columns={[railTypes.map((t) => ({ ...t, label: t.name, value: t.id }))]} visible={railPickerVisible} onClose={() => { setRailPickerVisible(false); }} value={railId} - onConfirm={v => { + onConfirm={(v) => { setRailId(v); }} /> diff --git a/src/services/wsTypes.ts b/src/services/wsTypes.ts new file mode 100644 index 0000000..47ac59e --- /dev/null +++ b/src/services/wsTypes.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; diff --git a/src/store/index.ts b/src/store/index.ts new file mode 100644 index 0000000..0251611 --- /dev/null +++ b/src/store/index.ts @@ -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; +export type AppDispatch = typeof store.dispatch; diff --git a/src/store/measureSlice.ts b/src/store/measureSlice.ts new file mode 100644 index 0000000..e5245bd --- /dev/null +++ b/src/store/measureSlice.ts @@ -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) => { + state.taskState = action.payload; + if (action.payload === 'START_RECORD_SIG' || action.payload === 'WRONG_SIDE') { + state.leftPoints = []; + state.rightPoints = []; + } + }, + addNewPoint: (state, action: PayloadAction) => { + if (isLeftFinished(state)) { + state.rightPoints.push(action.payload); + } else { + state.leftPoints.push(action.payload); + } + }, + }, +}); + +export const { updateTaskState, addNewPoint } = measureSlice.actions; +export default measureSlice.reducer; diff --git a/src/utils/bridge.ts b/src/utils/bridge.ts new file mode 100644 index 0000000..3fd88ec --- /dev/null +++ b/src/utils/bridge.ts @@ -0,0 +1,95 @@ +import { Subject } from 'rxjs'; + +declare global { + interface Window { + WebViewJavascriptBridge: { + callHandler: ( + name: string, + args: Record, + 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 | 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); + } + } +} diff --git a/src/utils/hooks.ts b/src/utils/hooks.ts new file mode 100644 index 0000000..965a9be --- /dev/null +++ b/src/utils/hooks.ts @@ -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 = useSelector \ No newline at end of file