更新
This commit is contained in:
		@@ -37,7 +37,9 @@ export default {
 | 
			
		||||
        this.outputData.push(item.outputNum)
 | 
			
		||||
        this.goodRateData.push(item.goodRate)
 | 
			
		||||
      })
 | 
			
		||||
      this.$nextTick(()=>{
 | 
			
		||||
      this.initChart();
 | 
			
		||||
      })
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  data() {
 | 
			
		||||
@@ -281,4 +283,4 @@ export default {
 | 
			
		||||
.qhd-chart-tooltip * {
 | 
			
		||||
  color: #fff !important;
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
</style>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,17 +1,20 @@
 | 
			
		||||
<template>
 | 
			
		||||
	<div ref='dataBoardBoxB' class='dataBoardBoxB'>
 | 
			
		||||
		<div id="dataBoardBox" class='dataBoardBox' style="width: 1920px;height: 1080px;" :style="{ transform: 'scale(' + scaleNum + ')' }">
 | 
			
		||||
	<div ref="dataBoardBoxB" class="dataBoardBoxB">
 | 
			
		||||
		<div
 | 
			
		||||
			id="dataBoardBox"
 | 
			
		||||
			class="dataBoardBox"
 | 
			
		||||
			style="width: 1920px; height: 1080px"
 | 
			
		||||
			:style="{ transform: 'scale(' + scaleNum + ')' }">
 | 
			
		||||
			<DataBoardHeader
 | 
			
		||||
				:is-full-screen="isFullScreen"
 | 
			
		||||
				@screenfullChange="screenfullChange"
 | 
			
		||||
			/>
 | 
			
		||||
			<LeftTop :dataObj='dataObj'/>
 | 
			
		||||
			<LeftBottom :dataObj='dataObj'/>
 | 
			
		||||
			<CenterTop :scaleNum='scaleNum' :dataObj='dataObj'/>
 | 
			
		||||
			<CenterBottomL :dataObj='dataObj'/>
 | 
			
		||||
			<CenterBottomR :dataObj='dataObj'/>
 | 
			
		||||
			<RightTop :dataObj='dataObj'/>
 | 
			
		||||
			<RightBottom :dataObj='dataObj'/>
 | 
			
		||||
				@screenfullChange="screenfullChange" />
 | 
			
		||||
			<LeftTop :dataObj="dataObj" />
 | 
			
		||||
			<LeftBottom :dataObj="dataObj" />
 | 
			
		||||
			<CenterTop :scaleNum="scaleNum" :dataObj="dataObj" />
 | 
			
		||||
			<CenterBottomL :dataObj="dataObj" />
 | 
			
		||||
			<CenterBottomR :dataObj="dataObj" />
 | 
			
		||||
			<RightTop :dataObj="dataObj" />
 | 
			
		||||
			<RightBottom :dataObj="dataObj" />
 | 
			
		||||
		</div>
 | 
			
		||||
	</div>
 | 
			
		||||
</template>
 | 
			
		||||
@@ -28,212 +31,249 @@ import RightBottom from './components/RightBottom.vue';
 | 
			
		||||
import { debounce } from '@/utils/debounce';
 | 
			
		||||
import screenfull from 'screenfull';
 | 
			
		||||
import { getAccessToken } from '@/utils/auth';
 | 
			
		||||
import store from "@/store";
 | 
			
		||||
import store from '@/store';
 | 
			
		||||
export default {
 | 
			
		||||
	name: 'DataBoard',
 | 
			
		||||
	components: { DataBoardHeader,LeftTop,LeftBottom,CenterTop,CenterBottomL,CenterBottomR,RightTop,RightBottom },
 | 
			
		||||
	components: {
 | 
			
		||||
		DataBoardHeader,
 | 
			
		||||
		LeftTop,
 | 
			
		||||
		LeftBottom,
 | 
			
		||||
		CenterTop,
 | 
			
		||||
		CenterBottomL,
 | 
			
		||||
		CenterBottomR,
 | 
			
		||||
		RightTop,
 | 
			
		||||
		RightBottom,
 | 
			
		||||
	},
 | 
			
		||||
	props: {},
 | 
			
		||||
	data() {
 | 
			
		||||
		return {
 | 
			
		||||
			isFullScreen: false,
 | 
			
		||||
			scaleNum: 0.8,
 | 
			
		||||
			dataObj:{},
 | 
			
		||||
			sseReader: null,        // 保存流读取器
 | 
			
		||||
      abortController: null,  // 用于中止 fetch 请求
 | 
			
		||||
      retryCount: 0,          // 当前重试次数
 | 
			
		||||
      isDestroyed: false      // 标记组件是否已销毁
 | 
			
		||||
			dataObj: {},
 | 
			
		||||
			sseReader: null, // 保存流读取器
 | 
			
		||||
			abortController: null, // 用于中止 fetch 请求
 | 
			
		||||
			retryCount: 0, // 当前重试次数
 | 
			
		||||
			isDestroyed: false, // 标记组件是否已销毁
 | 
			
		||||
		};
 | 
			
		||||
	},
 | 
			
		||||
	created() {
 | 
			
		||||
    this.init()
 | 
			
		||||
  },
 | 
			
		||||
		this.init();
 | 
			
		||||
	},
 | 
			
		||||
	mounted() {
 | 
			
		||||
	  this.boxReset = debounce(() => {
 | 
			
		||||
      this.resetSize()
 | 
			
		||||
    }, 300)
 | 
			
		||||
    this.boxReset()
 | 
			
		||||
    window.addEventListener('resize', () => {
 | 
			
		||||
      this.boxReset()
 | 
			
		||||
    })
 | 
			
		||||
		this.getData()
 | 
			
		||||
		this.boxReset = debounce(() => {
 | 
			
		||||
			this.resetSize();
 | 
			
		||||
		}, 300);
 | 
			
		||||
		this.boxReset();
 | 
			
		||||
		window.addEventListener('resize', () => {
 | 
			
		||||
			this.boxReset();
 | 
			
		||||
		});
 | 
			
		||||
		this.getData();
 | 
			
		||||
	},
 | 
			
		||||
	beforeDestroy() {
 | 
			
		||||
    this.closeSSE();
 | 
			
		||||
  },
 | 
			
		||||
  destroyed() {
 | 
			
		||||
    window.removeEventListener('resize', this.boxReset);
 | 
			
		||||
  },
 | 
			
		||||
		this.closeSSE();
 | 
			
		||||
	},
 | 
			
		||||
	destroyed() {
 | 
			
		||||
		window.removeEventListener('resize', this.boxReset);
 | 
			
		||||
	},
 | 
			
		||||
	computed: {
 | 
			
		||||
		sidebarOpened() {
 | 
			
		||||
			return this.$store.state.app.sidebar.opened
 | 
			
		||||
		}
 | 
			
		||||
			return this.$store.state.app.sidebar.opened;
 | 
			
		||||
		},
 | 
			
		||||
	},
 | 
			
		||||
	watch: {
 | 
			
		||||
		sidebarOpened(newVal, oldVal) {
 | 
			
		||||
			this.boxReset()
 | 
			
		||||
		}
 | 
			
		||||
			this.boxReset();
 | 
			
		||||
		},
 | 
			
		||||
	},
 | 
			
		||||
	methods: {
 | 
			
		||||
		async getData() {
 | 
			
		||||
			let _this = this;
 | 
			
		||||
			if (_this.isDestroyed) return;
 | 
			
		||||
			const url = process.env.VUE_APP_BASE_API+'/admin-api/monitoring/message/subscribe/'+store.getters.userId+'-'+Date.now();
 | 
			
		||||
			const token = getAccessToken()
 | 
			
		||||
			const url =
 | 
			
		||||
				process.env.VUE_APP_BASE_API +
 | 
			
		||||
				'/admin-api/monitoring/message/subscribe/' +
 | 
			
		||||
				store.getters.userId +
 | 
			
		||||
				'-' +
 | 
			
		||||
				Date.now();
 | 
			
		||||
			const token = getAccessToken();
 | 
			
		||||
			const headers = new Headers({
 | 
			
		||||
        'Authorization': `Bearer ${token}`,
 | 
			
		||||
        'Content-Type': 'text/event-stream'
 | 
			
		||||
      });
 | 
			
		||||
				Authorization: `Bearer ${token}`,
 | 
			
		||||
				'Content-Type': 'text/event-stream',
 | 
			
		||||
			});
 | 
			
		||||
			try {
 | 
			
		||||
        // 创建中止控制器
 | 
			
		||||
        this.abortController = new AbortController();
 | 
			
		||||
        // 发起 fetch 请求(替换为你的接口地址)
 | 
			
		||||
        const response = await fetch(url, {
 | 
			
		||||
          method: 'GET',
 | 
			
		||||
          headers: headers,
 | 
			
		||||
          signal: _this.abortController.signal // 绑定中止信号
 | 
			
		||||
        });
 | 
			
		||||
				// 创建中止控制器
 | 
			
		||||
				this.abortController = new AbortController();
 | 
			
		||||
				// 发起 fetch 请求(替换为你的接口地址)
 | 
			
		||||
				const response = await fetch(url, {
 | 
			
		||||
					method: 'GET',
 | 
			
		||||
					headers: headers,
 | 
			
		||||
					signal: _this.abortController.signal, // 绑定中止信号
 | 
			
		||||
				});
 | 
			
		||||
 | 
			
		||||
        // 获取流读取器
 | 
			
		||||
        _this.sseReader = response.body.getReader();
 | 
			
		||||
        const decoder = new TextDecoder();
 | 
			
		||||
				// 获取流读取器
 | 
			
		||||
				_this.sseReader = response.body.getReader();
 | 
			
		||||
				const decoder = new TextDecoder();
 | 
			
		||||
				let buffer = '';
 | 
			
		||||
				let receivedBytes = 0;
 | 
			
		||||
 | 
			
		||||
        // 持续读取流数据
 | 
			
		||||
        while (true) {
 | 
			
		||||
          const { done, value } = await _this.sseReader.read();
 | 
			
		||||
          if (done) {
 | 
			
		||||
            console.log('SSE 连接正常关闭');
 | 
			
		||||
            _this.handleReconnect(); // 触发重连
 | 
			
		||||
            break;
 | 
			
		||||
          }
 | 
			
		||||
          // 处理 SSE 事件数据
 | 
			
		||||
          const data = decoder.decode(value);
 | 
			
		||||
          console.log('收到消息:', data);
 | 
			
		||||
					if (_this.isValidData(data)){
 | 
			
		||||
						_this.upDateMsg(data);
 | 
			
		||||
				// 持续读取流数据
 | 
			
		||||
				while (true) {
 | 
			
		||||
					const { done, value } = await _this.sseReader.read();
 | 
			
		||||
					if (done) {
 | 
			
		||||
						console.log('SSE 连接正常关闭');
 | 
			
		||||
						_this.handleReconnect(); // 触发重连
 | 
			
		||||
						break;
 | 
			
		||||
					}
 | 
			
		||||
        }
 | 
			
		||||
      } catch (error) {
 | 
			
		||||
        // 主动中止的请求不报错
 | 
			
		||||
        if (error.name === 'AbortError') return;
 | 
			
		||||
        console.error('SSE 连接异常:', error);
 | 
			
		||||
        _this.handleReconnect(); // 触发重连
 | 
			
		||||
      }
 | 
			
		||||
					// const data = decoder.decode(value);
 | 
			
		||||
					// console.log('收到消息:', data);
 | 
			
		||||
 | 
			
		||||
					receivedBytes += value.length;
 | 
			
		||||
					console.log(
 | 
			
		||||
						`收到数据块: ${value.length} 字节, 累计: ${receivedBytes} 字节`
 | 
			
		||||
					);
 | 
			
		||||
					const chunk = decoder.decode(value, { stream: true });
 | 
			
		||||
					buffer += chunk;
 | 
			
		||||
 | 
			
		||||
					// 处理完整的消息
 | 
			
		||||
					const messages = buffer.split('\n\n'); // SSE 消息以双换行分隔
 | 
			
		||||
					buffer = messages.pop() || ''; // 保留最后一个不完整的消息
 | 
			
		||||
 | 
			
		||||
					for (const message of messages) {
 | 
			
		||||
						if (_this.isValidData(message)) {
 | 
			
		||||
							// console.log('完整 SSE 消息:', message);
 | 
			
		||||
								_this.upDateMsg(message);
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
 | 
			
		||||
					// 处理 SSE 事件数据
 | 
			
		||||
					// const data = decoder.decode(value);
 | 
			
		||||
					// console.log('收到消息:', data);
 | 
			
		||||
					// if (_this.isValidData(data)){
 | 
			
		||||
					// 	_this.upDateMsg(data);
 | 
			
		||||
					// }
 | 
			
		||||
				}
 | 
			
		||||
			} catch (error) {
 | 
			
		||||
				// 主动中止的请求不报错
 | 
			
		||||
				if (error.name === 'AbortError') return;
 | 
			
		||||
				console.error('SSE 连接异常:', error);
 | 
			
		||||
				_this.handleReconnect(); // 触发重连
 | 
			
		||||
			}
 | 
			
		||||
		},
 | 
			
		||||
		closeSSE() {
 | 
			
		||||
      this.isDestroyed = true; // 标记销毁
 | 
			
		||||
      if (this.abortController) {
 | 
			
		||||
        this.abortController.abort(); // 中止 fetch 请求
 | 
			
		||||
      }
 | 
			
		||||
      if (this.sseReader) {
 | 
			
		||||
        this.sseReader.cancel();      // 关闭流读取器
 | 
			
		||||
        this.sseReader = null;
 | 
			
		||||
      }
 | 
			
		||||
      console.log('SSE 连接已强制关闭');
 | 
			
		||||
    },
 | 
			
		||||
			this.isDestroyed = true; // 标记销毁
 | 
			
		||||
			if (this.abortController) {
 | 
			
		||||
				this.abortController.abort(); // 中止 fetch 请求
 | 
			
		||||
			}
 | 
			
		||||
			if (this.sseReader) {
 | 
			
		||||
				this.sseReader.cancel(); // 关闭流读取器
 | 
			
		||||
				this.sseReader = null;
 | 
			
		||||
			}
 | 
			
		||||
			console.log('SSE 连接已强制关闭');
 | 
			
		||||
		},
 | 
			
		||||
		handleReconnect() {
 | 
			
		||||
      if (this.isDestroyed) return;
 | 
			
		||||
      // 指数退避策略(最大重试 5 次)
 | 
			
		||||
      const maxRetries = 5;
 | 
			
		||||
      if (this.retryCount < maxRetries) {
 | 
			
		||||
        const delay = Math.pow(2, this.retryCount) * 1000;
 | 
			
		||||
        setTimeout(() => {
 | 
			
		||||
          this.retryCount++;
 | 
			
		||||
          this.initSSE();
 | 
			
		||||
        }, delay);
 | 
			
		||||
      } else {
 | 
			
		||||
        console.error('SSE 重连次数已达上限');
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
		isValidData (data) {
 | 
			
		||||
		  return data.trim().startsWith('data:{') && !data.includes('heartbeat');
 | 
			
		||||
			if (this.isDestroyed) return;
 | 
			
		||||
			// 指数退避策略(最大重试 5 次)
 | 
			
		||||
			const maxRetries = 5;
 | 
			
		||||
			if (this.retryCount < maxRetries) {
 | 
			
		||||
				const delay = Math.pow(2, this.retryCount) * 1000;
 | 
			
		||||
				setTimeout(() => {
 | 
			
		||||
					this.retryCount++;
 | 
			
		||||
					this.initSSE();
 | 
			
		||||
				}, delay);
 | 
			
		||||
			} else {
 | 
			
		||||
				console.error('SSE 重连次数已达上限');
 | 
			
		||||
			}
 | 
			
		||||
		},
 | 
			
		||||
		isValidData(data) {
 | 
			
		||||
			return data.trim().startsWith('data:{') && !data.includes('heartbeat');
 | 
			
		||||
		},
 | 
			
		||||
		upDateMsg(data) {
 | 
			
		||||
      const jsonStr = data.replace(/^data:/, '').trim();
 | 
			
		||||
      console.log('jsonStr', jsonStr);
 | 
			
		||||
			const jsonStr = data.replace(/^data:/, '').trim();
 | 
			
		||||
			console.log('jsonStr', jsonStr);
 | 
			
		||||
 | 
			
		||||
			try {
 | 
			
		||||
				const dataObj = JSON.parse(jsonStr);
 | 
			
		||||
				this.dataObj = dataObj
 | 
			
		||||
				console.log('dataObj',this.dataObj)
 | 
			
		||||
				this.dataObj = dataObj;
 | 
			
		||||
				console.log('dataObj', this.dataObj);
 | 
			
		||||
			} catch (e) {
 | 
			
		||||
				console.error('JSON 解析失败:', e);
 | 
			
		||||
			}
 | 
			
		||||
		},
 | 
			
		||||
		change() {
 | 
			
		||||
      this.isFullScreen = screenfull.isFullscreen
 | 
			
		||||
    },
 | 
			
		||||
			this.isFullScreen = screenfull.isFullscreen;
 | 
			
		||||
		},
 | 
			
		||||
		init() {
 | 
			
		||||
      if (!screenfull.isEnabled) {
 | 
			
		||||
        this.$message({
 | 
			
		||||
          message: 'you browser can not work',
 | 
			
		||||
          type: 'warning'
 | 
			
		||||
        })
 | 
			
		||||
        return false
 | 
			
		||||
      }
 | 
			
		||||
      screenfull.on('change', this.change)
 | 
			
		||||
    },
 | 
			
		||||
    destroy() {
 | 
			
		||||
      if (!screenfull.isEnabled) {
 | 
			
		||||
        this.$message({
 | 
			
		||||
          message: 'you browser can not work',
 | 
			
		||||
          type: 'warning'
 | 
			
		||||
        })
 | 
			
		||||
        return false
 | 
			
		||||
      }
 | 
			
		||||
      screenfull.off('change', this.change)
 | 
			
		||||
    },
 | 
			
		||||
			if (!screenfull.isEnabled) {
 | 
			
		||||
				this.$message({
 | 
			
		||||
					message: 'you browser can not work',
 | 
			
		||||
					type: 'warning',
 | 
			
		||||
				});
 | 
			
		||||
				return false;
 | 
			
		||||
			}
 | 
			
		||||
			screenfull.on('change', this.change);
 | 
			
		||||
		},
 | 
			
		||||
		destroy() {
 | 
			
		||||
			if (!screenfull.isEnabled) {
 | 
			
		||||
				this.$message({
 | 
			
		||||
					message: 'you browser can not work',
 | 
			
		||||
					type: 'warning',
 | 
			
		||||
				});
 | 
			
		||||
				return false;
 | 
			
		||||
			}
 | 
			
		||||
			screenfull.off('change', this.change);
 | 
			
		||||
		},
 | 
			
		||||
		// 全屏
 | 
			
		||||
    screenfullChange() {
 | 
			
		||||
      if (!screenfull.isEnabled) {
 | 
			
		||||
        this.$message({
 | 
			
		||||
          message: 'you browser can not work',
 | 
			
		||||
          type: 'warning'
 | 
			
		||||
        })
 | 
			
		||||
        return false
 | 
			
		||||
      }
 | 
			
		||||
      screenfull.toggle(this.$refs.dataBoardBoxB)
 | 
			
		||||
    },
 | 
			
		||||
		screenfullChange() {
 | 
			
		||||
			if (!screenfull.isEnabled) {
 | 
			
		||||
				this.$message({
 | 
			
		||||
					message: 'you browser can not work',
 | 
			
		||||
					type: 'warning',
 | 
			
		||||
				});
 | 
			
		||||
				return false;
 | 
			
		||||
			}
 | 
			
		||||
			screenfull.toggle(this.$refs.dataBoardBoxB);
 | 
			
		||||
		},
 | 
			
		||||
		resetSize() {
 | 
			
		||||
      const dataBoardBox = document.getElementById('dataBoardBox')
 | 
			
		||||
      const rw = parseFloat(window.innerWidth)
 | 
			
		||||
			const rh = parseFloat(window.innerHeight)
 | 
			
		||||
      const bw = parseFloat(dataBoardBox.style.width)
 | 
			
		||||
      const bh = parseFloat(dataBoardBox.style.height)
 | 
			
		||||
      let wx = 0
 | 
			
		||||
			let hy = 0
 | 
			
		||||
      if (screenfull.isFullscreen) {
 | 
			
		||||
        wx = rw / bw
 | 
			
		||||
				hy = rh / bh
 | 
			
		||||
      } else {
 | 
			
		||||
        if (this.$store.state.app.sidebar.opened) {
 | 
			
		||||
          wx = (rw - 283) / bw
 | 
			
		||||
        } else {
 | 
			
		||||
          wx = (rw - 88) / bw
 | 
			
		||||
        }
 | 
			
		||||
				hy = (rh - 150) / bh
 | 
			
		||||
      }
 | 
			
		||||
      this.scaleNum = wx < hy ? wx : hy
 | 
			
		||||
    },
 | 
			
		||||
			const dataBoardBox = document.getElementById('dataBoardBox');
 | 
			
		||||
			const rw = parseFloat(window.innerWidth);
 | 
			
		||||
			const rh = parseFloat(window.innerHeight);
 | 
			
		||||
			const bw = parseFloat(dataBoardBox.style.width);
 | 
			
		||||
			const bh = parseFloat(dataBoardBox.style.height);
 | 
			
		||||
			let wx = 0;
 | 
			
		||||
			let hy = 0;
 | 
			
		||||
			if (screenfull.isFullscreen) {
 | 
			
		||||
				wx = rw / bw;
 | 
			
		||||
				hy = rh / bh;
 | 
			
		||||
			} else {
 | 
			
		||||
				if (this.$store.state.app.sidebar.opened) {
 | 
			
		||||
					wx = (rw - 283) / bw;
 | 
			
		||||
				} else {
 | 
			
		||||
					wx = (rw - 88) / bw;
 | 
			
		||||
				}
 | 
			
		||||
				hy = (rh - 150) / bh;
 | 
			
		||||
			}
 | 
			
		||||
			this.scaleNum = wx < hy ? wx : hy;
 | 
			
		||||
		},
 | 
			
		||||
	},
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style scoped lang="scss">
 | 
			
		||||
	.dataBoardBoxB {
 | 
			
		||||
		width: 100%;
 | 
			
		||||
		height: calc(100vh - 150px);
 | 
			
		||||
		position: relative;
 | 
			
		||||
.dataBoardBoxB {
 | 
			
		||||
	width: 100%;
 | 
			
		||||
	height: calc(100vh - 150px);
 | 
			
		||||
	position: relative;
 | 
			
		||||
	overflow: auto;
 | 
			
		||||
	.dataBoardBox {
 | 
			
		||||
		position: absolute;
 | 
			
		||||
		transform-origin: 16px 8px;
 | 
			
		||||
		font-size: 16px;
 | 
			
		||||
		top: 0px;
 | 
			
		||||
		left: 0px;
 | 
			
		||||
		background: url('../../../assets/images/dataBoard/background.png') no-repeat;
 | 
			
		||||
		background-size: cover;
 | 
			
		||||
		background-position: 0 0;
 | 
			
		||||
		overflow: auto;
 | 
			
		||||
		.dataBoardBox {
 | 
			
		||||
			position: absolute;
 | 
			
		||||
			transform-origin: 16px 8px;
 | 
			
		||||
			font-size: 16px;
 | 
			
		||||
			top: 0px;
 | 
			
		||||
			left: 0px;
 | 
			
		||||
			background: url('../../../assets/images/dataBoard/background.png') no-repeat;
 | 
			
		||||
			background-size: cover;
 | 
			
		||||
			background-position: 0 0;
 | 
			
		||||
			overflow: auto;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user