@jnrs/lingshu-smart 2.2.47 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/AGENTS.md +281 -348
  2. package/dist/components/debug/DataLogPanel.vue.d.ts +13 -2
  3. package/dist/components/debug/PerformanceStats.vue.d.ts +1 -1
  4. package/dist/components/debug/ScriptRunner/ConfigPanel.vue.d.ts +1 -1
  5. package/dist/components/debug/ScriptRunner/StepsPanel.vue.d.ts +4 -2
  6. package/dist/components/debug/ScriptRunner/index.vue.d.ts +11 -0
  7. package/dist/components/debug/ScriptRunner/script.d.ts +131 -41
  8. package/dist/components/debug/ScriptRunner/scriptFile.d.ts +92 -0
  9. package/dist/components/debug/WsProxy/index.vue.d.ts +10 -8
  10. package/dist/components/debug/WsProxy/proxy.d.ts +114 -53
  11. package/dist/components/editor/ControlBar/CaseManger.vue.d.ts +18 -2
  12. package/dist/components/editor/ControlBar/CaseTitleEdit.vue.d.ts +2 -2
  13. package/dist/components/editor/ControlBar/PersistDialog.vue.d.ts +9 -0
  14. package/dist/components/index.js +8287 -6169
  15. package/dist/components/preview/index.vue.d.ts +6308 -4098
  16. package/dist/controller/AppController.d.ts +65 -3
  17. package/dist/controller/SocketDispatcher.d.ts +59 -0
  18. package/dist/fullscreen-BA3oea8O.js +53 -0
  19. package/dist/index-C6Liz-8o.js +1238 -0
  20. package/dist/index.js +36 -27
  21. package/dist/lingshu-smart/cases.json +5882 -14843
  22. package/dist/lingshu-smart/mockSocket.json +263 -0
  23. package/dist/lingshu-smart/wsProxyConfig.json +327 -725
  24. package/dist/playground/ViewPageDemo.vue.d.ts +13336 -8894
  25. package/dist/stores/index.js +1 -1
  26. package/dist/stores/useAppStore.d.ts +18952 -12321
  27. package/dist/stores/useDataStore.d.ts +19714 -12469
  28. package/dist/stores/useDebugStore.d.ts +11 -2
  29. package/dist/stores/useEditorStore.d.ts +3 -3
  30. package/dist/types/app.types.d.ts +1 -1
  31. package/dist/types/components/config.types.d.ts +16 -0
  32. package/dist/types/components/operating.types.d.ts +22 -13
  33. package/dist/types/enums.d.ts +22 -2
  34. package/dist/types/guards.d.ts +1 -0
  35. package/dist/types/index.d.ts +1 -0
  36. package/dist/types/models/config.types.d.ts +23 -5
  37. package/dist/types/models/operating.types.d.ts +57 -57
  38. package/dist/types/socket.types.d.ts +275 -0
  39. package/dist/ui/abstract/ModelAbs.d.ts +37 -2
  40. package/dist/ui/components/Resource.d.ts +0 -1
  41. package/dist/ui/components/StationSlot.d.ts +8 -2
  42. package/dist/ui/components/StationSlotGroup.d.ts +26 -1
  43. package/dist/ui/models/DoubleJointRobot.d.ts +14 -4
  44. package/dist/ui/models/MachineTool.d.ts +6 -1
  45. package/dist/ui/models/MaterialTrack.d.ts +14 -2
  46. package/dist/ui/models/Pallet.d.ts +20 -5
  47. package/dist/ui/models/ResourceOperationWindow.d.ts +0 -1
  48. package/dist/ui/models/ResourceStore.d.ts +7 -5
  49. package/dist/ui/models/Rgv.d.ts +4 -2
  50. package/dist/ui/models/ShuttleStation.d.ts +14 -2
  51. package/dist/ui/models/SingleJointRobot.d.ts +14 -4
  52. package/dist/ui/utils/resolveStationSlotLayers.d.ts +20 -0
  53. package/dist/ui/utils/resolveTypeStyle.d.ts +13 -0
  54. package/dist/useAppStore-DASwAOBy.js +7223 -0
  55. package/dist/utils/fullscreen.d.ts +9 -0
  56. package/dist/utils/index.d.ts +1 -0
  57. package/dist/vite.d.ts +15 -0
  58. package/dist/vite.js +39 -0
  59. package/package.json +6 -2
  60. package/dist/components/editor/OptionCard/Equipment/stationSlot/StationSlotGroupBoth.vue.d.ts +0 -2
  61. package/dist/guards-Bi8oNtaY.js +0 -35
  62. package/dist/index-Dsgrnpn0.js +0 -1186
  63. package/dist/useAppStore-CevWqcmT.js +0 -6147
  64. /package/dist/components/editor/OptionCard/Equipment/{stationSlot/StationSlotGroup.vue.d.ts → models/ResourceStore.vue.d.ts} +0 -0
package/AGENTS.md CHANGED
@@ -38,7 +38,7 @@ const saveCases = (data?: ICase[]) => {
38
38
  /* 持久化到后端 */
39
39
  }
40
40
  const handlePreview = (currentCase?: ICase) => {
41
- window.open(`/preview/${currentCase?.uuid}`)
41
+ window.open(`/preview/${currentCase?.id}`)
42
42
  }
