agentworkshop 0.7.21 → 0.7.22
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/{BA3XzwiD.js → 0exC4jVL.js} +1 -1
- package/.output/public/_nuxt/BAbAEW0D.js +1 -0
- package/.output/public/_nuxt/{DBXhDLgl.js → BuiGDfMB.js} +1 -1
- package/.output/public/_nuxt/Ci0uX4c4.js +1 -0
- package/.output/public/_nuxt/D6B0xnjk.js +1 -0
- package/.output/public/_nuxt/{TownView.BHLKAjEu.css → TownView.zu2rU5nS.css} +1 -1
- package/.output/public/_nuxt/_id_.C2VHq_vT.css +1 -0
- package/.output/public/_nuxt/_id_.Dswy2p0I.css +1 -0
- package/.output/public/_nuxt/builds/latest.json +1 -1
- package/.output/public/_nuxt/builds/meta/e2341fc0-6f3f-4680-abf8-935a7c9fb912.json +1 -0
- package/.output/public/_nuxt/{BOJaHO5V.js → c3mwkHWP.js} +2 -2
- package/.output/public/_nuxt/daq.BTdEun-8.css +1 -0
- package/.output/public/_nuxt/dbi5bHkY.js +2 -0
- package/.output/public/_nuxt/{DsOkkMX3.js → jyQyulh-.js} +1 -1
- package/.output/public/_nuxt/{B-E8fXAO.js → n3vMFfrv.js} +1 -1
- package/.output/server/chunks/_/nitro.mjs +873 -748
- 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_-B9RvEQFC.mjs} +33 -35
- package/.output/server/chunks/build/_id_-B9RvEQFC.mjs.map +1 -0
- package/.output/server/chunks/build/{_id_-Bs-euJPu.mjs → _id_-CqF0R5W5.mjs} +138 -137
- package/.output/server/chunks/build/_id_-CqF0R5W5.mjs.map +1 -0
- package/.output/server/chunks/build/{_wsId_-B3aB9bIp.mjs → _wsId_-YC2t_fRt.mjs} +3 -3
- package/.output/server/chunks/build/{_wsId_-B3aB9bIp.mjs.map → _wsId_-YC2t_fRt.mjs.map} +1 -1
- package/.output/server/chunks/build/{daq-epWL_sC7.mjs → daq-Bhwwz9vp.mjs} +146 -118
- package/.output/server/chunks/build/daq-Bhwwz9vp.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 +6 -6
- package/.output/server/chunks/virtual/precomputed.mjs +1 -1
- package/.output/server/package.json +1 -1
- package/app/components/workshop/town/TownView.vue +7082 -7076
- package/app/composables/workshop/useDaqStream.ts +10 -0
- package/app/pages/daq/[id].vue +4 -3
- package/app/pages/daq/index.vue +66 -21
- package/app/pages/dcw/[id].vue +5 -1
- package/package.json +148 -148
- package/scripts/_dbg-device-bind-e2e.mjs +129 -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 +408 -103
- package/server/data/dcw-lines.json +7 -0
- package/server/data/dcw-rollback.json +10 -10
- package/server/data/dcw-runs.json +25 -27
- package/server/data/dcw-writes.json +12 -0
- package/server/data/dcws.json +216 -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/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/builds/meta/5d344836-03a5-4f47-a0a7-fd30192f5a8f.json +0 -1
- package/.output/public/_nuxt/daq.6IrhVVfy.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
|
@@ -308,6 +308,15 @@ const createStore = () => {
|
|
|
308
308
|
upsert(data.node)
|
|
309
309
|
}
|
|
310
310
|
|
|
311
|
+
/** 多对多绑定:整体设定节点的绑定设备列表(服务端去重+校验) */
|
|
312
|
+
async function setNodeBindings(id: string, deviceIds: string[]): Promise<void> {
|
|
313
|
+
const data = await api<{ node: DaqNodeView }>(`/${id}/bindings`, {
|
|
314
|
+
method: 'PUT',
|
|
315
|
+
body: JSON.stringify({ deviceIds }),
|
|
316
|
+
})
|
|
317
|
+
upsert(data.node)
|
|
318
|
+
}
|
|
319
|
+
|
|
311
320
|
async function testDriver(driver: string, driverConfig: Record<string, string | number | boolean>): Promise<DaqDriverTestResult> {
|
|
312
321
|
const data = await api<{ test: DaqDriverTestResult }>('/test-driver', {
|
|
313
322
|
method: 'POST',
|
|
@@ -422,6 +431,7 @@ const createStore = () => {
|
|
|
422
431
|
saveTransform,
|
|
423
432
|
removeNode,
|
|
424
433
|
bindNode,
|
|
434
|
+
setNodeBindings,
|
|
425
435
|
controllerAction,
|
|
426
436
|
alarms,
|
|
427
437
|
fetchAlarms,
|
package/app/pages/daq/[id].vue
CHANGED
|
@@ -197,9 +197,10 @@ function driverPlanned(kind: string): boolean {
|
|
|
197
197
|
// ---------- 设备绑定 ----------
|
|
198
198
|
const bindDeviceId = ref('')
|
|
199
199
|
const boundDeviceName = computed(() => {
|
|
200
|
-
const
|
|
201
|
-
|
|
202
|
-
|
|
200
|
+
const ids = node.value?.deviceIds ?? (node.value?.deviceBindingId ? [node.value.deviceBindingId] : [])
|
|
201
|
+
return ids
|
|
202
|
+
.map(id => deviceTwins.twins.find(t => t.id === id)?.name ?? `${id.slice(0, 8)}…`)
|
|
203
|
+
.join(' / ')
|
|
203
204
|
})
|
|
204
205
|
function onBindToggle(): void {
|
|
205
206
|
void daq.bindNode(nodeId.value, node.value?.deviceBindingId ? null : (bindDeviceId.value || null))
|
package/app/pages/daq/index.vue
CHANGED
|
@@ -40,17 +40,38 @@ async function setNodeLine(id: string, e: Event): Promise<void> {
|
|
|
40
40
|
}
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
/** 节点绑定设备(
|
|
44
|
-
async function
|
|
43
|
+
/** 节点绑定设备(多对多:add 挂新设备 / remove 摘除一台;server 落库,与数字孪生同一 deviceIds 链路) */
|
|
44
|
+
async function addNodeDevice(id: string, e: Event): Promise<void> {
|
|
45
45
|
const deviceId = (e.target as HTMLSelectElement).value
|
|
46
|
+
if (!deviceId) return
|
|
47
|
+
const node = daq.nodes.find(n => n.id === id)
|
|
48
|
+
if (!node) return
|
|
49
|
+
if (node.deviceIds?.includes(deviceId)) return
|
|
46
50
|
try {
|
|
47
|
-
await daq.
|
|
51
|
+
await daq.setNodeBindings(id, [...(node.deviceIds ?? []), deviceId])
|
|
48
52
|
}
|
|
49
53
|
catch (err) {
|
|
50
54
|
message.error(apiErrorMessage(err))
|
|
51
55
|
}
|
|
56
|
+
;(e.target as HTMLSelectElement).value = ''
|
|
52
57
|
}
|
|
53
58
|
|
|
59
|
+
async function removeNodeDevice(id: string, deviceId: string): Promise<void> {
|
|
60
|
+
const node = daq.nodes.find(n => n.id === id)
|
|
61
|
+
if (!node) return
|
|
62
|
+
try {
|
|
63
|
+
await daq.setNodeBindings(id, (node.deviceIds ?? []).filter(d => d !== deviceId))
|
|
64
|
+
}
|
|
65
|
+
catch (err) {
|
|
66
|
+
message.error(apiErrorMessage(err))
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const nodeDeviceIds = (n: DaqNodeView): string[] =>
|
|
71
|
+
n.deviceIds ?? (n.deviceBindingId ? [n.deviceBindingId] : [])
|
|
72
|
+
const deviceNameById = (id: string): string =>
|
|
73
|
+
nodeDevices.get(id) ?? deviceTwins.twins.find(t => t.id === id)?.name ?? id
|
|
74
|
+
|
|
54
75
|
/** 单节点独立 启动/停止采集(PATCH enabled;server 权威,本地乐观翻转 + WS/轮询收敛) */
|
|
55
76
|
async function toggleNodeEnabled(n: { id: string, enabled: boolean, state: string }): Promise<void> {
|
|
56
77
|
const next = !n.enabled
|
|
@@ -295,7 +316,9 @@ function fmtPoint(v: number | null | undefined): string {
|
|
|
295
316
|
const boundDevices = computed<Array<{ id: string, name: string, count: number }>>(() => {
|
|
296
317
|
const used = new Map<string, number>()
|
|
297
318
|
for (const n of daq.nodes) {
|
|
298
|
-
|
|
319
|
+
for (const d of n.deviceIds ?? (n.deviceBindingId ? [n.deviceBindingId] : [])) {
|
|
320
|
+
used.set(d, (used.get(d) ?? 0) + 1)
|
|
321
|
+
}
|
|
299
322
|
}
|
|
300
323
|
return deviceTwins.twins
|
|
301
324
|
.filter(t => used.has(t.id))
|
|
@@ -371,7 +394,8 @@ function statePillOf(n: DaqNodeView): { key: RowState, label: string, tip: strin
|
|
|
371
394
|
|
|
372
395
|
const filteredNodes = computed<DaqNodeView[]>(() => daq.nodes.filter((n) => {
|
|
373
396
|
if (filters.lineId && (filters.lineId === 'none' ? !!n.lineId : (n.lineId ?? '') !== filters.lineId)) return false
|
|
374
|
-
|
|
397
|
+
const nodeDevIds = n.deviceIds ?? (n.deviceBindingId ? [n.deviceBindingId] : [])
|
|
398
|
+
if (filters.deviceId && (filters.deviceId === 'none' ? nodeDevIds.length > 0 : !nodeDevIds.includes(filters.deviceId))) return false
|
|
375
399
|
if (filters.template && n.templateRef !== filters.template) return false
|
|
376
400
|
if (filters.driver && n.driver !== filters.driver) return false
|
|
377
401
|
if (!lineRunMatch(n)) return false
|
|
@@ -1771,23 +1795,39 @@ async function doReconnect(): Promise<void> {
|
|
|
1771
1795
|
>--</span>
|
|
1772
1796
|
</td>
|
|
1773
1797
|
<td>
|
|
1774
|
-
<
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
<option value="">
|
|
1781
|
-
{{ $t('daq.k3own4q056') }}
|
|
1782
|
-
</option>
|
|
1783
|
-
<option
|
|
1784
|
-
v-for="d in bindableDeviceTwins"
|
|
1785
|
-
:key="d.id"
|
|
1786
|
-
:value="d.id"
|
|
1798
|
+
<div class="dev-binds">
|
|
1799
|
+
<span
|
|
1800
|
+
v-for="did in nodeDeviceIds(n)"
|
|
1801
|
+
:key="did"
|
|
1802
|
+
class="dev-bind-chip"
|
|
1803
|
+
:title="$t('daq.k2bindtip137')"
|
|
1787
1804
|
>
|
|
1788
|
-
{{
|
|
1789
|
-
|
|
1790
|
-
|
|
1805
|
+
{{ deviceNameById(did) }}
|
|
1806
|
+
<span
|
|
1807
|
+
class="dev-unbind"
|
|
1808
|
+
title="unbind"
|
|
1809
|
+
@click="removeNodeDevice(n.id, did)"
|
|
1810
|
+
>✕</span>
|
|
1811
|
+
</span>
|
|
1812
|
+
<select
|
|
1813
|
+
class="line-sel dev-add"
|
|
1814
|
+
:value="''"
|
|
1815
|
+
:title="$t('daq.k2bindtip137')"
|
|
1816
|
+
@change="addNodeDevice(n.id, $event)"
|
|
1817
|
+
>
|
|
1818
|
+
<option value="">
|
|
1819
|
+
{{ $t('daq.k3own4q056') }}
|
|
1820
|
+
</option>
|
|
1821
|
+
<option
|
|
1822
|
+
v-for="d in bindableDeviceTwins"
|
|
1823
|
+
:key="d.id"
|
|
1824
|
+
:value="d.id"
|
|
1825
|
+
:disabled="(n.deviceIds ?? []).includes(d.id)"
|
|
1826
|
+
>
|
|
1827
|
+
{{ nodeDevices.get(d.id) ?? d.name }}
|
|
1828
|
+
</option>
|
|
1829
|
+
</select>
|
|
1830
|
+
</div>
|
|
1791
1831
|
</td>
|
|
1792
1832
|
<td class="right">
|
|
1793
1833
|
<button
|
|
@@ -2395,6 +2435,11 @@ tr.row-recipe-alarm td:first-child { box-shadow: inset 3px 0 0 var(--tone-danger
|
|
|
2395
2435
|
.flt-search::placeholder { color: var(--ink-fainter); }
|
|
2396
2436
|
.count { padding-bottom: 4px; font-size: 11px; color: var(--ink-faint); }
|
|
2397
2437
|
|
|
2438
|
+
.dev-binds { display: flex; flex-wrap: wrap; gap: 4px; align-items: center; }
|
|
2439
|
+
.dev-bind-chip { display: inline-flex; gap: 4px; align-items: center; padding: 1px 7px; font-size: 11px; color: var(--accent); background: color-mix(in srgb, var(--accent) 8%, transparent); border: 1px solid color-mix(in srgb, var(--accent) 30%, transparent); border-radius: var(--radius-chip); }
|
|
2440
|
+
.dev-unbind { cursor: pointer; opacity: 0.55; font-size: 10px; }
|
|
2441
|
+
.dev-unbind:hover { opacity: 1; color: var(--tone-danger-dot); }
|
|
2442
|
+
.dev-add { max-width: 130px; font-size: 11px; }
|
|
2398
2443
|
.line-sel {
|
|
2399
2444
|
max-width: 120px;
|
|
2400
2445
|
padding: 3px 6px;
|
package/app/pages/dcw/[id].vue
CHANGED
|
@@ -25,6 +25,10 @@ void dcw.load()
|
|
|
25
25
|
const unsubDcw = dcw.ensureWsFeed()
|
|
26
26
|
onUnmounted(() => unsubDcw())
|
|
27
27
|
|
|
28
|
+
const nodeDeviceNames = (n: { deviceIds?: string[], deviceBindingId?: string | null }): string =>
|
|
29
|
+
(n.deviceIds ?? (n.deviceBindingId ? [n.deviceBindingId] : []))
|
|
30
|
+
.map(id => deviceName(id))
|
|
31
|
+
.join(' / ') || '—'
|
|
28
32
|
const deviceName = (id: string | null): string =>
|
|
29
33
|
id ? (deviceTwins.twins.find(t => t.id === id)?.name ?? id) : t('dcwDetail.k3own4q121')
|
|
30
34
|
|
|
@@ -1341,7 +1345,7 @@ function fmtPoint(p: { value?: number, avg?: number } | undefined): string {
|
|
|
1341
1345
|
:title="$t('dcwDetail.k9r7d4e016')"
|
|
1342
1346
|
>· {{ n.readIntervalMs === 0 ? $t('dcwDetail.k9r7d4e022') : (n.readIntervalMs == null ? $t('dcwDetail.k9r7d4e023') : `${n.readIntervalMs}ms`) }}</small>
|
|
1343
1347
|
</td>
|
|
1344
|
-
<td>{{
|
|
1348
|
+
<td>{{ nodeDeviceNames(n) }}</td>
|
|
1345
1349
|
<td class="right">
|
|
1346
1350
|
<button
|
|
1347
1351
|
class="mini-btn danger"
|
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.22",
|
|
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
|
+
})
|
|
@@ -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
|
})
|