From 482f6392a6f29e90cd3e64768254269cbe582408 Mon Sep 17 00:00:00 2001 From: hzz Date: Fri, 22 Nov 2024 16:58:50 +0800 Subject: [PATCH] update --- .env.development | 4 +- .env.production | 6 +- package.json | 2 + src/components/ZdScrollBoard/index.vue | 480 ++++++++++++++++++ src/utils/websocket.js | 220 ++++++++ .../R_D_Environment/component/barChart.vue | 87 ++-- .../screen/R_D_Environment/component/item.vue | 2 + src/views/screen/R_D_Environment/index.vue | 155 +++++- vite.config.js | 4 +- 9 files changed, 881 insertions(+), 79 deletions(-) create mode 100644 src/components/ZdScrollBoard/index.vue create mode 100644 src/utils/websocket.js diff --git a/.env.development b/.env.development index c47ecb6..e409d34 100644 --- a/.env.development +++ b/.env.development @@ -7,4 +7,6 @@ VITE_APP_ENV = 'development' # 若依管理系统/开发环境 VITE_APP_BASE_API = '/dev-api' -VITE_PUBLIC_BASE_PATH= +VITE_PUBLIC_BASE_PATH = '' + +VITE_APP_WS_API = 'ws://8.141.87.86:9018/' diff --git a/.env.production b/.env.production index 5d7eb08..8e31db1 100644 --- a/.env.production +++ b/.env.production @@ -8,4 +8,8 @@ VITE_APP_ENV = 'production' VITE_APP_BASE_API = '/prod-api' # 是否在打包时开启压缩,支持 gzip 和 brotli -VITE_BUILD_COMPRESS = gzip \ No newline at end of file +VITE_BUILD_COMPRESS = gzip + +VITE_PUBLIC_BASE_PATH = '/' + +VITE_APP_WS_API = '' \ No newline at end of file diff --git a/package.json b/package.json index 0fe307e..06f885b 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,8 @@ }, "dependencies": { "@element-plus/icons-vue": "2.0.10", + "@iamzzg/data-view": "^2.10.0", + "@jiaminghi/data-view": "^2.10.0", "@vueup/vue-quill": "1.1.0", "@vueuse/core": "9.5.0", "axios": "0.27.2", diff --git a/src/components/ZdScrollBoard/index.vue b/src/components/ZdScrollBoard/index.vue new file mode 100644 index 0000000..9e8850b --- /dev/null +++ b/src/components/ZdScrollBoard/index.vue @@ -0,0 +1,480 @@ + + + + + \ No newline at end of file diff --git a/src/utils/websocket.js b/src/utils/websocket.js new file mode 100644 index 0000000..d0f2278 --- /dev/null +++ b/src/utils/websocket.js @@ -0,0 +1,220 @@ + +// websocket实例 +let wsObj = null +// ws连接地址 +let wsUrl = null +// let userId = null; +// 是否执行重连 true/不执行 ; false/执行 +let lockReconnect = false +// 重连定时器 +let wsCreateHandler = null +// 连接成功,执行回调函数 +let messageCallback = null +// 连接失败,执行回调函数 +let errorCallback = null +// 发送给后台的数据 +let sendDatas = {} +/** + * 发起websocket请求函数 + * @param {string} url ws连接地址 + * @param {Object} agentData 传给后台的参数 + * @param {function} successCallback 接收到ws数据,对数据进行处理的回调函数 + * @param {function} errCallback ws连接错误的回调函数 + */ +export const connectWebsocket = (url = null, agentData, successCallback, errCallback) => { + //console.log(process.env); + if (!url) { + if (import.meta.env.VITE_APP_ENV == "production") { + wsUrl = `ws://${window.document.location.hostname}:9018/` + } else { + console.log(import.meta.env,'11111111'); + wsUrl = import.meta.env.VITE_APP_WS_API + } + + } else { + wsUrl = url + } + console.log('socket地址:',wsUrl); + createWebSoket() + messageCallback = successCallback + errorCallback = errCallback + sendDatas = agentData +} + +// 手动关闭websocket (这里手动关闭会执行onclose事件) +export const closeWebsocket = () => { + if (wsObj) { + writeToScreen('手动关闭websocket') + wsObj.close() // 关闭websocket + wsObj.onclose() // 关闭websocket(如果上面的关闭不生效就加上这一条) + // 关闭重连 + lockReconnect = true + wsCreateHandler && clearTimeout(wsCreateHandler) + // 关闭心跳检查 + heartCheck.stop() + } +} + +// 创建ws函数 +const createWebSoket = () => { + if (typeof (WebSocket) === 'undefined') { + writeToScreen('您的浏览器不支持WebSocket,无法获取数据') + return false + } + // const host = window.location.host; + // userId = GetQueryString("userId"); + // wsUrl = "ws://" + host + "/websoket" + userId; + + try { + wsObj = new WebSocket(wsUrl) + initWsEventHandle() + } catch (e) { + writeToScreen('连接异常,开始重连') + reconnect() + } +} + +const initWsEventHandle = () => { + try { + // 连接成功 + wsObj.onopen = (event) => { + onWsOpen(event) + // heartCheck.start() + } + + // 监听服务器端返回的信息 + wsObj.onmessage = (event) => { + onWsMessage(event) + // heartCheck.start() + } + + wsObj.onclose = (event) => { + writeToScreen('onclose执行关闭事件') + onWsClose(event) + } + + wsObj.onerror = (event) => { + writeToScreen('onerror执行error事件,开始重连') + onWsError(event) + reconnect() + } + } catch (err) { + writeToScreen('绑定事件没有成功,开始重连') + reconnect() + } +} + +const onWsOpen = (event) => { + writeToScreen('CONNECT') + // // 客户端与服务器端通信 + // wsObj.send('我发送消息给服务端'); + // 添加状态判断,当为OPEN时,发送消息 + if (wsObj.readyState === wsObj.OPEN) { // wsObj.OPEN = 1 + // 发给后端的数据需要字符串化 + console.log('发送标识', sendDatas) + // wsObj.send(sendDatas) + } + if (wsObj.readyState === wsObj.CLOSED) { // wsObj.CLOSED = 3 + writeToScreen('wsObj.readyState=3, ws连接异常,开始重连') + reconnect() + errorCallback() + } +} +const onWsMessage = (event) => { + if (event.data) { + messageCallback(event.data) + } + // const jsonStr = event.data + // writeToScreen('onWsMessage接收到服务器的数据: ', jsonStr) +} +const onWsClose = (event) => { + writeToScreen('DISCONNECT') + // e.code === 1000 表示正常关闭。 无论为何目的而创建, 该链接都已成功完成任务。 + // e.code !== 1000 表示非正常关闭。 + console.log('onclose event: ', event) + if (event && event.code !== 1000) { + writeToScreen('非正常关闭') + errorCallback() + // 如果不是手动关闭,这里的重连会执行;如果调用了手动关闭函数,这里重连不会执行 + reconnect() + } +} +const onWsError = (event) => { + // writeToScreen('onWsError: ', event.data) + console.log(event); + + errorCallback() +} + +const writeToScreen = (massage) => { + console.log(massage) +} + +// 重连函数 +const reconnect = () => { + if (lockReconnect) { + return + } + writeToScreen('3秒后重连') + lockReconnect = true + // 没连接上会一直重连,设置延迟避免请求过多 + wsCreateHandler && clearTimeout(wsCreateHandler) + wsCreateHandler = setTimeout(() => { + writeToScreen('重连...' + wsUrl) + createWebSoket() + lockReconnect = false + writeToScreen('重连完成') + }, 3000) +} + +// 从浏览器地址中获取对应参数 +// eslint-disable-next-line no-unused-vars +const GetQueryString = (name) => { + let reg = new RegExp('(^|&)' + name + '=([^&]*)(&|$)', 'i') + // 获取url中 ? 符后的字符串并正则匹配 + let r = window.location.search.substr(1).match(reg) + let context = '' + r && (context = r[2]) + reg = null + r = null + return context +} + +// 心跳检查(看看websocket是否还在正常连接中) +const heartCheck = { + timeout: 60000, + timeoutObj: null, + serverTimeoutObj: null, + // 重启 + reset() { + clearTimeout(this.timeoutObj) + clearTimeout(this.serverTimeoutObj) + this.start() + }, + // 停止 + stop() { + clearTimeout(this.timeoutObj) + clearTimeout(this.serverTimeoutObj) + }, + // 开启定时器 + start() { + this.timeoutObj && clearTimeout(this.timeoutObj) + this.serverTimeoutObj && clearTimeout(this.serverTimeoutObj) + // 15s之内如果没有收到后台的消息,则认为是连接断开了,需要重连 + this.timeoutObj = setTimeout(() => { + writeToScreen('心跳检查,发送ping到后台') + try { + const datas = { ping: true } + wsObj.send(JSON.stringify(datas)) + } catch (err) { + writeToScreen('发送ping异常') + } + console.log('内嵌定时器this.serverTimeoutObj: ', this.serverTimeoutObj) + // 内嵌定时器 + this.serverTimeoutObj = setTimeout(() => { + writeToScreen('没有收到后台的数据,重新连接') + reconnect() + }, 100) + }, this.timeout) + } +} diff --git a/src/views/screen/R_D_Environment/component/barChart.vue b/src/views/screen/R_D_Environment/component/barChart.vue index 95f74a6..617f70a 100644 --- a/src/views/screen/R_D_Environment/component/barChart.vue +++ b/src/views/screen/R_D_Environment/component/barChart.vue @@ -35,11 +35,21 @@ const options = computed(() => { padding: 10, extraCssText: 'border-radius: 8px; box-shadow: 0 0 15px rgba(0, 0, 0, 0.5);' }, + backgroundColor: 'transparent', + legend: { + show: true, + data: ['目标产量', '实际产量'], + textStyle: { + color: '#fff', + + }, + }, xAxis: { type: 'category', - data: ['办公室1', '办公室2', '办公室3', '办公室4', '办公室5'], + data: ['102410', '102411', '102412', '102413', '102414', '102415', '102416', '102417'], axisLabel: { color: "#ffffff", + interval: 0, }, axisLine: { lineStyle: { @@ -47,58 +57,29 @@ const options = computed(() => { } }, axisTick: { - show: false + show: false, } }, - yAxis: [{ - name:'次', - type: 'value', - splitLine: { - show: false, - lineStyle: { - color: '#1e90ff', - type: 'dashed' + yAxis: [ + { + type: 'value', + axisTick: { + show: false } }, - axisLabel: { - color: "#ffffff" - }, - axisLine: { - lineStyle: { - color: '#0091ea' + { + type: 'value', + axisTick: { + show: false } - }, - axisTick: { - show: false } - }, { - name:'kWh', - type: 'value', - splitLine: { - show: false, - lineStyle: { - color: '#1e90ff', - type: 'dashed' - } - }, - axisLabel: { - color: "#ffffff" - }, - axisLine: { - lineStyle: { - color: '#0091ea' - } - }, - axisTick: { - show: false - } - }], + ], series: [ { - name: '开关次数', - data: [106, 90, 25, 0, 35], + name: '目标产量', + data: [150, 105, 110, 80, 120, 100, 140, 180], + yAxisIndex: 0, type: 'bar', - barWidth: '20', barCategoryGap: '10%', showBackground: true, itemStyle: { @@ -109,8 +90,8 @@ const options = computed(() => { x2: 1, y2: 1, colorStops: [ - { offset: 0, color: '#00c6ff' }, - { offset: 1, color: '#0072ff' } + { offset: 0, color: '#5aaef3' }, + { offset: 1, color: '#1e90ff' } ] }, borderRadius: [60, 60, 60, 60], @@ -127,11 +108,10 @@ const options = computed(() => { } }, { - name: '用电量', - data: [210, 30, 110, 80, 120], + name: '实际产量', yAxisIndex: 1, + data: [106, 90, 25, 80, 35, 100, 140, 172], type: 'bar', - barWidth: '20', barCategoryGap: '10%', showBackground: true, itemStyle: { @@ -142,12 +122,12 @@ const options = computed(() => { x2: 1, y2: 1, colorStops: [ - { offset: 0, color: '#00c6ff' }, - { offset: 1, color: '#0072ff' } + { offset: 0, color: '#f8efd4' }, + { offset: 1, color: '#fbdc7f' } ] }, borderRadius: [60, 60, 60, 60], - stroke: '#00c6ff', + stroke: '#ff6347', lineWidth: 2 }, label: { @@ -159,9 +139,8 @@ const options = computed(() => { borderRadius: [60, 60, 60, 60], } }, - ], - };; + } }); diff --git a/src/views/screen/R_D_Environment/component/item.vue b/src/views/screen/R_D_Environment/component/item.vue index 6e9c245..8cda8eb 100644 --- a/src/views/screen/R_D_Environment/component/item.vue +++ b/src/views/screen/R_D_Environment/component/item.vue @@ -35,9 +35,11 @@ const prop = defineProps({ color: #fff; } .pos-l { + // background: linear-gradient(to right, #102238 0%, transparent 100%); background: url('/src/assets/images/box-title-l.png') no-repeat center center / 100% 100%; } .pos-r { + // background: linear-gradient(to left, #102238 0%, transparent 100%); background: url('/src/assets/images/box-title-r.png') no-repeat center center / 100% 100%; text-align: right; padding-right: 25px; diff --git a/src/views/screen/R_D_Environment/index.vue b/src/views/screen/R_D_Environment/index.vue index ab1f4a4..548e2f0 100644 --- a/src/views/screen/R_D_Environment/index.vue +++ b/src/views/screen/R_D_Environment/index.vue @@ -20,10 +20,11 @@
-
- - +
+ + +
{{ item.value + item.unit }}
{{ item.name }}
@@ -55,6 +56,8 @@
+
@@ -83,7 +86,9 @@ import SvgTVOC from './component/svgTVOC.vue'; import SvgShidu from './component/svgShidu.vue'; import SvgWendu from './component/svgWendu.vue'; import SvgYanwu from './component/svgYanwu.vue'; -import { getNoiseData,getTopData,getSensorDateHourByType } from '@/api/screen/R_D_Environment'; +import ZdScrollBoard from "@/components/ZdScrollBoard/index.vue"; +import { connectWebsocket, closeWebsocket } from '@/utils/websocket'; +import { getNoiseData, getTopData, getSensorDateHourByType } from '@/api/screen/R_D_Environment'; let noiseDataList = ref([ { @@ -103,7 +108,7 @@ let sensor_list = reactive([ value: 20, unit: '℃', type: 'AirTemp_Reg', - limit: 30, + limit: 40, status: "true" }, { @@ -113,7 +118,7 @@ let sensor_list = reactive([ value: 20, unit: '%', type: 'AirHumi_Reg', - limit: 30, + limit: 90, status: "true" }, { @@ -123,7 +128,7 @@ let sensor_list = reactive([ value: 20, unit: 'mg/m³', type: 'CH2O', - limit: 30, + limit: 0.08, status: "true" }, { @@ -133,7 +138,7 @@ let sensor_list = reactive([ value: 20, unit: 'mg/m³', type: 'TVOC', - limit: 30, + limit: 0.5, status: "true" }, { @@ -142,7 +147,7 @@ let sensor_list = reactive([ component: SvgPm25, value: 20, unit: 'mg/m³', - type:'HIGH_PM25_Reg', + type: 'HIGH_PM25_Reg', limit: 30, status: "true" }, @@ -152,7 +157,7 @@ let sensor_list = reactive([ component: SvgPm10, value: 20, unit: 'mg/m³', - type:'HIGH_PM10_Reg', + type: 'HIGH_PM10_Reg', limit: 30, status: "true" }, @@ -162,8 +167,8 @@ let sensor_list = reactive([ component: SvgZaosheng, value: 20, unit: 'dB', - type:'Noise_Reg', - limit: 30, + type: 'Noise_Reg', + limit: 85, status: "true" }, { @@ -172,8 +177,8 @@ let sensor_list = reactive([ component: SvgYanwu, value: 20, unit: 'mg/m³', - type:'Smoke_Reg', - limit: 30, + type: 'Smoke_Reg', + limit: 100, status: "true" } ]) @@ -181,11 +186,28 @@ let dustData = reactive({ pm25: 0, pm10: 0, }) + +let zd_config = ref({ + header: ['报警时间', '报警内容', '报警位置'], + rowNum: 4, + data: [ + ['行1列1', '行1列2', '行1列3'], + ['行2列1', '行2列2', '行2列3'], + ['行3列1', '行3列2', '行3列3'], + ['行4列1', '行4列2', '行4列3'], + ['行5列1', '行5列2', '行5列3'], + ['行6列1', '行6列2', '行6列3'], + ['行7列1', '行7列2', '行7列3'], + ['行8列1', '行8列2', '行8列3'], + ['行9列1', '行9列2', '行9列3'], + ['行10列1', '行10列2', '行10列3'] + ] +}) //检测是否超标 function checkCb(item) { if (item.status === 'false') { return 'gray' - }else if (item.value > item.limit) { + } else if (item.value > item.limit) { return '#FF0000' } else { return '#469DE9' @@ -205,7 +227,7 @@ function getNoiseDataList() { function getTopDataList() { getTopData().then(res => { if (res.code === 200) { - res.data.forEach(item =>{ + res.data.forEach(item => { let index = sensor_list.findIndex(sensor => sensor.type === item.type) sensor_list[index].value = item.data sensor_list[index].id = item.id @@ -215,7 +237,7 @@ function getTopDataList() { dustData.pm25 = item.data } else if (item.type === 'HIGH_PM10_Reg') { dustData.pm10 = item.data - + } }) @@ -233,9 +255,97 @@ function getSensorData(type) { } +//socket +function getWebsocket(val) { + try { + let data = JSON.parse(val); + + if (data.type == "HUMI_TEMP") { + let obj = data.msg; + if (obj.hasOwnProperty('temp')) { + let index = sensor_list.findIndex(sensor => sensor.type === 'AirTemp_Reg' && sensor.id === obj.temp.devId) + if (index !== -1) { + sensor_list[index].value = obj.temp.value + sensor_list[index].status = "true" + } + + } + if (obj.hasOwnProperty('humi')) { + let index = sensor_list.findIndex(sensor => sensor.type === 'AirHumi_Reg' && sensor.id === obj.humi.devId) + if (index !== -1) { + sensor_list[index].value = obj.humi.value + sensor_list[index].status = "true" + } + + } + } + if (data.type == "TVOC_CH2O") { + let obj = data.msg; + if (obj.hasOwnProperty('CH2O')) { + let index = sensor_list.findIndex(sensor => sensor.type === 'CH2O' && sensor.id === obj.CH2O.devId) + if (index !== -1) { + sensor_list[index].value = obj.CH2O.value + sensor_list[index].status = "true" + } + + } + if (obj.hasOwnProperty('TVOC')) { + let index = sensor_list.findIndex(sensor => sensor.type === 'TVOC' && sensor.id === obj.TVOC.devId) + if (index !== -1) { + sensor_list[index].value = obj.TVOC.value + sensor_list[index].status = "true" + } + + } + } + //粉尘 + if (data.type == "dust") { + let obj = data.msg; + + + let index_pm25 = sensor_list.findIndex(sensor => sensor.type === 'HIGH_PM25_Reg' && sensor.id === obj.devId) + let index_pm10 = sensor_list.findIndex(sensor => sensor.type === 'HIGH_PM10_Reg' && sensor.id === obj.devId) + if (index_pm25 !== -1) { + sensor_list[index_pm25].value = obj.pm25 + sensor_list[index_pm25].status = "true" + dustData.pm25 = obj.pm25 + } + if (index_pm10 !== -1) { + sensor_list[index_pm10].value = obj.pm10 + sensor_list[index_pm10].status = "true" + dustData.pm10 = obj.pm10 + } + } + //噪音 + if (data.type === "NOISE") { + let obj = data.msg; + let list_index = noiseDataList.value.findIndex(item => item.devId === obj.noise.devId) + if (list_index !== -1) { + noiseDataList.value[list_index].data = obj.noise.value + } else { + let index = sensor_list.findIndex(sensor => sensor.type === 'Noise_Reg' && sensor.id === obj.noise.devId) + if (index !== -1) { + sensor_list[index].value = obj.noise.value + sensor_list[index].status = "true" + } + } + } + } catch (err) { + console.log(err); + } +} +function errWebsocket(val) { + // console.log(val); +} + + onMounted(() => { getNoiseDataList() getTopDataList() + + + + connectWebsocket(null, null, getWebsocket, errWebsocket); }); @@ -274,7 +384,8 @@ onMounted(() => { width: 410px; height: 980px; z-index: 2; - background: url('/src/assets/images/preview-left.png') center center / 100% 100% no-repeat; + background: linear-gradient(to left, transparent 0%, rgba(15, 32, 54, 0.8) 50%, #102238 100%); + // background: url('/src/assets/images/preview-left.png') center center / 100% 100% no-repeat; display: flex; flex-direction: column; justify-content: space-between; @@ -329,7 +440,9 @@ onMounted(() => { display: flex; z-index: 2; justify-content: space-between; - background: url('/src/assets/images/preview-bottom.png') center center / 100% 100% no-repeat; + + background: linear-gradient(to bottom, transparent 0%, rgba(15, 32, 54, 0.8) 50%, #102238 100%); + // background: url('/src/assets/images/preview-bottom.png') center center / 100% 100% no-repeat; .cbox { width: 48%; @@ -349,7 +462,7 @@ onMounted(() => { flex-direction: column; justify-content: space-between; align-items: center; - background: url('/src/assets/images/preview-right.png') center center / 100% 100% no-repeat; + background: linear-gradient(to right, transparent 0%, rgba(15, 32, 54, 0.8) 50%, #102238 100%); .rbox-item { width: 100%; diff --git a/vite.config.js b/vite.config.js index 97fdf4e..72fe564 100644 --- a/vite.config.js +++ b/vite.config.js @@ -31,8 +31,8 @@ export default defineConfig(({ mode, command }) => { proxy: { // https://cn.vitejs.dev/config/#server-proxy '/dev-api': { - // target: 'http://192.168.10.98:9015', - target: 'http://8.141.87.86:9015', + target: 'http://192.168.10.97:9015', + // target: 'http://8.141.87.86:9015', // target: 'http://192.168.110.90:10393/mock/5ce74738-f63f-4d21-af85-b1d132c6f6fd', changeOrigin: true, rewrite: (p) => p.replace(/^\/dev-api/, '')