43
43
  const handleExit = () => {
44
44
  /* 退出逻辑 */
@@ -56,25 +56,23 @@ const handleExit = () => {
56
56
  | --------------- | ----------------------------------- | --------------------------- | --------------------------------------------------- |
57
57
  | `loadCases` | `(id?: string) => Promise<ICase[]>` | 编辑器初始化 / 方案管理弹窗 | **必填**。返回方案列表,组件内部写入 `useDataStore` |
58
58
  | `saveCases` | `(cases?: ICase[]) => void` | 点击保存按钮 | 将当前全部方案提交后端 |
59
- | `handlePreview` | `(currentCase?: ICase) => void` | 点击预览按钮 | 通常跳转至预览页并携带 `uuid` |
59
+ | `handlePreview` | `(currentCase?: ICase) => void` | 点击预览按钮 | 通常跳转至预览页并携带 `id` |
60
60
  | `handleExit` | `() => void` | 点击退出按钮 | 返回上一页或关闭窗口 |
61
61
 
62
- **注意:** 编辑模式不涉及 `onMessage` / `operating`。设备运转动画仅在预览看板中通过 WebSocket 驱动。
62
+ **注意:** 编辑模式不涉及消息协议。设备运转动画仅在预览看板中通过 `feedSocketMessage` `SocketDispatcher` 驱动。
63
63
 
64
64
  ---
65
65
 
66
66
  ### ViewPageDemo.vue — 预览/运行模式
67
67
 
68
- **职责:** 加载并展示指定方案、响应用户交互、接收 WebSocket 消息并驱动设备动画。
68
+ **职责:** 加载并展示指定方案、响应用户交互、接收符合《socket-operating-api.md》的标准 Socket 协议消息(`envelope + payload`)并驱动设备动画。
69
69
 
70
70
  **核心结构:**
71
71
 
72
72
  ```vue
73
73
  <script setup lang="ts">
74
74
  import { ref, computed, onMounted } from 'vue'
75
- import type { ICase, ILayer, ILayerOperating, InteractiveUi } from '@/types'
76
- import { EnumModel } from '@/types'
77
- import { transRuntimeToPixel, transPercentToPixel } from '@/utils'
75
+ import type { ICase, ILayer, InteractiveUi } from '@/types'
78
76
  import LingshuSmartPreview from '@/components/preview/index.vue'
79
77
 
80
78
  const lingshuSmartPreviewRef = ref()
@@ -97,29 +95,33 @@ const onDragEnd = (layer: ILayer, data: InteractiveUi) => {
97
95
  /* 拖拽结束 */
98
96
  }
99
97
 
100
- // 驱动设备运转转发至预览组件 expose 的方法
101
- const operating = (data: ILayerOperating) => {
102
- lingshuSmartPreviewRef.value?.operating(data)
98
+ // WebSocket 消息入口 将原始消息直接喂给协议分发器(见下文「Socket 协议接入指南」)
99
+ const onSocketMessage = (rawMessage: any) => {
100
+ lingshuSmartPreviewRef.value?.feedSocketMessage(rawMessage)
103
101
  }
104
102
 
105
- // WebSocket 消息入口 见下文「onMessage 编写指南」
106
- const onMessage = (rawMessage: any) => {
107
- /* ... */
103
+ // 订阅协议分发器回调(回执 command_ack / 透传 alarm_push 等,可选)
104
+ const setHandlers = () => {
105
+ lingshuSmartPreviewRef.value?.setSocketHandlers({
106
+ onAck: (ack) => console.log('[command_ack]', ack?.payload),
107
+ onRaw: (raw) => console.log('[透传]', raw?.envelope?.type)
108
+ })
108
109
  }
109
110
 
110
111
  onMounted(() => {
111
- // 从 URL 解析方案 UUID,例如 /preview/abc-123
112
+ // 从 URL 解析方案 ID,例如 /preview/abc-123
112
113
  const VIEW_PATH = '/preview/'
113
114
  if (location.pathname.includes(VIEW_PATH)) {
114
115
  currentCaseId.value = location.pathname.split(VIEW_PATH)[1]
115
116
  }
117
+ setHandlers()
116
118
  })
117
119
  </script>
118
120
 
119
121
  <template>
120
122
  <LingshuSmartPreview
121
123
  ref="lingshuSmartPreviewRef"
122
- :case-uuid="currentCaseId"
124
+ :case-id="currentCaseId"
123
125
  :actions="{ loadCases, onTap, onMenuTap, onDragEnd }"
124
126
  />
125
127
  </template>
@@ -136,330 +138,248 @@ onMounted(() => {
136
138
 
137
139
  **Preview 组件 expose:**
138
140
 
139
- | 属性/方法 | 类型 | 说明 |
140
- | ------------- | --------------------------------- | -------------------- |
141
- | `currentCase` | `ICase \| null` | 当前加载的方案 |
142
- | `operating` | `(data: ILayerOperating) => void` | 驱动指定设备运转动画 |
141
+ | 属性/方法 | 类型 | 说明 |
142
+ | ------------------- | ---------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- |
143
+ | `currentCase` | `ICase \| null` | 当前加载的方案 |
144
+ | `feedSocketMessage` | `(msg: unknown) => void` | **推荐**。下发一条标准信封协议消息(`envelope + payload`),经 `SocketDispatcher` 自动分发驱动画布 |
145
+ | `setSocketHandlers` | `(handlers: { onAck?: (ack) => void; onRaw?: (raw) => void }) => void` | 订阅回执 `command_ack` 与透传(`alarm_push` / `runtime_status_push`)回调 |
146
+ | `operating` | `(data: ILayerOperating) => void` | 旧链路内部 `operating` 引擎,仅 WsProxy 等内部组件可选使用 |
147
+ | `resetModels` | `() => void` | 一键复位画布(所有模型按初始配置重建,含资源清空) |
143
148
 
144
149
  **Props:**
145
150
 
146
- | 属性 | 类型 | 默认值 | 说明 |
147
- | --------------- | ---------------- | --------- | --------------------------- |
148
- | `caseUuid` | `string` | — | **必填**。要展示的方案 UUID |
149
- | `actions` | `PreviewActions` | `{}` | 事件与数据加载回调 |
150
- | `appHittable` | `boolean` | `true` | 画布是否可交互 |
151
- | `appFill` | `string` | `#424242` | 容器背景色 |
152
- | `hideSceneFill` | `boolean` | `false` | 是否隐藏场景背景 |
151
+ | 属性 | 类型 | 默认值 | 说明 |
152
+ | --------------- | ---------------- | --------- | ------------------------- |
153
+ | `caseId` | `string` | — | **必填**。要展示的方案 ID |
154
+ | `actions` | `PreviewActions` | `{}` | 事件与数据加载回调 |
155
+ | `appHittable` | `boolean` | `true` | 画布是否可交互 |
156
+ | `appFill` | `string` | `#424242` | 容器背景色 |
157
+ | `hideSceneFill` | `boolean` | `false` | 是否隐藏场景背景 |
153
158
 
154
159
  ---
155
160
 
156
- ## 📡 onMessage 编写指南
161
+ ## 📡 Socket 协议接入指南
162
+
163
+ 预览看板通过 WebSocket(或 SSE、轮询等)接收后端推送,将原始消息**直接交给** **`feedSocketMessage()`**。画布内置的 `SocketDispatcher` 负责解析、去重、校验并驱动设备动画,业务侧无需自行分支映射。
157
164
 
158
- 预览看板通过 WebSocket(或 SSE、轮询等)接收后端推送,在 `onMessage` 中将原始消息**转换为 `ILayerOperating`**,再调用 `operating()` 驱动画布。
165
+ > 协议规范以《socket-operating-api.md》为准(v3.5),本文仅摘录接入要点。
159
166
 
160
167
  ### 数据流
161
168
 
162
169
  ```
163
- WebSocket 原始消息
164
- onMessage 校验 & 解析
165
- 按 payload.type 分支映射
166
- operating(ILayerOperating)
167
- AppController.businessMap 三级匹配 `${className}_${id}` 工位号
168
- 对应 ModelAbs.handleOperation 子组件动画
170
+ WebSocket 原始消息(envelope + payload)
171
+ feedSocketMessage(rawMessage) // 无需手动解析
172
+ SocketDispatcher.dispatch
173
+ ├─ 识别传输层心跳 { type: 'ping' | 'pong' },不进业务流程
174
+ ├─ 校验信封(envelope.id / type / payload 缺失则丢弃)
175
+ ├─ envelope.id 去重(重复丢弃 + warn 日志)
176
+ └─ 按 envelope.type 分发
177
+ ├─ device_init → 全量初始化设备状态 + 初始资源实体
178
+ ├─ device_command → 增量驱动单设备状态(灯/进度/出入口/position 等)
179
+ ├─ resource_command→ 资源池生命周期:add / batch_add / transfer / remove / batch_remove
180
+ ├─ alarm_push → 设备告警高亮(resolved 解除)
181
+ ├─ runtime_status_push → 无模型,透传 onRaw 供全局 UI 处理
182
+ └─ scene_reset → 画布全部模型重建 + 资源清空 + 告警解除
183
+ → AppController / ResourcePool → ModelAbs.handleOperation → 子组件动画
184
+ → 处理完毕产出回执 command_ack(0 / 400 / 404 / 409 / 500),经 onAck 回调交给宿主业务;是否经 WebSocket 回推后端由业务按需决定(可选)
169
185
  ```
170
186
 
171
- ### 消息格式约定
172
-
173
- 参考 `src/playground/mockWebSocketData.ts`,推荐外层结构:
187
+ ### 消息格式约定(envelope + payload)
174
188
 
175
189
  ```typescript
176
- interface WebSocketMessage {
177
- traceId: string // 链路追踪 ID
178
- type: 'update' // 消息类型,目前仅处理 'update'
179
- payload: Payload // 业务载荷,内含 payload.type 标识设备/资源
190
+ interface MessageEnvelope {
191
+ id: string // 唯一 ID(雪花算法 Long 型字符串),画布据此去重
192
+ type: EnvelopeType // device_init | device_command | resource_command
193
+ // | alarm_push | runtime_status_push | scene_reset | command_ack
194
+ timestamp: string // ISO 8601
195
+ case_id?: string
180
196
  }
181
- ```
182
-
183
- **入口校验模板:**
184
197
 
185
- ```typescript
186
- const onMessage = (rawMessage: any) => {
187
- if (!rawMessage?.type || !rawMessage?.payload) {
188
- console.warn('无效的 WebSocket 消息格式', rawMessage)
189
- return
190
- }
191
-
192
- const { type, payload } = rawMessage
193
-
194
- if (type === 'update') {
195
- switch (payload.type) {
196
- case 'Resource':
197
- /* 资源增删 */ break
198
- case 'MachineTool':
199
- /* 机床状态 */ break
200
- case 'ResourceStore':
201
- /* 资源库/随行托 */ break
202
- case 'SingleJointRobot':
203
- /* 单关节机器人 */ break
204
- case 'DoubleJointRobot':
205
- /* 双关节机器人 */ break
206
- case 'Rgv':
207
- /* RGV */ break
208
- case 'LoadingStation':
209
- /* 装载站 */ break
210
- case 'MarkingMachine':
211
- /* 打标机 */ break
212
- case 'CleaningMachine':
213
- /* 清洗机 */ break
214
- case 'Rail':
215
- /* 轨道拖链 */ break
216
- }
217
- }
198
+ interface SocketMessage<T> {
199
+ envelope: MessageEnvelope
200
+ payload: T
218
201
  }
219
202
  ```
220
203
 
221
- ### 匹配规则(重要)
222
-
223
- `operating()` 通过三级匹配定位画布上的设备:`className → id → 工位号`:
224
-
225
- - `id` 作为前后端对于设备的唯一识别字段
226
- - `rawData` 为后端接口返回的原始数据 payload,其中包含 id 属性,如果设备注册了交互事件,则该对象会存在于回调函数的返回值中,供"点击设备后弹窗展示设备详情"等业务需求使用
227
- - `className` 使用 `EnumModel` 枚举值,例如 `EnumModel.MACHINE_TOOL`
228
- - 资源增删时,`addType` / `removeType` 为目标设备的 `className`,`addId` / `removeId` 为目标设备 `id`
229
- - **工位号匹配(三级)**:当同一 `id` 的设备(如 `ResourceStore`)在画布上被拆分为多个 UI(不同工位号范围)时,`businessMap` 以数组存储同 `className + id` 的多个模型,再通过 `resource.index`(工位号)匹配到具体 UI。模型通过 `containsStationIndex` 方法判断是否包含该工位号。仅有一个模型时直接使用,无需工位号匹配
230
-
231
- 匹配失败时控制台输出:`模型操作失败: 模型未匹配到`。
232
-
233
- ### 各业务场景映射示例
234
-
235
- #### 1. 资源增删(`payload.type === 'Resource'`)
236
-
237
- Resource 不在编辑器中进行维护,是根据业务数据动态添加或删除的一类 UI 图形,如“托盘”、“刀具”。
238
- 同一消息可同时包含新增与移除操作。
204
+ **入口模板(顺带订阅回执,可选):**
239
205
 
240
206
  ```typescript
241
- // 新增资源到设备工位
242
- if (payload.addType && payload.addId) {
243
- operating({
244
- className: payload.addType, // 如 EnumModel.MACHINE_TOOL
245
- id: payload.addId,
246
- rawData: payload,
247
- resource: {
248
- handleType: 'add',
249
- resourceType: payload.resourceType, // EnumResourceType.PALLET | TOOL
250
- name: payload.name,
251
- color: payload.status === 3 ? 'green' : '#424242',
252
- index: payload.addSiteNumber, // 工位槽索引
253
- rawData: payload
254
- }
255
- })
256
- }
257
-
258
- // 从设备工位移除资源
259
- if (payload.removeType && payload.removeId) {
260
- operating({
261
- className: payload.removeType,
262
- id: payload.removeId,
263
- rawData: payload,
264
- resource: {
265
- handleType: 'remove',
266
- resourceType: payload.resourceType,
267
- index: payload.removeSiteNumber,
268
- rawData: payload
269
- }
270
- })
207
+ // WebSocket onmessage → 直接透传,无需解析
208
+ const onSocketMessage = (rawMessage: any) => {
209
+ lingshuSmartPreviewRef.value?.feedSocketMessage(rawMessage)
271
210
  }
272
- ```
273
211
 
274
- #### 2. 机床(`payload.type === 'MachineTool'`)
275
-
276
- ```typescript
277
- operating({
278
- className: EnumModel.MACHINE_TOOL,
279
- id: payload.id,
280
- rawData: payload,
281
- light: {
282
- color: payload.status === 2 ? 'green' : '#424242',
283
- blinking: payload.status !== 2,
284
- rotating: payload.status === 2
285
- },
286
- progress: {
287
- percent: payload.percent, // 0 ~ 1
288
- textContent: payload.status === 2 ? '运行中' : '待机中'
289
- },
290
- entrance: {
291
- isIn: payload.isIn,
292
- isOut: payload.isOut
293
- },
294
- stationRotatable: payload.stationRotatable // false 横向 / true 纵向
212
+ // 订阅回执 command_ack 与透传消息(可选,用于联调与全局 UI)
213
+ lingshuSmartPreviewRef.value?.setSocketHandlers({
214
+ onAck: (ack) => console.log('[command_ack]', ack?.payload),
215
+ onRaw: (raw) => console.log('[透传]', raw?.envelope?.type)
295
216
  })
296
217
  ```
297
218
 
298
- #### 3. 资源库 / 随行托(`payload.type === 'ResourceStore'`)
219
+ ### 核心语义
299
220
 
300
- ```typescript
301
- operating({
302
- className: EnumModel.RESOURCE_STORE,
303
- id: payload.id,
304
- rawData: payload,
305
- light: {
306
- color: payload.status === 2 ? 'green' : '#424242',
307
- blinking: payload.status === 2
308
- },
309
- x: transPercentToPixel(payload.x, 2, 'width') // 沿轨道移动时需转换
310
- })
311
- ```
221
+ - **字段缺失即跳过(防抖动)**:`device_command` / `resource_command` 中未携带的字段保持当前状态;携带的字段(哪怕为 0 / false / 空串)必须按值设置。因此高频位置消息可只传变化字段,如仅 `position.x`
312
222
 
313
- #### 4. 单关节机器人(`payload.type === 'SingleJointRobot'`)
223
+ - **`device_init`** **为全量覆盖**:先复位该设备(缺失字段重置默认值、工位资源清空),再应用显式字段
314
224
 
315
- ```typescript
316
- operating({
317
- className: EnumModel.SINGLE_JOINT_ROBOT,
318
- id: payload.id,
319
- rawData: payload,
320
- light: {
321
- color: payload.status === 2 ? 'green' : '#424242',
322
- blinking: payload.status === 2
323
- },
324
- x: transPercentToPixel(payload.x, 2, 'width'),
325
- j1: payload.j1, // 一级关节角度(度)
326
- currentFixture: payload.currentFixture // EnumResourceType.PALLET | TOOL
327
- })
328
- ```
225
+ - **资源为独立实体**:由画布对象资源池管理,按 `resource_id` 全局索引,工位退化为纯位置槽(三级坐标:设备 → 层 → 工位)
329
226
 
330
- #### 5. 双关节机器人(`payload.type === 'DoubleJointRobot'`)
227
+ - **已满校验(静态强校验)**:资源类型与目标工位声明类型不匹配回执 `409`;设备/工位未找到回执 `404`;参数错误回执 `400`;成功回执 `0`;异常回执 `500`
331
228
 
332
- ```typescript
333
- operating({
334
- className: EnumModel.DOUBLE_JOINT_ROBOT,
335
- id: payload.id,
336
- rawData: payload,
337
- light: {
338
- color: payload.status === 0 ? '#f30' : '#424242'
339
- },
340
- currentFixture: payload.currentFixture,
341
- x: transPercentToPixel(payload.x, 2, 'width'),
342
- j1: payload.j1,
343
- j2: payload.j2
344
- })
345
- ```
229
+ - **字段全层级 snake_case**:`device_type` / `device_id` / `device_station` / `current_fixture` 等
346
230
 
347
- #### 6. 装载站(`payload.type === 'LoadingStation'`)
231
+ ### 设备类型枚举(device_type)
348
232
 
349
- ```typescript
350
- operating({
351
- className: EnumModel.LOADING_STATION,
352
- id: payload.id,
353
- rawData: payload,
354
- light: { color: payload.status === 2 ? 'green' : '#424242', blinking: payload.status === 2 },
355
- progress: { percent: payload.percent, textContent: payload.textContent },
356
- entrance: { isIn: payload.isIn, isOut: payload.isOut },
357
- stationRotatable: payload.stationRotatable
358
- })
359
- ```
233
+ | 枚举值 | 说明 |
234
+ | --------------------------- | ------------ |
235
+ | `machine_tool` | 机床 |
236
+ | `loading_station` | 装载站 |
237
+ | `shuttle_station` | 接驳站 |
238
+ | `material_track` | 料道 |
239
+ | `marking_machine` | 打标机 |
240
+ | `cleaning_machine` | 清洗机 |
241
+ | `single_joint_robot` | 单关节机器人 |
242
+ | `double_joint_robot` | 双关节机器人 |
243
+ | `rgv` | RGV |
244
+ | `resource_store` | 资源库 |
245
+ | `pallet` | 托盘 |
246
+ | `resource_operation_window` | 资源操作窗口 |
360
247
 
361
- #### 7. 打标机(`payload.type === 'MarkingMachine'`)
248
+ ### 资源类型枚举(resource_type)
362
249
 
363
- ```typescript
364
- operating({
365
- className: EnumModel.MARKING_MACHINE,
366
- id: payload.id,
367
- rawData: payload,
368
- light: { color: payload.lightColor, blinking: payload.blinking },
369
- progress: { percent: payload.percent, textContent: payload.textContent },
370
- entrance: { isIn: payload.isIn, isOut: payload.isOut },
371
- laserSiteNumberArray: payload.laserSiteNumberArray // { site, active }[]
372
- })
373
- ```
250
+ | 值 | 资源 |
251
+ | ----------- | ---- |
252
+ | `workpiece` | 工件 |
253
+ | `tool` | 刀具 |
254
+ | `auxiliary` | 辅料 |
374
255
 
375
- #### 8. 清洗机(`payload.type === 'CleaningMachine'`)
256
+ ### 各消息示例
376
257
 
377
- ```typescript
378
- operating({
379
- className: EnumModel.CLEANING_MACHINE,
380
- id: payload.id,
381
- rawData: payload,
382
- light: { color: payload.lightColor, blinking: payload.blinking },
383
- progress: { percent: payload.percent, textContent: payload.textContent },
384
- entrance: { isIn: payload.isIn, isOut: payload.isOut }
385
- })
386
- ```
258
+ #### 1. `device_command` — 驱动单设备状态(增量)
387
259
 
388
- #### 9. RGV(`payload.type === 'Rgv'`)
260
+ ```json
261
+ {
262
+ "envelope": { "id": "msg_xxx", "type": "device_command", "timestamp": "2026-08-31T10:30:00.000Z" },
263
+ "payload": {
264
+ "device_type": "single_joint_robot",
265
+ "device_id": "RBT-001",
266
+ "light": { "color": "#66DC7A", "blinking": true },
267
+ "position": { "x": 0.6, "j1": 45 },
268
+ "current_fixture": "tool"
269
+ }
270
+ }
271
+ ```
389
272
 
390
- ```typescript
391
- operating({
392
- className: EnumModel.RGV,
393
- id: payload.id,
394
- rawData: payload,
395
- light: { color: payload.lightColor, blinking: payload.blinking },
396
- x: transPercentToPixel(payload.x, 2, 'width'),
397
- j1: payload.j1, // 旋转角度
398
- forkXY: payload.forkXY, // 货叉进出移动比例 [−1,1]
399
- forkZ: payload.forkZ // 货叉升降比例 [0,1]
400
- })
273
+ #### 2. `device_init` — 设备初始化(全量)
274
+
275
+ ```json
276
+ {
277
+ "envelope": { "id": "msg_xxx", "type": "device_init", "timestamp": "2026-08-31T10:30:00.000Z" },
278
+ "payload": {
279
+ "devices": [
280
+ {
281
+ "device_type": "machine_tool",
282
+ "device_id": "CNC-001",
283
+ "light": { "color": "green", "blinking": false },
284
+ "resources": [{ "resource_type": "workpiece", "resource_id": "PRD-001", "name": "毛坯1", "device_station": 1 }]
285
+ }
286
+ ]
287
+ }
288
+ }
401
289
  ```
402
290
 
403
- #### 10. 轨道拖链(`payload.type === 'Rail'`)
291
+ #### 3. `resource_command` 资源生命周期
292
+
293
+ ```json
294
+ // add(进场)
295
+ { "envelope": { "id": "m1", "type": "resource_command", "timestamp": "..." },
296
+ "payload": { "operation": "add", "resource_id": "PRD-003", "resource_type": "workpiece",
297
+ "name": "毛坯3", "color": "#F2C34F",
298
+ "to": { "device_type": "machine_tool", "device_id": "CNC-001", "device_station": 1 } } }
299
+
300
+ // transfer(流转)
301
+ { "envelope": { "id": "m2", "type": "resource_command", "timestamp": "..." },
302
+ "payload": { "operation": "transfer", "resource_id": "PRD-001", "resource_type": "workpiece",
303
+ "from": { "device_type": "machine_tool", "device_id": "CNC-001", "device_station": 1 },
304
+ "to": { "device_type": "resource_store", "device_id": "RS-001", "device_station": 3 } } }
305
+
306
+ // remove(离场)
307
+ { "envelope": { "id": "m3", "type": "resource_command", "timestamp": "..." },
308
+ "payload": { "operation": "remove", "resource_id": "PRD-001" } }
309
+
310
+ // batch_add(批量进场) / batch_remove(批量离场)
311
+ { "envelope": { "id": "m4", "type": "resource_command", "timestamp": "..." },
312
+ "payload": { "operation": "batch_add", "resources": [ { "resource_id": "PRD-4", "resource_type": "workpiece",
313
+ "to": { "device_type": "machine_tool", "device_id": "CNC-001", "device_station": 2 } } ] } }
314
+ ```
404
315
 
405
- ```typescript
406
- operating({
407
- className: EnumModel.RAIL,
408
- id: payload.id, // 轨道图层 id
409
- rawData: payload,
410
- x: payload.x, // 拖链位置(像素,无需 runtime 转换)
411
- botId: payload.botId // 关联的移动设备 rawData.id
412
- })
316
+ #### 4. `alarm_push` — 全局告警推送(设备高亮)
317
+
318
+ ```json
319
+ {
320
+ "envelope": { "id": "m5", "type": "alarm_push", "timestamp": "..." },
321
+ "payload": {
322
+ "alarm_id": "ALM-001",
323
+ "device_type": "machine_tool",
324
+ "device_id": "CNC-001",
325
+ "device_name": "CNC-001",
326
+ "alarm_level": "critical",
327
+ "alarm_type": "threshold_exceeded",
328
+ "alarm_message": "主轴温度过高",
329
+ "current_value": 72.5,
330
+ "threshold_value": 70,
331
+ "status": "pending",
332
+ "create_time": 1776230000
333
+ }
334
+ }
413
335
  ```
414
336
 
415
- ### 运行时坐标转换
337
+ - `status: 'pending'` 点亮设备告警高亮;同一 `alarm_id` 以 `status: 'resolved'` 重推 → 解除高亮
416
338
 
417
- 后端推送的 `x` / `y` 通常为**物理/runtime 坐标**(如毫米),画布使用**像素坐标**。转换公式:
339
+ - 该消息同样透传给 `onRaw`,供业务弹窗等场景使用
418
340
 
419
- ```typescript
420
- import { transRuntimeToPixel } from '@jnrs/lingshu-smart'
341
+ #### 5. `scene_reset` — 场景复位
421
342
 
422
- // 宽方向:pixel = runtimeVal * (scene.width / scene.runtime_width)
423
- const pixelX = transRuntimeToPixel(payload.x, 1, 'width')
424
- const pixelY = transRuntimeToPixel(payload.y, 1, 'height')
343
+ ```json
344
+ { "envelope": { "id": "m6", "type": "scene_reset", "timestamp": "..." }, "payload": {} }
425
345
  ```
426
346
 
427
- ### 百分比转换为像素数值
347
+ ### 坐标与数值约定
428
348
 
429
- 后端推送的 `x` / `y` 通常为**百分比数值**(范围通常为[-1,1]),画布使用**像素坐标**。转换公式:
349
+ | 约定项 | 说明 |
350
+ | -------- | ----------------------------------------------------------------------------- |
351
+ | 序号体系 | 工位 `device_station` 与层 `device_layer` 均 **1-based** |
352
+ | 比例值 | 进度、货叉位移、设备位移 `position.x/y/z` 使用比例值 \[0,1],画布自动换算像素 |
353
+ | 角度值 | 关节/旋转角度 `position.j1/j2` 单位为 **度** |
354
+ | 时间格式 | `envelope.timestamp` 用 ISO 8601;payload 内业务时间戳用 Unix **秒** |
430
355
 
431
- ```typescript
432
- import { transPercentToPixel } from '@jnrs/lingshu-smart'
356
+ ### 心跳与重连
433
357
 
434
- // 宽方向:pixel = percentVal * scene.width
435
- const pixelX = transPercentToPixel(payload.x, 2, 'width')
436
- const pixelY = transPercentToPixel(payload.y, 1, 'height')
437
- ```
358
+ - 前端定时发送 `{ "type": "ping" }`,后端回复 `{ "type": "pong" }`;心跳为传输层消息,**不包信封**,画布识别后直接忽略不进业务流程
438
359
 
439
- **前提:** 方案 `IScene` 中需配置 `runtime_width` / `runtime_height`。该函数读取 `useEditorStore().scene`,预览页需确保场景数据已加载。
360
+ - 断线后指数退避重连:初始 1s,每次翻倍,上限 30s
440
361
 
441
362
  ### WebSocket 接入示例
442
363
 
443
364
  ```typescript
444
365
  onMounted(() => {
445
- const ws = new WebSocket('wss://your-server/ws')
366
+ const ws = new WebSocket(`wss://your-server/ws?token=${token}`)
446
367
  ws.onmessage = (event) => {
447
- try {
448
- onMessage(JSON.parse(event.data))
449
- } catch (e) {
450
- console.error('WebSocket 消息解析失败', e)
451
- }
368
+ onSocketMessage(JSON.parse(event.data))
369
+ }
370
+ ws.onclose = () => {
371
+ /* 断线重连(建议指数退避) */
452
372
  }
453
373
  })
454
374
  ```
455
375
 
456
- 本地调试可复用 `mockWebSocketData.ts` 中的 `firstBatchMessages` / `secondBatchMessages`。
457
-
458
376
  ---
459
377
 
460
378
  ## 📊 ILayerOperating 类型参考
461
379
 
462
- `ILayerOperating` 是设备层运转状态的**联合类型**,用于 `operating()` 入参。所有子类型均继承基类 `IModelOperating`,并通过 `className` 字段区分具体设备。
380
+ `ILayerOperating` 是设备层运转状态的**联合类型**,为画布 `operating()` 内部引擎的入参。所有子类型均继承基类 `IModelOperating`,并通过 `className` 字段区分具体设备。
381
+
382
+ > **业务接入说明:** 业务侧**不应直接构造** **`ILayerOperating`**。`SocketDispatcher` 已把标准协议消息(`envelope + payload`)转换为该内部类型并交由 `operating()` 驱动画布。下表仅用于理解内部驱动原理,或为 WsProxy 等内部组件的桥接逻辑提供类型参考。
463
383
 
464
384
  ### 基类 IModelOperating
465
385
 
@@ -491,42 +411,41 @@ type ILayerOperating =
491
411
 
492
412
  ### 子类型一览
493
413
 
494
- | 类型 | className | 特有属性 | 子组件能力 | 典型 payload.type |
495
- | ---------------------------- | -------------------- | -------------------------------------------------------------------- | --------------------------------------------------- | ------------------ |
496
- | `IMachineToolOperating` | `MACHINE_TOOL` | `light?`, `progress?`, `entrance?`, `stationRotatable?`, `isWorking?` | 状态灯(含旋转)、进度条、出入口、工位槽、工作状态(玻璃呼吸) | `MachineTool` |
497
- | `ILoadingStationOperating` | `LOADING_STATION` | `light?`, `progress?`, `entrance?`, `stationRotatable?` | 状态灯(无旋转)、进度条、出入口、工位槽 | `LoadingStation` |
498
- | `IShuttleStationOperating` | `SHUTTLE_STATION` | `light?`, `layerLightColor?`, `layerLightBlinking?`, `layerVisible?` | 状态灯、多工位层状态灯/显隐 | `ShuttleStation` |
499
- | `IMarkingMachineOperating` | `MARKING_MACHINE` | `light?`, `progress?`, `entrance?`, `laserSiteNumberArray?` | 状态灯、进度条、出入口、工位槽、激光工位(电报节奏闪动) | `MarkingMachine` |
500
- | `ICleaningMachineOperating` | `CLEANING_MACHINE` | `light?`, `progress?`, `entrance?`, `isWorking?` | 状态灯、进度条、出入口、工位槽、工作状态(波浪流动)| `CleaningMachine` |
501
- | `ISingleJointRobotOperating` | `SINGLE_JOINT_ROBOT` | `light?`, `x?`, `y?`, `j1?`, `currentFixture?`, `resource?` | 双状态灯、轨道位移、关节旋转、夹具切换、双工位槽 | `SingleJointRobot` |
502
- | `IDoubleJointRobotOperating` | `DOUBLE_JOINT_ROBOT` | `light?`, `x?`, `y?`, `j1?`, `j2?`, `currentFixture?`, `resource?` | 双状态灯、轨道位移、双关节旋转、夹具切换、双工位槽 | `DoubleJointRobot` |
503
- | `IRGVOperating` | `RGV` | `light?`, `x?`, `j1?`, `forkXY?`, `forkZ?`, `resource?` | 双状态灯、轨道位移、旋转、货叉 XY/Z、双工位槽 | `Rgv` |
504
- | `IRailOperating` | `RAIL` | `x?`, `botId?` | 拖链动画(关联 botId 的设备移动时拖链跟随) | `Rail` |
505
- | `IResourceStoreOperating` | `RESOURCE_STORE` | `light?`, `x?`, `resource?`, `stationColor?` | 状态灯、轨道位移、托盘/刀具工位槽、按工位序改背景色 | `ResourceStore` |
506
- | `IPalletOperating` | `PALLET` | `light?`, `x?`, `y?`, `resource?`, `stationColor?` | 状态灯、X/Y 位移、托盘/刀具工位槽、按工位序改背景色 | `Pallet` |
414
+ | 类型 | className | 特有属性 | 子组件能力 | 典型 payload.type |
415
+ | ---------------------------- | -------------------- | --------------------------------------------------------------------- | -------------------------------------------------------------- | ------------------ |
416
+ | `IMachineToolOperating` | `MACHINE_TOOL` | `light?`, `progress?`, `entrance?`, `station_rotation?`, `isWorking?` | 状态灯(含旋转)、进度条、出入口、工位槽、工作状态(玻璃呼吸) | `MachineTool` |
417
+ | `ILoadingStationOperating` | `LOADING_STATION` | `light?`, `progress?`, `entrance?` | 状态灯(无旋转)、进度条、出入口、工位槽 | `LoadingStation` |
418
+ | `IShuttleStationOperating` | `SHUTTLE_STATION` | `light?`, `layerLightColor?`, `layerLightBlinking?`, `layerVisible?` | 状态灯、多工位层状态灯/显隐 | `ShuttleStation` |
419
+ | `IMarkingMachineOperating` | `MARKING_MACHINE` | `light?`, `progress?`, `entrance?`, `laserSiteNumberArray?` | 状态灯、进度条、出入口、工位槽、激光工位(电报节奏闪动) | `MarkingMachine` |
420
+ | `ICleaningMachineOperating` | `CLEANING_MACHINE` | `light?`, `progress?`, `entrance?`, `isWorking?` | 状态灯、进度条、出入口、工位槽、工作状态(波浪流动) | `CleaningMachine` |
421
+ | `ISingleJointRobotOperating` | `SINGLE_JOINT_ROBOT` | `light?`, `x?`, `y?`, `j1?`, `currentFixture?`, `resource?` | 双状态灯、轨道位移、关节旋转、夹具切换、双工位槽 | `SingleJointRobot` |
422
+ | `IDoubleJointRobotOperating` | `DOUBLE_JOINT_ROBOT` | `light?`, `x?`, `y?`, `j1?`, `j2?`, `currentFixture?`, `resource?` | 双状态灯、轨道位移、双关节旋转、夹具切换、双工位槽 | `DoubleJointRobot` |
423
+ | `IRGVOperating` | `RGV` | `light?`, `x?`, `j1?`, `forkXY?`, `forkZ?`, `resource?` | 双状态灯、轨道位移、旋转、货叉 XY/Z、双工位槽 | `Rgv` |
424
+ | `IRailOperating` | `RAIL` | `x?`, `botId?` | 拖链动画(关联 botId 的设备移动时拖链跟随) | `Rail` |
425
+ | `IResourceStoreOperating` | `RESOURCE_STORE` | `light?`, `x?`, `resource?`, `stationColor?` | 状态灯、轨道位移、托盘/刀具工位槽、按工位序改背景色 | `ResourceStore` |
426
+ | `IPalletOperating` | `PALLET` | `light?`, `x?`, `y?`, `resource?`, `stationColor?` | 状态灯、X/Y 位移、托盘/刀具工位槽、按工位序改背景色 | `Pallet` |
507
427
 
508
428
  ### 子类型字段详情
509
429
 
510
430
  #### IMachineToolOperating
511
431
 
512
- | 属性 | 类型 | 说明 |
513
- | ------------------ | -------------------- | --------------------------------------- |
514
- | `light` | `ILightOperating` | 支持 `rotating`(运行中旋转灯效) |
515
- | `progress` | `IProgressOperating` | 加工进度 0~1 |
516
- | `entrance` | `IEntranceOperating` | 物料入口/出口动画 |
517
- | `stationRotatable` | `boolean` | 工位容器方向:`false` 横向,`true` 纵向 |
432
+ | 属性 | 类型 | 说明 |
433
+ | ------------------ | -------------------- | ----------------------------------------------------------- |
434
+ | `light` | `ILightOperating` | 支持 `rotating`(运行中旋转灯效) |
435
+ | `progress` | `IProgressOperating` | 加工进度 0\~1 |
436
+ | `entrance` | `IEntranceOperating` | 物料入口/出口动画 |
437
+ | `station_rotation` | `number` | 工位旋转增量(度):叠加在机床配置的工位容器方向角之上 |
518
438
  | `isWorking` | `boolean` | 工作状态:`true` 时工位容器玻璃背景呼吸闪动(默认 `false`) |
519
- | `resource` | `IResourceOperating` | 托盘等资源增删 |
439
+ | `resource` | `IResourceOperating` | 托盘等资源增删 |
520
440
 
521
441
  #### ILoadingStationOperating
522
442
 
523
- | 属性 | 类型 | 说明 |
524
- | ------------------ | ----------------------------------- | -------------- |
525
- | `light` | `Omit<ILightOperating, 'rotating'>` | 不支持旋转灯效 |
526
- | `progress` | `IProgressOperating` | 装载进度 |
527
- | `entrance` | `IEntranceOperating` | 出入口状态 |
528
- | `stationRotatable` | `boolean` | 工位容器方向 |
529
- | `resource` | `IResourceOperating` | 工位槽资源 |
443
+ | 属性 | 类型 | 说明 |
444
+ | ---------- | ----------------------------------- | -------------- |
445
+ | `light` | `Omit<ILightOperating, 'rotating'>` | 不支持旋转灯效 |
446
+ | `progress` | `IProgressOperating` | 装载进度 |
447
+ | `entrance` | `IEntranceOperating` | 出入口状态 |
448
+ | `resource` | `IResourceOperating` | 工位槽资源 |
530
449
 
531
450
  #### IShuttleStationOperating
532
451
 
@@ -541,13 +460,13 @@ type ILayerOperating =
541
460
 
542
461
  #### IMarkingMachineOperating
543
462
 
544
- | 属性 | 类型 | 说明 |
545
- | ---------------------- | ----------------------------------- | ------------------ |
546
- | `light` | `Omit<ILightOperating, 'rotating'>` | 状态灯 |
547
- | `progress` | `IProgressOperating` | 打标进度 |
548
- | `entrance` | `IEntranceOperating` | 出入口 |
463
+ | 属性 | 类型 | 说明 |
464
+ | ---------------------- | ----------------------------------- | --------------------------------------------------------- |
465
+ | `light` | `Omit<ILightOperating, 'rotating'>` | 状态灯 |
466
+ | `progress` | `IProgressOperating` | 打标进度 |
467
+ | `entrance` | `IEntranceOperating` | 出入口 |
549
468
  | `laserSiteNumberArray` | `ILaserSiteState[]` | 各激光工位打标状态(`active` 时点亮并按无序电报节奏闪动) |
550
- | `resource` | `IResourceOperating` | 工位槽资源 |
469
+ | `resource` | `IResourceOperating` | 工位槽资源 |
551
470
 
552
471
  **ILaserSiteState:**
553
472
 
@@ -558,13 +477,13 @@ type ILayerOperating =
558
477
 
559
478
  #### ICleaningMachineOperating
560
479
 
561
- | 属性 | 类型 | 说明 |
562
- | ---------- | ----------------------------------- | ---------- |
563
- | `light` | `Omit<ILightOperating, 'rotating'>` | 状态灯 |
564
- | `progress` | `IProgressOperating` | 清洗进度 |
565
- | `entrance` | `IEntranceOperating` | 出入口 |
480
+ | 属性 | 类型 | 说明 |
481
+ | ----------- | ----------------------------------- | ------------------------------------------------------------- |
482
+ | `light` | `Omit<ILightOperating, 'rotating'>` | 状态灯 |
483
+ | `progress` | `IProgressOperating` | 清洗进度 |
484
+ | `entrance` | `IEntranceOperating` | 出入口 |
566
485
  | `isWorking` | `boolean` | 工作状态:`true` 时工位容器内显示波浪流动效果(默认 `false`) |
567
- | `resource` | `IResourceOperating` | 工位槽资源 |
486
+ | `resource` | `IResourceOperating` | 工位槽资源 |
568
487
 
569
488
  #### ISingleJointRobotOperating
570
489
 
@@ -591,14 +510,14 @@ type ILayerOperating =
591
510
 
592
511
  #### IRGVOperating
593
512
 
594
- | 属性 | 类型 | 说明 |
595
- | ---------- | -------------------- | ------------------------- |
596
- | `light` | `ILightOperating` | 双灯同步更新 |
597
- | `x` | `number` | 沿轨道位移(像素)[0,1] |
598
- | `j1` | `number` | 车体旋转角度(度)[0,360] |
599
- | `forkXY` | `number` | 货叉进出移动比例 [−1,1] |
600
- | `forkZ` | `number` | 货叉升降比例 [0,1] |
601
- | `resource` | `IResourceOperating` | 托盘/刀具工位槽 |
513
+ | 属性 | 类型 | 说明 |
514
+ | ---------- | -------------------- | -------------------------- |
515
+ | `light` | `ILightOperating` | 双灯同步更新 |
516
+ | `x` | `number` | 沿轨道位移(像素)\[0,1] |
517
+ | `j1` | `number` | 车体旋转角度(度)\[0,360] |
518
+ | `forkXY` | `number` | 货叉进出移动比例 \[−1,1] |
519
+ | `forkZ` | `number` | 货叉升降比例 \[0,1] |
520
+ | `resource` | `IResourceOperating` | 托盘/刀具工位槽 |
602
521
 
603
522
  #### IRailOperating
604
523
 
@@ -630,12 +549,12 @@ type ILayerOperating =
630
549
 
631
550
  被 `ILayerOperating` 各字段引用的子组件操作类型,定义于 `src/types/components/operating.types.ts`:
632
551
 
633
- | 类型 | 用途 | 字段 |
634
- | -------------------- | ---------- | ---------------------------------------------------------------------------------------- |
635
- | `ILightOperating` | 状态灯 | `color`(必填), `blinking?`, `rotating?` |
636
- | `IProgressOperating` | 进度条 | `percent`(0~1), `textContent` |
637
- | `IEntranceOperating` | 出入口 | `isIn`, `isOut` |
638
- | `IResourceOperating` | 工位槽资源 | `resourceType`, `index`, `handleType`(`'add'\|'remove'`), `rawData`, `name?`, `color?` |
552
+ | 类型 | 用途 | 字段 |
553
+ | -------------------- | ---------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
554
+ | `ILightOperating` | 状态灯 | `color`(必填), `blinking?`, `rotating?` |
555
+ | `IProgressOperating` | 进度条 | `percent`(0\~1), `textContent` |
556
+ | `IEntranceOperating` | 出入口 | `isIn`, `isOut` |
557
+ | `IResourceOperating` | 工位槽资源 | `resource_type`, `resource_id?`, `name?`, `resource_color?`(显式颜色优先,未下发时按 `resource_state` 字典映射), `device_station`, `handle_type`(`'add'\|'remove'`), `raw_data` |
639
558
 
640
559
  ---
641
560
 
@@ -672,10 +591,11 @@ type ILayerOperating =
672
591
 
673
592
  **EnumResourceType — 资源类型**
674
593
 
675
- | 枚举值 | 说明 |
676
- | -------- | ---- |
677
- | `PALLET` | 托盘 |
678
- | `TOOL` | 刀具 |
594
+ | 枚举值 | 说明 |
595
+ | ----------- | ---- |
596
+ | `WORKPIECE` | 工件 |
597
+ | `TOOL` | 刀具 |
598
+ | `AUXILIARY` | 辅料 |
679
599
 
680
600
  #### 核心类型
681
601
 
@@ -695,7 +615,7 @@ type ILayerOperating =
695
615
 
696
616
  | 属性 | 类型 | 必填 | 说明 |
697
617
  | ---------- | ---------- | ---- | -------- |
698
- | `uuid` | `string` | ✓ | 唯一标识 |
618
+ | `id` | `string` | ✓ | 唯一标识 |
699
619
  | `isActive` | `boolean` | ✓ | 是否激活 |
700
620
  | `title` | `string` | ✓ | 标题 |
701
621
  | `scene` | `IScene` | ✓ | 场景 |
@@ -716,13 +636,14 @@ type ILayerOperating =
716
636
 
717
637
  ## 📝 注意事项
718
638
 
719
- 1. **编辑器 vs 预览:** 编辑模式配置 `EditorActions`;预览模式配置 `PreviewActions` + `onMessage` `operating`
720
- 2. **ID 匹配:** `operating` `id` 必须与方案图层 `rawData.id` 一致,否则静默失败(在前端层面该值即为编辑器组件中业务数据配置中必填项 id 的值,需根据后端预先提供的设备 id 在编辑器中进行配置)
721
- 3. **坐标系:** 轨道类设备的 `x` 通常需 `transPercentToPixel` 转换;`IRailOperating.x` 直接使用像素
722
- 4. **资源消息:** `Resource` 类型不对应独立图层,通过目标设备的 `resource` 字段操作工位槽
723
- 5. **引擎生命周期:** 组件卸载时自动 `destroyContainer`;手动集成需自行销毁
724
- 6. **类型安全:** 所有 API 均导出 TypeScript 类型,推荐 `import type { ILayerOperating, EnumModel } from '@jnrs/lingshu-smart'`
725
- 7. **调试模式:** 500ms 内点击主容器(`#lingshu-smart`)10 次可打开调试配置面板,支持运行时配置日志等级、阴影/动画开关、FPS 限制、交互禁用、性能统计。配置通过 `localStorage` 持久化,详见 README「调试功能」章节。
639
+ 1. **编辑器 vs 预览:** 编辑模式配置 `EditorActions`;预览模式配置 `PreviewActions`,并通过 `feedSocketMessage` 下发标准信封协议消息(`envelope + payload`)驱动画布。画布内置 `SocketDispatcher` 自动分发,业务侧无需自行解析 `payload.type`。
640
+ 2. **设备 ID 匹配:** 协议消息中 `device_id` 必须与方案图层 `rawData.id` 一致,否则回执 `404` 且不驱动(在前端层面该值即为编辑器组件中业务数据配置中必填项 id 的值,需根据后端预先提供的设备 id 在编辑器中进行配置)
641
+ 3. **坐标系:** 协议 `position.x/y/z` 为比例值 \[0,1],画布按场景自动换算为像素;`j1 / j2 / fork_xy / fork_z` 为角度/比例原值直接透传
642
+ 4. **资源消息:** `resource_command` 不再映射到独立图层,资源为独立实体,由画布对象资源池按 `resource_id` 管理,操作工位的纯位置槽
643
+ 5. **扩展工位(合并编号):** 托盘图层配置 `extendHostId`(宿主设备业务 ID)后,托盘工位并入宿主设备的合并编号空间——资源寻址宿主越界时按「宿主工位总数 + 前序扩展托盘工位总数」顺延落到扩展托盘(404/409 以实际承接工位判定);点击扩展托盘工位时 `onTap` 的 `layer` 为宿主设备、`data.index` 为合并编号(附 `extensionOf` / `localIndex` 溯源字段)。详见 socket-operating-api.md 3.6
644
+ 6. **引擎生命周期:** 组件卸载时自动 `destroyContainer`;手动集成需自行销毁
645
+ 7. **类型安全:** 所有 API 均导出 TypeScript 类型,推荐 `import type { EnvelopeType, SocketMessage, DeviceCommandPayload, ResourceCommandPayload, EnumDeviceType, EnumResourceType } from '@jnrs/lingshu-smart'`
646
+ 8. **调试模式:** 500ms 内点击主容器(`#lingshu-smart`)10 次可打开调试配置面板,支持运行时配置日志等级、阴影/动画开关、FPS 限制、交互禁用、性能统计。配置通过 `localStorage` 持久化,详见 README「调试功能」章节。
726
647
 
727
648
  ---
728
649
 
@@ -730,18 +651,19 @@ type ILayerOperating =
730
651
 
731
652
  ### 架构
732
653
 
733
- | 模块 | 职责 |
734
- | ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
735
- | `utils/logger.ts` | 统一日志工具,支持 tag 着色、LogLevel 过滤、`operating` 追踪级别 |
736
- | `utils/debug-flags.ts` | 模块级动画标志位,供 UI 组件直接导入控制循环动画 |
737
- | `utils/performance-stats.ts` | 性能统计工具,记录运行时长和 WebSocket 数据接收量 |
738
- | `stores/useDebugStore.ts` | 全局设置 Pinia store(facade):响应式读写 `useDataStore.globalSetting` / `scriptRunner`,持久化统一在 `@jnrs/lingshu-smart` |
739
- | `composables/useDebugEasterEgg.ts` | 调试触发检测:500ms 内 10 次点击打开面板 |
740
- | `components/debug/GlobalSetting.vue` | 全局设置对话框 UI(原 DebugPanel,含语言/日志等级/FPS/阴影/动画等开关) |
741
- | `components/debug/ScriptRunner/` | Socket 脚本模拟器:脚本步骤编排、断点调试、消息结构配置(code/msg、payload 字段名、type)、日志查看;设备下拉支持自定义/全部复位(resetAll)/资源操作 |
742
- | `components/debug/WsProxy/` | WebSocket 代理器:声明式字段映射将后端 socket 结构桥接为 operating,支持连接/断开/重连、日志、配置导出/导入、场景像素换算(percentToPixel 百分比/ runtimeToPixel 运行时坐标)、standalone 独立标签页模式 / remote 远程接收模式(BroadcastChannel 跨标签页流转数据);操作规则含 single/resource/global/resetAll(ResetAll 接收源类型值 `resetAll` 后生成全部复位 operating) |
743
- | `components/debug/WsProxy/WsProxyPage.vue` | `WsProxy` 独立路由页面组件:以独立 URL/标签页运行代理器配置,将 operating/raw/状态广播给预览页驱动图形运转 |
744
- | `controller/AppController.ts` | 提供 `toggleShadows` / `toggleAnimations` / `setFps` 方法 |
654
+ | 模块 | 职责 |
655
+ | ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --- |
656
+ | `utils/logger.ts` | 统一日志工具,支持 tag 着色、LogLevel 过滤、`operating` 追踪级别 |
657
+ | `utils/debug-flags.ts` | 模块级动画标志位,供 UI 组件直接导入控制循环动画 |
658
+ | `utils/performance-stats.ts` | 性能统计工具,记录运行时长和 WebSocket 数据接收量 |
659
+ | `stores/useDebugStore.ts` | 全局设置 Pinia store(facade):响应式读写 `useDataStore.globalSetting` / `scriptsConfig`(脚本按方案分组;其 `scriptSteps` / `scriptLooping` 基于「激活方案」,组件持有明确方案上下文时建议直接用 `useDataStore` 按 caseId 读写),持久化统一在 `@jnrs/lingshu-smart` | |
660
+ | `composables/useDebugEasterEgg.ts` | 调试触发检测:500ms 内 10 次点击打开面板 |
661
+ | `components/debug/GlobalSetting.vue` | 全局设置对话框 UI(原 DebugPanel,含语言/日志等级/FPS/阴影/动画等开关) |
662
+ | `components/debug/ScriptRunner/` | Socket 脚本模拟器:脚本步骤编排、断点调试,生成标准信封协议消息(`envelope + payload`,含 device_init / device_command / resource_command / alarm_push / scene_reset 等模板);设备下拉支持自定义/全部复位(resetAll)/资源操作,产物经 `feedSocketMessage` 直接驱动画布;脚本**按方案分组**存储(`ScriptCaseData[]`:caseId / caseName / steps / looping,与 `wsProxyConfig` 对称,经 `currentCase` prop 确定归属);支持**一键烧录**(二次确认后写入 `public/lingshu-smart/mockSocket.json`,开发服务器接口 `POST /lingshu-smart/__burn-mock-socket`,宿主无需改动)与**导入/导出** JSON 文件;弹窗打开时按「文件优先 + 未烧录改动确认」懒加载当前方案(见 `scriptFile.ts`) |
663
+ | `components/debug/WsProxy/` | WebSocket 代理器:声明式字段映射将后端 socket 结构桥接为标准信封协议消息(`envelope + payload`,snake_case)并经 `feedSocketMessage` 驱动画布,标准信封消息直接透传;支持连接/断开/重连(指数退避)、心跳 ping/pong、日志、配置导出/导入、场景像素换算(percentToPixel 百分比/ runtimeToPixel 运行时坐标)、standalone 独立标签页模式 / remote 远程接收模式(BroadcastChannel 跨标签页流转数据);操作规则含 single/resource/global/resetAll(ResetAll 接收源类型值 `resetAll` 后生成全部复位) |
664
+ | `components/debug/WsProxy/WsProxyPage.vue` | `WsProxy` 独立路由页面组件:以独立 URL/标签页运行代理器配置,将标准协议消息/状态广播给预览页驱动图形运转 |
665
+ | `controller/SocketDispatcher.ts` | Socket 协议分发器:解析 `envelope + payload`,识别心跳、按 `envelope.id` 去重、按类型分发(device_init / device_command / resource_command / alarm_push / runtime_status_push / scene_reset),对象资源池管理资源生命周期,处理完毕回执 `command_ack`(0/400/404/409/500) |
666
+ | `controller/AppController.ts` | 画布控制器:提供 `toggleShadows` / `toggleAnimations` / `setFps` / `resetAllModels` / `setAlarmHighlight` 等方法 |
745
667
 
746
668
  ### 日志系统
747
669
 
@@ -755,9 +677,13 @@ logger.warn('警告') // [WARN:MyModule] 警告(浏览器默认样式)
755
677
  ```
756
678
 
757
679
  - `log`/`info`/`operating` 级别:使用彩色单徽章(`LEVEL:Tag` 合并显示)
680
+
758
681
  - `warn`/`error` 级别:纯文本前缀 `[LEVEL:Tag]`
682
+
759
683
  - `operating` 级别:用于追踪 UI 模型操作指令,在 `ModelAbs.handleOperation` 和 `ComponentAbs.handleOperation` 中自动埋点
684
+
760
685
  - 日志等级优先级:`none` < `error` < `warn` < `info` < `log` < `operating`
686
+
761
687
  - 默认:开发环境 `log`(全开),生产环境 `warn`
762
688
 
763
689
  ### 调试配置项
@@ -772,18 +698,25 @@ logger.warn('警告') // [WARN:MyModule] 警告(浏览器默认样式)
772
698
  | 性能统计 | — | `trackWebSocketMessage()` |
773
699
 
774
700
  - **动画开关**:通过标志位控制循环动画(灯闪烁、入口通道动画等),关闭后保留静态状态(如灯颜色保持不变),不影响一次性动画(如机器人摆臂、进度条动画)
701
+
775
702
  - **阴影开关**:递归遍历所有元素切换 `shadow`/`innerShadow` 的 `visible` 属性,同时处理 `ShadowCanvas` 元素(`createShadow` 工厂创建的 Canvas 阴影)的 `visible` 属性
776
703
 
777
704
  ### localStorage 存储
778
705
 
779
706
  统一 key 为 `lingshu-smart`,debug 配置存储在 `debug` 子对象中,与 `useDataStore` 的持久化数据共享同一 key。
780
707
 
708
+ 脚本数据存于该 key 的 `scriptsConfig` 字段(**按方案分组**的 `ScriptCaseData[]`,与 `wsProxyConfig` 平级);旧版全局字段 `scriptRunner` / `scriptLooping` 在读取时自动归入当前激活方案(无 `isActive` 标记时兜底取第一个方案),随后删除。
709
+
710
+ 另有独立小 key `@jnrs/lingshu-smart/mock-socket-baseline`:记录**各方案**脚本与 `mockSocket.json` 的「已同步基线」hash,值为 `{ [caseId]: hash }`(`scriptFile.ts` 的 `readBaselineHash` / `writeBaselineHash`),用于检出该方案未烧录的脚本改动——仅在文件加载 / 烧录 / 导入成功后更新,用户手动编辑不更新。
711
+
781
712
  ---
782
713
 
783
714
  ## 🛠 技术栈
784
715
 
785
716
  - Vue 3 + TypeScript + Pinia
717
+
786
718
  - LeaferJS 2D 图形引擎
719
+
787
720
  - Element Plus UI
788
721
 
789
722
  ---