agentworkshop 0.7.40 → 0.7.44

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 (327) hide show
  1. package/.output/nitro.json +1 -1
  2. package/.output/public/_nuxt/B8uy9iRW.js +1 -0
  3. package/.output/public/_nuxt/BGTYYQH-.js +1 -0
  4. package/.output/public/_nuxt/BUmHEJ77.js +2 -0
  5. package/.output/public/_nuxt/BY7YrV_D.js +1 -0
  6. package/.output/public/_nuxt/Bqcgz95s.js +18 -0
  7. package/.output/public/_nuxt/C7UlqPeD.js +1 -0
  8. package/.output/public/_nuxt/CTmDn-YP.js +1 -0
  9. package/.output/public/_nuxt/Cs8pVeVG.js +1 -0
  10. package/.output/public/_nuxt/{DcsCcxp4.js → D98NH8kG.js} +1 -1
  11. package/.output/public/_nuxt/DKySmNPI.js +14 -0
  12. package/.output/public/_nuxt/Di0NY_ht.js +1 -0
  13. package/.output/public/_nuxt/Du9v6qGB.js +1 -0
  14. package/.output/public/_nuxt/{C_iYD-EB.js → EHlEMRii.js} +1 -1
  15. package/.output/public/_nuxt/Eke1fs-U.js +1 -0
  16. package/.output/public/_nuxt/{DeJykBFf.js → H0BcqZtT.js} +1 -1
  17. package/.output/public/_nuxt/{C4oW1HsE.js → HSYEkkce.js} +1 -1
  18. package/.output/public/_nuxt/{DZshyNLa.js → IREZ1eC2.js} +1 -1
  19. package/.output/public/_nuxt/{D_OTvQza.js → J9oU00D1.js} +1 -1
  20. package/.output/public/_nuxt/OmpTerminalPanel.BxR5-lvi.css +1 -0
  21. package/.output/public/_nuxt/SSyAPaHc.js +1 -0
  22. package/.output/public/_nuxt/TownView.li4EMQxK.css +1 -0
  23. package/.output/public/_nuxt/VdLk4jLE2.js +1 -0
  24. package/.output/public/_nuxt/_id_.BhsXoEuk.css +1 -0
  25. package/.output/public/_nuxt/_id_.DQyj-WKN.css +1 -0
  26. package/.output/public/_nuxt/{_wsId_.B4WiqkIS.css → _wsId_.CTx-BlfF.css} +1 -1
  27. package/.output/public/_nuxt/aShsv_rG.js +19 -0
  28. package/.output/public/_nuxt/{yF0aYMF1.js → aesgXKSr.js} +3 -3
  29. package/.output/public/_nuxt/agents.B21nUEUX.css +1 -0
  30. package/.output/public/_nuxt/aml.ekTjxFkf.css +1 -0
  31. package/.output/public/_nuxt/builds/latest.json +1 -1
  32. package/.output/public/_nuxt/builds/meta/b5506ef0-f48e-4f14-a6c0-d65e505d0b09.json +1 -0
  33. package/.output/public/_nuxt/ca4TJ3Op.js +1 -0
  34. package/.output/public/_nuxt/channel-templates.DplM1pJ3.css +1 -0
  35. package/.output/public/_nuxt/daq.BdVmoIfe.css +1 -0
  36. package/.output/public/_nuxt/dcw.DCgyzpWV.css +1 -0
  37. package/.output/public/_nuxt/{default.J32SgqFY.css → default.Bh3L2HU8.css} +1 -1
  38. package/.output/public/_nuxt/lHAxdSsj.js +1 -0
  39. package/.output/public/_nuxt/pages.j9rzRocr.css +1 -0
  40. package/.output/public/_nuxt/plugins.DhiZbHU8.css +1 -0
  41. package/.output/public/_nuxt/settings.BRx9rIDg.css +1 -0
  42. package/.output/public/_nuxt/teams.BLlOPB_Z.css +1 -0
  43. package/.output/public/_nuxt/tokens.De8xzszn.css +1 -0
  44. package/.output/public/_nuxt/workshop.D1jFNkrn.css +1 -0
  45. package/.output/server/chunks/_/nitro.mjs +1058 -830
  46. package/.output/server/chunks/_/nitro.mjs.map +1 -1
  47. package/.output/server/chunks/build/{OmpTerminalPanel-Cu_s4i0A.mjs → OmpTerminalPanel-CVvxpsF7.mjs} +15 -15
  48. package/.output/server/chunks/build/{OmpTerminalPanel-Cu_s4i0A.mjs.map → OmpTerminalPanel-CVvxpsF7.mjs.map} +1 -1
  49. package/.output/server/chunks/build/{TownView-BuLAsmpR.mjs → TownView-CJ6IsDCc.mjs} +229 -228
  50. package/.output/server/chunks/build/{TownView-BuLAsmpR.mjs.map → TownView-CJ6IsDCc.mjs.map} +1 -1
  51. package/.output/server/chunks/build/{_id_-BU1wfOID.mjs → _id_-CVD_H0ir.mjs} +149 -147
  52. package/.output/server/chunks/build/_id_-CVD_H0ir.mjs.map +1 -0
  53. package/.output/server/chunks/build/{_id_-BYsEua57.mjs → _id_-CtxMb3qp.mjs} +47 -46
  54. package/.output/server/chunks/build/_id_-CtxMb3qp.mjs.map +1 -0
  55. package/.output/server/chunks/build/{_wsId_-BxlDcJHg.mjs → _wsId_-DpY3T-md.mjs} +516 -446
  56. package/.output/server/chunks/build/{_wsId_-BxlDcJHg.mjs.map → _wsId_-DpY3T-md.mjs.map} +1 -1
  57. package/.output/server/chunks/build/{agents-CLQqJF2y.mjs → agents-DsCkuXDV.mjs} +49 -46
  58. package/.output/server/chunks/build/agents-DsCkuXDV.mjs.map +1 -0
  59. package/.output/server/chunks/build/{aml-_7FdFL2p.mjs → aml-CT0xKGKI.mjs} +122 -105
  60. package/.output/server/chunks/build/aml-CT0xKGKI.mjs.map +1 -0
  61. package/.output/server/chunks/build/builtin-catalog-edpDcgfL.mjs +117 -0
  62. package/.output/server/chunks/build/builtin-catalog-edpDcgfL.mjs.map +1 -0
  63. package/.output/server/chunks/build/{channel-templates-p6SZNjlM.mjs → channel-templates-Dla9FbYa.mjs} +49 -37
  64. package/.output/server/chunks/build/channel-templates-Dla9FbYa.mjs.map +1 -0
  65. package/.output/server/chunks/build/{daq-cgyjKmJu.mjs → daq-DWWSKGAV.mjs} +95 -93
  66. package/.output/server/chunks/build/daq-DWWSKGAV.mjs.map +1 -0
  67. package/.output/server/chunks/build/{dcw-CoTxBlAZ.mjs → dcw-CT8k17_E.mjs} +46 -45
  68. package/.output/server/chunks/build/dcw-CT8k17_E.mjs.map +1 -0
  69. package/.output/server/chunks/build/{default-BE4r0FZN.mjs → default-D3525UFX.mjs} +16 -15
  70. package/.output/server/chunks/build/{default-BE4r0FZN.mjs.map → default-D3525UFX.mjs.map} +1 -1
  71. package/.output/server/chunks/build/{logs-BSOeZwwI.mjs → logs--dByNyhw.mjs} +2 -2
  72. package/.output/server/chunks/build/{logs-BSOeZwwI.mjs.map → logs--dByNyhw.mjs.map} +1 -1
  73. package/.output/server/chunks/build/{monitor-C0okQaXz.mjs → monitor-Dh2Kvaq5.mjs} +2 -2
  74. package/.output/server/chunks/build/{monitor-C0okQaXz.mjs.map → monitor-Dh2Kvaq5.mjs.map} +1 -1
  75. package/.output/server/chunks/build/{pages-DFxjn9Xa.mjs → pages-BHcSBM2D.mjs} +26 -20
  76. package/.output/server/chunks/build/pages-BHcSBM2D.mjs.map +1 -0
  77. package/.output/server/chunks/build/{plugins-lGlUr1lj.mjs → plugins-BNgIi61W.mjs} +23 -17
  78. package/.output/server/chunks/build/plugins-BNgIi61W.mjs.map +1 -0
  79. package/.output/server/chunks/build/{settings-Ddd1qx2Z.mjs → settings-ipYBDWdn.mjs} +45 -44
  80. package/.output/server/chunks/build/settings-ipYBDWdn.mjs.map +1 -0
  81. package/.output/server/chunks/build/{teams-MEDLZdMn.mjs → teams-5lx-CwFa.mjs} +47 -44
  82. package/.output/server/chunks/build/teams-5lx-CwFa.mjs.map +1 -0
  83. package/.output/server/chunks/build/{tokens-C7fTM5C2.mjs → tokens-q-IDT8Io.mjs} +73 -53
  84. package/.output/server/chunks/build/tokens-q-IDT8Io.mjs.map +1 -0
  85. package/.output/server/chunks/build/{town-qMPql9Se.mjs → town-QJWB5wng.mjs} +5 -4
  86. package/.output/server/chunks/build/town-QJWB5wng.mjs.map +1 -0
  87. package/.output/server/chunks/build/{useDaqStream-CIBaiGAl.mjs → useDaqStream-DfCFpb3p.mjs} +9 -3
  88. package/.output/server/chunks/build/useDaqStream-DfCFpb3p.mjs.map +1 -0
  89. package/.output/server/chunks/build/{useDcwStream-CBbiYnWe.mjs → useDcwStream-UPiX-3iO.mjs} +11 -2
  90. package/.output/server/chunks/build/useDcwStream-UPiX-3iO.mjs.map +1 -0
  91. package/.output/server/chunks/build/{workshop-BL5bhfs0.mjs → workshop-BIlCj_ZP.mjs} +45 -45
  92. package/.output/server/chunks/build/workshop-BIlCj_ZP.mjs.map +1 -0
  93. package/.output/server/chunks/routes/api/workshop/channel-templates/_id/instantiate.post.mjs +1 -1
  94. package/.output/server/chunks/routes/api/workshop/channels.post.mjs +3 -3
  95. package/.output/server/chunks/routes/api/workshop/dcw/index5.post.mjs +1 -1
  96. package/.output/server/chunks/routes/api/workshop/dcw/lines/_id/start.post.mjs +1 -1
  97. package/.output/server/chunks/routes/api/workshop/dcw/lines/_id/stop.post.mjs +1 -1
  98. package/.output/server/chunks/routes/api/workshop/dcw/params/_id_.delete.mjs +1 -1
  99. package/.output/server/chunks/routes/api/workshop/dcw/params/_id_.patch.mjs +1 -1
  100. package/.output/server/chunks/routes/api/workshop/dcw/templates/_key_.delete.mjs +1 -1
  101. package/.output/server/chunks/routes/api/workshop/dcw/templates/_key_.patch.mjs +1 -1
  102. package/.output/server/chunks/routes/api/workshop/harnesses/_id/providers.get.mjs +1 -1
  103. package/.output/server/chunks/routes/api/workshop/harnesses.get.mjs +1 -1
  104. package/.output/server/chunks/routes/api/workshop/hitl/pending.get.mjs +1 -1
  105. package/.output/server/chunks/routes/api/workshop/hitl/respond.post.mjs +1 -1
  106. package/.output/server/chunks/routes/api/workshop/index.put.mjs +1 -1
  107. package/.output/server/chunks/routes/api/workshop/index5.get.mjs +3 -1
  108. package/.output/server/chunks/routes/api/workshop/index5.get.mjs.map +1 -1
  109. package/.output/server/chunks/routes/api/workshop/index6.get.mjs +3 -1
  110. package/.output/server/chunks/routes/api/workshop/index6.get.mjs.map +1 -1
  111. package/.output/server/chunks/routes/api/workshop/plugins/_name/disable.post.mjs +1 -1
  112. package/.output/server/chunks/routes/api/workshop/plugins/_name/enable.post.mjs +1 -1
  113. package/.output/server/chunks/routes/api/workshop/scene/layouts/_channelId_.delete.mjs +1 -1
  114. package/.output/server/chunks/routes/api/workshop/scene/layouts/_channelId_.put.mjs +1 -1
  115. package/.output/server/chunks/routes/api/workshop/workspaces/_id/channel-templates/_tplId_.post.mjs +1 -1
  116. package/.output/server/chunks/routes/renderer.mjs +2 -2
  117. package/.output/server/chunks/virtual/entry.mjs +639 -88
  118. package/.output/server/chunks/virtual/entry.mjs.map +1 -1
  119. package/.output/server/chunks/virtual/precomputed.mjs +1 -1
  120. package/.output/server/package.json +1 -1
  121. package/README-zh.md +16 -6
  122. package/README.md +17 -7
  123. package/app/components/AppHeader.vue +961 -959
  124. package/app/components/workshop/A2aRpcDebugger.vue +237 -237
  125. package/app/components/workshop/AgentInspectorDrawer.vue +424 -424
  126. package/app/components/workshop/AgentLanesView.vue +998 -998
  127. package/app/components/workshop/ArtifactCard.vue +120 -120
  128. package/app/components/workshop/AssetLibrary.vue +452 -452
  129. package/app/components/workshop/ChannelSessionList.vue +812 -804
  130. package/app/components/workshop/CommandPalette.vue +269 -269
  131. package/app/components/workshop/Composer.vue +827 -827
  132. package/app/components/workshop/DeviceTwinPanel.vue +643 -643
  133. package/app/components/workshop/EventBlock.vue +602 -602
  134. package/app/components/workshop/FileSelectorModal.vue +188 -188
  135. package/app/components/workshop/InspectorPanel.vue +301 -301
  136. package/app/components/workshop/MemoryPanel.vue +364 -364
  137. package/app/components/workshop/MultiChannelView.vue +189 -189
  138. package/app/components/workshop/TaskBoardView.vue +686 -686
  139. package/app/components/workshop/TaskInspectorDrawer.vue +390 -390
  140. package/app/components/workshop/TranscriptTimeline.vue +601 -601
  141. package/app/components/workshop/blocks/ClusterArtifact.vue +61 -61
  142. package/app/components/workshop/blocks/ClusterError.vue +85 -85
  143. package/app/components/workshop/blocks/ClusterLife.vue +87 -87
  144. package/app/components/workshop/blocks/ClusterMember.vue +95 -93
  145. package/app/components/workshop/blocks/ClusterMemory.vue +84 -84
  146. package/app/components/workshop/blocks/ClusterOther.vue +67 -67
  147. package/app/components/workshop/blocks/ClusterStatus.vue +149 -147
  148. package/app/components/workshop/blocks/ClusterTool.vue +158 -158
  149. package/app/components/workshop/terminal/OmpTerminalPanel.vue +1011 -1011
  150. package/app/components/workshop/town/TownView.vue +56 -56
  151. package/app/composables/workshop/useDaqStream.ts +7 -4
  152. package/app/composables/workshop/useDcwStream.ts +8 -2
  153. package/app/middleware/locale-cookie.global.ts +22 -0
  154. package/app/pages/aml/index.vue +24 -2
  155. package/app/pages/daq/[id].vue +20 -16
  156. package/app/pages/daq/index.vue +2310 -2307
  157. package/app/pages/dcw/[id].vue +26 -23
  158. package/app/pages/dcw/index.vue +3 -3
  159. package/app/pages/index.vue +987 -981
  160. package/app/pages/plugins/index.vue +317 -311
  161. package/app/pages/settings.vue +1564 -1562
  162. package/app/pages/tokens.vue +15 -15
  163. package/app/pages/workshop/agents.vue +612 -609
  164. package/app/pages/workshop/channel-templates.vue +18 -11
  165. package/app/pages/workshop/index.vue +668 -668
  166. package/app/pages/workshop/teams.vue +743 -740
  167. package/app/pages/workshop/w/[wsId].vue +704 -704
  168. package/app/plugins/locale-restore.client.ts +16 -1
  169. package/app/utils/builtin-catalog.ts +104 -0
  170. package/config.yml +170 -170
  171. package/docs/cli.en.md +2 -2
  172. package/docs/cli.md +2 -2
  173. package/docs/full-test-plan.md +2 -3
  174. package/docs/plugins.en.md +33 -1
  175. package/docs/plugins.md +29 -1
  176. package/docs/sdk.en.md +1 -1
  177. package/docs/sdk.md +1 -1
  178. package/i18n/locales/en.ts +293 -35
  179. package/i18n/locales/zh-CN.ts +265 -7
  180. package/package.json +4 -3
  181. package/scripts/_dbg-perms-e2e.mjs +132 -109
  182. package/scripts/_demo-agentteam-capture.mjs +278 -0
  183. package/scripts/_e2e-ui-functional.mjs +371 -0
  184. package/scripts/_i18n-en-cjk-scan.mjs +28 -0
  185. package/scripts/_i18n-en-verify.mjs +107 -0
  186. package/scripts/_i18n-extra-checks.mjs +53 -0
  187. package/scripts/_i18n-scan-hardcoded.mjs +180 -0
  188. package/scripts/_i18n-verify-keys.mjs +50 -0
  189. package/scripts/_loop-biax-campaign.mjs +130 -0
  190. package/scripts/_sweep-button-contrast.mjs +87 -0
  191. package/scripts/test-serial-bridge.mjs +418 -0
  192. package/scripts/ui/e2e-interactive.mjs +328 -0
  193. package/scripts/ui/record-demo.mjs +557 -0
  194. package/scripts/ui/seed-industrial-demo.mjs +171 -0
  195. package/server/api/workshop/daq/index.get.ts +3 -1
  196. package/server/api/workshop/dcw/index.get.ts +3 -0
  197. package/server/plugins/aw-plugins.ts +9 -7
  198. package/server/plugins-builtin/diag-bridge/index.mjs +608 -661
  199. package/server/plugins-builtin/rag-bridge/index.mjs +57 -133
  200. package/server/plugins-builtin/serial-bridge/client.mjs +210 -0
  201. package/server/plugins-builtin/serial-bridge/drivers.mjs +409 -0
  202. package/server/plugins-builtin/serial-bridge/i18n.json +62 -0
  203. package/server/plugins-builtin/serial-bridge/index.mjs +247 -0
  204. package/server/plugins-builtin/serial-bridge/lib.mjs +208 -0
  205. package/server/plugins-builtin/serial-bridge/rtu.mjs +168 -0
  206. package/server/services/workshop/daq/daq-controller.ts +7 -2
  207. package/server/services/workshop/daq/drivers.ts +89 -3
  208. package/server/services/workshop/dcw/dcw-controller.ts +5 -1
  209. package/server/services/workshop/dcw/drivers.ts +85 -2
  210. package/server/services/workshop/dcw/plugin-bridge.ts +45 -0
  211. package/server/services/workshop/plugins/host.mjs +30 -0
  212. package/shared/daq-protocol.ts +15 -0
  213. package/.output/public/_nuxt/88GOowgO2.js +0 -1
  214. package/.output/public/_nuxt/BOt2SP4z.js +0 -1
  215. package/.output/public/_nuxt/BP-NyKVG.js +0 -18
  216. package/.output/public/_nuxt/BXxH0UQE.js +0 -1
  217. package/.output/public/_nuxt/BsROmY89.js +0 -1
  218. package/.output/public/_nuxt/ClILjvWH.js +0 -1
  219. package/.output/public/_nuxt/CzOCSGLe.js +0 -1
  220. package/.output/public/_nuxt/D8qvkfyY.js +0 -1
  221. package/.output/public/_nuxt/DCn7QZ5N.js +0 -2
  222. package/.output/public/_nuxt/DS8qj8EL.js +0 -1
  223. package/.output/public/_nuxt/DUjW5Yq5.js +0 -1
  224. package/.output/public/_nuxt/Dihnimlk.js +0 -19
  225. package/.output/public/_nuxt/Dqw1lpr-.js +0 -1
  226. package/.output/public/_nuxt/Dr9v6C0f.js +0 -14
  227. package/.output/public/_nuxt/Du9ZJtnj.js +0 -1
  228. package/.output/public/_nuxt/OmpTerminalPanel.B_Pl5Ynm.css +0 -1
  229. package/.output/public/_nuxt/TownView.f-YOdXRs.css +0 -1
  230. package/.output/public/_nuxt/_id_.CM4bIgKY.css +0 -1
  231. package/.output/public/_nuxt/_id_.bNV69Dtf.css +0 -1
  232. package/.output/public/_nuxt/agents.ZD0rVdHE.css +0 -1
  233. package/.output/public/_nuxt/aml.BN8ZEW4h.css +0 -1
  234. package/.output/public/_nuxt/builds/meta/f4707884-7cf4-4a31-80d2-e3d72b73745f.json +0 -1
  235. package/.output/public/_nuxt/channel-templates.BOtahzPi.css +0 -1
  236. package/.output/public/_nuxt/daq.B0D3dPAc.css +0 -1
  237. package/.output/public/_nuxt/dcw.8Y7FqCVN.css +0 -1
  238. package/.output/public/_nuxt/pages.D7aSDlUI.css +0 -1
  239. package/.output/public/_nuxt/plugins.sb4nDGqr.css +0 -1
  240. package/.output/public/_nuxt/qr6R6Ha1.js +0 -1
  241. package/.output/public/_nuxt/settings.BTgR-NiE.css +0 -1
  242. package/.output/public/_nuxt/teams.CxnxKVVs.css +0 -1
  243. package/.output/public/_nuxt/tokens.BFY7qv4f.css +0 -1
  244. package/.output/public/_nuxt/workshop.CR_QzeAN.css +0 -1
  245. package/.output/server/chunks/build/_id_-BU1wfOID.mjs.map +0 -1
  246. package/.output/server/chunks/build/_id_-BYsEua57.mjs.map +0 -1
  247. package/.output/server/chunks/build/agents-CLQqJF2y.mjs.map +0 -1
  248. package/.output/server/chunks/build/aml-_7FdFL2p.mjs.map +0 -1
  249. package/.output/server/chunks/build/channel-templates-p6SZNjlM.mjs.map +0 -1
  250. package/.output/server/chunks/build/daq-cgyjKmJu.mjs.map +0 -1
  251. package/.output/server/chunks/build/dcw-CoTxBlAZ.mjs.map +0 -1
  252. package/.output/server/chunks/build/pages-DFxjn9Xa.mjs.map +0 -1
  253. package/.output/server/chunks/build/plugins-lGlUr1lj.mjs.map +0 -1
  254. package/.output/server/chunks/build/settings-Ddd1qx2Z.mjs.map +0 -1
  255. package/.output/server/chunks/build/teams-MEDLZdMn.mjs.map +0 -1
  256. package/.output/server/chunks/build/tokens-C7fTM5C2.mjs.map +0 -1
  257. package/.output/server/chunks/build/town-qMPql9Se.mjs.map +0 -1
  258. package/.output/server/chunks/build/useDaqStream-CIBaiGAl.mjs.map +0 -1
  259. package/.output/server/chunks/build/useDcwStream-CBbiYnWe.mjs.map +0 -1
  260. package/.output/server/chunks/build/workshop-BL5bhfs0.mjs.map +0 -1
  261. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.continue.json +0 -1
  262. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.json +0 -1
  263. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/054bb4bc4e4217a2ab0b0c6b663747214a9af464f5568ea60e784704e340bb0f.source +0 -153
  264. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/0554197c3d2bac203a1f5c48a5d52495d1336db1bd7aa4f4c19875810363e684.source +0 -153
  265. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/05c203d12a954fffba942e1d626e3f5fec05b18a5aef2bac003b7884a4b77406.source +0 -431
  266. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/145a6e6ba811af35aed31e0cc3211f53706a14980fa069ee9e3c1591139f96f0.source +0 -180
  267. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/1ae0fb281f97899c025e368ca73f41349b01fb5f85b19e08d7ebccff49095ed5.source +0 -112
  268. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/20ee9967aa0541c516db7575bdd4a1a745939bc7016bd10d7635903113edb8ec.source +0 -227
  269. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/233751843aeba9d4c5263f275b8102de00443c06f286918cbd23e0b6729e01aa.source +0 -131
  270. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/263c2e05b425e5c322e60c6475279f963bab3a873eb521b21df7fec935a46597.source +0 -160
  271. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/2ac5eaac28eaaee2178c4b32014df8e732f9dd432a285a872f5b7f31c1395fa4.source +0 -157
  272. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/33a0db0d2d95d2c11d0720090458e5fba1425376f5ceb5b422d1d3471be3226b.source +0 -348
  273. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/359e474ae83db35e4615d3728de6bf694b64d4dce9b536730a3b6518f9c0494a.source +0 -296
  274. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/37a4c3c31c4b70f6b719c53d1e9c4fcba603f31adfdd11bb6ef9f0d094923c56.source +0 -285
  275. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/461feca1c14a695eb8b0f17a6f95fe97eeceabc0510faaea4ba6d25bde147d8d.source +0 -217
  276. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/4cfadbda8591f29d5e1e230dc000e701b756e850441b3900fb596a4d2f6e98c2.source +0 -301
  277. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/50ca2810cf0b248c96948994c0c404531bbad6e553166b6816b3b03bfa0f75d2.source +0 -151
  278. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/52d9e8cb591a9ae86e238c3620a610cd386717bee222c0f37b25b2ac7e523dc4.source +0 -135
  279. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/6c87daafa4c006560a954636a97333f6123153c9d34d96203f7ee3a14e556357.source +0 -156
  280. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/77bc47854b577900621cacf1697f81fa2bc005af129d37614a97a3441c08f99f.source +0 -72
  281. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/7bbb789d3a527817118cf8399ed1861d6a962ade10878fd07223435d333a7089.source +0 -469
  282. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/86b7c1daafc8b60ba9982dde5bf1bcc74265709483c176e71f23ec6fc267451f.source +0 -259
  283. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/8df66d62bd41984d0970e8236882080c5085ac937d07a29779463d6a03b37c76.source +0 -97
  284. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/9962f2d1b3a85137bd40cc835725600b39f89ee02a27c06b2e0402e1eafde115.source +0 -277
  285. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/a1090ea066b792cb201739f4994e37bf7c12f52e805239a1eab87f18a537f266.source +0 -171
  286. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/a5241091650134d0919897e086a606a3846c8b2b54f8b4c84a8696c145ac3e9e.source +0 -425
  287. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/af19fa4c6e34d5540028be4b17600161283af3a2a2dfc61a3fdc0834436a0c14.source +0 -99
  288. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/b3713d78e2b2f93905ff6be8d58a8d0a2f42dea322eb67c987fda22ff6a084d5.source +0 -325
  289. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/b3849943c3aff102d1af815631fc843a2a52c17646a4b04696ece323e483bbec.source +0 -332
  290. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/bac735a3803a6650b7f1eb8f587e6dc5c68ea055000c3c99a11789438b78e837.source +0 -43
  291. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/d32c24d61eb8b33c73e5a8fc7e4dc7ef437aed238ee4371c2b00617e6104090a.source +0 -154
  292. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/d5170dc9a9879fda04fdf91d8a110d4efb6787ee38dda596d4da899883d36a72.source +0 -531
  293. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/dc59bcd7bb397be63ba2f2c3fec23f3782dcb17d2d75a9981eeced761f33eb32.source +0 -439
  294. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/eb5ff8cf66b92aec7bf6726781d8df74faa2b030c3aaac17d6b00c1fc04c9b32.source +0 -323
  295. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/f10615a6fae18436e70d1201b2d44fdae3174a3aa7bb4db97dea1b3ed291d721.source +0 -433
  296. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/f36d3e271668bc1260c2823542d22dbf1d0f7041ff16b1ac175bce2256e582ca.source +0 -280
  297. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/f94449380af09f31ac352152007146a75cc80bae8d6411ffe57b2c56f933b0ff.source +0 -117
  298. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/fe4dfd81178fd6b648335f6405d9ede71319aebde796008e50db21f8adf44c99.source +0 -160
  299. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/ffb4e3aa548cfdadabaa44ab599f9b6a3ca6288176a2cc7a4b2fc2a12b5fb848.source +0 -101
  300. package/scripts/.mimosa/hook-status/sess_aeaecb58-8833-4b83-b93c-050467d21e03-d944d71bb3.json +0 -14
  301. package/scripts/_dbg-audit-all-pages.mjs +0 -43
  302. package/scripts/_dbg-daq-frames-e2e.mjs +0 -118
  303. package/scripts/_dbg-daq-rowstyle-verify.mjs +0 -112
  304. package/scripts/_dbg-hitl-timeout-e2e.mjs +0 -93
  305. package/scripts/_dbg-live-line-e2e.mjs +0 -279
  306. package/scripts/_dbg-minimal-omp.mjs +0 -77
  307. package/scripts/_dbg-mode-nav.mjs +0 -173
  308. package/scripts/_dbg-real-scenario-timeline.log +0 -55
  309. package/scripts/_dbg-team-repro.mjs +0 -76
  310. package/scripts/_dbg-town-agents-probe.mjs +0 -60
  311. package/scripts/_dbg-visual-acceptance.mjs +0 -63
  312. package/scripts/_townview_style.css +0 -1185
  313. package/scripts/_townview_template.html +0 -1215
  314. package/server/data/agent-node-bindings.json +0 -554
  315. package/server/data/daq-templates.json +0 -16
  316. package/server/data/daqs.json +0 -7464
  317. package/server/data/dcw-lines.json +0 -1787
  318. package/server/data/dcw-products.json +0 -1626
  319. package/server/data/dcw-recipes.json +0 -4672
  320. package/server/data/dcw-rollback.json +0 -20754
  321. package/server/data/dcw-runs.json +0 -6354
  322. package/server/data/dcw-templates.json +0 -146
  323. package/server/data/dcw-writes.json +0 -15650
  324. package/server/data/dcws.json +0 -9033
  325. package/server/data/device-twins.json +0 -133
  326. package/server/data/line-runs.json +0 -322
  327. package/server/data/scene-layouts.json +0 -277
