Browse Source

系统设置添加蓝牙

feat_upload_server_url_0416
LiLongLong 4 months ago
parent
commit
ac79e9f36b
  1. 4
      package-lock.json
  2. 2
      scripts/start.js
  3. 1
      src/assets/icon_bluetooth.svg
  4. 38
      src/components/Header.tsx
  5. 7
      src/pages/measure/components/MeasureAction.tsx
  6. 2
      src/pages/measure/components/konva/MeasurementCanvas.tsx
  7. 119
      src/pages/system/Setting.tsx
  8. 6
      src/pages/system/types.ts
  9. 29
      src/services/ble/ble.ts
  10. 14
      src/services/wsTypes.ts
  11. 31
      src/store/ble/bleState.ts
  12. 6
      src/store/features/contextSlice.ts
  13. 2
      src/store/index.ts

4
package-lock.json

@ -47,7 +47,6 @@
"jest": "^27.4.3",
"jest-resolve": "^27.4.2",
"jest-watch-typeahead": "^1.0.0",
"konva": "^8.3.5",
"mini-css-extract-plugin": "^2.4.5",
"postcss-flexbugs-fixes": "^5.0.2",
"postcss-loader": "^6.2.1",
@ -12227,7 +12226,8 @@
"url": "https://github.com/sponsors/lavrton"
}
],
"license": "MIT"
"license": "MIT",
"peer": true
},
"node_modules/language-subtag-registry": {
"version": "0.3.23",

2
scripts/start.js

@ -44,7 +44,7 @@ if (!checkRequiredFiles([paths.appHtml, paths.appIndexJs])) {
}
// Tools like Cloud9 rely on this.
const DEFAULT_PORT = parseInt(process.env.PORT, 10) || 3000;
const DEFAULT_PORT = parseInt(process.env.PORT, 10) || 3010;
const HOST = process.env.HOST || '0.0.0.0';
if (process.env.HOST) {

1
src/assets/icon_bluetooth.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="10" height="15" viewBox="0 0 10 15"><g><path d="M6.7674,7.5L10,10.7572L5.78936,15L4.30071,15L4.30071,9.9855L1.05248,13.2585L5.32382e-7,12.1972L4.30071,7.86375L4.30071,7.13625L0,2.80275L1.05247,1.7415L4.30071,5.0145L4.30071,0L5.78936,0L10,4.24275L6.7674,7.5ZM5.78936,8.63625L5.78936,12.879L7.89431,10.7572L5.78936,8.63625ZM5.78936,6.36375L7.89431,4.24275L5.78936,2.121L5.78936,6.36375Z" fill="#3165D2" fill-opacity="1" style="mix-blend-mode:passthrough"/></g></svg>

38
src/components/Header.tsx

@ -1,4 +1,5 @@
import icon_usb from "../assets/icon_usb.svg";
import icon_bluetooth from "../assets/icon_bluetooth.svg";
import icon_battery from "../assets/icon_battery.svg";
import icon_avatar from "../assets/icon_avatar.svg";
import icon_logout from "../assets/icon_logout.svg";
@ -18,11 +19,24 @@ export default function Header() {
const navigate = useNavigate();
const dispatch = useAppDispatch();
const deviceInfo = useAppSelector(store => store.context.device);
const deviceState = useAppSelector(store => store.deviceState);
// const deviceState = useAppSelector(store => store.context.device);
const userInfo = useAppSelector(store => store.context.user.loginUser);
//获取当前websocet的状态
const showBlueImg = () => {
if (deviceState.isConnect) {
if (deviceInfo.isConnected && deviceInfo.connectedType === "BLE_CHANNEL") {
return (
<Popover content={getBtBlueContent()} title="" trigger="click">
<section className="bg-white rounded-md h-9 w-10 flex justify-center items-center mr-3">
<img src={icon_bluetooth} alt="" className="h-6" />
</section>
</Popover>
);
}
return null;
};
const showUsbImg = () => {
if (deviceInfo.isConnected && deviceInfo.connectedType === "UART_CHANNEL") {
return (
<Popover content={getBtContent()} title="" trigger="click">
<section className="bg-white rounded-md h-9 w-10 flex justify-center items-center mr-3">
@ -84,6 +98,22 @@ export default function Header() {
},
];
const getBtBlueContent = () => {
return (
<div>
<div>
<div className="bluetooth_c">
<img src={check_mark} alt="" className="ext-base ml-2 h-4" />
<div className="ml-[10px]"></div>
</div>
<div className="pl-[15px]">
<div className="mt-[1rem] ml-[1.6rem]">sn码{deviceInfo.sn}</div>
</div>
</div>
</div>
);
};
//设备已连接
const getBtContent = () => {
return (
@ -94,8 +124,7 @@ export default function Header() {
<div className="ml-[10px]"></div>
</div>
<div className="pl-[15px]">
<div className="mt-[1rem] ml-[1.6rem]">sn码{deviceState.sn}</div>
<div className="mt-[1rem]">{deviceState.descriptivePortName}</div>
<div className="mt-[1rem] ml-[1.6rem]">sn码{deviceInfo.sn}</div>
</div>
</div>
</div>
@ -124,6 +153,7 @@ export default function Header() {
</>
)}
{showBlueImg()}
{showUsbImg()}
<div className="mr-8 flex items-center min-w-[5rem]">
<Dropdown menu={{ items }} trigger={["click"]}>
<section className="flex items-center">

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

@ -119,7 +119,10 @@ export default function MeasureAction() {
// 开始/重新测量按钮点击事件
const onStart = useCallback(() => {
//电量低于20%时不可进行测量
if (!deviceInfo.isConnected) {
message.error("请先连接设备");
return;
}
if(deviceInfo.power < 20){
message.error('电量低于20%,请充电后再测量!')
return
@ -151,7 +154,7 @@ export default function MeasureAction() {
setStartBtnText("重新测量");
}
});
}, [initialStatusList, startBtnText]);
}, [initialStatusList, startBtnText, deviceInfo]);
//停止测量
const onStop = () => {

2
src/pages/measure/components/konva/MeasurementCanvas.tsx

@ -150,7 +150,7 @@ const MeasurementCanvas = forwardRef<MeasurementCanvasRef, MeasurementCanvasProp
const [measurementDataRight, setMeasurementDataRightState] = useState<Point[]>(initialMeasurementDataRight);
const [measurementCalibrationData, setMeasurementCalibrationDataState] = useState<Point[]>(initMeasurementCalibrationData);
const [measurementData, setMeasurementDataState] = useState<Point[]>([]);
const refreshInterval = 50;
const refreshInterval = 10;
const refreshTimer = useRef<number | null>(null);
useEffect(() => {
if (!refreshTimer.current) {

119
src/pages/system/Setting.tsx

@ -1,28 +1,51 @@
import { useState, useEffect, ReactNode } from 'react';
import type { CascaderProps } from 'antd';
import { Button, Cascader, Input, message } from 'antd';
import { useState, useEffect } from 'react';
import { Button, Cascader, Input, message, List, Typography } from 'antd';
import { getOrgListService } from '../../services/ktj/org';
import { options, OrgItem } from '../../services/ktjTypes';
import { child, GwdItem, orgCascaderType, systemItem } from './types';
import { OrgItem } from '../../services/ktjTypes';
import {bleItem, child, GwdItem, orgCascaderType, systemItem} from './types';
import { sysSet } from '../../services/user/system';
import { useAppDispatch, useAppSelector } from "../../utils/hooks";
import { updateSystemAccountState, updateSystemOrgState } from '../../store/system/systemSlice';
import {createWebSocket, sharedWsUrl} from "../../services/socket";
import {start, stop, disconnect, connect} from "../../services/ble/ble";
export default function Setting(){
useEffect(()=>{
queryRailData()
onBleStart()
querySettingData()
return ()=>{
onBleStop()
}
},[])
const dispatch = useAppDispatch();
const deviceInfo = useAppSelector(store => store.context.device);
const systemState = useAppSelector((store) => store.systemState);
const [systemList, setSystemList] = useState<systemItem[]>([])
const [accountInfo, setAccountInfo] = useState<systemItem>({})
const [orgInfo, setOrgInfo] = useState<systemItem>({})
const [orgValues, setOrgValues] = useState<string[]>([])
const [bleList, setBleList] = useState<bleItem[]>([])
// 创建 websocket 客户端
const wsClient = createWebSocket(sharedWsUrl);
function querySettingData(){
let systemInfo = systemState.systemInfo
setSystemList(systemInfo)
}
useEffect(() => {
const subscription = wsClient.dataOb.subscribe(data => {
// @ts-ignore
if (data.messageType === "STATE" && data.path === "/api/ble/ble-list") {
// @ts-ignore
setBleList(data.data)
}
});
wsClient.connect();
return () => subscription.unsubscribe();
});
useEffect(()=>{
if(systemState.orgInfo){
const cloneOrgItem = systemState.orgInfo
@ -144,14 +167,94 @@ export default function Setting(){
});
}
return <div>
function onBleStart() {
start().then(() => {
message.success('蓝牙开始扫描')
})
}
function onBleStop() {
stop().then(() => {
})
}
function bleConnect(address: string) {
connect(address).then((res) => {
if (res.status !== 0) {
message.error(res.data.info);
return
}
message.success('蓝牙连接成功')
})
}
function bleDisconnect() {
disconnect().then(() => {
message.success('蓝牙断开连接')
})
}
const connectionStatus = () => {
if (!deviceInfo.isConnected) {
return (
<section className='p-[20px]'>
<div></div>
{bleList.map(item => {
return <div key={item.address}>
<div className={'flex justify-between w-1/4 pt-2.5'}>
<p >
<p className={'text-[16px] text-gray-400'}>{item.name}</p>
<p className={'text-[12px] text-gray-400'}>{item.address}</p>
</p>
{
item.connect ? <p className={'text-[12px] text-gray-400'}></p> : <Button type={'primary'} onClick={() =>bleConnect(item.address)}></Button>
}
</div>
</div>
})}
</section>
);
} else {
if(deviceInfo.connectedType === 'UART_CHANNEL') {
return (
<div className='p-[20px]'>
usb连接
<div className="mt-[1rem] ml-[1.6rem]">sn码{deviceInfo.sn}</div>
</div>
)
}else if(deviceInfo.connectedType === 'BLE_CHANNEL') {
return (
<div className='p-[20px]'>
<Button className={'ml-[10px]'} onClick={bleDisconnect}></Button>
<div className="mt-[1rem] ml-[1.6rem]">sn码{deviceInfo.sn}</div>
</div>
)
}
}
};
// @ts-ignore
return <><div className={'pr-2.5 pl-2.5 w-full h-full'}>
<div className={'h-full w-full bg-white p-10 rounded-2xl'}>
<h1 className='text-[20px]'></h1>
<section className='p-[20px]'>
<div><Cascader className='w-[300px]' key={orgValues.length} defaultValue={orgValues} options={KTJOrgList} onChange={onOrgChange} placeholder="请选择局段线" /></div>
<div>
<span></span>
<Cascader className='w-[300px]' key={orgValues.length} defaultValue={orgValues} options={KTJOrgList} onChange={onOrgChange} placeholder="请选择局段线" /></div>
<div className='mt-[10px]'>
{accountInfo.name}
<span> {accountInfo.name}</span>
<Input key={accountInfo.value} defaultValue={accountInfo.value} onChange={(e)=>{onAccountChange(e.target.value)}} className='w-[300px]'></Input>
<Button className='ml-[10px]' size='small' type="primary" onClick={onSaveAccount}></Button>
</div>
</section>
<h1 className='text-[20px]'></h1>
{connectionStatus()}
</div>
</div>
</>
}

6
src/pages/system/types.ts

@ -22,3 +22,9 @@ export type systemItem = {
value?: string;
}
export type bleItem = {
"name": string;
"address": string;
"connect": boolean
}

29
src/services/ble/ble.ts

@ -0,0 +1,29 @@
import httpRequest, {type BaseResponse} from "../httpRequest";
export function start() {
return httpRequest<BaseResponse>({
url: "/api/ble/list/start",
method: "POST",
});
}
export function stop() {
return httpRequest<BaseResponse>({
url: "/api/ble/list/stop",
method: "POST",
});
}
export function connect(address: string) {
return httpRequest<BaseResponse>({
url: `/api/ble/connect/${address}`,
method: "POST"
});
}
export function disconnect() {
return httpRequest<BaseResponse>({
url: `/api/ble/disconnect`,
method: "POST"
});
}

14
src/services/wsTypes.ts

@ -129,10 +129,14 @@ export type DeviceStatus = {
messageType: "STATE";
data: {
isConnected: boolean; //是否链接
connectedType: "UART_CHANNEL" | "BLE_CHANNEL";
sn: string;
power: number; //电量
inclinatorX: number; //x轴倾斜
inclinatorY: number; //y轴倾斜
temperature: number; //温度
state: number;
flag: number
};
path: "/api/profiler-state/get-state";
};
@ -148,4 +152,14 @@ export type ProgressStatus = {
path: "/get-task-progress";
};
export type bleStatus = {
messageType: "STATE";
data: {
name: string;
address: string;
connect: boolean;
};
path: "/api/ble/ble-list";
};
export type Datagram = TrackRecordSig | TaskState | ContextMessage | MeasureState | ChannelMessage | DeviceStatus | ProgressStatus;

31
src/store/ble/bleState.ts

@ -0,0 +1,31 @@
// counterSlice.ts 文件
import { createSlice } from "@reduxjs/toolkit";
import { ChannelMessage } from "../../services/wsTypes";
const initialState: ChannelMessage["data"] = {
isConnect: false, //是否连接
connectPort: "COM4", //串口名
sn: "", //连接的设备ID
descriptivePortName: "COM4 serial ch340", //用于详细系
};
// 创建一个 Slice
export const bleStateSlice = createSlice({
name: "bleState",
initialState,
// 定义 reducers 并生成关联的操作
reducers: {
// 定义一个加的方法
updateBleState: (state, { payload }) => {
state.isConnect = payload.isConnect;
state.connectPort = payload.connectPort;
state.sn = payload.sn;
state.descriptivePortName = payload.descriptivePortName;
},
},
});
export const { updateBleState } = bleStateSlice.actions;
// 默认导出
export default bleStateSlice.reducer;

6
src/store/features/contextSlice.ts

@ -12,11 +12,15 @@ const initialState: ContextSlice = {
user: { loginFlag: false, loginUser: { nickname: "", userRole: "User", account: "" } },
newMeasureAfterSave: false,
device: {
isConnected: true, //是否链接
isConnected: false, //是否链接
connectedType: "BLE_CHANNEL",
power: 60, //电量
inclinatorX: 0.276, //x轴倾斜
inclinatorY: 3.019, //y轴倾斜
temperature: 32.026, //温度
sn: '00000',
state: 1,
flag: 0
},
};

2
src/store/index.ts

@ -4,6 +4,7 @@ import { configureStore } from "@reduxjs/toolkit";
import counterSlice from "./features/counterSlice";
import contextSlice from "./features/contextSlice";
import deviceStateSlice from "./device/deviceState";
import bleState from "./ble/bleState";
import orgStateSlice from "./ktj/orgState";
import measureStateSlice from "./measure/measureState";
import systemStateSlice from "./system/systemSlice";
@ -15,6 +16,7 @@ const store = configureStore({
counter: counterSlice,
context: contextSlice,
deviceState: deviceStateSlice,
bleState: bleState,
orgState: orgStateSlice,
measureState:measureStateSlice,
systemState:systemStateSlice

Loading…
Cancel
Save