diff --git a/package-lock.json b/package-lock.json index d67df19..73a6142 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "dependencies": { "@element-plus/icons-vue": "^2.3.1", "axios": "^1.18.1", + "echarts": "^6.1.0", "element-plus": "^2.9.7", "pinia": "^2.3.0", "vue": "^3.5.38", @@ -1287,6 +1288,22 @@ "node": ">= 0.4" } }, + "node_modules/echarts": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/echarts/-/echarts-6.1.0.tgz", + "integrity": "sha512-q0yaFPggC9FUdsWH4blavRWFmxdrIodbkoKNAjJudAI6CA9gNPxHtV2RcZNEepZVlk4yvBYkOkbk6HIVpIyHZA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "2.3.0", + "zrender": "6.1.0" + } + }, + "node_modules/echarts/node_modules/tslib": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz", + "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==", + "license": "0BSD" + }, "node_modules/element-plus": { "version": "2.14.2", "resolved": "https://registry.npmjs.org/element-plus/-/element-plus-2.14.2.tgz", @@ -2169,7 +2186,6 @@ "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -2842,6 +2858,21 @@ "integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==", "dev": true, "license": "MIT" + }, + "node_modules/zrender": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/zrender/-/zrender-6.1.0.tgz", + "integrity": "sha512-oEGMDB6pOP2S6OwRR4PdVv610zrjnA3Bh+JnSG12fYJlBKjtNAoEb5fSUoCOOINlH96I2fU38/A2UpRKs67xYQ==", + "license": "BSD-3-Clause", + "dependencies": { + "tslib": "2.3.0" + } + }, + "node_modules/zrender/node_modules/tslib": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz", + "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==", + "license": "0BSD" } } } diff --git a/package.json b/package.json index a4f5d19..00fe7aa 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "dependencies": { "@element-plus/icons-vue": "^2.3.1", "axios": "^1.18.1", + "echarts": "^6.1.0", "element-plus": "^2.9.7", "pinia": "^2.3.0", "vue": "^3.5.38", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 99001cb..4180b08 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -14,6 +14,9 @@ importers: axios: specifier: ^1.18.1 version: 1.18.1 + echarts: + specifier: ^6.1.0 + version: 6.1.0 element-plus: specifier: ^2.9.7 version: 2.14.2(vue@3.5.38) @@ -479,6 +482,9 @@ packages: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} + echarts@6.1.0: + resolution: {integrity: sha512-q0yaFPggC9FUdsWH4blavRWFmxdrIodbkoKNAjJudAI6CA9gNPxHtV2RcZNEepZVlk4yvBYkOkbk6HIVpIyHZA==} + element-plus@2.14.2: resolution: {integrity: sha512-eNH9uP3wQoNqieEIHXiNvIVv+zO5sZDU0CAZq5b0zqSN06DD0/V9xIq1R/qm3rw5k3nBTM1JvpxhCfRbaFLzDQ==} peerDependencies: @@ -844,6 +850,9 @@ packages: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} + tslib@2.3.0: + resolution: {integrity: sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==} + tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} @@ -959,6 +968,9 @@ packages: webpack-virtual-modules@0.6.2: resolution: {integrity: sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==} + zrender@6.1.0: + resolution: {integrity: sha512-oEGMDB6pOP2S6OwRR4PdVv610zrjnA3Bh+JnSG12fYJlBKjtNAoEb5fSUoCOOINlH96I2fU38/A2UpRKs67xYQ==} + snapshots: '@antfu/utils@0.7.10': {} @@ -1345,6 +1357,11 @@ snapshots: es-errors: 1.3.0 gopd: 1.2.0 + echarts@6.1.0: + dependencies: + tslib: 2.3.0 + zrender: 6.1.0 + element-plus@2.14.2(vue@3.5.38): dependencies: '@ctrl/tinycolor': 4.2.0 @@ -1694,6 +1711,8 @@ snapshots: dependencies: is-number: 7.0.0 + tslib@2.3.0: {} + tslib@2.8.1: optional: true @@ -1794,3 +1813,7 @@ snapshots: '@vue/shared': 3.5.38 webpack-virtual-modules@0.6.2: {} + + zrender@6.1.0: + dependencies: + tslib: 2.3.0 diff --git a/src/utils/fileHttp.js b/src/utils/fileHttp.js new file mode 100644 index 0000000..30007b7 --- /dev/null +++ b/src/utils/fileHttp.js @@ -0,0 +1,4 @@ +const fileHttp = { + ptApi: '10.23.22.43:8001' +} +export default fileHttp \ No newline at end of file diff --git a/src/utils/websocket.js b/src/utils/websocket.js index 277c8e5..9cb90fc 100644 --- a/src/utils/websocket.js +++ b/src/utils/websocket.js @@ -15,6 +15,7 @@ class WebSocketClient { this.heartbeatTimeout = options.heartbeatTimeout || 5000 // 心跳超时时间(ms) this.autoReconnect = options.autoReconnect !== false // 是否自动重连,默认 true this.debug = options.debug || false // 调试模式 + this.binaryType = options.binaryType || 'arraybuffer' // 二进制数据类型 this.ws = null // WebSocket 实例 this.reconnectTimes = 0 // 当前重连次数 @@ -80,6 +81,7 @@ class WebSocketClient { this.ws.onclose = this._onClose this.ws.onerror = this._onError this.ws.onmessage = this._onMessage + this.ws.binaryType = this.binaryType } catch (err) { console.error('[WebSocket] 创建连接失败:', err) this._emit('error', err) @@ -143,6 +145,18 @@ class WebSocketClient { return false } + /** + * 发送二进制数据(ArrayBuffer),用于文件切片上传等场景 + * @param {ArrayBuffer} buffer - 二进制数据 + */ + sendBinary(buffer) { + if (this.isConnected && this.ws && this.ws.readyState === WebSocket.OPEN) { + this.ws.send(buffer) + return true + } + return false + } + // ==================== 事件监听 ==================== on(event, callback) { @@ -173,7 +187,7 @@ class WebSocketClient { this._emit('open', event) // 发送认证信息(带 token) - this._sendAuthMessage() + // this._sendAuthMessage() // 发送待发送队列中的消息 this._flushPendingMessages() @@ -202,9 +216,14 @@ class WebSocketClient { const data = JSON.parse(event.data) this._log('收到消息:', data) + // 任何服务端消息都视为心跳信号,重置超时检测 + if (this.heartbeatCheckTimer) { + clearTimeout(this.heartbeatCheckTimer) + this.heartbeatCheckTimer = null + } + // 处理心跳回复 if (data.type === 'pong') { - this._handlePong() return } @@ -216,7 +235,11 @@ class WebSocketClient { this._emit('message', data) } catch { - // 非 JSON 格式,透传原始消息 + // 非 JSON 格式(如二进制文件chunk接收),也重置心跳 + if (this.heartbeatCheckTimer) { + clearTimeout(this.heartbeatCheckTimer) + this.heartbeatCheckTimer = null + } this._emit('message', event.data) } } @@ -242,6 +265,9 @@ class WebSocketClient { _startHeartbeat() { this._clearHeartbeat() + // heartbeatInterval 为 0 或非正值时关闭主动心跳 + if (!this.heartbeatInterval || this.heartbeatInterval <= 0) return + // 定时发送 ping this.heartbeatTimer = setInterval(() => { if (this.isConnected && this.ws) { @@ -259,15 +285,6 @@ class WebSocketClient { }, this.heartbeatInterval) } - _handlePong() { - this._log('收到心跳 pong') - // 清除超时检测 - if (this.heartbeatCheckTimer) { - clearTimeout(this.heartbeatCheckTimer) - this.heartbeatCheckTimer = null - } - } - _clearHeartbeat() { if (this.heartbeatTimer) { clearInterval(this.heartbeatTimer) @@ -282,7 +299,7 @@ class WebSocketClient { // ==================== 重连机制 ==================== _tryReconnect() { - if (this.isDestroyed) return + if (this.isDestroyed || !this.autoReconnect) return if (this.reconnectTimes >= this.maxReconnectTimes) { this._log('已达到最大重连次数,停止重连') diff --git a/src/views/home/components/ChartsPanel.vue b/src/views/home/components/ChartsPanel.vue new file mode 100644 index 0000000..d5d094f --- /dev/null +++ b/src/views/home/components/ChartsPanel.vue @@ -0,0 +1,173 @@ + + + + + + + + + + + + + + diff --git a/src/views/home/components/ControlButtons.vue b/src/views/home/components/ControlButtons.vue new file mode 100644 index 0000000..b91570b --- /dev/null +++ b/src/views/home/components/ControlButtons.vue @@ -0,0 +1,46 @@ + + + + {{ streamRunning ? '关闭视频流' : '启动视频流' }} + + + {{ algorithmRunning ? '关闭算法' : '启动算法' }} + + + + + + + diff --git a/src/views/home/components/CountsCard.vue b/src/views/home/components/CountsCard.vue new file mode 100644 index 0000000..13b35e3 --- /dev/null +++ b/src/views/home/components/CountsCard.vue @@ -0,0 +1,64 @@ + + + 检测计数 + + + {{ item.name }} + {{ item.count }} + + + + + + + + diff --git a/src/views/home/components/DetectionProgress.vue b/src/views/home/components/DetectionProgress.vue new file mode 100644 index 0000000..773766a --- /dev/null +++ b/src/views/home/components/DetectionProgress.vue @@ -0,0 +1,66 @@ + + + + + {{ statusText || '检测中...' }} + {{ progress.toFixed(1) }}% + + + + + + {{ paused ? '恢复处理' : '暂停处理' }} + + + + + + + + + diff --git a/src/views/home/components/FlowLog.vue b/src/views/home/components/FlowLog.vue new file mode 100644 index 0000000..0f149f4 --- /dev/null +++ b/src/views/home/components/FlowLog.vue @@ -0,0 +1,103 @@ + + + 流程日志 + + + {{ log.time }} + {{ log.message }} + + 暂无日志 + + + + + + + diff --git a/src/views/home/components/VideoPlayer.vue b/src/views/home/components/VideoPlayer.vue new file mode 100644 index 0000000..f4d8ef2 --- /dev/null +++ b/src/views/home/components/VideoPlayer.vue @@ -0,0 +1,104 @@ + + + + + + + + + + + + 暂无视频画面 + 点击上方「切换」加载实时流,或上传离线视频 + + + + + + + diff --git a/src/views/home/components/VideoToolbar.vue b/src/views/home/components/VideoToolbar.vue new file mode 100644 index 0000000..e89da34 --- /dev/null +++ b/src/views/home/components/VideoToolbar.vue @@ -0,0 +1,76 @@ + + + + 实时视频流: + + 切换 + + + + + + 离线视频 + + + + + + + + + diff --git a/src/views/home/index.vue b/src/views/home/index.vue index 71587f7..31f1474 100644 --- a/src/views/home/index.vue +++ b/src/views/home/index.vue @@ -1,167 +1,76 @@ - - - - - 实时视频流: - - 切换 - - - - - - 离线视频 - - - - - - - - - - - - - - - - - - - {{ formatTime(currentVideoTime) }} / {{ formatTime(videoDuration) }} - - - + - - - - 暂无视频画面 - 点击上方「切换」加载实时流,或上传离线视频 + + + {{ item.name }} + {{ item.count }} - - - - - {{ isPlaying ? '暂停' : '播放' }} - - + + + + + + + + - - - - - - {{ streamRunning ? '关闭视频流' : '启动视频流' }} - - - {{ algorithmRunning ? '关闭算法' : '启动算法' }} - - - - - - - 配件名称: - 充电线、充电头、隔板、信封... - - - 已检查配件数量: - - 充电线{{ partsCount.chargerLine }}个、充电头{{ partsCount.chargerHead }}个、隔板{{ partsCount.divider }}个、信封{{ partsCount.envelope }}个 - - - - - - - - 已完成的流程数量 - {{ completedCount }} - - - 未完成的流程数量 - {{ uncompletedCount }} - - - 查看流程详情 - - - - - - - - 流程日志 - - - {{ log.time }} - {{ log.message }} - - 暂无日志 - - - + + + + + + SOP 视频检测 WebSocket 前端测试 + 未连接 + + WebSocket 地址 + + + HTTP 服务地址,用于播放结果视频 + + + 选择要上传的视频 + + + + + conf + + + + iou + + + + skip + + + + max_frames,0 表示完整视频 + + + + + + 上传并开始检测 + 暂停处理 + 结束检测 + 关闭连接 + + + + + 实时进度 + + 0% + + + + 实时计数 + 暂无计数 + + + + 实时日志 + + + + + 处理后视频 + + + + + + + +
暂无视频画面
点击上方「切换」加载实时流,或上传离线视频
0%