@@ -1,1215 +0,0 @@
1
- <template>
2
- <div class="town-view">
3
- <!-- ================= 顶部导航 ================= -->
4
- <header class="topnav">
5
- <div class="brand">
6
- <svg
7
- class="brand-glyph"
8
- viewBox="0 0 32 32"
9
- fill="none"
10
- aria-hidden="true"
11
- >
12
- <path
13
- d="M16 2.5 27.5 9v14L16 29.5 4.5 23V9L16 2.5Z"
14
- stroke="#35e0a0"
15
- stroke-width="1.6"
16
- />
17
- <path
18
- d="M16 8.2 22.5 12v8L16 23.8 9.5 20v-8L16 8.2Z"
19
- stroke="#41c8f4"
20
- stroke-width="1.3"
21
- opacity=".85"
22
- />
23
- <circle
24
- cx="16"
25
- cy="16"
26
- r="2.4"
27
- fill="#35e0a0"
28
- />
29
- <circle
30
- cx="25.5"
31
- cy="7.5"
32
- r="1.6"
33
- fill="#41c8f4"
34
- />
35
- </svg>
36
- <div>
37
- <div class="brand-name">
38
- DIGITAL <em>TWIN</em>
39
- </div>
40
- <div class="brand-sub">
41
- AGENT 数字孪生空间
42
- </div>
43
- </div>
44
- </div>
45
- <nav
46
- class="nav-tabs"
47
- aria-label="场景模式"
48
- >
49
- <div class="seg">
50
- <button
51
- :class="{ on: mode === 'browse' }"
52
- @click="mode === 'edit' && toggleMode()"
53
- >
54
- 运行
55
- </button>
56
- <button
57
- :class="{ on: mode === 'edit' }"
58
- @click="mode === 'browse' && toggleMode()"
59
- >
60
- 编辑
61
- </button>
62
- </div>
63
- <button
64
- class="nav-action"
65
- title="把全部设备的位置/朝向/缩放写入数据库"
66
- @click="saveLayout"
67
- >
68
- 保存布局
69
- </button>
70
- <span
71
- v-if="saveState && saveState.state !== 'idle'"
72
- class="save-chip"
73
- :class="`s-${saveState.state}`"
74
- >{{ saveStateLabel }}</span>
75
- </nav>
76
- <div class="nav-right">
77
- <span class="nav-chip mono">{{ fps }} FPS</span>
78
- <div
79
- class="avatar-chip"
80
- title="当前用户"
81
- >
82
- <div class="avatar-fallback">
83
- {{ (userStore.name ?? 'OP').slice(0, 2).toUpperCase() }}
84
- </div>
85
- <span>{{ userStore.name || '产线管理员' }}</span>
86
- </div>
87
- </div>
88
- </header>
89
-
90
- <!-- ================= 三栏应用区 ================= -->
91
- <div class="app">
92
- <!-- 左轨:设备资源 / 数采节点 / 场景管理 -->
93
- <aside class="rail rail-left">
94
- <section class="panel">
95
- <div class="panel-hd">
96
- <h3>设备资源</h3>
97
- <span class="panel-tag">{{ deviceModels.length }}</span>
98
- </div>
99
- <WorkshopAssetLibrary class="lib-embed" />
100
- </section>
101
-
102
- <section class="panel">
103
- <div class="panel-hd">
104
- <h3>数采节点 · DAQ</h3>
105
- <span class="panel-tag">{{ daqTwins.length }}</span>
106
- </div>
107
- <div class="daq-list">
108
- <div
109
- v-for="tpl in daqTemplates"
110
- :key="tpl.id"
111
- class="daq-card"
112
- draggable="true"
113
- :title="`${tpl.name} · 拖到场景实例化,量程 ${tpl.min} ~ ${tpl.max} ${tpl.unit}`"
114
- @dragstart="onDaqDragStart($event, tpl)"
115
- >
116
- <span class="daq-ico" />
117
- <div class="daq-meta">
118
- <span class="daq-name">{{ tpl.name }}</span>
119
- <span class="daq-code">{{ tpl.code }}</span>
120
- </div>
121
- <span class="daq-count">×{{ daqCount(tpl.id) }}</span>
122
- </div>
123
- </div>
124
- </section>
125
-
126
- <section class="panel">
127
- <div class="panel-hd">
128
- <h3>场景管理</h3>
129
- <span class="panel-tag">{{ dockChannels.length }}</span>
130
- </div>
131
- <div class="scene-list">
132
- <div
133
- v-for="ch in dockChannels"
134
- :key="ch.channelId"
135
- class="scene-row"
136
- :class="{ active: ch.placed }"
137
- :draggable="!ch.placed"
138
- :data-channel-id="ch.channelId"
139
- :title="ch.placed ? '已在场景中,点击定位' : '拖拽到场景放置'"
140
- @dragstart="ch.placed ? undefined : onChannelDragStart($event, ch.channelId)"
141
- @click="onDockCardClick(ch)"
142
- >
143
- <span
144
- class="scene-ico"
145
- :style="{ '--ch': ch.color }"
146
- />
147
- <div class="scene-meta-wrap">
148
- <span class="scene-name">{{ ch.name }}</span>
149
- <span class="scene-meta">{{ ch.agentCount }} 精魂 · {{ ch.placed ? '已放置' : '未放置' }}</span>
150
- </div>
151
- <span
152
- v-if="ch.placed"
153
- class="scene-cur"
154
- >✓</span>
155
- <span
156
- v-else
157
- class="scene-add"
158
- >+</span>
159
- </div>
160
- <div
161
- v-if="dockHint"
162
- class="scene-hint"
163
- >
164
- {{ dockHint }}
165
- </div>
166
- </div>
167
- </section>
168
- </aside>
169
-
170
- <!-- 中栏:舞台 + 底部坞 -->
171
- <main class="stage-col">
172
- <div
173
- ref="stageRef"
174
- class="stage"
175
- >
176
- <div
177
- id="town-host"
178
- ref="hostRef"
179
- class="town-host"
180
- />
181
- <div class="stage-vignette" />
182
-
183
- <div class="stage-top">
184
- <div class="vp-title">
185
- <h2>产线孪生总览</h2>
186
- <span class="vp-id">CH · {{ activeChannelName || '加载中' }}</span>
187
- </div>
188
- <div class="vp-tools">
189
- <button
190
- class="vp-tool"
191
- :class="{ on: showCallouts }"
192
- title="数据标注显隐"
193
- @click="showCallouts = !showCallouts"
194
- >
195
- 标
196
- </button>
197
- <button
198
- class="vp-tool"
199
- title="定位选中实体"
200
- @click="locateSelected"
201
- >
202
- 定
203
- </button>
204
- <button
205
- class="vp-tool"
206
- title="全屏舞台"
207
- @click="fullscreenStage"
208
- >
209
- 全
210
- </button>
211
- </div>
212
- </div>
213
-
214
- <div class="angle-chip">
215
- <select
216
- :value="viewPreset"
217
- aria-label="视角预设"
218
- @change="onViewPreset($event)"
219
- >
220
- <option value="std">
221
- 3D 视角 · 标准
222
- </option>
223
- <option value="top">
224
- 俯视 · TOP
225
- </option>
226
- <option value="front">
227
- 前视 · FRONT
228
- </option>
229
- <option value="side">
230
- 侧视 · SIDE
231
- </option>
232
- </select>
233
- </div>
234
-
235
- <!-- 数据标注层 -->
236
- <div
237
- v-if="showCallouts"
238
- class="callout-layer"
239
- >
240
- <div
241
- v-for="c in callouts"
242
- :key="c.id"
243
- class="callout"
244
- :class="{ warn: c.warn }"
245
- :style="{ left: c.x + 'px', top: c.y + 'px' }"
246
- >
247
- <div class="co-label">
248
- <span class="co-dot" />{{ c.label }}
249
- </div>
250
- <div class="co-val">
251
- {{ c.value }}<small>{{ c.unit }}</small>
252
- </div>
253
- <div class="co-range">
254
- 正常范围 {{ c.min }} ~ {{ c.max }}
255
- </div>
256
- </div>
257
- </div>
258
-
259
- <!-- KPI 条 -->
260
- <div class="kpi-strip">
261
- <div class="kpi">
262
- <span class="kpi-ico c1">设</span>
263
- <div class="kpi-meta">
264
- <div class="kpi-label">
265
- 设备总数
266
- </div>
267
- <div class="kpi-val">
268
- {{ deviceCount }}<small>台</small>
269
- </div>
270
- </div>
271
- </div>
272
- <div class="kpi">
273
- <span class="kpi-ico c2">运</span>
274
- <div class="kpi-meta">
275
- <div class="kpi-label">
276
- 运行设备
277
- </div>
278
- <div class="kpi-val">
279
- {{ runningCount }}<small>台</small>
280
- </div>
281
- </div>
282
- </div>
283
- <div class="kpi">
284
- <span class="kpi-ico c3">领</span>
285
- <div class="kpi-meta">
286
- <div class="kpi-label">
287
- 领地
288
- </div>
289
- <div class="kpi-val">
290
- {{ blockCount }}
291
- </div>
292
- </div>
293
- </div>
294
- <div class="kpi">
295
- <span class="kpi-ico c4">魂</span>
296
- <div class="kpi-meta">
297
- <div class="kpi-label">
298
- 精魂
299
- </div>
300
- <div class="kpi-val">
301
- {{ agentCount }}
302
- </div>
303
- </div>
304
- </div>
305
- <div class="kpi">
306
- <span class="kpi-ico c5">帧</span>
307
- <div class="kpi-meta">
308
- <div class="kpi-label">
309
- FPS
310
- </div>
311
- <div class="kpi-val">
312
- {{ fps }}
313
- </div>
314
- </div>
315
- </div>
316
- </div>
317
-
318
- <div
319
- v-if="blockCount === 0 && ready"
320
- class="empty-hint"
321
- >
322
- <b>空场景</b>
323
- <span>从左侧「设备资源 / 数采节点」拖入实体,开始搭建数字孪生空间</span>
324
- </div>
325
-
326
- <!-- 精魂会话台(选中角色;浮动) -->
327
- <div
328
- v-if="selected?.kind === 'agent'"
329
- class="agent-chat drag-panel"
330
- :data-agent-id="selected.id"
331
- :style="panelPos['agent-chat'] ? { left: panelPos['agent-chat'].x + 'px', top: panelPos['agent-chat'].y + 'px', bottom: 'auto', transform: 'none' } : undefined"
332
- >
333
- <div
334
- class="agent-chat-head drag-grip"
335
- title="拖动移动面板"
336
- @pointerdown="onPanelGripDown($event, 'agent-chat')"
337
- >
338
- <span
339
- class="chat-badge"
340
- :style="{ '--p-acc': agentChatColor }"
341
- >{{ agentChatTitle.slice(0, 1).toUpperCase() }}</span>
342
- <div class="chat-nameplate">
343
- <span class="chat-name">{{ agentChatTitle }}</span>
344
- <span class="chat-meta">
345
- <span
346
- class="chat-state"
347
- :class="`s-${selectedAgentMeta?.state ?? 'idle'}`"
348
- >{{ agentChatStateLabel }}</span>
349
- <span
350
- v-if="selectedAgentMeta?.harness"
351
- class="chat-harness"
352
- >{{ selectedAgentMeta.harness.toUpperCase() }}</span>
353
- <span class="chat-count">{{ agentHistory.length + agentChatRows.length }} 条</span>
354
- </span>
355
- </div>
356
- <button
357
- class="chat-refresh"
358
- title="重新拉取该角色的历史对话"
359
- @click="onRefreshHistory"
360
- >
361
- {{ historyLoading ? '…' : '↻' }}
362
- </button>
363
- </div>
364
- <div
365
- ref="chatScroll"
366
- class="rpg-lines"
367
- >
368
- <div
369
- v-if="historyLoading"
370
- class="rpg-note"
371
- >
372
- 正在读取历史对话…
373
- </div>
374
- <template v-if="agentHistory.length">
375
- <div class="rpg-divider">
376
- 历史记录 · {{ agentHistory.length }}
377
- </div>
378
- <div
379
- v-for="r in agentHistory"
380
- :key="r.id"
381
- class="rpg-line hist"
382
- >
383
- <span class="rpg-time">{{ fmtTime(r.at) }}</span>
384
- <div class="rpg-bubble">
385
- <span
386
- v-if="chatKindLabel(r.kind)"
387
- class="rpg-kind"
388
- :class="`k-${r.kind}`"
389
- >{{ chatKindLabel(r.kind) }}</span>
390
- <span class="rpg-text">{{ r.text }}</span>
391
- </div>
392
- </div>
393
- </template>
394
- <div
395
- v-if="agentChatRows.length"
396
- class="rpg-divider live"
397
- >
398
- 实时 · {{ agentChatRows.length }}
399
- </div>
400
- <div
401
- v-for="r in agentChatRows"
402
- :key="r.id"
403
- class="rpg-line live"
404
- >
405
- <span class="rpg-time">{{ fmtTime(r.at) }}</span>
406
- <div class="rpg-bubble">
407
- <span class="rpg-text">{{ r.text }}</span>
408
- </div>
409
- </div>
410
- <div
411
- v-if="selectedAgentMeta?.state === 'busy'"
412
- class="rpg-typing"
413
- >
414
- <span class="ty-dot" /><span class="ty-dot" /><span class="ty-dot" />
415
- <span class="ty-label">执行中</span>
416
- </div>
417
- <div
418
- v-if="!historyLoading && !agentHistory.length && !agentChatRows.length"
419
- class="rpg-note"
420
- >
421
- 暂无对话 · 该精魂尚未开口
422
- </div>
423
- </div>
424
- </div>
425
-
426
- <!-- 频道边界编辑(浮动) -->
427
- <div
428
- v-if="selectedChannel && boundaryDraft"
429
- class="boundary-panel drag-panel"
430
- :style="panelPos.boundary ? { left: panelPos.boundary.x + 'px', top: panelPos.boundary.y + 'px', bottom: 'auto', transform: 'none' } : undefined"
431
- >
432
- <div
433
- class="bp-title drag-grip"
434
- title="拖动移动面板"
435
- @pointerdown="onPanelGripDown($event, 'boundary')"
436
- >
437
- <span class="bp-name">{{ entities.channels[selectedChannel]?.name ?? selectedChannel.slice(0, 8) }}</span>
438
- <span class="bp-sub">频道管理</span>
439
- <div class="bp-tabs">
440
- <button
441
- class="bp-tab"
442
- :class="{ on: channelPanelTab === 'boundary' }"
443
- @click="openChannelTab('boundary')"
444
- >
445
- 边界
446
- </button>
447
- <button
448
- class="bp-tab"
449
- :class="{ on: channelPanelTab === 'members' }"
450
- @click="openChannelTab('members')"
451
- >
452
- 成员 {{ channelMembers.length }}
453
- </button>
454
- </div>
455
- </div>
456
- <template v-if="channelPanelTab === 'boundary'">
457
- <div class="bp-row">
458
- <span class="bp-label">形状</span>
459
- <div class="bp-seg">
460
- <button
461
- class="seg-btn"
462
- :class="{ on: boundaryDraft.shape === 'ellipse' }"
463
- @click="boundaryDraft.shape = 'ellipse'; applyBoundaryDraft()"
464
- >
465
- 椭圆
466
- </button>
467
- <button
468
- class="seg-btn"
469
- :class="{ on: boundaryDraft.shape === 'rect' }"
470
- @click="boundaryDraft.shape = 'rect'; applyBoundaryDraft()"
471
- >
472
- 矩形
473
- </button>
474
- </div>
475
- </div>
476
- <div class="bp-row">
477
- <span class="bp-label">横轴半径</span>
478
- <input
479
- v-model.number="boundaryDraft.radiusX"
480
- class="bp-range"
481
- type="range"
482
- min="80"
483
- max="4000"
484
- step="8"
485
- @change="applyBoundaryDraft"
486
- >
487
- <span class="bp-val">{{ Math.round(boundaryDraft.radiusX) }}</span>
488
- </div>
489
- <div class="bp-row">
490
- <span class="bp-label">纵轴半径</span>
491
- <input
492
- v-model.number="boundaryDraft.radiusZ"
493
- class="bp-range"
494
- type="range"
495
- min="60"
496
- max="4000"
497
- step="8"
498
- @change="applyBoundaryDraft"
499
- >
500
- <span class="bp-val">{{ Math.round(boundaryDraft.radiusZ) }}</span>
501
- </div>
502
- <div class="bp-row">
503
- <span class="bp-label">朝向</span>
504
- <input
505
- v-model.number="boundaryDraft.rotationY"
506
- class="bp-range"
507
- type="range"
508
- min="0"
509
- max="360"
510
- step="5"
511
- @change="applyBoundaryDraft"
512
- >
513
- <span class="bp-val">{{ Math.round(boundaryDraft.rotationY ?? 0) }}°</span>
514
- </div>
515
- </template>
516
- <template v-else>
517
- <div class="bp-hint">
518
- 为频道成员选择 3D 角色模型(实时换装并持久化)
519
- </div>
520
- <div class="member-list">
521
- <div
522
- v-for="m in channelMembers"
523
- :key="m.agentId"
524
- class="member-row"
525
- >
526
- <span
527
- class="member-ava"
528
- :style="{ color: hashColor(selectedChannel) }"
529
- >{{ m.name.charAt(0).toUpperCase() }}</span>
530
- <div class="member-info">
531
- <span class="member-name">{{ m.name }}</span>
532
- <span class="member-role">{{ m.role === 'lead' ? '领队' : '成员' }}</span>
533
- </div>
534
- <select
535
- class="member-select"
536
- :value="(m.modelRef ?? '') || 'hero-3d'"
537
- @change="bindMemberModel(m.agentId, ($event.target as HTMLSelectElement).value)"
538
- >
539
- <option
540
- v-for="model in agentModels"
541
- :key="model.id"
542
- :value="model.id"
543
- >
544
- {{ model.name }}
545
- </option>
546
- </select>
547
- </div>
548
- <div
549
- v-if="channelMembers.length === 0"
550
- class="bp-hint"
551
- >
552
- 该频道暂无成员
553
- </div>
554
- </div>
555
- </template>
556
- <div class="bp-actions">
557
- <button
558
- v-if="channelPanelTab === 'boundary'"
559
- class="bp-btn save"
560
- @click="saveChannelLayout"
561
- >
562
- 保存边界
563
- </button>
564
- <button
565
- class="bp-btn danger"
566
- @click="removeChannelFromScene"
567
- >
568
- 移除频道
569
- </button>
570
- <button
571
- class="bp-btn"
572
- @click="onSelectChannel(null)"
573
- >
574
- 关闭
575
- </button>
576
- </div>
577
- </div>
578
-
579
- <!-- 反馈芯片 -->
580
- <div
581
- v-if="lastDropText"
582
- class="drop-chip"
583
- >
584
- {{ lastDropText }}
585
- </div>
586
- <div
587
- v-if="errorText"
588
- class="error-chip"
589
- >
590
- {{ errorText }}
591
- </div>
592
-
593
- <!-- 加载遮罩 -->
594
- <div
595
- v-if="!ready"
596
- data-hud="town-loading"
597
- class="loading-mask"
598
- >
599
- <div class="loading-spinner" />
600
- <span class="loading-text">正在铺设数字孪生空间…</span>
601
- </div>
602
- </div>
603
-
604
- <!-- 底部坞:场景控制 + 趋势分析 -->
605
- <div class="dock">
606
- <section class="dock-card">
607
- <div class="dock-hd">
608
- <h3>场景控制</h3>
609
- <span class="dock-mode">{{ mode === 'edit' ? '编辑模式' : '运行模式' }}</span>
610
- </div>
611
- <div class="ctl-row">
612
- <span class="ctl-name">环境光照</span>
613
- <input
614
- class="ctl-range"
615
- type="range"
616
- min="30"
617
- max="220"
618
- :value="Math.round(exposure * 100)"
619
- @input="onExposureInput($event)"
620
- >
621
- <span class="ctl-val">{{ Math.round(exposure * 100) }}%</span>
622
- </div>
623
- <div class="ctl-row">
624
- <span class="ctl-name">领地染色</span>
625
- <input
626
- class="ctl-range"
627
- type="range"
628
- min="10"
629
- max="100"
630
- :value="Math.round(tintOpacity * 100)"
631
- @input="onTintInput($event)"
632
- >
633
- <span class="ctl-val">{{ Math.round(tintOpacity * 100) }}%</span>
634
- </div>
635
- <div class="ctl-row">
636
- <span class="ctl-name">网格吸附</span>
637
- <button
638
- class="snap-toggle"
639
- :class="{ on: snap }"
640
- @click="toggleSnap"
641
- >
642
- {{ snap ? '开' : '关' }}
643
- </button>
644
- <span class="ctl-val" />
645
- </div>
646
- <div class="ctl-btns">
647
- <button
648
- class="btn btn-primary"
649
- @click="onResetView"
650
- >
651
- 重置视角
652
- </button>
653
- <button
654
- class="btn btn-ghost"
655
- @click="saveLayout"
656
- >
657
- 保存布局
658
- </button>
659
- </div>
660
- </section>
661
-
662
- <section class="dock-card">
663
- <div class="dock-hd">
664
- <h3>趋势分析</h3>
665
- <div class="trend-legend">
666
- <button
667
- v-for="t in daqTwins"
668
- :key="t.id"
669
- class="lg-chip"
670
- :class="{ off: hiddenTrends[t.id] }"
671
- @click="toggleTrend(t.id)"
672
- >
673
- <span
674
- class="lg-dot"
675
- :style="{ background: trendColor(t.id) }"
676
- />{{ t.name }}
677
- </button>
678
- </div>
679
- </div>
680
- <div class="trend-wrap">
681
- <canvas
682
- ref="trendCanvas"
683
- class="trend-cv"
684
- />
685
- <span
686
- v-if="!daqTwins.length"
687
- class="trend-empty"
688
- >从左侧拖入数采节点,实时趋势将在此绘制</span>
689
- </div>
690
- </section>
691
- </div>
692
- </main>
693
-
694
- <!-- 右轨:Inspector / 设备运行状态 / 关键设备 / 实时事件 / 导航 -->
695
- <aside class="rail rail-right">
696
- <section
697
- v-if="selected"
698
- class="panel inspector"
699
- >
700
- <div class="panel-hd">
701
- <h3>{{ selected.kind === 'device' ? (selectedIsDaq ? '数采节点' : '设备实例') : '精魂' }}</h3>
702
- <button
703
- class="mini-btn"
704
- title="取消选中"
705
- @click="closeScale"
706
- >
707
- ✕
708
- </button>
709
- </div>
710
-
711
- <div class="ins-chip-row">
712
- <span class="ins-chip mono">{{ selected.id.slice(0, 8) }}</span>
713
- <span
714
- v-if="selected.kind === 'device' && selectedIsDaq"
715
- class="ins-chip accent"
716
- >DAQ</span>
717
- </div>
718
-
719
- <template v-if="selected.kind === 'device'">
720
- <div
721
- v-if="selectedIsDaq"
722
- class="daq-info"
723
- >
724
- <div class="daq-info-row">
725
- <span>实时值</span>
726
- <b class="cy">{{ selectedDaqSim ? fmtDaq(selectedDaqSim) : '--' }} {{ selectedDaqSim?.tpl.unit }}</b>
727
- </div>
728
- <div class="daq-info-row">
729
- <span>正常范围</span>
730
- <b>{{ selectedDaqSim?.tpl.min }} ~ {{ selectedDaqSim?.tpl.max }}</b>
731
- </div>
732
- <div class="daq-info-row">
733
- <span>绑定设备</span>
734
- <b>{{ daqBoundDeviceName || '未绑定' }}</b>
735
- </div>
736
- <div class="bind-add">
737
- <select
738
- v-model="bindPick"
739
- class="bind-select"
740
- >
741
- <option value="">
742
- 选择设备实例…
743
- </option>
744
- <option
745
- v-for="dv in deviceTwins.twins.filter(x => x.kind !== 'daq' && x.id !== selected.id)"
746
- :key="dv.id"
747
- :value="dv.id"
748
- >
749
- {{ dv.name }}
750
- </option>
751
- </select>
752
- <button
753
- class="bind-add-btn"
754
- :disabled="!bindPick"
755
- @click="daqBoundDeviceName ? unbindDaq(selected.id) : bindDaq(selected.id, bindPick); bindPick = ''"
756
- >
757
- {{ daqBoundDeviceName ? '解绑' : '绑定' }}
758
- </button>
759
- </div>
760
- </div>
761
- <template v-else>
762
- <div class="obj-row">
763
- <span class="obj-label">名称</span>
764
- <input
765
- v-model="objNameDraft"
766
- class="obj-input"
767
- placeholder="设备名称"
768
- @change="onObjNameCommit"
769
- @keydown.enter="onObjNameCommit"
770
- >
771
- </div>
772
- <div class="obj-row">
773
- <span class="obj-label">模型</span>
774
- <select
775
- class="obj-select"
776
- :value="scene3dRef?.getDeviceModelRef?.(selected.id) ?? ''"
777
- @change="bindDeviceModel(($event.target as HTMLSelectElement).value)"
778
- >
779
- <option
780
- v-for="m in deviceModels"
781
- :key="m.id"
782
- :value="m.id"
783
- >
784
- {{ m.name }}
785
- </option>
786
- </select>
787
- </div>
788
-
789
- <!-- 数采绑定 -->
790
- <div class="sect-hd">
791
- 数采绑定
792
- </div>
793
- <div
794
- v-for="r in boundDaqRows"
795
- :key="r.daqId"
796
- class="bind-row-item"
797
- >
798
- <span
799
- class="bind-dot"
800
- :style="{ background: r.color }"
801
- />
802
- <span class="bind-name">{{ r.name }}</span>
803
- <span class="bind-val">{{ r.value }} {{ r.unit }}</span>
804
- <button
805
- class="bind-x"
806
- title="解除绑定"
807
- @click="unbindDaq(r.daqId)"
808
- >
809
- ✕
810
- </button>
811
- </div>
812
- <div
813
- v-if="!boundDaqRows.length"
814
- class="ins-empty"
815
- >
816
- 暂无绑定 · 可拖入数采节点后绑定
817
- </div>
818
- <div class="bind-add">
819
- <select
820
- v-model="bindPick"
821
- class="bind-select"
822
- >
823
- <option value="">
824
- 选择数采节点…
825
- </option>
826
- <option
827
- v-for="dq in daqTwins"
828
- :key="dq.id"
829
- :value="dq.id"
830
- >
831
- {{ dq.name }}
832
- </option>
833
- </select>
834
- <button
835
- class="bind-add-btn"
836
- :disabled="!bindPick"
837
- @click="bindDaq(bindPick, selected.id); bindPick = ''"
838
- >
839
- 绑定
840
- </button>
841
- </div>
842
-
843
- <!-- 变换模式(Blender G/R/S) -->
844
- <div class="sect-hd">
845
- 变换 · BLENDER
846
- </div>
847
- <div class="xz-seg">
848
- <button
849
- class="seg-btn"
850
- :class="{ on: tMode === 'translate' }"
851
- @click="setTMode('translate')"
852
- >
853
- 移动 G
854
- </button>
855
- <button
856
- class="seg-btn"
857
- :class="{ on: tMode === 'rotate' }"
858
- @click="setTMode('rotate')"
859
- >
860
- 旋转 R
861
- </button>
862
- <button
863
- class="seg-btn"
864
- :class="{ on: tMode === 'scale' }"
865
- @click="setTMode('scale')"
866
- >
867
- 缩放 S
868
- </button>
869
- </div>
870
- <div class="scale-row">
871
- <span class="scale-min">0.2×</span>
872
- <input
873
- class="scale-range"
874
- type="range"
875
- min="0.2"
876
- max="5"
877
- step="0.05"
878
- :value="selected.scale"
879
- @input="onScaleInput(Number(($event.target as HTMLInputElement).value))"
880
- @change="onScaleCommit(Number(($event.target as HTMLInputElement).value))"
881
- >
882
- <span class="scale-max">5×</span>
883
- </div>
884
- <div class="scale-val">
885
- {{ Math.round(selected.scale * 100) }}%
886
- </div>
887
-
888
- <div class="ins-foot">
889
- <button
890
- class="btn btn-danger ins-del"
891
- :class="{ armed: deviceDeleteArmed === selected.id }"
892
- @click="removeSelectedDevice"
893
- >
894
- {{ deviceDeleteArmed === selected.id ? '再次点击确认删除' : '删除设备实例' }}
895
- </button>
896
- </div>
897
- </template>
898
- </template>
899
-
900
- <template v-else-if="selected.kind === 'agent'">
901
- <div class="obj-row">
902
- <span class="obj-label">模型</span>
903
- <select
904
- class="obj-select"
905
- :value="(scene3dRef?.getAgentModel?.(selected.id) ?? '') || 'hero-3d'"
906
- @change="bindAgentModel(($event.target as HTMLSelectElement).value)"
907
- >
908
- <option
909
- v-for="m in agentModels"
910
- :key="m.id"
911
- :value="m.id"
912
- >
913
- {{ m.name }}
914
- </option>
915
- </select>
916
- </div>
917
- <div class="obj-sep" />
918
- <div class="obj-row">
919
- <span class="obj-label">活动范围</span>
920
- <span class="range-status">{{ agentRangeStatusText }}</span>
921
- <button
922
- class="obj-mini"
923
- :class="{ on: agentDrawingRange }"
924
- title="在场景中拖动框选,确定该角色的移动范围"
925
- @click="onToggleRangeDraw"
926
- >
927
- {{ agentDrawingRange ? '绘制中' : '框选绘制' }}
928
- </button>
929
- </div>
930
- <template v-if="agentRangeDraft">
931
- <div class="obj-row">
932
- <span class="obj-label">形状</span>
933
- <div class="bp-seg">
934
- <button
935
- class="seg-btn"
936
- :class="{ on: agentRangeDraft.shape === 'ellipse' }"
937
- @click="agentRangeDraft.shape = 'ellipse'; applyAgentRangeDraft()"
938
- >
939
- 椭圆
940
- </button>
941
- <button
942
- class="seg-btn"
943
- :class="{ on: agentRangeDraft.shape === 'rect' }"
944
- @click="agentRangeDraft.shape = 'rect'; applyAgentRangeDraft()"
945
- >
946
- 矩形
947
- </button>
948
- </div>
949
- </div>
950
- <div class="obj-row">
951
- <span class="obj-label">横轴</span>
952
- <input
953
- v-model.number="agentRangeDraft.radiusX"
954
- class="obj-range"
955
- type="range"
956
- min="40"
957
- max="4000"
958
- step="8"
959
- @input="applyAgentRangeDraft"
960
- @change="onAgentRangeCommit"
961
- >
962
- <span class="bp-val">{{ Math.round(agentRangeDraft.radiusX) }}</span>
963
- </div>
964
- <div class="obj-row">
965
- <span class="obj-label">纵轴</span>
966
- <input
967
- v-model.number="agentRangeDraft.radiusZ"
968
- class="obj-range"
969
- type="range"
970
- min="40"
971
- max="4000"
972
- step="8"
973
- @input="applyAgentRangeDraft"
974
- @change="onAgentRangeCommit"
975
- >
976
- <span class="bp-val">{{ Math.round(agentRangeDraft.radiusZ) }}</span>
977
- </div>
978
- <button
979
- class="obj-mini danger"
980
- @click="onClearAgentRange"
981
- >
982
- 清除范围(回退频道)
983
- </button>
984
- </template>
985
- <div
986
- v-else
987
- class="ins-empty"
988
- >
989
- 未设置:沿用频道领地活动;点「框选绘制」定制
990
- </div>
991
- </template>
992
- </section>
993
-
994
- <section class="panel">
995
- <div class="panel-hd">
996
- <h3>设备运行状态</h3>
997
- </div>
998
- <div class="health-body">
999
- <div class="donut-wrap">
1000
- <canvas
1001
- ref="donutCanvas"
1002
- width="118"
1003
- height="118"
1004
- />
1005
- <div class="donut-center">
1006
- <b>{{ healthPct }}%</b>
1007
- <span>设备健康度</span>
1008
- </div>
1009
- </div>
1010
- <div class="health-legend">
1011
- <div class="hl-row">
1012
- <span
1013
- class="hl-dot"
1014
- :style="{ background: 'var(--hud-accent)' }"
1015
- />
1016
- 运行中
1017
- <span class="n">{{ runningCount }}</span>
1018
- </div>
1019
- <div class="hl-row">
1020
- <span
1021
- class="hl-dot"
1022
- :style="{ background: 'var(--hud-amber)' }"
1023
- />
1024
- 待命
1025
- <span class="n">{{ idleCount }}</span>
1026
- </div>
1027
- <div class="hl-row">
1028
- <span
1029
- class="hl-dot"
1030
- :style="{ background: 'var(--hud-danger)' }"
1031
- />
1032
- 告警
1033
- <span class="n">{{ alarmCount }}</span>
1034
- </div>
1035
- </div>
1036
- </div>
1037
- </section>
1038
-
1039
- <section class="panel">
1040
- <div class="panel-hd">
1041
- <h3>关键设备监控</h3>
1042
- <span class="panel-tag">{{ deviceTwins.twins.length }}</span>
1043
- </div>
1044
- <WorkshopDeviceTwinPanel
1045
- class="kd-embed"
1046
- @focus-device="onFocusDevice"
1047
- />
1048
- </section>
1049
-
1050
- <section class="panel">
1051
- <div class="panel-hd">
1052
- <h3>实时事件</h3>
1053
- <span class="panel-tag">{{ ticker.length }}</span>
1054
- </div>
1055
- <div class="event-list">
1056
- <div
1057
- v-for="(t, i) in [...ticker].reverse()"
1058
- :key="`${t.at}-${i}`"
1059
- class="event-row"
1060
- >
1061
- <span class="ev-time">{{ fmtTime(t.at) }}</span>
1062
- <span
1063
- class="ev-name"
1064
- :title="t.agentName"
1065
- >{{ t.agentName }}</span>
1066
- <span
1067
- class="ev-text"
1068
- :title="t.text"
1069
- >{{ t.text }}</span>
1070
- </div>
1071
- <div
1072
- v-if="!ticker.length"
1073
- class="event-empty"
1074
- >
1075
- 事件总线待命
1076
- </div>
1077
- </div>
1078
- </section>
1079
-
1080
- <section class="panel">
1081
- <div class="panel-hd">
1082
- <h3>3D 视图导航</h3>
1083
- </div>
1084
- <div
1085
- v-if="minimap && miniView"
1086
- class="mm-body"
1087
- >
1088
- <div class="mm-wrap">
1089
- <svg
1090
- :viewBox="`${miniView.ox} ${miniView.oy} ${miniView.winW} ${miniView.winH}`"
1091
- class="mini-svg"
1092
- role="img"
1093
- aria-label="镜头居中导航图,点击跳转,拖动平移镜头"
1094
- @click="onMinimapClick"
1095
- @pointerdown="onMinimapDown"
1096
- @pointermove="onMinimapMove"
1097
- @pointerup="onMinimapUp"
1098
- @pointercancel="onMinimapUp"
1099
- @pointerleave="onMinimapUp"
1100
- >
1101
- <rect
1102
- :x="miniView.ox"
1103
- :y="miniView.oy"
1104
- :width="miniView.winW"
1105
- :height="miniView.winH"
1106
- fill="#060b13"
1107
- />
1108
- <circle
1109
- v-for="b in minimap.blocks"
1110
- :key="`b-${b.name}`"
1111
- :cx="b.x * minimap.world.w"
1112
- :cy="b.y * minimap.world.h"
1113
- :rx="(b.rx ?? 0.04) * minimap.world.w"
1114
- :ry="(b.rz ?? 0.03) * minimap.world.h"
1115
- :fill="toHex(b.color)"
1116
- fill-opacity="0.12"
1117
- :stroke="toHex(b.color)"
1118
- stroke-opacity="0.6"
1119
- stroke-width="3"
1120
- />
1121
- <rect
1122
- v-for="d in minimap.devices"
1123
- :key="`d-${d.x.toFixed(4)}-${d.y.toFixed(4)}`"
1124
- :x="d.x * minimap.world.w - 23"
1125
- :y="d.y * minimap.world.h - 23"
1126
- width="46"
1127
- height="46"
1128
- rx="6"
1129
- :fill="toHex(d.color)"
1130
- opacity="0.95"
1131
- />
1132
- <circle
1133
- v-for="a in minimap.agents"
1134
- :key="`a-${a.x.toFixed(4)}-${a.y.toFixed(4)}`"
1135
- :cx="a.x * minimap.world.w"
1136
- :cy="a.y * minimap.world.h"
1137
- r="27"
1138
- :fill="toHex(a.color)"
1139
- stroke="rgba(6,10,16,0.7)"
1140
- stroke-width="6"
1141
- />
1142
- <g class="mini-reticle">
1143
- <rect
1144
- :x="miniView.ox + miniView.winW / 2 - 8"
1145
- :y="miniView.oy + miniView.winH / 2 - 8"
1146
- width="16"
1147
- height="16"
1148
- fill="none"
1149
- stroke="#35e0a0"
1150
- stroke-width="4"
1151
- />
1152
- </g>
1153
- </svg>
1154
- </div>
1155
- <div class="mm-col">
1156
- <button
1157
- class="vp-tool"
1158
- title="重置视角"
1159
- @click="onResetView"
1160
- >
1161
- 重
1162
- </button>
1163
- <button
1164
- class="vp-tool"
1165
- title="缩小"
1166
- @click="scene3dRef?.zoomBy(0.25)"
1167
- >
1168
- −
1169
- </button>
1170
- <button
1171
- class="vp-tool"
1172
- title="放大"
1173
- @click="scene3dRef?.zoomBy(-0.25)"
1174
- >
1175
- +
1176
- </button>
1177
- </div>
1178
- </div>
1179
- <div class="mm-meta mono">
1180
- {{ blockCount }} 领地 · {{ minimap.devices?.length ?? 0 }} 设备
1181
- </div>
1182
- </section>
1183
- </aside>
1184
- </div>
1185
-
1186
- <!-- ================= 状态栏 ================= -->
1187
- <footer class="statusbar">
1188
- <span>
1189
- <span
1190
- class="sb-dot"
1191
- :class="{ red: conn.state !== 'open' }"
1192
- />系统状态 <b>{{ conn.state === 'open' ? '正常' : syncing ? '同步中' : '离线' }}</b>
1193
- </span>
1194
- <span>
1195
- 领地 {{ blockCount }} · 精魂 {{ agentCount }} · 设备 {{ deviceCount }}
1196
- </span>
1197
- <span class="sb-lat mono">
1198
- {{ fps }} FPS
1199
- </span>
1200
- <span class="copy">
1201
- © 2026 ABO · DIGITAL TWIN · 演示数据为模拟信号
1202
- </span>
1203
- </footer>
1204
-
1205
- <!-- 加载遮罩(全页) -->
1206
- <div
1207
- v-if="!ready"
1208
- data-hud="town-loading"
1209
- class="loading-mask"
1210
- >
1211
- <div class="loading-spinner" />
1212
- <span class="loading-text">正在铺设数字孪生空间…</span>
1213
- </div>
1214
- </div>
1215
- </template>