diff --git a/src/api/order.js b/src/api/order.js index 6136038..fde84f8 100644 --- a/src/api/order.js +++ b/src/api/order.js @@ -107,4 +107,13 @@ method: 'get', params: query, }); +} + +// business/workOrder/listWorkOrderNextUser +export function listWorkOrderNextUser(query) { + return request({ + url: '/business/workOrder/listWorkOrderNextUser', + method: 'get', + params: query, + }); } \ No newline at end of file diff --git a/src/api/order.js b/src/api/order.js index 6136038..fde84f8 100644 --- a/src/api/order.js +++ b/src/api/order.js @@ -107,4 +107,13 @@ method: 'get', params: query, }); +} + +// business/workOrder/listWorkOrderNextUser +export function listWorkOrderNextUser(query) { + return request({ + url: '/business/workOrder/listWorkOrderNextUser', + method: 'get', + params: query, + }); } \ No newline at end of file diff --git a/src/main.js b/src/main.js index 9ee25c3..1a817a7 100644 --- a/src/main.js +++ b/src/main.js @@ -29,6 +29,9 @@ //权限控制 import './permission'; // permission control +// ws +import useSocket from '@/utils/ws/useSocket.js'; + import { useDict } from '@/utils/dict'; import 'amfe-flexible'; // rem 布局适配 @@ -98,6 +101,11 @@ app.component('svg-icon', SvgIcon); //全局注册 + +// WS 注入 +const socket = useSocket('mess_notice') +app.provide('useSocket', socket) + // 错误打印 app.config.errorHandler = (err, instance, info) => { // 处理错误,例如:报告给一个服务 diff --git a/src/api/order.js b/src/api/order.js index 6136038..fde84f8 100644 --- a/src/api/order.js +++ b/src/api/order.js @@ -107,4 +107,13 @@ method: 'get', params: query, }); +} + +// business/workOrder/listWorkOrderNextUser +export function listWorkOrderNextUser(query) { + return request({ + url: '/business/workOrder/listWorkOrderNextUser', + method: 'get', + params: query, + }); } \ No newline at end of file diff --git a/src/main.js b/src/main.js index 9ee25c3..1a817a7 100644 --- a/src/main.js +++ b/src/main.js @@ -29,6 +29,9 @@ //权限控制 import './permission'; // permission control +// ws +import useSocket from '@/utils/ws/useSocket.js'; + import { useDict } from '@/utils/dict'; import 'amfe-flexible'; // rem 布局适配 @@ -98,6 +101,11 @@ app.component('svg-icon', SvgIcon); //全局注册 + +// WS 注入 +const socket = useSocket('mess_notice') +app.provide('useSocket', socket) + // 错误打印 app.config.errorHandler = (err, instance, info) => { // 处理错误,例如:报告给一个服务 diff --git a/src/utils/ws/useSocket.js b/src/utils/ws/useSocket.js new file mode 100644 index 0000000..db1a1af --- /dev/null +++ b/src/utils/ws/useSocket.js @@ -0,0 +1,75 @@ +import { ref, watchEffect, watch } from 'vue'; +import useWebSocket, { SocketStatus } from './useWebSocket'; +// 用户的store,用于检测登录 +// import { useAdminStore } from '@stores/adminStore'; +import useUserStore from '@/store/modules/user'; + +import { getToken } from '@/utils/auth'; + +export default function useSocket(path) { + // window.location.host + // '58.51.132.86:8102' + + let url = + import.meta.env.VITE_APP_ENV == 'development' + ? 'ws://192.168.16.254:13002/' + '/mess_notice' + : 'ws://' + window.location.host + '/' + path; + const { status, message, error, connect, disconnect } = useWebSocket({ + url, + heartBeatData: 'ping', + token: getToken(), + }); + + const { userInfo } = storeToRefs(useUserStore()); + const chatMessage = ref(null); + const socketStatusText = ref(''); + + window.addEventListener('offline', function () { + console.log('网络连接已断开'); + }); + + window.addEventListener('online', function () { + console.log('网络连接已恢复'); + // 在网络连接恢复后执行的操作 + retryConnect(); + }); + + watch( + () => status.value, + newVal => { + if (newVal != SocketStatus.Connected) { + socketStatusText.value = newVal; + } + } + ); + + watch( + () => message.value, + newVal => { + if (newVal) { + chatMessage.value = newVal; + } + } + ); + + watchEffect(() => { + // 检测登录后就发起连接,退出后断开连接 + if (userInfo.value.userId) { + connect(); + } else { + disconnect(); + } + }); + + const retryConnect = () => { + if (status.value !== SocketStatus.Connected) { + connect(); + } + }; + + return { + socketStatusText, + chatMessage, + retryConnect, + }; +} diff --git a/src/api/order.js b/src/api/order.js index 6136038..fde84f8 100644 --- a/src/api/order.js +++ b/src/api/order.js @@ -107,4 +107,13 @@ method: 'get', params: query, }); +} + +// business/workOrder/listWorkOrderNextUser +export function listWorkOrderNextUser(query) { + return request({ + url: '/business/workOrder/listWorkOrderNextUser', + method: 'get', + params: query, + }); } \ No newline at end of file diff --git a/src/main.js b/src/main.js index 9ee25c3..1a817a7 100644 --- a/src/main.js +++ b/src/main.js @@ -29,6 +29,9 @@ //权限控制 import './permission'; // permission control +// ws +import useSocket from '@/utils/ws/useSocket.js'; + import { useDict } from '@/utils/dict'; import 'amfe-flexible'; // rem 布局适配 @@ -98,6 +101,11 @@ app.component('svg-icon', SvgIcon); //全局注册 + +// WS 注入 +const socket = useSocket('mess_notice') +app.provide('useSocket', socket) + // 错误打印 app.config.errorHandler = (err, instance, info) => { // 处理错误,例如:报告给一个服务 diff --git a/src/utils/ws/useSocket.js b/src/utils/ws/useSocket.js new file mode 100644 index 0000000..db1a1af --- /dev/null +++ b/src/utils/ws/useSocket.js @@ -0,0 +1,75 @@ +import { ref, watchEffect, watch } from 'vue'; +import useWebSocket, { SocketStatus } from './useWebSocket'; +// 用户的store,用于检测登录 +// import { useAdminStore } from '@stores/adminStore'; +import useUserStore from '@/store/modules/user'; + +import { getToken } from '@/utils/auth'; + +export default function useSocket(path) { + // window.location.host + // '58.51.132.86:8102' + + let url = + import.meta.env.VITE_APP_ENV == 'development' + ? 'ws://192.168.16.254:13002/' + '/mess_notice' + : 'ws://' + window.location.host + '/' + path; + const { status, message, error, connect, disconnect } = useWebSocket({ + url, + heartBeatData: 'ping', + token: getToken(), + }); + + const { userInfo } = storeToRefs(useUserStore()); + const chatMessage = ref(null); + const socketStatusText = ref(''); + + window.addEventListener('offline', function () { + console.log('网络连接已断开'); + }); + + window.addEventListener('online', function () { + console.log('网络连接已恢复'); + // 在网络连接恢复后执行的操作 + retryConnect(); + }); + + watch( + () => status.value, + newVal => { + if (newVal != SocketStatus.Connected) { + socketStatusText.value = newVal; + } + } + ); + + watch( + () => message.value, + newVal => { + if (newVal) { + chatMessage.value = newVal; + } + } + ); + + watchEffect(() => { + // 检测登录后就发起连接,退出后断开连接 + if (userInfo.value.userId) { + connect(); + } else { + disconnect(); + } + }); + + const retryConnect = () => { + if (status.value !== SocketStatus.Connected) { + connect(); + } + }; + + return { + socketStatusText, + chatMessage, + retryConnect, + }; +} diff --git a/src/utils/ws/useWebSocket.js b/src/utils/ws/useWebSocket.js new file mode 100644 index 0000000..6abb88c --- /dev/null +++ b/src/utils/ws/useWebSocket.js @@ -0,0 +1,192 @@ +import { ref } from 'vue'; + +const DEFAULT_OPTIONS = { + url: '', // websocket url + heartBeatData: '', // 你的心跳数据 + heartBeatStartInterval: 60 * 1000, // 心跳间隔,单位ms + heartBeatEndInterval: 5000, // 心跳间隔,单位ms 判断心跳是否成功时间 + reconnectInterval: 5000, // 断线重连间隔,单位ms + maxReconnectAttempts: 10, // 最大重连次数 +}; + +export const SocketStatus = { + Connecting: '正在连接...', //表示正在连接,这是初始状态。 + Connected: '连接已建立', //表示连接已经建立。 + Disconnecting: '连接正在关闭', //表示连接正在关闭。 + Disconnected: '连接已断开', //表示连接已经关闭 +}; + +const SocketCloseCode = 1000; + +export default function useWebSocket(options = {}) { + const state = { + options: { ...DEFAULT_OPTIONS, ...options }, + + socket: null, + reconnectAttempts: 0, // 尝试重连次数 + reconnectTimeout: null, + + heartBetaSendTimer: null, // 心跳发送定时器 + heartBetaTimeoutTimer: null, // 心跳超时定时器 + + // 心跳检测是否通过 + isHeartingSuccess: true, + }; + + // 连接状态 + const status = ref(SocketStatus.Disconnected); + const message = ref(null); + const error = ref(null); + + // 连接 + const connect = () => { + disconnect(); + + status.value = SocketStatus.Connecting; + if (!window.navigator.onLine) { + setTimeout(() => { + status.value = SocketStatus.Disconnected; + }, 500); + return; + } + + state.socket = new WebSocket(state.options.url, state.options.token); + + state.socket.onopen = openEvent => { + console.log('socket连接:', openEvent); + state.reconnectAttempts = 0; + status.value = SocketStatus.Connected; + error.value = null; + startHeartBeat(); + }; + + state.socket.onmessage = msgEvent => { + // console.log('socket消息:', msgEvent, state.isHeartingSuccess); + + // 收到任何数据,重新开始心跳 + // startHeartBeat() + + const { data } = msgEvent; + if (!state.isHeartingSuccess) { + if (data === 'pong') { + // console.log('🚀 ~ connect ~ msg1:', data); + state.isHeartingSuccess = true; + return; + } + } + const msg = JSON.parse(data); + // console.log('🚀 ~ connect ~ msg2:', msg); + + //心跳数据, 可自行修改 + // if (+msg.msg_id === 0) { + // return + // } + // if (data === 'pong') return; + message.value = msg; + }; + + state.socket.onclose = closeEvent => { + console.log('socket关闭:', closeEvent); + status.value = SocketStatus.Disconnected; + // 非正常关闭,尝试重连 + if (closeEvent.code !== SocketCloseCode) { + reconnect(); + } + }; + + state.socket.onerror = errEvent => { + console.log('socket报错:', errEvent); + status.value = SocketStatus.Disconnected; + error.value = errEvent; + // 连接失败,尝试重连 + reconnect(); + }; + }; + + const disconnect = () => { + if (state.socket && (state.socket.OPEN || state.socket.CONNECTING)) { + console.log('socket断开连接'); + status.value = SocketStatus.Disconnecting; + state.socket.onmessage = null; + state.socket.onerror = null; + state.socket.onclose = null; + // 发送关闭帧给服务端 + state.socket.close(SocketCloseCode, 'normal closure'); + status.value = SocketStatus.Disconnected; + state.socket = null; + } + stopHeartBeat(); + stopReconnect(); + }; + + const startHeartBeat = () => { + stopHeartBeat(); + onHeartBeat(() => { + if (status.value === SocketStatus.Connected) { + state.isHeartingSuccess = false; + state.socket.send(state.options.heartBeatData); + console.log('socket心跳发送:', state.options.heartBeatData); + } + }); + }; + + const onHeartBeat = callback => { + state.heartBetaSendTimer = setTimeout(() => { + callback && callback(); + state.heartBetaTimeoutTimer = setTimeout(() => { + // 心跳超时,直接关闭socket,抛出自定义code=4444, onclose里进行重连 + // state.socket.close(4444, 'heart timeout'); + + if (!state.isHeartingSuccess) { + state.socket.close(4444, 'heart timeout'); + state.isHeartingSuccess = true; + } else { + console.log('心跳检测成功,开始新一轮的心跳检测'); + startHeartBeat(); + } + }, state.options.heartBeatEndInterval); + }, state.options.heartBeatStartInterval); + }; + + const stopHeartBeat = () => { + state.heartBetaSendTimer && clearTimeout(state.heartBetaSendTimer); + state.heartBetaTimeoutTimer && clearTimeout(state.heartBetaTimeoutTimer); + }; + + // 重连 + const reconnect = () => { + if (status.value === SocketStatus.Connected || status.value === SocketStatus.Connecting) { + return; + } + stopHeartBeat(); + if (state.reconnectAttempts < state.options.maxReconnectAttempts) { + console.log('socket重连:', state.reconnectAttempts); + + // 重连间隔,5秒起步,下次递增1秒 + const interval = Math.max(state.options.reconnectInterval, state.reconnectAttempts * 1000); + console.log('间隔时间:', interval); + state.reconnectTimeout = setTimeout(() => { + if (status.value !== SocketStatus.Connected && status.value !== SocketStatus.Connecting) { + connect(); + } + }, interval); + state.reconnectAttempts += 1; + } else { + status.value = SocketStatus.Disconnected; + stopReconnect(); + } + }; + + // 停止重连 + const stopReconnect = () => { + state.reconnectTimeout && clearTimeout(state.reconnectTimeout); + }; + + return { + status, + message, + error, + connect, + disconnect, + }; +} diff --git a/src/api/order.js b/src/api/order.js index 6136038..fde84f8 100644 --- a/src/api/order.js +++ b/src/api/order.js @@ -107,4 +107,13 @@ method: 'get', params: query, }); +} + +// business/workOrder/listWorkOrderNextUser +export function listWorkOrderNextUser(query) { + return request({ + url: '/business/workOrder/listWorkOrderNextUser', + method: 'get', + params: query, + }); } \ No newline at end of file diff --git a/src/main.js b/src/main.js index 9ee25c3..1a817a7 100644 --- a/src/main.js +++ b/src/main.js @@ -29,6 +29,9 @@ //权限控制 import './permission'; // permission control +// ws +import useSocket from '@/utils/ws/useSocket.js'; + import { useDict } from '@/utils/dict'; import 'amfe-flexible'; // rem 布局适配 @@ -98,6 +101,11 @@ app.component('svg-icon', SvgIcon); //全局注册 + +// WS 注入 +const socket = useSocket('mess_notice') +app.provide('useSocket', socket) + // 错误打印 app.config.errorHandler = (err, instance, info) => { // 处理错误,例如:报告给一个服务 diff --git a/src/utils/ws/useSocket.js b/src/utils/ws/useSocket.js new file mode 100644 index 0000000..db1a1af --- /dev/null +++ b/src/utils/ws/useSocket.js @@ -0,0 +1,75 @@ +import { ref, watchEffect, watch } from 'vue'; +import useWebSocket, { SocketStatus } from './useWebSocket'; +// 用户的store,用于检测登录 +// import { useAdminStore } from '@stores/adminStore'; +import useUserStore from '@/store/modules/user'; + +import { getToken } from '@/utils/auth'; + +export default function useSocket(path) { + // window.location.host + // '58.51.132.86:8102' + + let url = + import.meta.env.VITE_APP_ENV == 'development' + ? 'ws://192.168.16.254:13002/' + '/mess_notice' + : 'ws://' + window.location.host + '/' + path; + const { status, message, error, connect, disconnect } = useWebSocket({ + url, + heartBeatData: 'ping', + token: getToken(), + }); + + const { userInfo } = storeToRefs(useUserStore()); + const chatMessage = ref(null); + const socketStatusText = ref(''); + + window.addEventListener('offline', function () { + console.log('网络连接已断开'); + }); + + window.addEventListener('online', function () { + console.log('网络连接已恢复'); + // 在网络连接恢复后执行的操作 + retryConnect(); + }); + + watch( + () => status.value, + newVal => { + if (newVal != SocketStatus.Connected) { + socketStatusText.value = newVal; + } + } + ); + + watch( + () => message.value, + newVal => { + if (newVal) { + chatMessage.value = newVal; + } + } + ); + + watchEffect(() => { + // 检测登录后就发起连接,退出后断开连接 + if (userInfo.value.userId) { + connect(); + } else { + disconnect(); + } + }); + + const retryConnect = () => { + if (status.value !== SocketStatus.Connected) { + connect(); + } + }; + + return { + socketStatusText, + chatMessage, + retryConnect, + }; +} diff --git a/src/utils/ws/useWebSocket.js b/src/utils/ws/useWebSocket.js new file mode 100644 index 0000000..6abb88c --- /dev/null +++ b/src/utils/ws/useWebSocket.js @@ -0,0 +1,192 @@ +import { ref } from 'vue'; + +const DEFAULT_OPTIONS = { + url: '', // websocket url + heartBeatData: '', // 你的心跳数据 + heartBeatStartInterval: 60 * 1000, // 心跳间隔,单位ms + heartBeatEndInterval: 5000, // 心跳间隔,单位ms 判断心跳是否成功时间 + reconnectInterval: 5000, // 断线重连间隔,单位ms + maxReconnectAttempts: 10, // 最大重连次数 +}; + +export const SocketStatus = { + Connecting: '正在连接...', //表示正在连接,这是初始状态。 + Connected: '连接已建立', //表示连接已经建立。 + Disconnecting: '连接正在关闭', //表示连接正在关闭。 + Disconnected: '连接已断开', //表示连接已经关闭 +}; + +const SocketCloseCode = 1000; + +export default function useWebSocket(options = {}) { + const state = { + options: { ...DEFAULT_OPTIONS, ...options }, + + socket: null, + reconnectAttempts: 0, // 尝试重连次数 + reconnectTimeout: null, + + heartBetaSendTimer: null, // 心跳发送定时器 + heartBetaTimeoutTimer: null, // 心跳超时定时器 + + // 心跳检测是否通过 + isHeartingSuccess: true, + }; + + // 连接状态 + const status = ref(SocketStatus.Disconnected); + const message = ref(null); + const error = ref(null); + + // 连接 + const connect = () => { + disconnect(); + + status.value = SocketStatus.Connecting; + if (!window.navigator.onLine) { + setTimeout(() => { + status.value = SocketStatus.Disconnected; + }, 500); + return; + } + + state.socket = new WebSocket(state.options.url, state.options.token); + + state.socket.onopen = openEvent => { + console.log('socket连接:', openEvent); + state.reconnectAttempts = 0; + status.value = SocketStatus.Connected; + error.value = null; + startHeartBeat(); + }; + + state.socket.onmessage = msgEvent => { + // console.log('socket消息:', msgEvent, state.isHeartingSuccess); + + // 收到任何数据,重新开始心跳 + // startHeartBeat() + + const { data } = msgEvent; + if (!state.isHeartingSuccess) { + if (data === 'pong') { + // console.log('🚀 ~ connect ~ msg1:', data); + state.isHeartingSuccess = true; + return; + } + } + const msg = JSON.parse(data); + // console.log('🚀 ~ connect ~ msg2:', msg); + + //心跳数据, 可自行修改 + // if (+msg.msg_id === 0) { + // return + // } + // if (data === 'pong') return; + message.value = msg; + }; + + state.socket.onclose = closeEvent => { + console.log('socket关闭:', closeEvent); + status.value = SocketStatus.Disconnected; + // 非正常关闭,尝试重连 + if (closeEvent.code !== SocketCloseCode) { + reconnect(); + } + }; + + state.socket.onerror = errEvent => { + console.log('socket报错:', errEvent); + status.value = SocketStatus.Disconnected; + error.value = errEvent; + // 连接失败,尝试重连 + reconnect(); + }; + }; + + const disconnect = () => { + if (state.socket && (state.socket.OPEN || state.socket.CONNECTING)) { + console.log('socket断开连接'); + status.value = SocketStatus.Disconnecting; + state.socket.onmessage = null; + state.socket.onerror = null; + state.socket.onclose = null; + // 发送关闭帧给服务端 + state.socket.close(SocketCloseCode, 'normal closure'); + status.value = SocketStatus.Disconnected; + state.socket = null; + } + stopHeartBeat(); + stopReconnect(); + }; + + const startHeartBeat = () => { + stopHeartBeat(); + onHeartBeat(() => { + if (status.value === SocketStatus.Connected) { + state.isHeartingSuccess = false; + state.socket.send(state.options.heartBeatData); + console.log('socket心跳发送:', state.options.heartBeatData); + } + }); + }; + + const onHeartBeat = callback => { + state.heartBetaSendTimer = setTimeout(() => { + callback && callback(); + state.heartBetaTimeoutTimer = setTimeout(() => { + // 心跳超时,直接关闭socket,抛出自定义code=4444, onclose里进行重连 + // state.socket.close(4444, 'heart timeout'); + + if (!state.isHeartingSuccess) { + state.socket.close(4444, 'heart timeout'); + state.isHeartingSuccess = true; + } else { + console.log('心跳检测成功,开始新一轮的心跳检测'); + startHeartBeat(); + } + }, state.options.heartBeatEndInterval); + }, state.options.heartBeatStartInterval); + }; + + const stopHeartBeat = () => { + state.heartBetaSendTimer && clearTimeout(state.heartBetaSendTimer); + state.heartBetaTimeoutTimer && clearTimeout(state.heartBetaTimeoutTimer); + }; + + // 重连 + const reconnect = () => { + if (status.value === SocketStatus.Connected || status.value === SocketStatus.Connecting) { + return; + } + stopHeartBeat(); + if (state.reconnectAttempts < state.options.maxReconnectAttempts) { + console.log('socket重连:', state.reconnectAttempts); + + // 重连间隔,5秒起步,下次递增1秒 + const interval = Math.max(state.options.reconnectInterval, state.reconnectAttempts * 1000); + console.log('间隔时间:', interval); + state.reconnectTimeout = setTimeout(() => { + if (status.value !== SocketStatus.Connected && status.value !== SocketStatus.Connecting) { + connect(); + } + }, interval); + state.reconnectAttempts += 1; + } else { + status.value = SocketStatus.Disconnected; + stopReconnect(); + } + }; + + // 停止重连 + const stopReconnect = () => { + state.reconnectTimeout && clearTimeout(state.reconnectTimeout); + }; + + return { + status, + message, + error, + connect, + disconnect, + }; +} diff --git a/src/views/oneMap/BIM/WangJiaPIng.vue b/src/views/oneMap/BIM/WangJiaPIng.vue index 207f181..d62ffc9 100644 --- a/src/views/oneMap/BIM/WangJiaPIng.vue +++ b/src/views/oneMap/BIM/WangJiaPIng.vue @@ -55,9 +55,9 @@ name: '静力水准仪', id: `YuanDian1`, position: { - x: 0, + x: -0.5, y: 0, - z: -0.8, + z: -0.5, }, icon: '/Three/icon/jlszy_icon.png', data: [], @@ -83,9 +83,9 @@ name: '静力水准仪', id: `YuanDian2`, position: { - x: 40, + x: -40, y: 0, - z: -0.8, + z: -0.5, }, icon: '/Three/icon/jlszy_icon.png', data: [], @@ -93,16 +93,16 @@ pointCode: 'qGxgnkX', dataTime: '', toP: { - x: 23.84115813826754, - y: 4.51261351630611, - z: 13.627431159473852, + x: -58.221584999469584, + y: 6.590211176632594, + z: 9.665579736149148, duration: 2, ease: 'power4.out', }, toC: { - x: 48.263595232463544, - y: 2.3904974662750225, - z: -3.6653763005726376, + x: 1, + y: 1, + z: -20, duration: 2, ease: 'power4.out', }, @@ -111,7 +111,7 @@ name: '应变计', id: `YuanDian3`, position: { - x: -37, + x: -37.5, y: 0, z: -0.2, }, @@ -162,9 +162,9 @@ name: '加速度计', id: `YuanDian5`, position: { - x: 0, + x: 0.5, y: 0, - z: -5, + z: -0.5, }, icon: '/Three/icon/jsdj_icon.png', data: [], @@ -190,9 +190,9 @@ name: '加速度计', id: `YuanDian6`, position: { - x: -37, + x: -36.5, y: 0, - z: -5, + z: -0.2, }, icon: '/Three/icon/jsdj_icon.png', data: [], @@ -214,6 +214,62 @@ ease: 'power4.out', }, }, + { + name: '风速风向仪', + id: `YuanDian7`, + position: { + x: -45.5, + y: 2.5, + z: -0.2, + }, + icon: '/Three/icon/fsfx_icon.png', + data: [], + show: false, + pointCode: '112', + dataTime: '', + toP: { + x: -58.221584999469584, + y: 6.590211176632594, + z: 9.665579736149148, + duration: 2, + ease: 'power4.out', + }, + toC: { + x: 1, + y: 1, + z: -20, + duration: 2, + ease: 'power4.out', + }, + }, + { + name: '裂缝计', + id: `YuanDian8`, + position: { + x: -43.5, + y: -0.3, + z: 2, + }, + icon: '/Three/icon/lfj_mx.png', + data: [], + show: false, + pointCode: '113', + dataTime: '', + toP: { + x: -58.221584999469584, + y: 6.590211176632594, + z: 9.665579736149148, + duration: 2, + ease: 'power4.out', + }, + toC: { + x: 1, + y: 1, + z: -20, + duration: 2, + ease: 'power4.out', + }, + }, ]); const deviceCode = ref(null); const stCode = ref(null); @@ -414,8 +470,8 @@ // 是否开启右键拖拽 Controls.value.enablePan = true; // AxesHelper:辅助观察的坐标系 - // const axesHelper = new THREE.AxesHelper(3000); - // Scene.add(axesHelper); + const axesHelper = new THREE.AxesHelper(3000); + Scene.add(axesHelper); }; /** @@ -518,20 +574,17 @@ if (data.name[0] == '静力水准仪') { // 摄像机位置 gsap.to(Camera.value.position, { - x: -17.414766616979897, - y: 7.132760656412787, - z: 23.413620521050024, - // x: -69.16984920750228, - // y: 9.22906292776404, - // z: 14.97197297303164, + x: -57.41155667894531, + y: 6.7641604399912865, + z: 11.781994826043844, duration: 3, ease: 'power4.out', }); // 视角 gsap.to(Controls.value.target, { - x: 12.660351499015919, - y: 3.159510377176017, - z: 0.8474660132082192, + x: -0.6994858490355302, + y: 0.5764769980367779, + z: -15.294633781993175, duration: 3, ease: 'power4.out', }); @@ -554,16 +607,46 @@ }); } else if (data.name.includes('加速度计')) { gsap.to(Camera.value.position, { - x: -54.128536618308566, - y: 7.793304901368697, - z: 11.677764722420267, + x: -52.91501071801578, + y: 7.683177659311091, + z: 13.794583117848436, duration: 3, ease: 'power4.out', }); gsap.to(Controls.value.target, { - x: -1.2994219650783805, - y: 0.5111824820704524, - z: -18.98688213983899, + x: -0.08589571801577686, + y: 0.40105465931109363, + z: -16.870063882151577, + duration: 3, + ease: 'power4.out', + }); + } else if (data.name.includes('风速风向仪')) { + gsap.to(Camera.value.position, { + x: -58.221584999469584, + y: 6.590211176632594, + z: 9.665579736149148, + duration: 3, + ease: 'power4.out', + }); + gsap.to(Controls.value.target, { + x: 1, + y: 1, + z: -20, + duration: 3, + ease: 'power4.out', + }); + } else if (data.name.includes('裂缝计')) { + gsap.to(Camera.value.position, { + x: -58.221584999469584, + y: 6.590211176632594, + z: 9.665579736149148, + duration: 3, + ease: 'power4.out', + }); + gsap.to(Controls.value.target, { + x: 1, + y: 1, + z: -20, duration: 3, ease: 'power4.out', }); diff --git a/src/api/order.js b/src/api/order.js index 6136038..fde84f8 100644 --- a/src/api/order.js +++ b/src/api/order.js @@ -107,4 +107,13 @@ method: 'get', params: query, }); +} + +// business/workOrder/listWorkOrderNextUser +export function listWorkOrderNextUser(query) { + return request({ + url: '/business/workOrder/listWorkOrderNextUser', + method: 'get', + params: query, + }); } \ No newline at end of file diff --git a/src/main.js b/src/main.js index 9ee25c3..1a817a7 100644 --- a/src/main.js +++ b/src/main.js @@ -29,6 +29,9 @@ //权限控制 import './permission'; // permission control +// ws +import useSocket from '@/utils/ws/useSocket.js'; + import { useDict } from '@/utils/dict'; import 'amfe-flexible'; // rem 布局适配 @@ -98,6 +101,11 @@ app.component('svg-icon', SvgIcon); //全局注册 + +// WS 注入 +const socket = useSocket('mess_notice') +app.provide('useSocket', socket) + // 错误打印 app.config.errorHandler = (err, instance, info) => { // 处理错误,例如:报告给一个服务 diff --git a/src/utils/ws/useSocket.js b/src/utils/ws/useSocket.js new file mode 100644 index 0000000..db1a1af --- /dev/null +++ b/src/utils/ws/useSocket.js @@ -0,0 +1,75 @@ +import { ref, watchEffect, watch } from 'vue'; +import useWebSocket, { SocketStatus } from './useWebSocket'; +// 用户的store,用于检测登录 +// import { useAdminStore } from '@stores/adminStore'; +import useUserStore from '@/store/modules/user'; + +import { getToken } from '@/utils/auth'; + +export default function useSocket(path) { + // window.location.host + // '58.51.132.86:8102' + + let url = + import.meta.env.VITE_APP_ENV == 'development' + ? 'ws://192.168.16.254:13002/' + '/mess_notice' + : 'ws://' + window.location.host + '/' + path; + const { status, message, error, connect, disconnect } = useWebSocket({ + url, + heartBeatData: 'ping', + token: getToken(), + }); + + const { userInfo } = storeToRefs(useUserStore()); + const chatMessage = ref(null); + const socketStatusText = ref(''); + + window.addEventListener('offline', function () { + console.log('网络连接已断开'); + }); + + window.addEventListener('online', function () { + console.log('网络连接已恢复'); + // 在网络连接恢复后执行的操作 + retryConnect(); + }); + + watch( + () => status.value, + newVal => { + if (newVal != SocketStatus.Connected) { + socketStatusText.value = newVal; + } + } + ); + + watch( + () => message.value, + newVal => { + if (newVal) { + chatMessage.value = newVal; + } + } + ); + + watchEffect(() => { + // 检测登录后就发起连接,退出后断开连接 + if (userInfo.value.userId) { + connect(); + } else { + disconnect(); + } + }); + + const retryConnect = () => { + if (status.value !== SocketStatus.Connected) { + connect(); + } + }; + + return { + socketStatusText, + chatMessage, + retryConnect, + }; +} diff --git a/src/utils/ws/useWebSocket.js b/src/utils/ws/useWebSocket.js new file mode 100644 index 0000000..6abb88c --- /dev/null +++ b/src/utils/ws/useWebSocket.js @@ -0,0 +1,192 @@ +import { ref } from 'vue'; + +const DEFAULT_OPTIONS = { + url: '', // websocket url + heartBeatData: '', // 你的心跳数据 + heartBeatStartInterval: 60 * 1000, // 心跳间隔,单位ms + heartBeatEndInterval: 5000, // 心跳间隔,单位ms 判断心跳是否成功时间 + reconnectInterval: 5000, // 断线重连间隔,单位ms + maxReconnectAttempts: 10, // 最大重连次数 +}; + +export const SocketStatus = { + Connecting: '正在连接...', //表示正在连接,这是初始状态。 + Connected: '连接已建立', //表示连接已经建立。 + Disconnecting: '连接正在关闭', //表示连接正在关闭。 + Disconnected: '连接已断开', //表示连接已经关闭 +}; + +const SocketCloseCode = 1000; + +export default function useWebSocket(options = {}) { + const state = { + options: { ...DEFAULT_OPTIONS, ...options }, + + socket: null, + reconnectAttempts: 0, // 尝试重连次数 + reconnectTimeout: null, + + heartBetaSendTimer: null, // 心跳发送定时器 + heartBetaTimeoutTimer: null, // 心跳超时定时器 + + // 心跳检测是否通过 + isHeartingSuccess: true, + }; + + // 连接状态 + const status = ref(SocketStatus.Disconnected); + const message = ref(null); + const error = ref(null); + + // 连接 + const connect = () => { + disconnect(); + + status.value = SocketStatus.Connecting; + if (!window.navigator.onLine) { + setTimeout(() => { + status.value = SocketStatus.Disconnected; + }, 500); + return; + } + + state.socket = new WebSocket(state.options.url, state.options.token); + + state.socket.onopen = openEvent => { + console.log('socket连接:', openEvent); + state.reconnectAttempts = 0; + status.value = SocketStatus.Connected; + error.value = null; + startHeartBeat(); + }; + + state.socket.onmessage = msgEvent => { + // console.log('socket消息:', msgEvent, state.isHeartingSuccess); + + // 收到任何数据,重新开始心跳 + // startHeartBeat() + + const { data } = msgEvent; + if (!state.isHeartingSuccess) { + if (data === 'pong') { + // console.log('🚀 ~ connect ~ msg1:', data); + state.isHeartingSuccess = true; + return; + } + } + const msg = JSON.parse(data); + // console.log('🚀 ~ connect ~ msg2:', msg); + + //心跳数据, 可自行修改 + // if (+msg.msg_id === 0) { + // return + // } + // if (data === 'pong') return; + message.value = msg; + }; + + state.socket.onclose = closeEvent => { + console.log('socket关闭:', closeEvent); + status.value = SocketStatus.Disconnected; + // 非正常关闭,尝试重连 + if (closeEvent.code !== SocketCloseCode) { + reconnect(); + } + }; + + state.socket.onerror = errEvent => { + console.log('socket报错:', errEvent); + status.value = SocketStatus.Disconnected; + error.value = errEvent; + // 连接失败,尝试重连 + reconnect(); + }; + }; + + const disconnect = () => { + if (state.socket && (state.socket.OPEN || state.socket.CONNECTING)) { + console.log('socket断开连接'); + status.value = SocketStatus.Disconnecting; + state.socket.onmessage = null; + state.socket.onerror = null; + state.socket.onclose = null; + // 发送关闭帧给服务端 + state.socket.close(SocketCloseCode, 'normal closure'); + status.value = SocketStatus.Disconnected; + state.socket = null; + } + stopHeartBeat(); + stopReconnect(); + }; + + const startHeartBeat = () => { + stopHeartBeat(); + onHeartBeat(() => { + if (status.value === SocketStatus.Connected) { + state.isHeartingSuccess = false; + state.socket.send(state.options.heartBeatData); + console.log('socket心跳发送:', state.options.heartBeatData); + } + }); + }; + + const onHeartBeat = callback => { + state.heartBetaSendTimer = setTimeout(() => { + callback && callback(); + state.heartBetaTimeoutTimer = setTimeout(() => { + // 心跳超时,直接关闭socket,抛出自定义code=4444, onclose里进行重连 + // state.socket.close(4444, 'heart timeout'); + + if (!state.isHeartingSuccess) { + state.socket.close(4444, 'heart timeout'); + state.isHeartingSuccess = true; + } else { + console.log('心跳检测成功,开始新一轮的心跳检测'); + startHeartBeat(); + } + }, state.options.heartBeatEndInterval); + }, state.options.heartBeatStartInterval); + }; + + const stopHeartBeat = () => { + state.heartBetaSendTimer && clearTimeout(state.heartBetaSendTimer); + state.heartBetaTimeoutTimer && clearTimeout(state.heartBetaTimeoutTimer); + }; + + // 重连 + const reconnect = () => { + if (status.value === SocketStatus.Connected || status.value === SocketStatus.Connecting) { + return; + } + stopHeartBeat(); + if (state.reconnectAttempts < state.options.maxReconnectAttempts) { + console.log('socket重连:', state.reconnectAttempts); + + // 重连间隔,5秒起步,下次递增1秒 + const interval = Math.max(state.options.reconnectInterval, state.reconnectAttempts * 1000); + console.log('间隔时间:', interval); + state.reconnectTimeout = setTimeout(() => { + if (status.value !== SocketStatus.Connected && status.value !== SocketStatus.Connecting) { + connect(); + } + }, interval); + state.reconnectAttempts += 1; + } else { + status.value = SocketStatus.Disconnected; + stopReconnect(); + } + }; + + // 停止重连 + const stopReconnect = () => { + state.reconnectTimeout && clearTimeout(state.reconnectTimeout); + }; + + return { + status, + message, + error, + connect, + disconnect, + }; +} diff --git a/src/views/oneMap/BIM/WangJiaPIng.vue b/src/views/oneMap/BIM/WangJiaPIng.vue index 207f181..d62ffc9 100644 --- a/src/views/oneMap/BIM/WangJiaPIng.vue +++ b/src/views/oneMap/BIM/WangJiaPIng.vue @@ -55,9 +55,9 @@ name: '静力水准仪', id: `YuanDian1`, position: { - x: 0, + x: -0.5, y: 0, - z: -0.8, + z: -0.5, }, icon: '/Three/icon/jlszy_icon.png', data: [], @@ -83,9 +83,9 @@ name: '静力水准仪', id: `YuanDian2`, position: { - x: 40, + x: -40, y: 0, - z: -0.8, + z: -0.5, }, icon: '/Three/icon/jlszy_icon.png', data: [], @@ -93,16 +93,16 @@ pointCode: 'qGxgnkX', dataTime: '', toP: { - x: 23.84115813826754, - y: 4.51261351630611, - z: 13.627431159473852, + x: -58.221584999469584, + y: 6.590211176632594, + z: 9.665579736149148, duration: 2, ease: 'power4.out', }, toC: { - x: 48.263595232463544, - y: 2.3904974662750225, - z: -3.6653763005726376, + x: 1, + y: 1, + z: -20, duration: 2, ease: 'power4.out', }, @@ -111,7 +111,7 @@ name: '应变计', id: `YuanDian3`, position: { - x: -37, + x: -37.5, y: 0, z: -0.2, }, @@ -162,9 +162,9 @@ name: '加速度计', id: `YuanDian5`, position: { - x: 0, + x: 0.5, y: 0, - z: -5, + z: -0.5, }, icon: '/Three/icon/jsdj_icon.png', data: [], @@ -190,9 +190,9 @@ name: '加速度计', id: `YuanDian6`, position: { - x: -37, + x: -36.5, y: 0, - z: -5, + z: -0.2, }, icon: '/Three/icon/jsdj_icon.png', data: [], @@ -214,6 +214,62 @@ ease: 'power4.out', }, }, + { + name: '风速风向仪', + id: `YuanDian7`, + position: { + x: -45.5, + y: 2.5, + z: -0.2, + }, + icon: '/Three/icon/fsfx_icon.png', + data: [], + show: false, + pointCode: '112', + dataTime: '', + toP: { + x: -58.221584999469584, + y: 6.590211176632594, + z: 9.665579736149148, + duration: 2, + ease: 'power4.out', + }, + toC: { + x: 1, + y: 1, + z: -20, + duration: 2, + ease: 'power4.out', + }, + }, + { + name: '裂缝计', + id: `YuanDian8`, + position: { + x: -43.5, + y: -0.3, + z: 2, + }, + icon: '/Three/icon/lfj_mx.png', + data: [], + show: false, + pointCode: '113', + dataTime: '', + toP: { + x: -58.221584999469584, + y: 6.590211176632594, + z: 9.665579736149148, + duration: 2, + ease: 'power4.out', + }, + toC: { + x: 1, + y: 1, + z: -20, + duration: 2, + ease: 'power4.out', + }, + }, ]); const deviceCode = ref(null); const stCode = ref(null); @@ -414,8 +470,8 @@ // 是否开启右键拖拽 Controls.value.enablePan = true; // AxesHelper:辅助观察的坐标系 - // const axesHelper = new THREE.AxesHelper(3000); - // Scene.add(axesHelper); + const axesHelper = new THREE.AxesHelper(3000); + Scene.add(axesHelper); }; /** @@ -518,20 +574,17 @@ if (data.name[0] == '静力水准仪') { // 摄像机位置 gsap.to(Camera.value.position, { - x: -17.414766616979897, - y: 7.132760656412787, - z: 23.413620521050024, - // x: -69.16984920750228, - // y: 9.22906292776404, - // z: 14.97197297303164, + x: -57.41155667894531, + y: 6.7641604399912865, + z: 11.781994826043844, duration: 3, ease: 'power4.out', }); // 视角 gsap.to(Controls.value.target, { - x: 12.660351499015919, - y: 3.159510377176017, - z: 0.8474660132082192, + x: -0.6994858490355302, + y: 0.5764769980367779, + z: -15.294633781993175, duration: 3, ease: 'power4.out', }); @@ -554,16 +607,46 @@ }); } else if (data.name.includes('加速度计')) { gsap.to(Camera.value.position, { - x: -54.128536618308566, - y: 7.793304901368697, - z: 11.677764722420267, + x: -52.91501071801578, + y: 7.683177659311091, + z: 13.794583117848436, duration: 3, ease: 'power4.out', }); gsap.to(Controls.value.target, { - x: -1.2994219650783805, - y: 0.5111824820704524, - z: -18.98688213983899, + x: -0.08589571801577686, + y: 0.40105465931109363, + z: -16.870063882151577, + duration: 3, + ease: 'power4.out', + }); + } else if (data.name.includes('风速风向仪')) { + gsap.to(Camera.value.position, { + x: -58.221584999469584, + y: 6.590211176632594, + z: 9.665579736149148, + duration: 3, + ease: 'power4.out', + }); + gsap.to(Controls.value.target, { + x: 1, + y: 1, + z: -20, + duration: 3, + ease: 'power4.out', + }); + } else if (data.name.includes('裂缝计')) { + gsap.to(Camera.value.position, { + x: -58.221584999469584, + y: 6.590211176632594, + z: 9.665579736149148, + duration: 3, + ease: 'power4.out', + }); + gsap.to(Controls.value.target, { + x: 1, + y: 1, + z: -20, duration: 3, ease: 'power4.out', }); diff --git a/src/views/oneMap/index.vue b/src/views/oneMap/index.vue index 63fa715..b6a4878 100644 --- a/src/views/oneMap/index.vue +++ b/src/views/oneMap/index.vue @@ -5,26 +5,16 @@
- {{ - userStore.ZhuanTiType == 1 - ? "延安市城市生命线安全运行监测系统" - : "延安市海绵监测平台" - }} + {{ userStore.ZhuanTiType == 1 ? '延安市城市生命线安全运行监测系统' : '延安市海绵监测平台' }}
- + 延安
@@ -43,15 +33,9 @@
- + - + @@ -90,11 +74,7 @@ :class="[showPanel ? 'TuChengImgTrue' : 'TuChengImgFalse']" v-if="AllData.MapShowBol" /> - + - + - + - + - - + - + - + - +
@@ -166,11 +117,7 @@