agentworkshop 0.7.21 → 0.7.23
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.
- package/.output/nitro.json +1 -1
- package/.output/public/_nuxt/BJbhwS5o.js +1 -0
- package/.output/public/_nuxt/Bo8t5fkJ.js +1 -0
- package/.output/public/_nuxt/{DBXhDLgl.js → BuiGDfMB.js} +1 -1
- package/.output/public/_nuxt/{QT54MQV9.js → C53drffV.js} +1 -1
- package/.output/public/_nuxt/{BA3XzwiD.js → CEHmSufQ.js} +2 -2
- package/.output/public/_nuxt/Dc32T41C.js +1 -0
- package/.output/public/_nuxt/DtzZqC0z.js +1 -0
- package/.output/public/_nuxt/{BOJaHO5V.js → Ko9mMsJj.js} +3 -3
- package/.output/public/_nuxt/{TownView.BHLKAjEu.css → TownView.zu2rU5nS.css} +1 -1
- package/.output/public/_nuxt/_id_.C-YXjn8Q.css +1 -0
- package/.output/public/_nuxt/_id_.MbzdFygI.css +1 -0
- package/.output/public/_nuxt/_wsId_.BqWBlY7X.css +1 -0
- package/.output/public/_nuxt/builds/latest.json +1 -1
- package/.output/public/_nuxt/builds/meta/1adc96e0-8bbd-4a79-ac7e-b6952556fef8.json +1 -0
- package/.output/public/_nuxt/daq.3OKj58HH.css +1 -0
- package/.output/public/_nuxt/dbi5bHkY.js +2 -0
- package/.output/public/_nuxt/dcw.rXH2fc2Z.css +1 -0
- package/.output/public/_nuxt/entry.OR_psZHp.css +1 -0
- package/.output/public/_nuxt/{DsOkkMX3.js → jyQyulh-.js} +1 -1
- package/.output/public/_nuxt/logs.DMOM-iHe.css +1 -0
- package/.output/public/_nuxt/{B-E8fXAO.js → n3vMFfrv.js} +1 -1
- package/.output/server/chunks/_/nitro.mjs +842 -717
- package/.output/server/chunks/_/nitro.mjs.map +1 -1
- package/.output/server/chunks/build/{TownView-ql2eHZOg.mjs → TownView-DxeQe1Bl.mjs} +144 -143
- package/.output/server/chunks/build/{TownView-ql2eHZOg.mjs.map → TownView-DxeQe1Bl.mjs.map} +1 -1
- package/.output/server/chunks/build/{_id_-CwR5oLu9.mjs → _id_-DQDqj-cZ.mjs} +33 -35
- package/.output/server/chunks/build/_id_-DQDqj-cZ.mjs.map +1 -0
- package/.output/server/chunks/build/{_id_-Bs-euJPu.mjs → _id_-DY6v6sqB.mjs} +138 -137
- package/.output/server/chunks/build/_id_-DY6v6sqB.mjs.map +1 -0
- package/.output/server/chunks/build/{_wsId_-B3aB9bIp.mjs → _wsId_-C2O5zZsI.mjs} +6 -6
- package/.output/server/chunks/build/{_wsId_-B3aB9bIp.mjs.map → _wsId_-C2O5zZsI.mjs.map} +1 -1
- package/.output/server/chunks/build/{daq-epWL_sC7.mjs → daq-B-3xCp_2.mjs} +146 -118
- package/.output/server/chunks/build/daq-B-3xCp_2.mjs.map +1 -0
- package/.output/server/chunks/build/{dcw-CBPaFj2f.mjs → dcw-BL2dhuz7.mjs} +43 -43
- package/.output/server/chunks/build/{dcw-CBPaFj2f.mjs.map → dcw-BL2dhuz7.mjs.map} +1 -1
- package/.output/server/chunks/build/{logs-Bz1p6x_5.mjs → logs-Dto_umFY.mjs} +44 -38
- package/.output/server/chunks/build/logs-Dto_umFY.mjs.map +1 -0
- package/.output/server/chunks/build/{pages-Dq7_H4FK.mjs → pages-D9tOoJsf.mjs} +2 -2
- package/.output/server/chunks/build/{pages-Dq7_H4FK.mjs.map → pages-D9tOoJsf.mjs.map} +1 -1
- package/.output/server/chunks/build/{town-CstiKxf_.mjs → town-Dsh0jlwZ.mjs} +3 -3
- package/.output/server/chunks/build/{town-CstiKxf_.mjs.map → town-Dsh0jlwZ.mjs.map} +1 -1
- package/.output/server/chunks/build/{useDaqStream-BOOZ-p-l.mjs → useDaqStream-DkRL3tEW.mjs} +9 -1
- package/.output/server/chunks/build/{useDaqStream-BOOZ-p-l.mjs.map → useDaqStream-DkRL3tEW.mjs.map} +1 -1
- package/.output/server/chunks/routes/api/workshop/daq/_id/bind.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/daq/_id/bind.post.mjs.map +1 -1
- package/.output/server/chunks/routes/api/workshop/daq/_id/bindings.put.mjs +34 -0
- package/.output/server/chunks/routes/api/workshop/daq/_id/bindings.put.mjs.map +1 -0
- package/.output/server/chunks/routes/api/workshop/dcw/_id/bind.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/dcw/_id/bind.post.mjs.map +1 -1
- package/.output/server/chunks/routes/api/workshop/dcw/_id/bindings.put.mjs +33 -0
- package/.output/server/chunks/routes/api/workshop/dcw/_id/bindings.put.mjs.map +1 -0
- package/.output/server/chunks/routes/renderer.mjs +1 -1
- package/.output/server/chunks/virtual/entry.mjs +16 -8
- package/.output/server/chunks/virtual/precomputed.mjs +1 -1
- package/.output/server/package.json +1 -1
- package/app/assets/css/main.css +306 -161
- package/app/components/workshop/blocks/ClusterArtifact.vue +0 -16
- package/app/components/workshop/town/TownView.vue +7082 -7076
- package/app/composables/workshop/useDaqStream.ts +10 -0
- package/app/pages/daq/[id].vue +919 -937
- package/app/pages/daq/index.vue +67 -43
- package/app/pages/dcw/[id].vue +6 -5
- package/app/pages/dcw/index.vue +1 -30
- package/app/pages/logs/index.vue +99 -87
- package/i18n/locales/en.ts +3 -0
- package/i18n/locales/zh-CN.ts +5 -0
- package/package.json +148 -148
- package/scripts/_dbg-device-bind-e2e.mjs +129 -0
- package/scripts/_dbg-logs-ui-e2e.py +125 -0
- package/server/api/workshop/daq/[id]/bind.post.ts +5 -2
- package/server/api/workshop/daq/[id]/bindings.put.ts +22 -0
- package/server/api/workshop/dcw/[id]/bind.post.ts +4 -2
- package/server/api/workshop/dcw/[id]/bindings.put.ts +21 -0
- package/server/data/daqs.json +405 -100
- package/server/data/dcw-lines.json +7 -0
- package/server/data/dcw-rollback.json +21 -10
- package/server/data/dcw-runs.json +50 -54
- package/server/data/dcw-writes.json +24 -0
- package/server/data/dcws.json +505 -216
- package/server/data/line-runs.json +18 -18
- package/server/services/workshop/agents/industrial-context.ts +231 -222
- package/server/services/workshop/daq/daq-controller.ts +76 -23
- package/server/services/workshop/daq/daq-node.ts +11 -1
- package/server/services/workshop/dcw/dcw-controller.ts +50 -2
- package/server/services/workshop/dcw/dcw-node.ts +10 -1
- package/shared/daq-protocol.ts +2 -0
- package/shared/dcw-protocol.ts +2 -0
- package/.output/public/_nuxt/B_C3GLsD.js +0 -1
- package/.output/public/_nuxt/Ba7YofCb.js +0 -2
- package/.output/public/_nuxt/C_2bTK0C.js +0 -1
- package/.output/public/_nuxt/CgT5wMfz.js +0 -1
- package/.output/public/_nuxt/D4CLp1hc.js +0 -1
- package/.output/public/_nuxt/_id_.DIxEKj88.css +0 -1
- package/.output/public/_nuxt/_id_.IbmpkhH4.css +0 -1
- package/.output/public/_nuxt/_wsId_.BFyCuUwS.css +0 -1
- package/.output/public/_nuxt/builds/meta/5d344836-03a5-4f47-a0a7-fd30192f5a8f.json +0 -1
- package/.output/public/_nuxt/daq.6IrhVVfy.css +0 -1
- package/.output/public/_nuxt/dcw.rpqpOYQ8.css +0 -1
- package/.output/public/_nuxt/entry.DRUzDwmX.css +0 -1
- package/.output/public/_nuxt/logs.BarkrvUp.css +0 -1
- package/.output/server/chunks/build/_id_-Bs-euJPu.mjs.map +0 -1
- package/.output/server/chunks/build/_id_-CwR5oLu9.mjs.map +0 -1
- package/.output/server/chunks/build/daq-epWL_sC7.mjs.map +0 -1
- package/.output/server/chunks/build/logs-Bz1p6x_5.mjs.map +0 -1
package/package.json
CHANGED
|
@@ -1,148 +1,148 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "agentworkshop",
|
|
3
|
-
"version": "0.7.
|
|
4
|
-
"type": "module",
|
|
5
|
-
"description": "AgentWorkShop 软件开发系统 - 基于 Nuxt 4 的配置驱动运行范式",
|
|
6
|
-
"license": "PolyForm-Noncommercial-1.0.0",
|
|
7
|
-
"exports": {
|
|
8
|
-
".": {
|
|
9
|
-
"types": "./sdk/index.d.mts",
|
|
10
|
-
"default": "./sdk/index.mjs"
|
|
11
|
-
},
|
|
12
|
-
"./sdk": {
|
|
13
|
-
"types": "./sdk/index.d.mts",
|
|
14
|
-
"default": "./sdk/index.mjs"
|
|
15
|
-
},
|
|
16
|
-
"./sdk/client": {
|
|
17
|
-
"types": "./sdk/client.d.mts",
|
|
18
|
-
"default": "./sdk/client.mjs"
|
|
19
|
-
},
|
|
20
|
-
"./package.json": "./package.json"
|
|
21
|
-
},
|
|
22
|
-
"bin": {
|
|
23
|
-
"aw": "bin/aw.mjs",
|
|
24
|
-
"agentworkshop": "bin/aw.mjs"
|
|
25
|
-
},
|
|
26
|
-
"files": [
|
|
27
|
-
".output",
|
|
28
|
-
"bin",
|
|
29
|
-
"cli",
|
|
30
|
-
"sdk",
|
|
31
|
-
"app",
|
|
32
|
-
"server",
|
|
33
|
-
"shared",
|
|
34
|
-
"i18n",
|
|
35
|
-
"public",
|
|
36
|
-
"scripts",
|
|
37
|
-
".AgentWorkShop/prompts",
|
|
38
|
-
"config.yml",
|
|
39
|
-
"nuxt.config.ts",
|
|
40
|
-
"tsconfig.json",
|
|
41
|
-
"uno.config.ts",
|
|
42
|
-
"docker-compose.yml",
|
|
43
|
-
".env.example",
|
|
44
|
-
"README-zh.md",
|
|
45
|
-
"docs/cli.md",
|
|
46
|
-
"docs/plugins.md",
|
|
47
|
-
"docs/sdk.md",
|
|
48
|
-
"docs/tui.md",
|
|
49
|
-
"tui"
|
|
50
|
-
],
|
|
51
|
-
"packageManager": "pnpm@11.9.0",
|
|
52
|
-
"engines": {
|
|
53
|
-
"node": ">=23.4.0"
|
|
54
|
-
},
|
|
55
|
-
"scripts": {
|
|
56
|
-
"dev": "node bin/aw.mjs dev",
|
|
57
|
-
"build": "node bin/aw.mjs build",
|
|
58
|
-
"start": "node bin/aw.mjs start",
|
|
59
|
-
"generate": "nuxt generate",
|
|
60
|
-
"preview": "node bin/aw.mjs start",
|
|
61
|
-
"cli": "node bin/aw.mjs",
|
|
62
|
-
"tui": "node tui/aw-tui.mjs",
|
|
63
|
-
"postinstall": "node scripts/home-bootstrap.mjs",
|
|
64
|
-
"prepublishOnly": "node bin/aw.mjs build",
|
|
65
|
-
"lint": "eslint .",
|
|
66
|
-
"lint:fix": "eslint . --fix",
|
|
67
|
-
"typecheck": "nuxt typecheck",
|
|
68
|
-
"prepare": "husky",
|
|
69
|
-
"game:test": "tsx scripts/test-agent-brain.ts && tsx scripts/test-game-session.ts",
|
|
70
|
-
"test:api-live": "node scripts/api-live-e2e.mjs",
|
|
71
|
-
"game:verify": "node scripts/verify-game.mjs",
|
|
72
|
-
"game:build-assets": "node scripts/build-player-sheet.mjs",
|
|
73
|
-
"game:build-wuwa": "node scripts/build-wuwa-assets.mjs",
|
|
74
|
-
"game:build-models": "node scripts/build-character-models.mjs",
|
|
75
|
-
"game:build-world": "node scripts/build-world-map.mjs",
|
|
76
|
-
"game:build-model3d": "node scripts/build-model-3d.mjs"
|
|
77
|
-
},
|
|
78
|
-
"lint-staged": {
|
|
79
|
-
"*.{js,mjs,cjs,ts,vue}": "eslint --fix"
|
|
80
|
-
},
|
|
81
|
-
"dependencies": {
|
|
82
|
-
"@ant-design-vue/nuxt": "^1.4.6",
|
|
83
|
-
"@ant-design/icons-vue": "^7.0.1",
|
|
84
|
-
"@earendil-works/pi-tui": "^0.84.4",
|
|
85
|
-
"@fontsource-variable/fraunces": "^5.3.0",
|
|
86
|
-
"@fontsource-variable/geist": "^5.3.0",
|
|
87
|
-
"@fontsource-variable/geist-mono": "^5.3.0",
|
|
88
|
-
"@fontsource-variable/inter": "^5.3.0",
|
|
89
|
-
"@fontsource/eb-garamond": "^5.3.0",
|
|
90
|
-
"@fontsource/ibm-plex-mono": "^5.3.0",
|
|
91
|
-
"@iconify-json/tabler": "^1.2.0",
|
|
92
|
-
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
93
|
-
"@nuxtjs/i18n": "^9.0.0",
|
|
94
|
-
"@pinia/nuxt": "^0.11.0",
|
|
95
|
-
"@unocss/nuxt": "^66.0.0",
|
|
96
|
-
"@unocss/preset-attributify": "^66.0.0",
|
|
97
|
-
"@unocss/preset-icons": "^66.7.5",
|
|
98
|
-
"@unocss/preset-typography": "^66.0.0",
|
|
99
|
-
"@vueuse/core": "^13.0.0",
|
|
100
|
-
"@vueuse/nuxt": "^13.0.0",
|
|
101
|
-
"@xterm/addon-fit": "^0.11.0",
|
|
102
|
-
"@xterm/xterm": "^6.0.0",
|
|
103
|
-
"ant-design-vue": "^4.2.6",
|
|
104
|
-
"axios": "^1.7.9",
|
|
105
|
-
"echarts": "^5.6.0",
|
|
106
|
-
"h3": "2.0.1-rc.26",
|
|
107
|
-
"js-yaml": "^4.1.0",
|
|
108
|
-
"minio": "^8.0.7",
|
|
109
|
-
"modbus-serial": "^8.0.25",
|
|
110
|
-
"mqtt": "^5.15.2",
|
|
111
|
-
"node-opcua": "^2.178.0",
|
|
112
|
-
"nuxt": "^4.0.0",
|
|
113
|
-
"nuxt-auth-utils": "^0.5.0",
|
|
114
|
-
"pg": "^8.23.0",
|
|
115
|
-
"phaser": "^4.0.0",
|
|
116
|
-
"pinia": "^3.0.0",
|
|
117
|
-
"pinia-plugin-persistedstate": "^4.2.0",
|
|
118
|
-
"sqlite-vec": "^0.1.9",
|
|
119
|
-
"three": "^0.185.1",
|
|
120
|
-
"unocss": "^66.0.0",
|
|
121
|
-
"unplugin-icons": "^22.0.0",
|
|
122
|
-
"vue": "^3.5.13",
|
|
123
|
-
"vue-echarts": "^7.0.3",
|
|
124
|
-
"vue-router": "^4.5.0",
|
|
125
|
-
"zod": "^3.24.1"
|
|
126
|
-
},
|
|
127
|
-
"devDependencies": {
|
|
128
|
-
"@commitlint/cli": "^19.6.1",
|
|
129
|
-
"@commitlint/config-conventional": "^19.6.0",
|
|
130
|
-
"@nuxt/eslint": "^1.0.0",
|
|
131
|
-
"@types/js-yaml": "^4.0.9",
|
|
132
|
-
"@types/node": "^26.2.0",
|
|
133
|
-
"@types/three": "^0.185.4",
|
|
134
|
-
"eslint": "^9.17.0",
|
|
135
|
-
"husky": "^9.1.7",
|
|
136
|
-
"lint-staged": "^15.3.0",
|
|
137
|
-
"pngjs": "^7.0.0",
|
|
138
|
-
"puppeteer-core": "^25.5.0",
|
|
139
|
-
"tsx": "^4.23.12",
|
|
140
|
-
"typescript": "^5.7.3",
|
|
141
|
-
"vue-tsc": "^3.3.10"
|
|
142
|
-
},
|
|
143
|
-
"pnpm": {
|
|
144
|
-
"ignoredBuiltDependencies": [
|
|
145
|
-
"@serialport/bindings-cpp"
|
|
146
|
-
]
|
|
147
|
-
}
|
|
148
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "agentworkshop",
|
|
3
|
+
"version": "0.7.23",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "AgentWorkShop 软件开发系统 - 基于 Nuxt 4 的配置驱动运行范式",
|
|
6
|
+
"license": "PolyForm-Noncommercial-1.0.0",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./sdk/index.d.mts",
|
|
10
|
+
"default": "./sdk/index.mjs"
|
|
11
|
+
},
|
|
12
|
+
"./sdk": {
|
|
13
|
+
"types": "./sdk/index.d.mts",
|
|
14
|
+
"default": "./sdk/index.mjs"
|
|
15
|
+
},
|
|
16
|
+
"./sdk/client": {
|
|
17
|
+
"types": "./sdk/client.d.mts",
|
|
18
|
+
"default": "./sdk/client.mjs"
|
|
19
|
+
},
|
|
20
|
+
"./package.json": "./package.json"
|
|
21
|
+
},
|
|
22
|
+
"bin": {
|
|
23
|
+
"aw": "bin/aw.mjs",
|
|
24
|
+
"agentworkshop": "bin/aw.mjs"
|
|
25
|
+
},
|
|
26
|
+
"files": [
|
|
27
|
+
".output",
|
|
28
|
+
"bin",
|
|
29
|
+
"cli",
|
|
30
|
+
"sdk",
|
|
31
|
+
"app",
|
|
32
|
+
"server",
|
|
33
|
+
"shared",
|
|
34
|
+
"i18n",
|
|
35
|
+
"public",
|
|
36
|
+
"scripts",
|
|
37
|
+
".AgentWorkShop/prompts",
|
|
38
|
+
"config.yml",
|
|
39
|
+
"nuxt.config.ts",
|
|
40
|
+
"tsconfig.json",
|
|
41
|
+
"uno.config.ts",
|
|
42
|
+
"docker-compose.yml",
|
|
43
|
+
".env.example",
|
|
44
|
+
"README-zh.md",
|
|
45
|
+
"docs/cli.md",
|
|
46
|
+
"docs/plugins.md",
|
|
47
|
+
"docs/sdk.md",
|
|
48
|
+
"docs/tui.md",
|
|
49
|
+
"tui"
|
|
50
|
+
],
|
|
51
|
+
"packageManager": "pnpm@11.9.0",
|
|
52
|
+
"engines": {
|
|
53
|
+
"node": ">=23.4.0"
|
|
54
|
+
},
|
|
55
|
+
"scripts": {
|
|
56
|
+
"dev": "node bin/aw.mjs dev",
|
|
57
|
+
"build": "node bin/aw.mjs build",
|
|
58
|
+
"start": "node bin/aw.mjs start",
|
|
59
|
+
"generate": "nuxt generate",
|
|
60
|
+
"preview": "node bin/aw.mjs start",
|
|
61
|
+
"cli": "node bin/aw.mjs",
|
|
62
|
+
"tui": "node tui/aw-tui.mjs",
|
|
63
|
+
"postinstall": "node scripts/home-bootstrap.mjs",
|
|
64
|
+
"prepublishOnly": "node bin/aw.mjs build",
|
|
65
|
+
"lint": "eslint .",
|
|
66
|
+
"lint:fix": "eslint . --fix",
|
|
67
|
+
"typecheck": "nuxt typecheck",
|
|
68
|
+
"prepare": "husky",
|
|
69
|
+
"game:test": "tsx scripts/test-agent-brain.ts && tsx scripts/test-game-session.ts",
|
|
70
|
+
"test:api-live": "node scripts/api-live-e2e.mjs",
|
|
71
|
+
"game:verify": "node scripts/verify-game.mjs",
|
|
72
|
+
"game:build-assets": "node scripts/build-player-sheet.mjs",
|
|
73
|
+
"game:build-wuwa": "node scripts/build-wuwa-assets.mjs",
|
|
74
|
+
"game:build-models": "node scripts/build-character-models.mjs",
|
|
75
|
+
"game:build-world": "node scripts/build-world-map.mjs",
|
|
76
|
+
"game:build-model3d": "node scripts/build-model-3d.mjs"
|
|
77
|
+
},
|
|
78
|
+
"lint-staged": {
|
|
79
|
+
"*.{js,mjs,cjs,ts,vue}": "eslint --fix"
|
|
80
|
+
},
|
|
81
|
+
"dependencies": {
|
|
82
|
+
"@ant-design-vue/nuxt": "^1.4.6",
|
|
83
|
+
"@ant-design/icons-vue": "^7.0.1",
|
|
84
|
+
"@earendil-works/pi-tui": "^0.84.4",
|
|
85
|
+
"@fontsource-variable/fraunces": "^5.3.0",
|
|
86
|
+
"@fontsource-variable/geist": "^5.3.0",
|
|
87
|
+
"@fontsource-variable/geist-mono": "^5.3.0",
|
|
88
|
+
"@fontsource-variable/inter": "^5.3.0",
|
|
89
|
+
"@fontsource/eb-garamond": "^5.3.0",
|
|
90
|
+
"@fontsource/ibm-plex-mono": "^5.3.0",
|
|
91
|
+
"@iconify-json/tabler": "^1.2.0",
|
|
92
|
+
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
93
|
+
"@nuxtjs/i18n": "^9.0.0",
|
|
94
|
+
"@pinia/nuxt": "^0.11.0",
|
|
95
|
+
"@unocss/nuxt": "^66.0.0",
|
|
96
|
+
"@unocss/preset-attributify": "^66.0.0",
|
|
97
|
+
"@unocss/preset-icons": "^66.7.5",
|
|
98
|
+
"@unocss/preset-typography": "^66.0.0",
|
|
99
|
+
"@vueuse/core": "^13.0.0",
|
|
100
|
+
"@vueuse/nuxt": "^13.0.0",
|
|
101
|
+
"@xterm/addon-fit": "^0.11.0",
|
|
102
|
+
"@xterm/xterm": "^6.0.0",
|
|
103
|
+
"ant-design-vue": "^4.2.6",
|
|
104
|
+
"axios": "^1.7.9",
|
|
105
|
+
"echarts": "^5.6.0",
|
|
106
|
+
"h3": "2.0.1-rc.26",
|
|
107
|
+
"js-yaml": "^4.1.0",
|
|
108
|
+
"minio": "^8.0.7",
|
|
109
|
+
"modbus-serial": "^8.0.25",
|
|
110
|
+
"mqtt": "^5.15.2",
|
|
111
|
+
"node-opcua": "^2.178.0",
|
|
112
|
+
"nuxt": "^4.0.0",
|
|
113
|
+
"nuxt-auth-utils": "^0.5.0",
|
|
114
|
+
"pg": "^8.23.0",
|
|
115
|
+
"phaser": "^4.0.0",
|
|
116
|
+
"pinia": "^3.0.0",
|
|
117
|
+
"pinia-plugin-persistedstate": "^4.2.0",
|
|
118
|
+
"sqlite-vec": "^0.1.9",
|
|
119
|
+
"three": "^0.185.1",
|
|
120
|
+
"unocss": "^66.0.0",
|
|
121
|
+
"unplugin-icons": "^22.0.0",
|
|
122
|
+
"vue": "^3.5.13",
|
|
123
|
+
"vue-echarts": "^7.0.3",
|
|
124
|
+
"vue-router": "^4.5.0",
|
|
125
|
+
"zod": "^3.24.1"
|
|
126
|
+
},
|
|
127
|
+
"devDependencies": {
|
|
128
|
+
"@commitlint/cli": "^19.6.1",
|
|
129
|
+
"@commitlint/config-conventional": "^19.6.0",
|
|
130
|
+
"@nuxt/eslint": "^1.0.0",
|
|
131
|
+
"@types/js-yaml": "^4.0.9",
|
|
132
|
+
"@types/node": "^26.2.0",
|
|
133
|
+
"@types/three": "^0.185.4",
|
|
134
|
+
"eslint": "^9.17.0",
|
|
135
|
+
"husky": "^9.1.7",
|
|
136
|
+
"lint-staged": "^15.3.0",
|
|
137
|
+
"pngjs": "^7.0.0",
|
|
138
|
+
"puppeteer-core": "^25.5.0",
|
|
139
|
+
"tsx": "^4.23.12",
|
|
140
|
+
"typescript": "^5.7.3",
|
|
141
|
+
"vue-tsc": "^3.3.10"
|
|
142
|
+
},
|
|
143
|
+
"pnpm": {
|
|
144
|
+
"ignoredBuiltDependencies": [
|
|
145
|
+
"@serialport/bindings-cpp"
|
|
146
|
+
]
|
|
147
|
+
}
|
|
148
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 节点 ↔ 设备 多对多绑定 E2E(隔离实例 :3021)
|
|
3
|
+
*
|
|
4
|
+
* 验证:
|
|
5
|
+
* 1) legacy 单绑 POST bind {deviceId} → deviceIds=[d1](迁移语义)
|
|
6
|
+
* 2) PUT bindings [d1,d2] → 两台设备;重复设定不产生重复对
|
|
7
|
+
* 3) 数采回写:节点采样值同时出现在 d1/d2 的 telemetry(多对多回写)
|
|
8
|
+
* 4) 摘除 d2 → 仅 d1 继续回写;d2 的 telemetry 冻结在摘除时刻
|
|
9
|
+
* 5) 级联:删除 d1 → 节点 deviceIds 自动移除 d1(保留 d2)
|
|
10
|
+
* 6) dcw 同模型:PUT bindings 双设备 + legacy 兼容
|
|
11
|
+
* 7) 负向:绑定不存在的设备 404
|
|
12
|
+
* 用法:NO_PROXY='127.0.0.1,localhost' node scripts/_dbg-device-bind-e2e.mjs [base]
|
|
13
|
+
*/
|
|
14
|
+
const BASE = process.argv[2] ?? 'http://127.0.0.1:3021'
|
|
15
|
+
const TAG = `db${Math.random().toString(36).slice(2, 6)}`
|
|
16
|
+
let pass = 0
|
|
17
|
+
let fail = 0
|
|
18
|
+
const ok = (cond, name, extra = '') => {
|
|
19
|
+
if (cond) { pass++; console.log(` ✓ ${name}`) }
|
|
20
|
+
else { fail++; console.log(` ✗ ${name} ${extra}`) }
|
|
21
|
+
}
|
|
22
|
+
const sleep = ms => new Promise(r => setTimeout(r, ms))
|
|
23
|
+
const api = async (method, path, { body, token } = {}) => {
|
|
24
|
+
const res = await fetch(`${BASE}${path}`, {
|
|
25
|
+
method,
|
|
26
|
+
headers: { 'content-type': 'application/json', ...(token ? { authorization: `Bearer ${token}` } : {}) },
|
|
27
|
+
body: body === undefined ? undefined : JSON.stringify(body),
|
|
28
|
+
signal: AbortSignal.timeout(30_000),
|
|
29
|
+
})
|
|
30
|
+
return { status: res.status, ...(await res.json().catch(() => ({}))) }
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async function main() {
|
|
34
|
+
console.log(`━━━ 节点↔设备多对多绑定 E2E @ ${BASE} (tag=${TAG}) ━━━`)
|
|
35
|
+
const login = await api('POST', '/api/users/login', { body: { email: 'admin', password: 'admin123' } })
|
|
36
|
+
const token = login.data?.token
|
|
37
|
+
ok(Boolean(token), 'admin 登录')
|
|
38
|
+
|
|
39
|
+
// ── 夹具:2 设备 + 1 数采节点(mock)+ 1 数控节点(mock)──
|
|
40
|
+
const d1 = (await api('POST', '/api/workshop/device-twins', { body: { name: `dev1-${TAG}`, kind: 'device' }, token })).data?.twin
|
|
41
|
+
const d2 = (await api('POST', '/api/workshop/device-twins', { body: { name: `dev2-${TAG}`, kind: 'device' }, token })).data?.twin
|
|
42
|
+
ok(Boolean(d1?.id && d2?.id), '两台设备孪生创建')
|
|
43
|
+
const line = (await api('POST', '/api/workshop/dcw/lines', { body: { name: `bindline-${TAG}` }, token })).data?.line
|
|
44
|
+
const node = (await api('POST', '/api/workshop/daq', {
|
|
45
|
+
body: { name: `daq-${TAG}`, templateRef: 'temp-tc', driver: 'mock', intervalMs: 1000, min: 0, max: 200, lineId: line.id },
|
|
46
|
+
token,
|
|
47
|
+
})).data?.node
|
|
48
|
+
const dcw = (await api('POST', '/api/workshop/dcw', {
|
|
49
|
+
body: { name: `dcw-${TAG}`, templateRef: 'temp-sp', driver: 'mock', driverConfig: { key: `${TAG}` }, min: 0, max: 200, unit: '℃' },
|
|
50
|
+
token,
|
|
51
|
+
})).data?.node
|
|
52
|
+
ok(Boolean(node?.id && dcw?.id), '数采/数控节点创建')
|
|
53
|
+
// 采样门控:开跑产线(产品/配方非必需?start 需要 recipeId —— 先建最小产品+配方)
|
|
54
|
+
const product = (await api('POST', '/api/workshop/dcw/products', { body: { name: `bindprod-${TAG}`, lineId: line.id }, token })).data?.product
|
|
55
|
+
const recipe = (await api('POST', '/api/workshop/dcw/recipes', { body: { name: `bindrec-${TAG}`, productId: product.id, params: [{ nodeId: dcw.id, value: 50 }] }, token })).data?.recipe
|
|
56
|
+
const st = await api('POST', `/api/workshop/dcw/lines/${line.id}/start`, { body: { recipeId: recipe.id }, token })
|
|
57
|
+
ok(st.status === 200, '产线开跑(采样门控放行)')
|
|
58
|
+
|
|
59
|
+
const nodeById = async (id) => (await api('GET', '/api/workshop/daq', { token })).data?.nodes?.find(n => n.id === id)
|
|
60
|
+
const dcwById = async (id) => (await api('GET', '/api/workshop/dcw', { token })).data?.nodes?.find(n => n.id === id)
|
|
61
|
+
const twinById = async (id) => (await api('GET', '/api/workshop/device-twins', { token })).data?.twins?.find(t => t.id === id)
|
|
62
|
+
?? (await api('GET', '/api/workshop/device-twins', { token })).data?.find?.(t => t.id === id)
|
|
63
|
+
|
|
64
|
+
// ── 1. legacy 单绑 ──
|
|
65
|
+
let n = (await api('POST', `/api/workshop/daq/${node.id}/bind`, { body: { deviceId: d1.id }, token })).data?.node
|
|
66
|
+
ok(Array.isArray(n?.deviceIds) && n.deviceIds.length === 1 && n.deviceIds[0] === d1.id && n.deviceBindingId === d1.id,
|
|
67
|
+
`legacy 单绑 → deviceIds=[d1](实际 ${JSON.stringify(n?.deviceIds)})`)
|
|
68
|
+
|
|
69
|
+
// ── 2. PUT bindings 双设备 + 唯一对 ──
|
|
70
|
+
n = (await api('PUT', `/api/workshop/daq/${node.id}/bindings`, { body: { deviceIds: [d1.id, d2.id, d1.id] }, token })).data?.node
|
|
71
|
+
ok(n?.deviceIds?.length === 2 && n.deviceIds.includes(d1.id) && n.deviceIds.includes(d2.id),
|
|
72
|
+
`PUT bindings 去重 → [d1,d2](实际 ${JSON.stringify(n?.deviceIds)})`)
|
|
73
|
+
n = (await api('PUT', `/api/workshop/daq/${node.id}/bindings`, { body: { deviceIds: [d1.id, d2.id] }, token })).data?.node
|
|
74
|
+
ok(n?.deviceIds?.length === 2, '重复设定仍为 2 台(无重复对)')
|
|
75
|
+
|
|
76
|
+
// ── 3. 采样回写双设备 ──
|
|
77
|
+
await sleep(6000)
|
|
78
|
+
const t1 = await twinById(d1.id)
|
|
79
|
+
const t2 = await twinById(d2.id)
|
|
80
|
+
const key1 = Object.keys(t1?.telemetry ?? {})
|
|
81
|
+
ok(key1.length > 0 && Object.keys(t2?.telemetry ?? {}).length > 0,
|
|
82
|
+
`采样回写双设备(d1 keys=${key1.join(',')} | d2 keys=${Object.keys(t2?.telemetry ?? {}).join(',')})`)
|
|
83
|
+
|
|
84
|
+
// ── 4. 摘除 d2 → 仅 d1 继续回写 ──
|
|
85
|
+
n = (await api('PUT', `/api/workshop/daq/${node.id}/bindings`, { body: { deviceIds: [d1.id] }, token })).data?.node
|
|
86
|
+
ok(n?.deviceIds?.length === 1, '摘除 d2 → deviceIds=[d1]')
|
|
87
|
+
const t2frozen = JSON.stringify((await twinById(d2.id))?.telemetry ?? {})
|
|
88
|
+
await sleep(5000)
|
|
89
|
+
const t2after = JSON.stringify((await twinById(d2.id))?.telemetry ?? {})
|
|
90
|
+
ok(t2frozen === t2after, `d2 摘除后遥测冻结(不再回写)`)
|
|
91
|
+
const t1b = JSON.stringify((await twinById(d1.id))?.telemetry ?? {})
|
|
92
|
+
await sleep(5000)
|
|
93
|
+
const t1c = JSON.stringify((await twinById(d1.id))?.telemetry ?? {})
|
|
94
|
+
ok(t1b !== t1c, 'd1 绑定保留,持续回写')
|
|
95
|
+
|
|
96
|
+
// ── 5. 级联:删除 d1 → 节点自动解绑 d1 ──
|
|
97
|
+
await api('DELETE', `/api/workshop/device-twins/${d1.id}`, { token })
|
|
98
|
+
n = await nodeById(node.id)
|
|
99
|
+
ok(Array.isArray(n?.deviceIds) && !n.deviceIds.includes(d1.id), `删除 d1 → 节点级联解绑(deviceIds=${JSON.stringify(n?.deviceIds)})`)
|
|
100
|
+
|
|
101
|
+
// ── 6. dcw 同模型 ──
|
|
102
|
+
let dcn = (await api('PUT', `/api/workshop/dcw/${dcw.id}/bindings`, { body: { deviceIds: [d2.id] }, token })).data?.node
|
|
103
|
+
ok(dcn?.deviceIds?.[0] === d2.id, '[dcw] PUT bindings 单设备')
|
|
104
|
+
dcn = (await api('POST', `/api/workshop/dcw/${dcw.id}/bind`, { body: { deviceId: null }, token })).data?.node
|
|
105
|
+
ok((dcn?.deviceIds ?? []).length === 0, '[dcw] legacy bind null → 清空')
|
|
106
|
+
dcn = (await api('PUT', `/api/workshop/dcw/${dcw.id}/bindings`, { body: { deviceIds: [d2.id] }, token })).data?.node
|
|
107
|
+
ok(dcn?.deviceIds?.length === 1, '[dcw] 恢复绑定')
|
|
108
|
+
|
|
109
|
+
// ── 7. 负向:不存在的设备 ──
|
|
110
|
+
const bad = await api('PUT', `/api/workshop/daq/${node.id}/bindings`, { body: { deviceIds: ['no-such-device'] }, token })
|
|
111
|
+
ok(bad.status === 404, `负向:不存在设备 404(实际 ${bad.status})`)
|
|
112
|
+
|
|
113
|
+
// ── 清理 ──
|
|
114
|
+
await api('POST', `/api/workshop/dcw/lines/${line.id}/stop`, { body: {}, token }).catch(() => {})
|
|
115
|
+
await api('DELETE', `/api/workshop/daq/${node.id}`, { token }).catch(() => {})
|
|
116
|
+
await api('DELETE', `/api/workshop/dcw/${dcw.id}`, { token }).catch(() => {})
|
|
117
|
+
await api('DELETE', `/api/workshop/dcw/recipes/${recipe.id}`, { token }).catch(() => {})
|
|
118
|
+
await api('DELETE', `/api/workshop/dcw/products/${product.id}`, { token }).catch(() => {})
|
|
119
|
+
await api('DELETE', `/api/workshop/dcw/lines/${line.id}`, { token }).catch(() => {})
|
|
120
|
+
await api('DELETE', `/api/workshop/device-twins/${d2.id}`, { token }).catch(() => {})
|
|
121
|
+
|
|
122
|
+
console.log(`\n━━━ DeviceBind E2E: ${pass} passed / ${fail} failed ━━━`)
|
|
123
|
+
process.exit(fail === 0 ? 0 : 1)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
main().catch((err) => {
|
|
127
|
+
console.error('E2E 异常:', err)
|
|
128
|
+
process.exit(1)
|
|
129
|
+
})
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# 运维日志 UI 三项修复浏览器实测:
|
|
2
|
+
# 1) 详情按钮行内展开/无详情显示"无"/人工记录可写详情
|
|
3
|
+
# 2) 全局按钮统一(pill accent,非白底默认)
|
|
4
|
+
# 3) 页头 aw-page-head 规范(标题30px + 描述间距)
|
|
5
|
+
# 附带:daq/dcw 页按钮抽查截图。产物 → .e2e-shots/
|
|
6
|
+
import json
|
|
7
|
+
import sys
|
|
8
|
+
from playwright.sync_api import sync_playwright
|
|
9
|
+
|
|
10
|
+
BASE = 'http://127.0.0.1:3000'
|
|
11
|
+
SHOT = '.e2e-shots'
|
|
12
|
+
passed = 0
|
|
13
|
+
failed = 0
|
|
14
|
+
|
|
15
|
+
def ok(cond, name, extra=''):
|
|
16
|
+
global passed, failed
|
|
17
|
+
if cond:
|
|
18
|
+
passed += 1
|
|
19
|
+
print(f' OK {name}')
|
|
20
|
+
else:
|
|
21
|
+
failed += 1
|
|
22
|
+
print(f' FAIL {name} {extra}')
|
|
23
|
+
|
|
24
|
+
with sync_playwright() as p:
|
|
25
|
+
browser = p.chromium.launch(headless=True)
|
|
26
|
+
page = browser.new_page(viewport={'width': 1440, 'height': 900})
|
|
27
|
+
page.set_default_timeout(30000)
|
|
28
|
+
|
|
29
|
+
# ---- 1. 登录(/logs 不强制跳登录,先去 /workshop 触发登录墙) ----
|
|
30
|
+
page.goto(BASE + '/workshop', wait_until='domcontentloaded', timeout=90000)
|
|
31
|
+
try:
|
|
32
|
+
page.wait_for_selector('input[placeholder="邮箱 / 用户名"]', timeout=20000)
|
|
33
|
+
except Exception:
|
|
34
|
+
page.goto(BASE + '/workshop', wait_until='domcontentloaded', timeout=60000)
|
|
35
|
+
page.wait_for_selector('input[placeholder="邮箱 / 用户名"]', timeout=60000)
|
|
36
|
+
for attempt in range(3):
|
|
37
|
+
page.wait_for_timeout(2500 + attempt * 2000)
|
|
38
|
+
page.fill('input[placeholder="邮箱 / 用户名"]', 'admin')
|
|
39
|
+
page.fill('.ant-input-password input', 'admin123')
|
|
40
|
+
try:
|
|
41
|
+
with page.expect_response(lambda r: '/api/users/login' in r.url, timeout=15000) as ri:
|
|
42
|
+
page.locator('.ant-input-password input').first.press('Enter')
|
|
43
|
+
status = ri.value.status
|
|
44
|
+
except Exception:
|
|
45
|
+
status = None
|
|
46
|
+
inp = page.locator('input[placeholder="邮箱 / 用户名"]')
|
|
47
|
+
if status == 200 and (inp.count() == 0 or not inp.first.is_visible()):
|
|
48
|
+
break
|
|
49
|
+
ok(status == 200, f'登录(admin) status={status}')
|
|
50
|
+
page.wait_for_timeout(1500)
|
|
51
|
+
|
|
52
|
+
# ---- 2. 页头规范 ----
|
|
53
|
+
page.goto(BASE + '/logs', wait_until='domcontentloaded', timeout=60000)
|
|
54
|
+
page.wait_for_timeout(2500)
|
|
55
|
+
head = page.locator('.aw-page-head')
|
|
56
|
+
ok(head.count() > 0, '页头使用 aw-page-head 规范组件')
|
|
57
|
+
h1 = page.locator('.aw-page-head h1').first
|
|
58
|
+
fs = h1.evaluate('el => getComputedStyle(el).fontSize')
|
|
59
|
+
ok(fs == '30px', f'标题字号与其他页统一 30px(实际 {fs})')
|
|
60
|
+
sub = page.locator('.aw-page-head .sub').first
|
|
61
|
+
mt = sub.evaluate('el => getComputedStyle(el).marginTop')
|
|
62
|
+
ok(float(mt.replace('px', '')) >= 8, f'标题-描述间距放大 marginTop={mt}')
|
|
63
|
+
page.screenshot(path=f'{SHOT}/logs-head.png', clip={'x': 0, 'y': 0, 'width': 1440, 'height': 240})
|
|
64
|
+
|
|
65
|
+
# ---- 3. 按钮统一(非白底默认;accent 药丸) ----
|
|
66
|
+
def bg_of(sel):
|
|
67
|
+
return page.locator(sel).first.evaluate('el => getComputedStyle(el).backgroundColor')
|
|
68
|
+
man_bg = bg_of('.head-actions .pill-btn')
|
|
69
|
+
ok(man_bg not in ('rgb(255, 255, 255)', 'rgba(0, 0, 0, 0)'), f'人工记录按钮有主题底色({man_bg})')
|
|
70
|
+
man_radius = page.locator('.head-actions .pill-btn').first.evaluate('el => getComputedStyle(el).borderRadius')
|
|
71
|
+
ok(man_radius == '9999px', f'人工记录按钮药丸形(radius={man_radius})')
|
|
72
|
+
ok(True, '查询/重置按钮走全局 pill/mini 词汇(截图目视)')
|
|
73
|
+
page.screenshot(path=f'{SHOT}/logs-buttons.png', clip={'x': 0, 'y': 100, 'width': 1440, 'height': 260})
|
|
74
|
+
|
|
75
|
+
# ---- 4. 人工记录:摘要 + 详情(可选) 入册 ----
|
|
76
|
+
stamp = f'UI验证-{json.dumps("", ensure_ascii=False)}{page.evaluate("Date.now()")}'
|
|
77
|
+
summary = f'浏览器实测人工记录 {page.evaluate("new Date().toISOString().slice(11,19)")}'
|
|
78
|
+
detail = f'处置详情备注:round-{page.evaluate("Date.now()")}'
|
|
79
|
+
page.locator('.head-actions .pill-btn').click()
|
|
80
|
+
page.wait_for_selector('.modal', timeout=10000)
|
|
81
|
+
page.locator('.modal textarea').nth(0).fill(summary)
|
|
82
|
+
page.locator('.modal textarea').nth(1).fill(detail)
|
|
83
|
+
page.screenshot(path=f'{SHOT}/logs-manual-modal.png')
|
|
84
|
+
with page.expect_response(lambda r: '/api/workshop/ops-logs' in r.url, timeout=15000) as ri:
|
|
85
|
+
page.locator('.modal .pill-btn').click()
|
|
86
|
+
ok(ri.value.status in (200, 201), f'人工记录入册 status={ri.value.status}')
|
|
87
|
+
page.wait_for_timeout(1200)
|
|
88
|
+
|
|
89
|
+
# ---- 5. 详情行:新记录带详情按钮,点击行内展开 ----
|
|
90
|
+
row = page.locator('tr', has_text=summary).first
|
|
91
|
+
row.wait_for(state='visible', timeout=15000)
|
|
92
|
+
btn = row.locator('.detail-cell .mini-btn')
|
|
93
|
+
ok(btn.count() == 1, '新人工记录带"详情"按钮')
|
|
94
|
+
btn.click()
|
|
95
|
+
page.wait_for_timeout(400)
|
|
96
|
+
detail_box = row.locator('+ tr .detail-box')
|
|
97
|
+
ok(detail_box.count() == 1, '点击详情 → 行内紧贴展开详情面板')
|
|
98
|
+
txt = detail_box.first.inner_text() if detail_box.count() else ''
|
|
99
|
+
ok(detail in txt, '详情内容含人工填写的备注(note)')
|
|
100
|
+
row.scroll_into_view_if_needed()
|
|
101
|
+
page.wait_for_timeout(300)
|
|
102
|
+
page.screenshot(path=f'{SHOT}/logs-detail-expanded.png')
|
|
103
|
+
btn.click()
|
|
104
|
+
page.wait_for_timeout(300)
|
|
105
|
+
ok(detail_box.count() == 0 or not detail_box.first.is_visible(), '再点收起 → 详情面板关闭')
|
|
106
|
+
|
|
107
|
+
# ---- 6. 无详情行显示"无" ----
|
|
108
|
+
none_cells = page.locator('.detail-cell', has_text='无')
|
|
109
|
+
ok(none_cells.count() > 0, f'无详情行显示"无"(共 {none_cells.count()} 行)')
|
|
110
|
+
|
|
111
|
+
# ---- 7. daq/dcw 页按钮抽查(全局词汇无回归) ----
|
|
112
|
+
page.goto(BASE + '/dcw', wait_until='domcontentloaded', timeout=60000)
|
|
113
|
+
page.wait_for_timeout(2500)
|
|
114
|
+
lc_btn = page.locator('.pill-btn').first
|
|
115
|
+
if lc_btn.count():
|
|
116
|
+
b = lc_btn.evaluate('el => getComputedStyle(el).backgroundColor')
|
|
117
|
+
ok(b not in ('rgb(255, 255, 255)', 'rgba(0, 0, 0, 0)'), f'dcw 页 pill-btn 有主题底色({b})')
|
|
118
|
+
page.screenshot(path=f'{SHOT}/dcw-buttons.png', clip={'x': 0, 'y': 0, 'width': 1440, 'height': 420})
|
|
119
|
+
else:
|
|
120
|
+
ok(True, 'dcw 页无 pill-btn 可查(空态)')
|
|
121
|
+
|
|
122
|
+
browser.close()
|
|
123
|
+
|
|
124
|
+
print(f'\n== logs UI e2e: {passed} passed, {failed} failed ==')
|
|
125
|
+
sys.exit(1 if failed else 0)
|
|
@@ -16,7 +16,10 @@ export default defineApiHandler(async (event) => {
|
|
|
16
16
|
const id = getRouterParam(event, 'id') ?? ''
|
|
17
17
|
// 产线权限:数采设备绑定=操控能力(绑定后通道值回写设备)
|
|
18
18
|
requireLineMode(user, getDaqController().byId(id)?.lineId, 'operate')
|
|
19
|
-
const body = await readBody<{ deviceId?: string | null }>(event) ?? {}
|
|
20
|
-
|
|
19
|
+
const body = await readBody<{ deviceId?: string | null, deviceIds?: string[] }>(event) ?? {}
|
|
20
|
+
// 两种形态:{ deviceId } 排他设置(兼容旧客户端);{ deviceIds: [...] } 整体设定多对多
|
|
21
|
+
const node = Array.isArray(body.deviceIds)
|
|
22
|
+
? getDaqController().setDeviceBindings(id, body.deviceIds)
|
|
23
|
+
: getDaqController().bind(id, body.deviceId ?? null)
|
|
21
24
|
return { node: node.toView() }
|
|
22
25
|
})
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PUT /api/workshop/daq/:id/bindings —— 整体设定数采节点的绑定设备列表(多对多)。
|
|
3
|
+
* body: { deviceIds: string[] }(全量替换,去重,逐一校验设备存在;(节点,设备)对唯一)
|
|
4
|
+
*/
|
|
5
|
+
import { getRouterParam, readBody } from 'h3'
|
|
6
|
+
import { resolveUser } from '@/server/api/workshop/caller'
|
|
7
|
+
import { requireLineMode } from '@/server/services/workshop/permissions'
|
|
8
|
+
import { defineApiHandler } from '@/server/utils/response'
|
|
9
|
+
import { bindDaqHost } from '@/server/services/workshop/daq/host-bindings'
|
|
10
|
+
import { getDaqController } from '@/server/services/workshop/daq/daq-controller'
|
|
11
|
+
import { broadcastSceneEvent } from '../../../../services/workshop/scene-events'
|
|
12
|
+
|
|
13
|
+
export default defineApiHandler(async (event) => {
|
|
14
|
+
const user = resolveUser(event)
|
|
15
|
+
bindDaqHost(broadcastSceneEvent)
|
|
16
|
+
const id = getRouterParam(event, 'id') ?? ''
|
|
17
|
+
// 产线权限:设备绑定=操控能力(绑定后通道值回写设备)
|
|
18
|
+
requireLineMode(user, getDaqController().byId(id)?.lineId, 'operate')
|
|
19
|
+
const body = await readBody<{ deviceIds?: string[] }>(event) ?? {}
|
|
20
|
+
const node = getDaqController().setDeviceBindings(id, Array.isArray(body.deviceIds) ? body.deviceIds : [])
|
|
21
|
+
return { node: node.toView() }
|
|
22
|
+
})
|
|
@@ -14,7 +14,9 @@ export default defineApiHandler(async (event) => {
|
|
|
14
14
|
const id = getRouterParam(event, 'id') ?? ''
|
|
15
15
|
// 产线权限:设备孪生绑定=操控能力
|
|
16
16
|
requireLineMode(user, getDcwController().byId(id)?.lineId, 'operate')
|
|
17
|
-
const body = await readBody<{ deviceId?: string | null }>(event) ?? {}
|
|
18
|
-
const node =
|
|
17
|
+
const body = await readBody<{ deviceId?: string | null, deviceIds?: string[] }>(event) ?? {}
|
|
18
|
+
const node = Array.isArray(body.deviceIds)
|
|
19
|
+
? getDcwController().setDeviceBindings(id, body.deviceIds)
|
|
20
|
+
: getDcwController().bind(id, body.deviceId ?? null)
|
|
19
21
|
return { node: node.toView() }
|
|
20
22
|
})
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PUT /api/workshop/dcw/:id/bindings —— 整体设定数控节点的绑定设备列表(多对多)。
|
|
3
|
+
* body: { deviceIds: string[] }(全量替换,去重,逐一校验设备存在;(节点,设备)对唯一)
|
|
4
|
+
*/
|
|
5
|
+
import { getRouterParam, readBody } from 'h3'
|
|
6
|
+
import { resolveUser } from '@/server/api/workshop/caller'
|
|
7
|
+
import { requireLineMode } from '@/server/services/workshop/permissions'
|
|
8
|
+
import { defineApiHandler } from '@/server/utils/response'
|
|
9
|
+
import { bindDcwBroadcast, getDcwController } from '@/server/services/workshop/dcw/dcw-controller'
|
|
10
|
+
import { broadcastSceneEvent } from '../../../../services/workshop/scene-events'
|
|
11
|
+
|
|
12
|
+
export default defineApiHandler(async (event) => {
|
|
13
|
+
const user = resolveUser(event)
|
|
14
|
+
bindDcwBroadcast(broadcastSceneEvent)
|
|
15
|
+
const id = getRouterParam(event, 'id') ?? ''
|
|
16
|
+
// 产线权限:设备孪生绑定=操控能力
|
|
17
|
+
requireLineMode(user, getDcwController().byId(id)?.lineId, 'operate')
|
|
18
|
+
const body = await readBody<{ deviceIds?: string[] }>(event) ?? {}
|
|
19
|
+
const node = getDcwController().setDeviceBindings(id, Array.isArray(body.deviceIds) ? body.deviceIds : [])
|
|
20
|
+
return { node: node.toView() }
|
|
21
|
+
})
|