Showing
2 changed files
with
0 additions
and
636 deletions
docs/salesman-quotation-peer-websocket-integration.md
deleted
100644 → 0
| 1 | -# 同行报价信息 WebSocket 联调说明 | |
| 2 | - | |
| 3 | -## 1. 功能说明 | |
| 4 | - | |
| 5 | -本功能用于支撑业务员报价实时看板,采用 WebSocket 协议向前端实时推送“当日同行报价信息全量数据”。 | |
| 6 | - | |
| 7 | -注意: | |
| 8 | - | |
| 9 | -- 本功能为独立新增能力,不影响原有查询接口 | |
| 10 | -- 服务端仅推送“当日产生”的同行报价信息 | |
| 11 | -- 新增、修改成功并事务提交后,会向所有在线客户端广播最新当日全量数据 | |
| 12 | -- 前端断线重连后,可主动发起补推请求 | |
| 13 | - | |
| 14 | -## 2. WebSocket 地址 | |
| 15 | - | |
| 16 | -服务端路径: | |
| 17 | - | |
| 18 | -```text | |
| 19 | -/ws/procurement/salesmanQuotationPeer/realtime | |
| 20 | -``` | |
| 21 | - | |
| 22 | -联调示例: | |
| 23 | - | |
| 24 | -```text | |
| 25 | -ws://localhost:8080/ws/procurement/salesmanQuotationPeer/realtime | |
| 26 | -``` | |
| 27 | - | |
| 28 | -请将 `localhost:8080` 替换为实际后端服务地址。 | |
| 29 | - | |
| 30 | - | |
| 31 | -## 4. 服务端推送规则 | |
| 32 | - | |
| 33 | -### 4.1 建连后初始化推送 | |
| 34 | - | |
| 35 | -客户端连接成功后,服务端会立即回推一份“当日完整数据”: | |
| 36 | - | |
| 37 | -- `eventType = INIT` | |
| 38 | -- `messageType = FULL_SYNC` | |
| 39 | - | |
| 40 | -### 4.2 新增后广播 | |
| 41 | - | |
| 42 | -当日同行报价基础数据新增成功并事务提交后: | |
| 43 | - | |
| 44 | -- 向所有在线客户端广播 | |
| 45 | -- `eventType = CREATE` | |
| 46 | -- `messageType = FULL_SYNC` | |
| 47 | - | |
| 48 | -### 4.3 修改后广播 | |
| 49 | - | |
| 50 | -当日同行报价基础数据修改成功并事务提交后: | |
| 51 | - | |
| 52 | -- 向所有在线客户端广播 | |
| 53 | -- `eventType = UPDATE` | |
| 54 | -- `messageType = FULL_SYNC` | |
| 55 | - | |
| 56 | -### 4.4 断线重连补推 | |
| 57 | - | |
| 58 | -客户端重连成功后,可主动发送以下文本消息之一: | |
| 59 | - | |
| 60 | -- `SYNC_TODAY` | |
| 61 | -- `RESYNC` | |
| 62 | - | |
| 63 | -服务端收到后,会重新回推“当日完整数据”: | |
| 64 | - | |
| 65 | -- `eventType = RESYNC` | |
| 66 | -- `messageType = FULL_SYNC` | |
| 67 | - | |
| 68 | -## 5. 消息结构 | |
| 69 | - | |
| 70 | -### 5.1 服务端返回消息体 | |
| 71 | - | |
| 72 | -```json | |
| 73 | -{ | |
| 74 | - "messageType": "FULL_SYNC", | |
| 75 | - "eventType": "INIT", | |
| 76 | - "businessDate": "2026-07-10", | |
| 77 | - "pushTime": "2026-07-10T16:30:00", | |
| 78 | - "records": [ | |
| 79 | - { | |
| 80 | - "id": "1900000000000000001", | |
| 81 | - "peerName": "某同行", | |
| 82 | - "peerProductId": "1900000000000000002", | |
| 83 | - "peerProductName": "紫铜", | |
| 84 | - "peerProductCode": "ZT001", | |
| 85 | - "peerPrice": 76800.50, | |
| 86 | - "remark": "测试数据", | |
| 87 | - "createById": "1001", | |
| 88 | - "createBy": "张三", | |
| 89 | - "updateById": "1001", | |
| 90 | - "updateBy": "张三", | |
| 91 | - "createTime": "2026-07-10T16:28:10", | |
| 92 | - "updateTime": "2026-07-10T16:29:45" | |
| 93 | - } | |
| 94 | - ] | |
| 95 | -} | |
| 96 | -``` | |
| 97 | - | |
| 98 | -### 5.2 字段说明 | |
| 99 | - | |
| 100 | -| 字段 | 类型 | 说明 | | |
| 101 | -| --- | --- | --- | | |
| 102 | -| `messageType` | `String` | 当前固定为 `FULL_SYNC`,表示推送的是当日全量数据 | | |
| 103 | -| `eventType` | `String` | 事件类型,可能为 `INIT`、`CREATE`、`UPDATE`、`RESYNC` | | |
| 104 | -| `businessDate` | `LocalDate` | 当前业务日期 | | |
| 105 | -| `pushTime` | `LocalDateTime` | 本次推送时间 | | |
| 106 | -| `records` | `Array` | 当日同行报价信息列表 | | |
| 107 | - | |
| 108 | -`records` 中主要字段如下: | |
| 109 | - | |
| 110 | -| 字段 | 类型 | 说明 | | |
| 111 | -| --- | --- | --- | | |
| 112 | -| `id` | `String` | 主键 ID | | |
| 113 | -| `peerName` | `String` | 同行名称 | | |
| 114 | -| `peerProductId` | `String` | 同行品种 ID | | |
| 115 | -| `peerProductName` | `String` | 同行品种名称 | | |
| 116 | -| `peerProductCode` | `String` | 同行品种代号 | | |
| 117 | -| `peerPrice` | `BigDecimal` | 同行报价 | | |
| 118 | -| `remark` | `String` | 备注 | | |
| 119 | -| `createById` | `String` | 创建人 ID | | |
| 120 | -| `createBy` | `String` | 创建人 | | |
| 121 | -| `updateById` | `String` | 更新人 ID | | |
| 122 | -| `updateBy` | `String` | 更新人 | | |
| 123 | -| `createTime` | `LocalDateTime` | 创建时间 | | |
| 124 | -| `updateTime` | `LocalDateTime` | 更新时间 | | |
| 125 | - | |
| 126 | -## 6. 前端接入建议 | |
| 127 | - | |
| 128 | -建议前端采用“收到即全量覆盖”的方式刷新看板数据,不需要自己做增量合并。 | |
| 129 | - | |
| 130 | -推荐接入流程: | |
| 131 | - | |
| 132 | -1. 页面加载后先确认用户已登录 | |
| 133 | -2. 建立 WebSocket 连接 | |
| 134 | -3. 收到首包 `INIT` 后,用 `records` 初始化看板 | |
| 135 | -4. 收到 `CREATE`、`UPDATE` 后,直接用最新 `records` 覆盖页面数据 | |
| 136 | -5. 连接关闭后执行自动重连 | |
| 137 | -6. 重连成功后主动发送 `RESYNC` | |
| 138 | - | |
| 139 | -## 7. 联调自测建议 | |
| 140 | - | |
| 141 | -### 7.1 单连接测试 | |
| 142 | - | |
| 143 | -1. 使用已登录且有权限的账号打开测试页 | |
| 144 | -2. 连接 WebSocket | |
| 145 | -3. 确认收到 `INIT` 全量数据 | |
| 146 | - | |
| 147 | -### 7.2 多客户端广播测试 | |
| 148 | - | |
| 149 | -1. 同时打开 2 个或以上客户端连接 | |
| 150 | -2. 调用新增接口新增一条当日同行报价 | |
| 151 | -3. 检查所有客户端是否都收到 `CREATE` | |
| 152 | -4. 调用修改接口修改一条当日同行报价 | |
| 153 | -5. 检查所有客户端是否都收到 `UPDATE` | |
| 154 | - | |
| 155 | -### 7.3 断线重连测试 | |
| 156 | - | |
| 157 | -1. 建立连接并收到初始化数据 | |
| 158 | -2. 手动断开连接或模拟网络波动 | |
| 159 | -3. 重连成功后发送 `RESYNC` | |
| 160 | -4. 检查是否重新收到当日全量数据 | |
| 161 | - | |
| 162 | -### 7.4 回归验证 | |
| 163 | - | |
| 164 | -联调过程中同步回归以下原有能力: | |
| 165 | - | |
| 166 | -- 同行报价信息原分页查询接口 | |
| 167 | -- 同行报价信息详情接口 | |
| 168 | -- 同行报价信息新增接口 | |
| 169 | -- 同行报价信息修改接口 | |
| 170 | - | |
| 171 | -确认新增 WebSocket 能力未影响原有接口行为。 | |
| 172 | - | |
| 173 | -## 8. 本地测试页 | |
| 174 | - | |
| 175 | -仓库中已提供本地测试页: | |
| 176 | - | |
| 177 | -```text | |
| 178 | -docs/salesman-quotation-peer-websocket-test.html | |
| 179 | -``` | |
| 180 | - | |
| 181 | -可直接使用浏览器打开。 | |
| 182 | - | |
| 183 | -注意: | |
| 184 | - | |
| 185 | -- 建议使用已登录系统的同一浏览器用户环境测试 | |
| 186 | -- 如果浏览器环境没有把登录态带到 WebSocket 握手中,连接会返回 `403` | |
| 187 | -- 如出现该情况,请优先在已登录业务系统页面同浏览器环境中测试 |
docs/salesman-quotation-peer-websocket-test.html
deleted
100644 → 0
| 1 | -<!DOCTYPE html> | |
| 2 | -<html lang="zh-CN"> | |
| 3 | -<head> | |
| 4 | - <meta charset="UTF-8"> | |
| 5 | - <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| 6 | - <title>同行报价信息 WebSocket 测试页</title> | |
| 7 | - <style> | |
| 8 | - body { | |
| 9 | - margin: 0; | |
| 10 | - font-family: Arial, sans-serif; | |
| 11 | - background: #f5f7fa; | |
| 12 | - color: #1f2329; | |
| 13 | - } | |
| 14 | - | |
| 15 | - .page { | |
| 16 | - max-width: 1200px; | |
| 17 | - margin: 0 auto; | |
| 18 | - padding: 24px; | |
| 19 | - } | |
| 20 | - | |
| 21 | - .card { | |
| 22 | - background: #fff; | |
| 23 | - border-radius: 8px; | |
| 24 | - box-shadow: 0 2px 12px rgba(31, 35, 41, 0.08); | |
| 25 | - padding: 16px; | |
| 26 | - margin-bottom: 16px; | |
| 27 | - } | |
| 28 | - | |
| 29 | - .title { | |
| 30 | - margin: 0 0 12px; | |
| 31 | - font-size: 20px; | |
| 32 | - font-weight: 700; | |
| 33 | - } | |
| 34 | - | |
| 35 | - .desc { | |
| 36 | - margin: 0; | |
| 37 | - line-height: 1.7; | |
| 38 | - color: #4e5969; | |
| 39 | - } | |
| 40 | - | |
| 41 | - .row { | |
| 42 | - display: flex; | |
| 43 | - gap: 12px; | |
| 44 | - flex-wrap: wrap; | |
| 45 | - margin-bottom: 12px; | |
| 46 | - } | |
| 47 | - | |
| 48 | - .field { | |
| 49 | - flex: 1; | |
| 50 | - min-width: 280px; | |
| 51 | - } | |
| 52 | - | |
| 53 | - label { | |
| 54 | - display: block; | |
| 55 | - margin-bottom: 6px; | |
| 56 | - font-size: 14px; | |
| 57 | - font-weight: 600; | |
| 58 | - } | |
| 59 | - | |
| 60 | - input, | |
| 61 | - textarea, | |
| 62 | - button { | |
| 63 | - box-sizing: border-box; | |
| 64 | - font-size: 14px; | |
| 65 | - font-family: Arial, sans-serif; | |
| 66 | - } | |
| 67 | - | |
| 68 | - input, | |
| 69 | - textarea { | |
| 70 | - width: 100%; | |
| 71 | - padding: 10px 12px; | |
| 72 | - border: 1px solid #d0d7de; | |
| 73 | - border-radius: 6px; | |
| 74 | - background: #fff; | |
| 75 | - } | |
| 76 | - | |
| 77 | - textarea { | |
| 78 | - min-height: 120px; | |
| 79 | - resize: vertical; | |
| 80 | - } | |
| 81 | - | |
| 82 | - .actions { | |
| 83 | - display: flex; | |
| 84 | - gap: 12px; | |
| 85 | - flex-wrap: wrap; | |
| 86 | - } | |
| 87 | - | |
| 88 | - button { | |
| 89 | - border: none; | |
| 90 | - border-radius: 6px; | |
| 91 | - padding: 10px 16px; | |
| 92 | - cursor: pointer; | |
| 93 | - color: #fff; | |
| 94 | - background: #1677ff; | |
| 95 | - } | |
| 96 | - | |
| 97 | - button.secondary { | |
| 98 | - background: #00b578; | |
| 99 | - } | |
| 100 | - | |
| 101 | - button.warn { | |
| 102 | - background: #ff7d00; | |
| 103 | - } | |
| 104 | - | |
| 105 | - button.danger { | |
| 106 | - background: #f53f3f; | |
| 107 | - } | |
| 108 | - | |
| 109 | - button.gray { | |
| 110 | - background: #86909c; | |
| 111 | - } | |
| 112 | - | |
| 113 | - .status { | |
| 114 | - font-weight: 700; | |
| 115 | - } | |
| 116 | - | |
| 117 | - .status.connected { | |
| 118 | - color: #00b578; | |
| 119 | - } | |
| 120 | - | |
| 121 | - .status.closed { | |
| 122 | - color: #f53f3f; | |
| 123 | - } | |
| 124 | - | |
| 125 | - .muted { | |
| 126 | - color: #86909c; | |
| 127 | - font-size: 13px; | |
| 128 | - } | |
| 129 | - | |
| 130 | - pre { | |
| 131 | - margin: 0; | |
| 132 | - padding: 12px; | |
| 133 | - background: #111827; | |
| 134 | - color: #e5e7eb; | |
| 135 | - border-radius: 6px; | |
| 136 | - overflow: auto; | |
| 137 | - white-space: pre-wrap; | |
| 138 | - word-break: break-word; | |
| 139 | - line-height: 1.6; | |
| 140 | - } | |
| 141 | - | |
| 142 | - .log-box { | |
| 143 | - max-height: 360px; | |
| 144 | - overflow: auto; | |
| 145 | - background: #111827; | |
| 146 | - color: #e5e7eb; | |
| 147 | - border-radius: 6px; | |
| 148 | - padding: 12px; | |
| 149 | - font-size: 13px; | |
| 150 | - line-height: 1.6; | |
| 151 | - } | |
| 152 | - | |
| 153 | - .log-line { | |
| 154 | - margin-bottom: 6px; | |
| 155 | - } | |
| 156 | - | |
| 157 | - table { | |
| 158 | - width: 100%; | |
| 159 | - border-collapse: collapse; | |
| 160 | - background: #fff; | |
| 161 | - } | |
| 162 | - | |
| 163 | - th, | |
| 164 | - td { | |
| 165 | - border-bottom: 1px solid #edf1f5; | |
| 166 | - padding: 10px 8px; | |
| 167 | - text-align: left; | |
| 168 | - font-size: 13px; | |
| 169 | - vertical-align: top; | |
| 170 | - } | |
| 171 | - | |
| 172 | - th { | |
| 173 | - background: #f7f8fa; | |
| 174 | - } | |
| 175 | - </style> | |
| 176 | -</head> | |
| 177 | -<body> | |
| 178 | - <div class="page"> | |
| 179 | - <div class="card"> | |
| 180 | - <h1 class="title">同行报价信息 WebSocket 测试页</h1> | |
| 181 | - <p class="desc"> | |
| 182 | - 使用说明:请先在同一浏览器中登录业务系统,再打开本页测试。当前服务端握手鉴权依赖现有登录态, | |
| 183 | - 如果浏览器没有把登录态带到 WebSocket 握手,请求会返回 403。 | |
| 184 | - </p> | |
| 185 | - </div> | |
| 186 | - | |
| 187 | - <div class="card"> | |
| 188 | - <div class="row"> | |
| 189 | - <div class="field"> | |
| 190 | - <label for="wsUrl">WebSocket 地址</label> | |
| 191 | - <input id="wsUrl" value="ws://localhost:8081/ws/procurement/salesmanQuotationPeer/realtime"> | |
| 192 | - </div> | |
| 193 | - <div class="field"> | |
| 194 | - <label for="statusText">连接状态</label> | |
| 195 | - <input id="statusText" value="未连接" readonly> | |
| 196 | - </div> | |
| 197 | - </div> | |
| 198 | - | |
| 199 | - <div class="actions"> | |
| 200 | - <button id="connectBtn">连接</button> | |
| 201 | - <button id="disconnectBtn" class="danger">断开</button> | |
| 202 | - <button id="syncBtn" class="secondary">发送 SYNC_TODAY</button> | |
| 203 | - <button id="resyncBtn" class="warn">发送 RESYNC</button> | |
| 204 | - <button id="clearBtn" class="gray">清空日志</button> | |
| 205 | - </div> | |
| 206 | - | |
| 207 | - <p class="muted" style="margin-top: 12px;"> | |
| 208 | - 建连成功后,服务端会自动推送一份 <code>INIT</code> 全量数据;重连后可手动发送 <code>RESYNC</code>。 | |
| 209 | - </p> | |
| 210 | - </div> | |
| 211 | - | |
| 212 | - <div class="card"> | |
| 213 | - <h2 class="title">自定义消息</h2> | |
| 214 | - <div class="row"> | |
| 215 | - <div class="field"> | |
| 216 | - <label for="customMessage">发送内容</label> | |
| 217 | - <textarea id="customMessage">RESYNC</textarea> | |
| 218 | - </div> | |
| 219 | - </div> | |
| 220 | - <div class="actions"> | |
| 221 | - <button id="sendCustomBtn">发送自定义消息</button> | |
| 222 | - </div> | |
| 223 | - </div> | |
| 224 | - | |
| 225 | - <div class="card"> | |
| 226 | - <h2 class="title">最新消息摘要</h2> | |
| 227 | - <div class="row"> | |
| 228 | - <div class="field"> | |
| 229 | - <label for="messageType">messageType</label> | |
| 230 | - <input id="messageType" readonly> | |
| 231 | - </div> | |
| 232 | - <div class="field"> | |
| 233 | - <label for="eventType">eventType</label> | |
| 234 | - <input id="eventType" readonly> | |
| 235 | - </div> | |
| 236 | - <div class="field"> | |
| 237 | - <label for="businessDate">businessDate</label> | |
| 238 | - <input id="businessDate" readonly> | |
| 239 | - </div> | |
| 240 | - <div class="field"> | |
| 241 | - <label for="pushTime">pushTime</label> | |
| 242 | - <input id="pushTime" readonly> | |
| 243 | - </div> | |
| 244 | - <div class="field"> | |
| 245 | - <label for="recordCount">records 数量</label> | |
| 246 | - <input id="recordCount" readonly> | |
| 247 | - </div> | |
| 248 | - </div> | |
| 249 | - </div> | |
| 250 | - | |
| 251 | - <div class="card"> | |
| 252 | - <h2 class="title">最新原始消息</h2> | |
| 253 | - <pre id="rawMessage">暂无数据</pre> | |
| 254 | - </div> | |
| 255 | - | |
| 256 | - <div class="card"> | |
| 257 | - <h2 class="title">数据预览</h2> | |
| 258 | - <table> | |
| 259 | - <thead> | |
| 260 | - <tr> | |
| 261 | - <th>ID</th> | |
| 262 | - <th>同行名称</th> | |
| 263 | - <th>品种名称</th> | |
| 264 | - <th>品种代号</th> | |
| 265 | - <th>同行报价</th> | |
| 266 | - <th>备注</th> | |
| 267 | - <th>创建时间</th> | |
| 268 | - <th>更新时间</th> | |
| 269 | - </tr> | |
| 270 | - </thead> | |
| 271 | - <tbody id="recordsBody"> | |
| 272 | - <tr> | |
| 273 | - <td colspan="8">暂无数据</td> | |
| 274 | - </tr> | |
| 275 | - </tbody> | |
| 276 | - </table> | |
| 277 | - </div> | |
| 278 | - | |
| 279 | - <div class="card"> | |
| 280 | - <h2 class="title">运行日志</h2> | |
| 281 | - <div id="logBox" class="log-box"></div> | |
| 282 | - </div> | |
| 283 | - </div> | |
| 284 | - | |
| 285 | - <script> | |
| 286 | - let socket = null; | |
| 287 | - | |
| 288 | - const wsUrlInput = document.getElementById("wsUrl"); | |
| 289 | - const statusText = document.getElementById("statusText"); | |
| 290 | - const customMessage = document.getElementById("customMessage"); | |
| 291 | - const rawMessage = document.getElementById("rawMessage"); | |
| 292 | - const logBox = document.getElementById("logBox"); | |
| 293 | - const recordsBody = document.getElementById("recordsBody"); | |
| 294 | - const messageTypeInput = document.getElementById("messageType"); | |
| 295 | - const eventTypeInput = document.getElementById("eventType"); | |
| 296 | - const businessDateInput = document.getElementById("businessDate"); | |
| 297 | - const pushTimeInput = document.getElementById("pushTime"); | |
| 298 | - const recordCountInput = document.getElementById("recordCount"); | |
| 299 | - | |
| 300 | - function nowText() { | |
| 301 | - return new Date().toLocaleString(); | |
| 302 | - } | |
| 303 | - | |
| 304 | - function addLog(message) { | |
| 305 | - const line = document.createElement("div"); | |
| 306 | - line.className = "log-line"; | |
| 307 | - line.textContent = "[" + nowText() + "] " + message; | |
| 308 | - logBox.prepend(line); | |
| 309 | - } | |
| 310 | - | |
| 311 | - function setStatus(text, connected) { | |
| 312 | - statusText.value = text; | |
| 313 | - statusText.className = connected ? "status connected" : "status closed"; | |
| 314 | - } | |
| 315 | - | |
| 316 | - function renderSummary(data) { | |
| 317 | - messageTypeInput.value = data.messageType || ""; | |
| 318 | - eventTypeInput.value = data.eventType || ""; | |
| 319 | - businessDateInput.value = data.businessDate || ""; | |
| 320 | - pushTimeInput.value = data.pushTime || ""; | |
| 321 | - recordCountInput.value = Array.isArray(data.records) ? String(data.records.length) : "0"; | |
| 322 | - } | |
| 323 | - | |
| 324 | - function renderTable(records) { | |
| 325 | - if (!Array.isArray(records) || records.length === 0) { | |
| 326 | - recordsBody.innerHTML = "<tr><td colspan=\"8\">暂无数据</td></tr>"; | |
| 327 | - return; | |
| 328 | - } | |
| 329 | - | |
| 330 | - const rows = records.map(function(record) { | |
| 331 | - return "<tr>" | |
| 332 | - + "<td>" + escapeHtml(record.id) + "</td>" | |
| 333 | - + "<td>" + escapeHtml(record.peerName) + "</td>" | |
| 334 | - + "<td>" + escapeHtml(record.peerProductName) + "</td>" | |
| 335 | - + "<td>" + escapeHtml(record.peerProductCode) + "</td>" | |
| 336 | - + "<td>" + escapeHtml(record.peerPrice) + "</td>" | |
| 337 | - + "<td>" + escapeHtml(record.remark) + "</td>" | |
| 338 | - + "<td>" + escapeHtml(record.createTime) + "</td>" | |
| 339 | - + "<td>" + escapeHtml(record.updateTime) + "</td>" | |
| 340 | - + "</tr>"; | |
| 341 | - }); | |
| 342 | - | |
| 343 | - recordsBody.innerHTML = rows.join(""); | |
| 344 | - } | |
| 345 | - | |
| 346 | - function escapeHtml(value) { | |
| 347 | - if (value === null || value === undefined) { | |
| 348 | - return ""; | |
| 349 | - } | |
| 350 | - | |
| 351 | - return String(value) | |
| 352 | - .replace(/&/g, "&") | |
| 353 | - .replace(/</g, "<") | |
| 354 | - .replace(/>/g, ">") | |
| 355 | - .replace(/\"/g, """) | |
| 356 | - .replace(/'/g, "'"); | |
| 357 | - } | |
| 358 | - | |
| 359 | - function connect() { | |
| 360 | - const url = wsUrlInput.value.trim(); | |
| 361 | - if (!url) { | |
| 362 | - addLog("连接失败:WebSocket 地址不能为空"); | |
| 363 | - return; | |
| 364 | - } | |
| 365 | - | |
| 366 | - if (socket && socket.readyState === WebSocket.OPEN) { | |
| 367 | - addLog("当前已连接,无需重复连接"); | |
| 368 | - return; | |
| 369 | - } | |
| 370 | - | |
| 371 | - addLog("开始连接:" + url); | |
| 372 | - socket = new WebSocket(url); | |
| 373 | - | |
| 374 | - socket.onopen = function() { | |
| 375 | - setStatus("已连接", true); | |
| 376 | - addLog("连接成功"); | |
| 377 | - }; | |
| 378 | - | |
| 379 | - socket.onmessage = function(event) { | |
| 380 | - addLog("收到消息:" + event.data); | |
| 381 | - rawMessage.textContent = event.data; | |
| 382 | - | |
| 383 | - try { | |
| 384 | - const data = JSON.parse(event.data); | |
| 385 | - renderSummary(data); | |
| 386 | - renderTable(data.records); | |
| 387 | - } catch (error) { | |
| 388 | - addLog("消息解析失败:" + error.message); | |
| 389 | - } | |
| 390 | - }; | |
| 391 | - | |
| 392 | - socket.onerror = function() { | |
| 393 | - addLog("连接出现错误,请检查登录态、服务地址和网络"); | |
| 394 | - }; | |
| 395 | - | |
| 396 | - socket.onclose = function(event) { | |
| 397 | - setStatus("已断开", false); | |
| 398 | - addLog("连接关闭,code=" + event.code + ",reason=" + (event.reason || "无")); | |
| 399 | - }; | |
| 400 | - } | |
| 401 | - | |
| 402 | - function disconnect() { | |
| 403 | - if (!socket) { | |
| 404 | - addLog("当前没有可断开的连接"); | |
| 405 | - return; | |
| 406 | - } | |
| 407 | - socket.close(); | |
| 408 | - } | |
| 409 | - | |
| 410 | - function sendMessage(message) { | |
| 411 | - if (!socket || socket.readyState !== WebSocket.OPEN) { | |
| 412 | - addLog("发送失败:当前连接未建立"); | |
| 413 | - return; | |
| 414 | - } | |
| 415 | - | |
| 416 | - socket.send(message); | |
| 417 | - addLog("已发送消息:" + message); | |
| 418 | - } | |
| 419 | - | |
| 420 | - document.getElementById("connectBtn").addEventListener("click", connect); | |
| 421 | - document.getElementById("disconnectBtn").addEventListener("click", disconnect); | |
| 422 | - document.getElementById("syncBtn").addEventListener("click", function() { | |
| 423 | - sendMessage("SYNC_TODAY"); | |
| 424 | - }); | |
| 425 | - document.getElementById("resyncBtn").addEventListener("click", function() { | |
| 426 | - sendMessage("RESYNC"); | |
| 427 | - }); | |
| 428 | - document.getElementById("sendCustomBtn").addEventListener("click", function() { | |
| 429 | - sendMessage(customMessage.value.trim()); | |
| 430 | - }); | |
| 431 | - document.getElementById("clearBtn").addEventListener("click", function() { | |
| 432 | - logBox.innerHTML = ""; | |
| 433 | - rawMessage.textContent = "暂无数据"; | |
| 434 | - renderSummary({ | |
| 435 | - messageType: "", | |
| 436 | - eventType: "", | |
| 437 | - businessDate: "", | |
| 438 | - pushTime: "", | |
| 439 | - records: [] | |
| 440 | - }); | |
| 441 | - renderTable([]); | |
| 442 | - addLog("日志已清空"); | |
| 443 | - }); | |
| 444 | - | |
| 445 | - setStatus("未连接", false); | |
| 446 | - addLog("页面已加载,可开始测试"); | |
| 447 | - </script> | |
| 448 | -</body> | |
| 449 | -</html> |