10 changed files with 237 additions and 59 deletions
-
1.env
-
1package.json
-
1public/index.html
-
25public/manifest.json
-
25src/App.tsx
-
41src/pages/Measure.tsx
-
41src/services/httpRequest.ts
-
120src/services/socket.ts
-
16src/store/features/measureSlice.ts
-
25src/utils/bridge.ts
@ -0,0 +1 @@ |
|||
REACT_APP_WS_URL=localhost:8080/ws |
@ -1,25 +0,0 @@ |
|||
{ |
|||
"short_name": "React App", |
|||
"name": "Create React App Sample", |
|||
"icons": [ |
|||
{ |
|||
"src": "favicon.ico", |
|||
"sizes": "64x64 32x32 24x24 16x16", |
|||
"type": "image/x-icon" |
|||
}, |
|||
{ |
|||
"src": "logo192.png", |
|||
"type": "image/png", |
|||
"sizes": "192x192" |
|||
}, |
|||
{ |
|||
"src": "logo512.png", |
|||
"type": "image/png", |
|||
"sizes": "512x512" |
|||
} |
|||
], |
|||
"start_url": ".", |
|||
"display": "standalone", |
|||
"theme_color": "#000000", |
|||
"background_color": "#ffffff" |
|||
} |
@ -0,0 +1,41 @@ |
|||
|
|||
type HttpReqParam = { |
|||
url: string; |
|||
method?: "GET" | "POST" | "PATCH" | "PUT" | "DELETE"; |
|||
params?: Record<string, any>; |
|||
encode?: "form" | "json"; // 入参编码类型
|
|||
headers?: Record<string, any>; |
|||
}; |
|||
|
|||
export default async function httpRequest<T>({ url, method = "GET", params = {}, encode = "json", headers = {} }: HttpReqParam) { |
|||
// const token = sessionStorage.getItem("token");
|
|||
// if (token) {
|
|||
// headers = { Authorization: token, ...headers };
|
|||
// }
|
|||
if (method === "GET") { |
|||
const query = urlEncode(params); |
|||
const _url = query ? url + "?" + query : url; |
|||
const res = await fetch(_url, { headers }); |
|||
return res.json() as Promise<T>; |
|||
} else { |
|||
const body = encode === "json" ? JSON.stringify(params) : urlEncode(params); |
|||
const _headers = |
|||
encode === "json" |
|||
? { "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 Promise<T>; |
|||
} |
|||
} |
|||
|
|||
export function urlEncode(params?: Record<string, any>) { |
|||
let query = ""; |
|||
if (params && Object.keys(params).length > 0) { |
|||
const qs = []; |
|||
for (let attr in params) { |
|||
qs.push(`${attr}=${encodeURIComponent(params[attr])}`); |
|||
} |
|||
query = qs.join("&"); |
|||
} |
|||
return query; |
|||
} |
@ -0,0 +1,120 @@ |
|||
import { Subject } from "rxjs"; |
|||
|
|||
export type SocketState = "open" | "close" | "error"; |
|||
|
|||
class WebSocketClient { |
|||
private ws: WebSocket | null = null; |
|||
private url: string; |
|||
private reconnectAttempts: number = -1; |
|||
private maxReconnectAttempts: number = 5; |
|||
private reconnectInterval: number = 3000; |
|||
|
|||
private dataSub = new Subject<{ func: string; data: Record<string, any> | any[] }>(); |
|||
get dataOb() { |
|||
return this.dataSub.asObservable(); |
|||
} |
|||
private stateSub = new Subject<SocketState>(); |
|||
get stateOb() { |
|||
return this.stateSub.asObservable(); |
|||
} |
|||
constructor(url: string) { |
|||
this.url = url; |
|||
} |
|||
|
|||
// 连接 WebSocket
|
|||
connect(): void { |
|||
try { |
|||
// WebSocket.CONNECTING (0) WebSocket.OPEN (1)
|
|||
if (this.ws && this.ws.readyState <= 1) { |
|||
// 已连接
|
|||
console.log(`${this.url} 正在连接或已连接,无需重复连接`); |
|||
} else { |
|||
this.ws = new WebSocket(this.url); |
|||
this.bindEvents(); |
|||
} |
|||
localStorage.setItem('wsReadyState', `${this.ws.readyState}`) |
|||
} catch (error) { |
|||
console.error("WebSocket 连接失败:", error); |
|||
this.reconnect(); |
|||
} |
|||
} |
|||
|
|||
// 绑定事件
|
|||
private bindEvents(): void { |
|||
if (!this.ws) return; |
|||
|
|||
// 连接建立时的处理
|
|||
this.ws.onopen = () => { |
|||
console.log("WebSocket 连接已建立"); |
|||
this.reconnectAttempts = -1; // 重置重连次数
|
|||
this.stateSub.next("open"); |
|||
}; |
|||
|
|||
// 接收消息的处理
|
|||
this.ws.onmessage = (event: MessageEvent) => { |
|||
try { |
|||
const data = JSON.parse(event.data) as { func: string; data: Record<string, any> | any[] }; |
|||
// console.log("🚀 ~ WebSocketClient ~ bindEvents ~ data:", data);
|
|||
// if (data.type === "cmd") {
|
|||
// this.dataSub.next({ type: data.type, data: { ...data.data, success: data.data.status === "D0000" } });
|
|||
// } else {
|
|||
this.dataSub.next(data); |
|||
// }
|
|||
} catch (error) { |
|||
console.error("消息解析错误:", error); |
|||
} |
|||
}; |
|||
|
|||
this.ws.onclose = () => { |
|||
this.stateSub.next("close"); |
|||
console.log("WebSocket 连接已关闭"); |
|||
this.reconnect(); |
|||
}; |
|||
|
|||
this.ws.onerror = error => { |
|||
this.stateSub.next("error"); |
|||
console.error("WebSocket 错误:", error); |
|||
}; |
|||
} |
|||
|
|||
// 重连机制
|
|||
private reconnect(): void { |
|||
if (this.reconnectAttempts === -1) { |
|||
this.reconnectAttempts = 0; |
|||
} |
|||
if (this.reconnectAttempts >= this.maxReconnectAttempts) { |
|||
console.log("达到最大重连次数,停止重连"); |
|||
this.reconnectAttempts = -1; |
|||
return; |
|||
} |
|||
|
|||
setTimeout(() => { |
|||
console.log(`尝试第 ${this.reconnectAttempts + 1} 次重连...`); |
|||
this.reconnectAttempts++; |
|||
this.connect(); |
|||
}, this.reconnectInterval); |
|||
} |
|||
|
|||
// 关闭连接
|
|||
disconnect(): void { |
|||
if (this.ws) { |
|||
this.ws.close(); |
|||
this.ws = null; |
|||
} |
|||
} |
|||
} |
|||
|
|||
const urlSocketMap = new Map<string, WebSocketClient>(); |
|||
|
|||
// 导出 WebSocket 客户端
|
|||
export const createWebSocket = (url: string): WebSocketClient => { |
|||
if (urlSocketMap.has(url)) { |
|||
return urlSocketMap.get(url)!; |
|||
} else { |
|||
const client = new WebSocketClient(url); |
|||
urlSocketMap.set(url, client); |
|||
return client; |
|||
} |
|||
}; |
|||
|
|||
export const sharedWsUrl = `ws://${process.env.REACT_APP_WS_URL}`; |
Write
Preview
Loading…
Cancel
Save
Reference in new issue