diff --git a/public/static/videos/01.webm b/public/static/videos/01.webm index 9f019980..92b557bc 100644 Binary files a/public/static/videos/01.webm and b/public/static/videos/01.webm differ diff --git a/src/views/base/dataBoard/index.vue b/src/views/base/dataBoard/index.vue index 54942d13..14699998 100644 --- a/src/views/base/dataBoard/index.vue +++ b/src/views/base/dataBoard/index.vue @@ -37,14 +37,17 @@ export default { return { isFullScreen: false, scaleNum: 0.8, - dataObj:{} + dataObj:{}, + sseReader: null, // 保存流读取器 + abortController: null, // 用于中止 fetch 请求 + retryCount: 0, // 当前重试次数 + isDestroyed: false // 标记组件是否已销毁 }; }, created() { this.init() }, mounted() { - console.log('dataBoard mounted') this.boxReset = debounce(() => { this.resetSize() }, 300) @@ -54,9 +57,9 @@ export default { }) this.getData() }, - destroyed() { - console.log('dataBoard destroyed') - }, + beforeDestroy() { + this.closeSSE(); + }, computed: { sidebarOpened() { return this.$store.state.app.sidebar.opened @@ -68,40 +71,76 @@ export default { } }, methods: { - getData() { + 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 headers = new Headers({ 'Authorization': `Bearer ${token}`, 'Content-Type': 'text/event-stream' }); - fetch(url, { headers }) - .then(response => { - const reader = response.body.getReader(); - const decoder = new TextDecoder(); - const readStream = () => { - reader.read().then(({ done, value }) => { - if (done) { - console.log('SSE 连接关闭'); - return; - } - const data = decoder.decode(value); - console.log('收到消息:', data); - if (_this.isValidData(data)){ - _this.upDateMsg(data); - } - readStream(); // 继续读取 - }).catch(error => { - console.error('SSE 读取错误:', error); - }); - }; - readStream(); - }) - .catch(error => { - console.error('SSE 连接失败:', error); - }); + try { + // 创建中止控制器 + 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(); + + // 持续读取流数据 + 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); + } + } + } 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 连接已强制关闭'); + }, + 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'); },