This commit is contained in:
Teng 2026-01-19 17:35:31 +08:00
parent 5834d65356
commit 33e48294eb
10 changed files with 274 additions and 118 deletions

20
App.vue
View File

@ -41,19 +41,19 @@
},
onShow() {
console.log('App Show');
if (uni.getStorageSync('userInfo')) {
//
connectWs()
}
// console.log('App Show');
// if (uni.getStorageSync('userInfo')) {
// //
// connectWs()
// }
},
onHide() {
console.log('App Hide');
if (uni.getStorageSync('userInfo')) {
// socket
closeWs()
}
// console.log('App Hide');
// if (uni.getStorageSync('userInfo')) {
// // socket
// closeWs()
// }
}
}
</script>

View File

@ -2,15 +2,18 @@
// 带中文日志与消息体打印选项的 WsRequest替换你当前文件即可
class WsRequest {
// 静态:防止重复全局绑定
static _globalBound = false;
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
lang: 'zh', // 'zh' 或 'en'
showMessageBody: true,
filterPing: true,
heartbeatInterval: 30000,
heartbeatTimeout: 15000,
pingMessage: { type: 'ping' },
@ -30,7 +33,18 @@ class WsRequest {
this._hbTimeoutTimer = null;
this._subscriptions = new Map();
if (this.options.bindGlobal) this._bindGlobalSocketEvents();
// 是否允许自动重连(主动 close 时设为 false
this._shouldReconnect = true;
this._manualClose = false;
// 如果要求全局绑定且还没绑定过,则绑定一次(静态控制)
if (this.options.bindGlobal && !WsRequest._globalBound) {
this._bindGlobalSocketEvents();
WsRequest._globalBound = true;
} else if (this.options.bindGlobal && WsRequest._globalBound) {
// 已由其他实例绑定,跳过
}
if (this.options.autoConnect) setTimeout(() => this.open(), 0);
}
@ -69,10 +83,9 @@ class WsRequest {
return this.options.lang === 'zh' ? zh[key] || key : en[key] || key;
}
// 简单统一日志
// 简单统一日志(恢复 console 输出,便于调试)
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); }
@ -86,32 +99,63 @@ class WsRequest {
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);
// 主动 open允许后续自动重连如果被之前的手动关闭禁止过open 表示想要恢复)
this._shouldReconnect = true;
this._manualClose = false;
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') {
/**
* close(code, reason, manual)
* manual: 是否为手动/意图性关闭默认 true
* - manual === true 时将禁止自动重连用于用户主动关闭
* - manual === false 时视为异常或内部触发的关闭允许重连
*/
close(code = 1000, reason = 'client close', manual = true) {
// 标记手动关闭以禁止后续自动重连
if (manual) {
this._shouldReconnect = false;
this._manualClose = true;
} else {
// 非手动关闭(例如心跳超时)则保持 _shouldReconnect 原有值(通常为 true
this._manualClose = false;
}
this._stopHeartbeat();
try {
if (this.socketTask && typeof this.socketTask.close === 'function') {
try {
this.socketTask.close({ code, reason });
} catch (e) {
// 某些平台 socketTask.close 可能会抛错
this.log(this._label('closeError'), e);
// 尝试全局 close
if (typeof uni.closeSocket === 'function') uni.closeSocket();
}
} else if (typeof uni.closeSocket === 'function') {
uni.closeSocket();
}
} catch (e) {
this.log(this._label('closeError'), e);
}
this.connected = false;
// 清理 socketTask 引用,释放资源,防止重复使用导致 FD 泄漏
this.socketTask = null;
}
send(payload) {
@ -150,6 +194,10 @@ class WsRequest {
this._startHeartbeat();
this._flushQueue();
this.options.onOpen && this.options.onOpen(res);
// 打开时允许后续重连(如果连接成功)
this._shouldReconnect = true;
this._manualClose = false;
});
task.onMessage(msg => {
@ -161,6 +209,7 @@ class WsRequest {
this.connected = false;
this._stopHeartbeat();
this.options.onClose && this.options.onClose(res);
// 仅当允许重连时才触发重连逻辑
this._tryReconnect();
});
@ -182,15 +231,13 @@ class WsRequest {
}
const type = (data && data.type) || '__default__';
// 过滤 ping/pong (若你想看 ping/pong把 filterPing 设为 false
// 过滤 ping/pong
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;
@ -202,13 +249,12 @@ class WsRequest {
}
}
// 外部回调与订阅异步派发
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) {}
@ -223,12 +269,15 @@ class WsRequest {
this._startHeartbeat();
this._flushQueue();
this.options.onOpen && this.options.onOpen(res);
// 同上,打开时允许重连
this._shouldReconnect = true;
this._manualClose = false;
});
} catch (e) {}
try {
uni.onSocketMessage(msg => {
// 全局收到消息也走统一处理
if (this.options.debug) this.log(this._label('globalOnMessage'));
this._handleIncoming(msg);
});
@ -267,7 +316,8 @@ class WsRequest {
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); }
// 由心跳触发的关闭视为“异常/内部”关闭 — 传 manual = false 以允许重连
try { this.close(1000, 'hb timeout', false); } catch (e) { this.log('hb close fail', e); }
}
}, this.options.heartbeatTimeout);
}, this.options.heartbeatInterval);
@ -279,14 +329,26 @@ class WsRequest {
}
_tryReconnect() {
// 先检查是否允许自动重连(主动关闭时应被禁用)
if (!this._shouldReconnect) {
this.log(this._label('reconnectScheduled'), 'reconnect disabled (manual close)');
return;
}
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);
// 指数退避 + jitter避免同时大量重连
const base = this.options.reconnectDelayBase || 1000;
const delay = Math.min(base * Math.pow(1.5, this.reconnectAttempts - 1), 30000);
const jitter = Math.floor(Math.random() * 401) - 200; // -200 .. +200 ms
const finalDelay = Math.max(0, Math.floor(delay) + jitter);
this.log(this._label('reconnectScheduled'), finalDelay + 'ms', 'attempt', this.reconnectAttempts);
setTimeout(() => {
this.socketTask = null;
this.connectIfForeground();
}, delay);
}, finalDelay);
}
connectIfForeground() {

View File

@ -3,25 +3,49 @@ import WsRequest from '@/common/websocket.js';
let globalWs = null;
const initWs = (url, options) => {
// 如果已经有 WebSocket 实例,直接返回
if (globalWs) return globalWs;
const initWs = (url, options = {}) => {
// 如果已经有 WebSocket 实例且 URL 相同,直接返回
if (globalWs) {
if (globalWs.url === url) {
return globalWs;
}
// 否则先将旧实例以“手动关闭”方式关闭并清理,避免旧实例干扰
try {
globalWs._shouldReconnect = false;
globalWs.close(1000, 'reinit', true);
} catch (e) {
// 忽略关闭错误
}
globalWs = null;
}
globalWs = new WsRequest(url, options);
// 强烈建议:全局只 bind 一次WsRequest 内部已用静态保护)
const opt = Object.assign({ bindGlobal: true }, options);
globalWs = new WsRequest(url, opt);
return globalWs;
};
const connectWs = () => {
if (globalWs) {
globalWs.reconnectAttempts = 0; // 重置重连计数
globalWs.open(); // 打开 WebSocket 连接
}
if (!globalWs) return;
// 如果已经连接,直接返回,避免重复 open
if (globalWs.connected) return;
// 明确表示这是一次主动连接请求,允许自动重连
globalWs.reconnectAttempts = 0;
globalWs._shouldReconnect = true;
globalWs._manualClose = false;
globalWs.open();
};
const closeWs = () => {
if (globalWs) {
globalWs.close(); // 关闭 WebSocket 连接
const closeWs = (manual = true) => {
if (!globalWs) return;
// 主动关闭时先禁止自动重连
if (manual) {
globalWs._shouldReconnect = false;
}
// 传 manual 参数到 close以便内部区分是否允许重连
globalWs.close(1000, 'manager close', !!manual);
// 如果你想下次重新 init 时创建新实例,可以把 globalWs 置空
// globalWs = null;
};
export { initWs, connectWs, closeWs };

View File

@ -43,7 +43,7 @@
// String/Date/Number
const props = defineProps({
modelValue: {
type: [String, Date, Number, Object],
default: null
}
});

View File

@ -1186,7 +1186,7 @@
},200)
})
onHide(() => {
playall.value = false;
photoplay.value = false;
})
const filteredMenu = (index : number) => {
return leftMenuArray.value.filter(item => Number(item.areaFlag) - 1 == index);

View File

@ -1728,12 +1728,12 @@
}
const savePackagelist = ref([]);
onMounted(() => {
// if(uni.getStorageSync('elderId')===null){
// uni.setStorageSync('elderId', "");
// }
if(uni.getStorageSync('elderId')===null){
uni.setStorageSync('elderId', "");
}
savePackagelist.value = uni.getStorageSync('Packagelist') || []
let res = uni.getStorageSync('saveTree2')
console.log("啥啊啊啊啊",savePackagelist.value,res)
// console.log("",myArray)
let goodArray = []
myArray.forEach((element : any) => {

View File

@ -465,39 +465,60 @@
width: 100%;
height: 100rpx;
display: flex;
align-items: center;
/* align-items: center; */
/* background-color: red; */
.order-month {
margin-left: 30rpx;
font-size: 37rpx;
font-weight: 800;
width: 100rpx;
width: 105rpx;
/* background-color: green; */
height: 100rpx;
display: flex;
align-items: center;
position: relative;
.order-month-right{
position: absolute;
right: 0;
top: 50%;
transform: translateY(-50%);
height: 60%;
width: 10rpx;
background: linear-gradient(to right, #ececec, white);
}
}
.order-day {
width: 440rpx;
height: 100rpx;
/* background-color: red; */
.days-father {
display: flex;
align-items: center;
font-size: 31rpx;
margin-top: 20rpx;
.days {
height: 75rpx;
min-width: 75rpx;
height: 59rpx;
min-width: 59rpx;
margin: 0 8rpx;
display: flex;
justify-content: center;
align-items: center;
/* background-color: red; */
border-radius: 50%;
/* color: #BBBABA; */
}
.targetdays {
height: 75rpx;
min-width: 75rpx;
height: 59rpx;
min-width: 59rpx;
margin: 0 8rpx;
display: flex;
justify-content: center;
align-items: center;
@ -517,46 +538,51 @@
position: relative;
.history-spe {
min-width: 86%;
min-width: 88%;
height: 100%;
margin-left: 14%;
margin-left: 11%;
background-color: #F7F8F9;
border-radius: 25rpx;
padding-top: 10rpx;
padding-left: 20rpx;
/* position: relative; */
.history-left-jiao {
position: absolute;
right: 0;
top: 0;
width: 85rpx;
right: 30rpx;
top: 15rpx;
width: 100rpx;
height: 45rpx;
background-color: #D9E5FF;
background-color: #4690FF;
display: flex;
align-items: center;
justify-content: center;
border-top-right-radius: 25rpx;
border-bottom-left-radius: 25rpx;
color: #3C9CFD;
font-size: 19rpx;
border: 2rpx solid #4690FF;
margin-top: 20rpx;
margin-right: 30rpx;
border-radius: 10rpx;
color: #fff;
font-size: 27rpx;
font-weight: 600;
}
.history-left-jiao-error {
position: absolute;
right: 0;
top: 0;
width: 85rpx;
right: 30rpx;
top: 15rpx;
width: 100rpx;
height: 45rpx;
background-color: #FFEBEB;
display: flex;
align-items: center;
justify-content: center;
border-top-right-radius: 25rpx;
border-bottom-left-radius: 25rpx;
border: 2rpx solid #F76E6E;
margin-top: 20rpx;
margin-right: 30rpx;
border-radius: 10rpx;
color: #F76E6E;
font-size: 19rpx;
font-size: 27rpx;
font-weight: 600;
}
}
@ -564,27 +590,37 @@
/* background-color: red; */
.history-title {
width: 100%;
height: 70rpx;
height: 80rpx;
display: flex;
align-items: center;
padding: 0 30rpx;
justify-content: space-between;
.history-left {
font-size: 30rpx;
.history-blue{
width: 8rpx;
height: 25rpx;
margin-top: 8rpx;
border-radius: 10rpx;
background-color: #0089FE;
margin-right: 15rpx;
}
font-size: 33rpx;
margin-top: 10rpx;
display:flex;
position: relative;
}
.history-right {
margin-top: 0.5vw;
width: 7vw;
height: 2.5vw;
height: 2.8vw;
background: linear-gradient(to bottom, #009DEF, #0076FF);
display: flex;
justify-content: center;
align-items: center;
border-radius: 40rpx;
border-radius: 10rpx;
color: #fff;
font-size: 28rpx;
position: relative;
@ -594,13 +630,14 @@
.history-items {
width: 100%;
margin-top: 15rpx;
height: 440rpx;
height: 570rpx;
/* background-color: red; */
.history-item {
/* margin-left: 1%; */
width: 95%;
height: 120rpx;
margin-bottom: 13rpx;
height: 176rpx;
margin-bottom: 20rpx;
position: relative;
display: flex;
align-items: center;
@ -609,6 +646,7 @@
.history-left {
/* margin-left: 58rpx; */
width: 120rpx;
}
.history-right {
@ -655,10 +693,10 @@
.history-shu-up {
position: absolute;
left: 32rpx;
left: 33rpx;
bottom: calc(50% + 13rpx);
width: 3rpx;
height: 110rpx;
height: 173rpx;
background-color: #E5E5E5;
transition: height 0.3s ease;
@ -669,8 +707,8 @@
left: 20rpx;
top: 50%;
transform: translateY(-50%);
width: 26.5rpx;
height: 26.5rpx;
width: 30rpx;
height: 30rpx;
border-radius: 50%;
border: 4.5rpx solid #D0D1D1;
z-index: 1;
@ -681,30 +719,31 @@
.order-future {
width: 100%;
height: 640rpx;
margin-top: -12rpx;
height: 480rpx;
/* margin-top: -12rpx; */
position: relative;
/* background-color: red; */
.future-items {
width: 100%;
height: 100%;
.future-fonts {
font-size: 27rpx;
font-size: 30rpx;
display: flex;
}
.future-item-target {
width: 90%;
margin-left: 5%;
height: 152rpx;
margin-bottom: 10rpx;
height: 140rpx;
margin-bottom: 20rpx;
background-color: #F7F8F9;
border-radius: 30rpx;
/* padding: 25rpx; */
padding-top: 25rpx;
padding-top: 20rpx;
padding-left: 25rpx;
font-size: 25rpx;
font-size: 30rpx;
color: #555555;
position: relative;
border: 2rpx solid #4690FF;
@ -715,14 +754,14 @@
.future-item {
width: 90%;
margin-left: 5%;
height: 152rpx;
margin-bottom: 10rpx;
height: 140rpx;
margin-bottom: 20rpx;
background-color: #F7F8F9;
border-radius: 30rpx;
/* padding: 25rpx; */
padding-top: 25rpx;
padding-top: 20rpx;
padding-left: 25rpx;
font-size: 25rpx;
font-size: 30rpx;
color: #555555;
position: relative;
border: 2rpx solid #F7F8F9;
@ -732,13 +771,13 @@
.future-time {
width: 100%;
margin-top: 20rpx;
margin-top: 12rpx;
height: 50rpx;
display: flex;
align-items: center;
.time {
font-size: 48rpx;
font-size: 52rpx;
font-weight: 800;
color: black;
}
@ -747,7 +786,7 @@
}
}
.future-tag {
/* .future-tag {
position: absolute;
right: 23rpx;
top: 23rpx;
@ -761,7 +800,7 @@
display: flex;
justify-content: center;
align-items: center;
}
} */
.future-info {
position: absolute;
@ -876,14 +915,14 @@
.time-right-bad {
color: #666666;
font-size: 24rpx;
font-size: 27rpx;
margin-left: 10rpx;
margin-top: 10rpx;
}
.time-right-blue {
color: #4690FF;
font-size: 24rpx;
font-size: 27rpx;
margin-left: 10rpx;
margin-top: 10rpx;
display: flex;
@ -951,6 +990,7 @@
.serviceContent {
color: #999999;
margin-top: 20rpx;
font-size: 25rpx;
}
.weight-time {
@ -958,3 +998,7 @@
font-size: 32rpx;
margin-top: -5rpx;
}
.spec-shu{
margin: 0 7rpx;
color: #BABABA;
}

View File

@ -108,6 +108,9 @@
<view class="order-title">
<view class="order-month" @click="dateshow()">
{{ selectdata.month }}
<view class="order-month-right">
</view>
</view>
<scroll-view class="order-day" scroll-with-animation scroll-x :scroll-left="movetime">
<view class="days-father">
@ -128,7 +131,17 @@
<view class="card-over">
{{ item.directiveName }}
</view>
{{ ` | ` +item.serviceDuration + `分钟` }}
<view class="spec-shu">
|
</view>
<text style="color: #4690FF;">
每天
</text>
<view class="spec-shu">
|
</view>
{{ item.serviceDuration + `分钟` }}
</view>
<view class="future-time">
<text class="time">
@ -145,9 +158,9 @@
</view>
</view>
</view>
<view class="future-tag">
<!-- <view class="future-tag">
{{ item.cycleType }}
</view>
</view> -->
<view class="open-img-father" v-if="!item.tagtype"
@click.stop="clickfirstarray(item,index);"
:style="firstListTarget===index?{transform: `rotate(180deg)`}:{}">
@ -157,15 +170,24 @@
<view
style="position: absolute;top: 0;left: 0;transition: height 0.3s, top 0.3s;z-index: 9999; ;"
:class="moveById===item.id? `future-item-target`: `future-item`" :style="{
height: firstListTarget === index ? '320rpx' : '152rpx',
height: firstListTarget === index ? '300rpx' : '140rpx',
left: firstListTargetShow === index ? '0' : '999rpx',
top: index === upmenuarray.length - 1 && firstListTarget === index? `-168rpx`:`0rpx`
top: index === upmenuarray.length - 1 && firstListTarget === index? `-164rpx`:`0rpx`
}">
<view class="future-fonts">
<view class="card-over">
{{ item.directiveName }}
</view>
{{ ` | ` +item.serviceDuration + `分钟` }}
<view class="spec-shu">
|
</view>
<text style="color: #4690FF;">
每天
</text>
<view class="spec-shu">
|
</view>
{{ item.serviceDuration + `分钟` }}
</view>
<view class="future-time">
<text class="time">
@ -182,9 +204,7 @@
</view>
</view>
</view>
<view class="future-tag">
{{ item.cycleType }}
</view>
<view class="future-info" v-if="firstListTarget===index">
<view class="info" v-if="item.optType == 2">
<image class="info-img" src="/static/index/leftpeople.png" lazy-load />
@ -222,7 +242,9 @@
</view>
<view class="order-history">
<view class="history-title">
<view class="history-left">
<view class="history-blue"></view>
服务指令
</view>
<view class="history-right">
@ -241,7 +263,7 @@
<view class="history-left">
<view
:class="item.executeStatus!==`hisOk`?`history-left-jiao-error`: `history-left-jiao`"
style="top: 1.5rpx;right: 1.5rpx;"
v-show="secondListTargetShow !== index">
{{ item.rightshow }}
</view>
@ -262,7 +284,7 @@
<view class="history-spe"
style="position: absolute;top: 0;left: 0;transition: height 0.3s, top 0.3s;z-index: 9999; "
:style="{
height: secondListTarget === index ? '260rpx' : '120rpx',
height: secondListTarget === index ? '372rpx' : '176rpx',
left: secondListTargetShow === index ? '0' : '999rpx',
top: index === downmenuarray.length - 1 && secondListTarget === index ? '-140rpx' : '0rpx',
border: moveById === item.id ? '2rpx solid #46B2F6' : '2rpx solid transparent'
@ -270,7 +292,9 @@
<view class="history-left">
<view
:class="item.executeStatus!==`hisOk`?`history-left-jiao-error`: `history-left-jiao`"
v-show="secondListTargetShow === index">
v-show="secondListTargetShow === index"
>
{{ item.rightshow }}
</view>
<view class="history-time">
@ -509,12 +533,13 @@
}
daysarray.value = Array.from({ length: endDay }, (_, i) => String(i + 1).padStart(2, '0'))
// console.log("", daysarray.value)
movetime.value = (Number(selectdata.value.day) - 3) * 25
movetime.value = (Number(selectdata.value.day) - 3) * 37.5
}
const daytarget = ref(0)
const clickday = (item : string, index : number) => {
selectdata.value.day = item;
movetime.value = (index - 2) * 25
selectdata.value = JSON.parse(JSON.stringify(selectdata.value))
movetime.value = (index - 2) * 37.5
getTable()
}
const facedonghua = ref(false)
@ -822,8 +847,8 @@
getDirectiveOrders(time).then((data) => {
firstListTarget.value = -1;
secondListTarget.value = -1;
// console.log("", data.result.all)
// console.log("", data.result.future)
// console.log("", time,uni.getStorageSync('serverUrl'),uni.getStorageSync('nuId'),uni.getStorageSync('elderId'))
// console.log("", uni.getStorageSync('token'))
data.result.current.forEach((element : any) => {
element.tagtype = 0
})
@ -845,6 +870,7 @@
}
})
downmenuarray.value = [...data.result.history]
console.log("aaaaa", downmenuarray.value)
timearr.value = Array.from({ length: 24 }, (_, hour) => ({
positioning: hour.toString(),
children: minuteArr.map(() => ({

View File

@ -884,7 +884,7 @@
nuId:nuId
}
queryCountByType(data).then(res=>{
console.log(res)
// console.log(res)
hldyobj.value = res.result
})
}

View File

@ -141,7 +141,7 @@
if (len === 10) return '1.3vw' // 10
if (len < 10) return '1.5vw' // 10
if (len <= 15) return '1.1vw' // 11~15
return '0.9vw' // 15
return '0.9vw' // 151
})
onHide(() => {
playall.value = false;