diff --git a/App.vue b/App.vue index 4a59124..cd005d6 100644 --- a/App.vue +++ b/App.vue @@ -1,18 +1,32 @@ + /* 每个页面公共css */ + \ No newline at end of file diff --git a/common/websocket.js b/common/websocket.js new file mode 100644 index 0000000..76eec43 --- /dev/null +++ b/common/websocket.js @@ -0,0 +1,319 @@ +// common/websocket.js +// 带中文日志与消息体打印选项的 WsRequest(替换你当前文件即可) + +class WsRequest { + constructor(url = '', options = {}) { + this.url = url || ''; + this.options = Object.assign({ + header: {}, + protocols: [], + debug: true, + lang: 'zh', // 'zh' 或 'en',控制日志语言(默认中文) + showMessageBody: true, // 是否在日志中显示收到消息的完整 JSON(默认 true) + filterPing: true, // 是否过滤掉 ping/pong 日志(默认 true) + heartbeatInterval: 30000, + heartbeatTimeout: 15000, + pingMessage: { type: 'ping' }, + parseJSON: true, + maxReconnectAttempts: 10, + reconnectDelayBase: 1000, + autoConnect: false, + bindGlobal: true + }, options); + + this.socketTask = null; + this.connected = false; + this._msgQueue = []; + this.reconnectAttempts = 0; + this._lastPongAt = Date.now(); + this._hbTimer = null; + this._hbTimeoutTimer = null; + this._subscriptions = new Map(); + + if (this.options.bindGlobal) this._bindGlobalSocketEvents(); + if (this.options.autoConnect) setTimeout(() => this.open(), 0); + } + + // 内部多语言 Label + _label(key) { + const zh = { + open: '打开连接 ->', + alreadyConnected: '已连接,忽略 open', + connectNoTask: 'connectSocket 未返回 task,使用全局回调', + onOpen: '连接已打开', + onMessage: '收到消息', + onClose: '连接已关闭', + onError: '连接错误', + globalOnOpen: '全局 onSocketOpen', + globalOnMessage: '全局 onSocketMessage', + hbTimeout: '心跳超时', + reconnectScheduled: '计划重连', + sendFailed: '发送失败', + closeError: '关闭错误' + }; + const en = { + open: 'open ->', + alreadyConnected: 'already connected, ignore open', + connectNoTask: 'connectSocket returned no task (using global callbacks if available)', + onOpen: 'onOpen', + onMessage: 'onMessage', + onClose: 'onClose', + onError: 'onError', + globalOnOpen: 'global onSocketOpen', + globalOnMessage: 'global onSocketMessage', + hbTimeout: 'hb timeout', + reconnectScheduled: 'reconnect scheduled', + sendFailed: 'send failed', + closeError: 'close error' + }; + return this.options.lang === 'zh' ? zh[key] || key : en[key] || key; + } + + // 简单统一日志 + log(...args) { + if (!this.options.debug) return; + // 如果是对象,格式化输出以便控制台可读 + const out = args.map(a => { + if (typeof a === 'object') { + try { return JSON.stringify(a, null, 2); } catch (e) { return String(a); } + } + return String(a); + }).join(' '); + console.log('[WsRequest]', out); + } + + // ---------- 对外控制 ---------- + open() { + if (!this.url) { this.log(this._label('open'), this._label('noUrl') || 'no url'); return; } + if (this.connected) { this.log(this._label('alreadyConnected')); return; } + this.log(this._label('open'), this.url); + try { + const task = uni.connectSocket({ url: this.url, header: this.options.header, protocols: this.options.protocols }); + if (task && typeof task.onOpen === 'function') { + this._bindSocketTask(task); + } else { + this.log(this._label('connectNoTask')); + // 依赖全局回调,_bindGlobalSocketEvents 已经绑定则会处理 onOpen/onMessage + } + } catch (e) { + console.error(e); + } + } + + close(code = 1000, reason = 'client close') { + this._stopHeartbeat(); + try { + if (this.socketTask && typeof this.socketTask.close === 'function') { + this.socketTask.close({ code, reason }); + } else if (typeof uni.closeSocket === 'function') { + uni.closeSocket(); + } + } catch (e) { + this.log(this._label('closeError'), e); + } + this.connected = false; + } + + send(payload) { + const data = typeof payload === 'string' ? payload : JSON.stringify(payload); + if (this.socketTask && typeof this.socketTask.send === 'function' && this.connected) { + try { this.socketTask.send({ data }); return; } catch (e) { this.log(this._label('sendFailed'), e); this._msgQueue.push(data); } + } else { + try { uni.sendSocketMessage({ data }); return; } catch (e) { this._msgQueue.push(data); } + } + } + + _flushQueue() { + if (!this.connected) return; + while (this._msgQueue.length) { + const d = this._msgQueue.shift(); + try { + if (this.socketTask && typeof this.socketTask.send === 'function') { + this.socketTask.send({ data: d }); + } else { + uni.sendSocketMessage({ data: d }); + } + } catch (e) { this._msgQueue.unshift(d); break; } + } + } + + _bindSocketTask(task) { + this.socketTask = task; + if (task.__ws_bound__) return; + task.__ws_bound__ = true; + + task.onOpen(res => { + this.log(this._label('onOpen'), res); + this.connected = true; + this.reconnectAttempts = 0; + this._lastPongAt = Date.now(); + this._startHeartbeat(); + this._flushQueue(); + this.options.onOpen && this.options.onOpen(res); + }); + + task.onMessage(msg => { + this._handleIncoming(msg); + }); + + task.onClose(res => { + this.log(this._label('onClose'), res); + this.connected = false; + this._stopHeartbeat(); + this.options.onClose && this.options.onClose(res); + this._tryReconnect(); + }); + + task.onError(err => { + this.log(this._label('onError'), err); + this.connected = false; + this._stopHeartbeat(); + this.options.onError && this.options.onError(err); + this._tryReconnect(); + }); + } + + // 处理收到的消息(task 或 全局都使用) + _handleIncoming(msg) { + this._lastPongAt = Date.now(); + let data = msg && msg.data; + if (this.options.parseJSON) { + try { data = JSON.parse(msg.data); } catch (e) { /* keep raw */ } + } + const type = (data && data.type) || '__default__'; + + // 过滤 ping/pong (若你想看 ping/pong,把 filterPing 设为 false) + const isPing = data && (data.type === 'ping' || data.type === 'pong' || (typeof data === 'string' && (data === 'ping' || data === 'pong'))); + if (isPing && this.options.filterPing) { + // 仍更新 lastPong,但不打印消息体,除非 showMessageBody 强制 true + if (this.options.debug && this.options.showMessageBody === false) { + this.log(this._label('onMessage'), type); + } + } else { + // 打印消息类型与(可选)格式化消息体 + if (this.options.debug) { + if (this.options.showMessageBody) { + let bodyStr; + try { bodyStr = JSON.stringify(data, null, 2); } catch (e) { bodyStr = String(data); } + this.log(this.options.lang === 'zh' ? `${this._label('onMessage')} (${type}):` : `${this._label('onMessage')} (${type}):`, '\n' + bodyStr); + } else { + this.log(this._label('onMessage'), type); + } + } + } + + // 外部回调与订阅异步派发 + if (this.options.onMessage) setTimeout(() => this.options.onMessage(data, msg), 0); + setTimeout(() => this._dispatch(type, data, msg), 0); + } + + _bindGlobalSocketEvents() { + // 解绑默认全局再绑定,避免重复 + try { uni.offSocketOpen && uni.offSocketOpen(); } catch (e) {} + try { uni.offSocketMessage && uni.offSocketMessage(); } catch (e) {} + try { uni.offSocketClose && uni.offSocketClose(); } catch (e) {} + try { uni.offSocketError && uni.offSocketError(); } catch (e) {} + + try { + uni.onSocketOpen(res => { + this.log(this._label('globalOnOpen'), res); + this.connected = true; + this.reconnectAttempts = 0; + this._lastPongAt = Date.now(); + this._startHeartbeat(); + this._flushQueue(); + this.options.onOpen && this.options.onOpen(res); + }); + } catch (e) {} + + try { + uni.onSocketMessage(msg => { + // 全局收到消息也走统一处理 + if (this.options.debug) this.log(this._label('globalOnMessage')); + this._handleIncoming(msg); + }); + } catch (e) {} + + try { + uni.onSocketClose(res => { + this.log(this._label('globalOnClose'), res); + this.connected = false; + this._stopHeartbeat(); + this.options.onClose && this.options.onClose(res); + this._tryReconnect(); + }); + } catch (e) {} + + try { + uni.onSocketError(err => { + this.log(this._label('globalOnError'), err); + this.connected = false; + this._stopHeartbeat(); + this.options.onError && this.options.onError(err); + this._tryReconnect(); + }); + } catch (e) {} + } + + _startHeartbeat() { + this._stopHeartbeat(); + if (!this.options.heartbeatInterval) return; + const pingData = typeof this.options.pingMessage === 'string' ? this.options.pingMessage : JSON.stringify(this.options.pingMessage); + this._hbTimer = setInterval(() => { + if (!this.connected) return; + try { this.send(pingData); } catch (e) { this.log('hb send err', e); } + clearTimeout(this._hbTimeoutTimer); + this._hbTimeoutTimer = setTimeout(() => { + const since = Date.now() - this._lastPongAt; + if (since > this.options.heartbeatTimeout) { + this.log(this._label('hbTimeout'), since); + try { this.close(); } catch (e) { this.log('hb close fail', e); } + } + }, this.options.heartbeatTimeout); + }, this.options.heartbeatInterval); + } + + _stopHeartbeat() { + if (this._hbTimer) { clearInterval(this._hbTimer); this._hbTimer = null; } + if (this._hbTimeoutTimer) { clearTimeout(this._hbTimeoutTimer); this._hbTimeoutTimer = null; } + } + + _tryReconnect() { + if (this.reconnectAttempts >= this.options.maxReconnectAttempts) { this.log(this._label('reconnectScheduled'), 'exhausted'); return; } + this.reconnectAttempts++; + const delay = Math.min(this.options.reconnectDelayBase * Math.pow(1.5, this.reconnectAttempts - 1), 30000); + this.log(this._label('reconnectScheduled'), delay + 'ms', 'attempt', this.reconnectAttempts); + setTimeout(() => { + this.socketTask = null; + this.connectIfForeground(); + }, delay); + } + + connectIfForeground() { + // 如果你在项目中有前台检测,这里可改为判断前台才 open() + this.open(); + } + + subscribe(type, handler) { + if (!this._subscriptions) this._subscriptions = new Map(); + if (!this._subscriptions.has(type)) this._subscriptions.set(type, new Set()); + this._subscriptions.get(type).add(handler); + return () => this.unsubscribe(type, handler); + } + + unsubscribe(type, handler) { + if (!this._subscriptions) return; + const s = this._subscriptions.get(type); if (!s) return; s.delete(handler); + if (s.size === 0) this._subscriptions.delete(type); + } + + _dispatch(type, data, raw) { + if (!this._subscriptions) return; + const handlers = this._subscriptions.get(type); + if (handlers) for (const h of Array.from(handlers)) try { h(data, raw); } catch (e) { this.log('handler err', e); } + const def = this._subscriptions.get('__default__'); + if (def) for (const h of Array.from(def)) try { h(data, raw); } catch (e) { this.log('default handler err', e); } + } +} + +export default WsRequest; diff --git a/common/websocketManager.js b/common/websocketManager.js new file mode 100644 index 0000000..9e1c30b --- /dev/null +++ b/common/websocketManager.js @@ -0,0 +1,27 @@ +// websocketManager.js +import WsRequest from '@/common/websocket.js'; + +let globalWs = null; + +const initWs = (url, options) => { + // 如果已经有 WebSocket 实例,直接返回 + if (globalWs) return globalWs; + + globalWs = new WsRequest(url, options); + return globalWs; +}; + +const connectWs = () => { + if (globalWs) { + globalWs.reconnectAttempts = 0; // 重置重连计数 + globalWs.open(); // 打开 WebSocket 连接 + } +}; + +const closeWs = () => { + if (globalWs) { + globalWs.close(); // 关闭 WebSocket 连接 + } +}; + +export { initWs, connectWs, closeWs }; diff --git a/component/public/exit.vue b/component/public/exit.vue index 486149e..8295eb0 100644 --- a/component/public/exit.vue +++ b/component/public/exit.vue @@ -53,6 +53,7 @@ } const go = () => { uni.setStorageSync('token', 1); + uni.setStorageSync('userInfo', null); uni.redirectTo({ url: '/pages/login/login' diff --git a/main.js b/main.js index 60486b4..a3c8d2c 100644 --- a/main.js +++ b/main.js @@ -1,40 +1,32 @@ -import App from './App' -// 引入 uView UI -import uView from './uni_modules/vk-uview-ui'; - - -// #ifndef VUE3 -import Vue from 'vue' -import './uni.promisify.adaptor' -Vue.config.productionTip = false -App.mpType = 'app' -const app = new Vue({ - ...App -}) -app.$mount() -// #endif - // #ifdef VUE3 -import { - createSSRApp -} from 'vue' +import { createSSRApp } from 'vue' +import App from './App' +import uView from './uni_modules/vk-uview-ui' import donghua from '@/component/public/donghua.vue' import errorshow from '@/component/public/errorshow.vue' -import tanchuang from '@/pages/procurement/components/tanchuang.vue'; -// import arrowkeys from '@/component/public/newgame/arrowkeys.vue' -export function createApp() { - const app = createSSRApp(App) +import tanchuang from '@/pages/procurement/components/tanchuang.vue' +// import WsRequest from '@/common/websocket.js' // default 导入,文件必须有 export default - // 使用 uView UI - app.use(uView) - app.component('donghua', donghua) - app.component('tanchuang', tanchuang) - app.component('errorshow', errorshow) - - - // app.component('arrowkeys', arrowkeys) - return { - app - } +export function createApp() { + const app = createSSRApp(App) + + // // 延后创建实例(构造不会阻塞主线程) + // const websocket = new WsRequest( + // 'wss://www.focusnu.com/ws101/sdWebsocket/1942419556028956674', + // { + // debug: true, + // heartbeatInterval: 25000 + // } + // ) + + // app.config.globalProperties.$socket = websocket + // app.provide('socket', websocket) + + app.use(uView) + app.component('donghua', donghua) + app.component('tanchuang', tanchuang) + app.component('errorshow', errorshow) + + return { app } } -// #endif \ No newline at end of file +// #endif diff --git a/pages/NursingNew/component/api.js b/pages/NursingNew/component/api.js index 09c5989..b53ad20 100644 --- a/pages/NursingNew/component/api.js +++ b/pages/NursingNew/component/api.js @@ -8,7 +8,13 @@ export const electricityMeterlist = () => { method: 'get' }) } - +// 获得护理单元主页大图 +export const queryWorkCareList = (data) => { + return request({ + url: `${uni.getStorageSync('serverUrl')}/api/pad/care/queryWorkCareList?workType=${data.workType}&employeeId=${data.employeeId}&nuId=${data.nuId}`, + method: 'get' + }) +} // 智能电表设备信息清零 export const electricityMetereleReset = (cid,address) => { return request({ diff --git a/pages/NursingNew/component/equipment.vue b/pages/NursingNew/component/equipment.vue index b437124..007532b 100644 --- a/pages/NursingNew/component/equipment.vue +++ b/pages/NursingNew/component/equipment.vue @@ -1,5 +1,5 @@