@xtalpi/agentic-lab-skills 0.0.10 → 0.0.11
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/README.md +1 -1
- package/package.json +14 -14
- package/skills/lab-flow-designer/SKILL.md +612 -612
- package/skills/lab-flow-designer/embedded-template/SKILL.md +103 -103
- package/skills/lab-flow-designer/embedded-template/pools//345/205/245/345/217/243/346/261/240.md +21 -21
- package/skills/lab-flow-designer/embedded-template/pools//345/207/272/345/217/243/346/261/240.md +21 -21
- package/skills/lab-flow-designer/embedded-template/scripts//347/244/272/344/276/213/346/225/260/346/215/256/344/270/216/346/240/241/351/252/214/351/227/250/346/216/247.js +142 -142
- package/skills/lab-flow-designer/embedded-template/valves//347/244/272/344/276/213/346/225/260/346/215/256/344/270/216/346/240/241/351/252/214/351/227/250/346/216/247.md +114 -114
- package/skills/lab-flow-designer/references/agentic-lab-processer.md +122 -122
- package/skills/lab-flow-designer/references/agentic-lab-sdk.md +534 -361
- package/skills/lab-flow-designer/references/rhea-api/README.md +7 -7
- package/skills/lab-flow-designer/references/rhea-api/execute_process_batch.md +58 -58
- package/skills/lab-flow-designer/references/skill-package-layout.md +268 -268
- package/skills/lab-flow-designer/references//344/270/232/345/212/241/346/265/201/347/250/213/346/226/207/346/241/243/346/240/207/345/207/206.md +216 -216
- package/skills/lab-flow-designer/templates//344/270/232/345/212/241/346/265/201/347/250/213/346/226/207/346/241/243/346/250/241/346/235/277.md +192 -192
- package/skills/lab-flow-designer/templates//344/270/232/345/212/241/346/265/201/347/250/213/346/226/207/346/241/243/347/244/272/344/276/213.md +207 -207
- package/skills/lab-flow-designer/testing/test-processer.mjs +1240 -1240
- package/skills/lab-nocobase-flow-generator/SKILL.md +164 -164
- package/skills/lab-nocobase-flow-generator/examples/setting/350/241/250/350/216/267/345/217/226/345/244/226/351/203/250/346/234/215/345/212/241.js +70 -70
- package/skills/lab-nocobase-flow-generator/examples//346/237/245/350/257/242/345/214/226/345/255/246/345/223/201/344/277/241/346/201/257.js +30 -30
- package/skills/lab-nocobase-flow-generator/references/doc-standard.md +84 -84
- package/skills/lab-nocobase-flow-generator/references/runtime-api.md +224 -224
- package/skills/lab-nocobase-flow-generator/templates//350/204/232/346/234/254/351/200/273/350/276/221/346/226/207/346/241/243/346/250/241/346/235/277.md +121 -121
- package/skills/lab-nocobase-flow-generator/templates//350/204/232/346/234/254/351/200/273/350/276/221/346/226/207/346/241/243/347/244/272/344/276/213.md +67 -67
- package/skills/lab-orbit-component-builder/SKILL.md +353 -353
- package/skills/lab-orbit-component-builder/examples/xnb-component-template/.env.local.example +27 -27
- package/skills/lab-orbit-component-builder/examples/xnb-component-template/.eslintignore +7 -7
- package/skills/lab-orbit-component-builder/examples/xnb-component-template/.eslintrc.cjs +88 -88
- package/skills/lab-orbit-component-builder/examples/xnb-component-template/.nvmrc +1 -1
- package/skills/lab-orbit-component-builder/examples/xnb-component-template/AgenticAppAPI.md +268 -268
- package/skills/lab-orbit-component-builder/examples/xnb-component-template/Jenkinsfile +106 -106
- package/skills/lab-orbit-component-builder/examples/xnb-component-template/OrbitAPI.md +453 -453
- package/skills/lab-orbit-component-builder/examples/xnb-component-template/README.md +176 -176
- package/skills/lab-orbit-component-builder/examples/xnb-component-template/dev/public/index.html +12 -12
- package/skills/lab-orbit-component-builder/examples/xnb-component-template/dev/src/App.vue +151 -151
- package/skills/lab-orbit-component-builder/examples/xnb-component-template/dev/src/components/DevOpenerLauncher.vue +143 -143
- package/skills/lab-orbit-component-builder/examples/xnb-component-template/dev/src/global.d.ts +77 -77
- package/skills/lab-orbit-component-builder/examples/xnb-component-template/dev/src/main.ts +308 -308
- package/skills/lab-orbit-component-builder/examples/xnb-component-template/dev/src/mockXNBBitable.ts +119 -119
- package/skills/lab-orbit-component-builder/examples/xnb-component-template/dev/src/shims-vue.d.ts +6 -6
- package/skills/lab-orbit-component-builder/examples/xnb-component-template/dev/src/utils/devOpenerHost.ts +75 -75
- package/skills/lab-orbit-component-builder/examples/xnb-component-template/index.html +13 -13
- package/skills/lab-orbit-component-builder/examples/xnb-component-template/package.json +60 -60
- package/skills/lab-orbit-component-builder/examples/xnb-component-template/src/api/agenticlabTickets.ts +110 -110
- package/skills/lab-orbit-component-builder/examples/xnb-component-template/src/entries/bitable.ts +4 -4
- package/skills/lab-orbit-component-builder/examples/xnb-component-template/src/entries/custom-page.ts +4 -4
- package/skills/lab-orbit-component-builder/examples/xnb-component-template/src/index.ts +1 -1
- package/skills/lab-orbit-component-builder/examples/xnb-component-template/src/styles/orbit-quasar-host.scss +19 -19
- package/skills/lab-orbit-component-builder/examples/xnb-component-template/src/types/context.ts +15 -15
- package/skills/lab-orbit-component-builder/examples/xnb-component-template/src/types/xnb-context.ts +70 -70
- package/skills/lab-orbit-component-builder/examples/xnb-component-template/src/use/useBitablePage.ts +189 -189
- package/skills/lab-orbit-component-builder/examples/xnb-component-template/src/use/useSuperCellDemo.ts +257 -257
- package/skills/lab-orbit-component-builder/examples/xnb-component-template/src/use/useSuperTableBitableLifecycle.ts +555 -555
- package/skills/lab-orbit-component-builder/examples/xnb-component-template/src/utils/openerInitParams.ts +158 -158
- package/skills/lab-orbit-component-builder/examples/xnb-component-template/src/utils/openerTicketIds.ts +32 -32
- package/skills/lab-orbit-component-builder/examples/xnb-component-template/src/utils/orbitHttpClient.ts +110 -110
- package/skills/lab-orbit-component-builder/examples/xnb-component-template/src/utils/request.ts +92 -92
- package/skills/lab-orbit-component-builder/examples/xnb-component-template/src/views/bitable.vue +67 -67
- package/skills/lab-orbit-component-builder/examples/xnb-component-template/src/views/custom-page.vue +140 -140
- package/skills/lab-orbit-component-builder/examples/xnb-component-template/tsconfig.json +45 -45
- package/skills/lab-orbit-component-builder/examples/xnb-component-template/vite.config.ts +170 -170
- package/skills/lab-orbit-component-builder/examples/xnb-component-template/vite.dev.config.ts +58 -58
- package/skills/lab-orbit-component-builder/references/flow-document-human-ui.md +65 -65
- package/skills/lab-orbit-component-builder/references/orbit-vue-conventions.md +133 -133
- package/skills/lab-orbit-component-builder/references/pool-schema-to-columns.md +67 -67
- package/skills/lab-orbit-component-builder/references/vue-template-checklist.md +179 -179
- package/skills/lab-orbit-component-builder/references/xnb-context-vue-props.md +49 -49
- package/skills/lab-orbit-component-builder/references/xnbitable-vue-parity.md +32 -32
|
@@ -1,555 +1,555 @@
|
|
|
1
|
-
/* eslint-disable @typescript-eslint/typedef, prefer-destructuring, no-continue, quote-props, operator-linebreak */
|
|
2
|
-
import { computed, ref, watch } from 'vue'
|
|
3
|
-
import type { Ref } from 'vue'
|
|
4
|
-
import { fetchAgenticlabTicketList, postAgenticlabExecutionComplete } from '@/api/agenticlabTickets'
|
|
5
|
-
import type { XNBBitableChoreo, XnbSuperCellContext } from '@/types/xnb-context'
|
|
6
|
-
import { extractTicketIdsFromPayload, extractValveIdFromPayload } from '@/utils/openerTicketIds'
|
|
7
|
-
|
|
8
|
-
/** 与 Book 子表、updateSubBitable 最后一参 `name` 对齐(示例名) */
|
|
9
|
-
export const DEMO_SUB_TABLE_NAME = '演示'
|
|
10
|
-
|
|
11
|
-
/** index 须与子表编辑器一致;单主表且编辑器为空时可试 `''`(多子表勿用空串) */
|
|
12
|
-
export const DEMO_LIST_SUB = { index: 'sheet-demo', name: DEMO_SUB_TABLE_NAME, row: 200 } as const
|
|
13
|
-
|
|
14
|
-
const HEADER_GRAY = '#9e9e9e'
|
|
15
|
-
|
|
16
|
-
/** 列定义:field 为表格中文列头,key 为接口/内存行字段 */
|
|
17
|
-
export const DEMO_LIST_COLS = [
|
|
18
|
-
{ field: '编码', key: 'code' },
|
|
19
|
-
{ field: '名称', key: 'name' },
|
|
20
|
-
{ field: '数量', key: 'qty' }
|
|
21
|
-
] as const
|
|
22
|
-
|
|
23
|
-
export interface DemoListRow {
|
|
24
|
-
id: string
|
|
25
|
-
code: string
|
|
26
|
-
name: string
|
|
27
|
-
qty: number
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
const bitableConfig = {
|
|
31
|
-
columnlen: { '0': 120, '1': 160, '2': 100 },
|
|
32
|
-
rowlen: { 0: 34 }
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function buildDataColumns (): Array<Record<string, unknown>> {
|
|
36
|
-
const cols: Array<Record<string, unknown>> = [
|
|
37
|
-
{
|
|
38
|
-
w: 120,
|
|
39
|
-
dv: { type: 'text_length' },
|
|
40
|
-
field: '编码',
|
|
41
|
-
readonly: true,
|
|
42
|
-
visible: true,
|
|
43
|
-
dbRelated: false,
|
|
44
|
-
filters: null,
|
|
45
|
-
configurable: false,
|
|
46
|
-
headerColor: HEADER_GRAY
|
|
47
|
-
},
|
|
48
|
-
{
|
|
49
|
-
w: 160,
|
|
50
|
-
dv: { type: 'text_length' },
|
|
51
|
-
field: '名称',
|
|
52
|
-
readonly: true,
|
|
53
|
-
visible: true,
|
|
54
|
-
dbRelated: false,
|
|
55
|
-
filters: null,
|
|
56
|
-
configurable: false,
|
|
57
|
-
headerColor: HEADER_GRAY
|
|
58
|
-
},
|
|
59
|
-
{
|
|
60
|
-
w: 100,
|
|
61
|
-
dv: { type: 'number' },
|
|
62
|
-
ct: { fa: '0', t: 'n' },
|
|
63
|
-
field: '数量',
|
|
64
|
-
readonly: true,
|
|
65
|
-
visible: true,
|
|
66
|
-
dbRelated: false,
|
|
67
|
-
filters: null,
|
|
68
|
-
configurable: false,
|
|
69
|
-
headerColor: HEADER_GRAY
|
|
70
|
-
}
|
|
71
|
-
]
|
|
72
|
-
cols.forEach((dc, i) => {
|
|
73
|
-
dc.c = i
|
|
74
|
-
})
|
|
75
|
-
return cols
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
const dataColumns = buildDataColumns()
|
|
79
|
-
|
|
80
|
-
/** opener `payload` 中带 `demoRows` 时用于首屏/刷新数据源(类型宽松便于对接) */
|
|
81
|
-
export function parseDemoRowsFromOpenerPayload (payload: unknown): DemoListRow[] | null {
|
|
82
|
-
if (payload == null || typeof payload !== 'object') return null
|
|
83
|
-
const rows = (payload as { demoRows?: unknown }).demoRows
|
|
84
|
-
if (!Array.isArray(rows) || rows.length === 0) return null
|
|
85
|
-
const out: DemoListRow[] = []
|
|
86
|
-
for (const r of rows) {
|
|
87
|
-
if (r == null || typeof r !== 'object') continue
|
|
88
|
-
const o = r as Record<string, unknown>
|
|
89
|
-
const id = o.id != null ? String(o.id) : ''
|
|
90
|
-
if (!id) continue
|
|
91
|
-
const code = o.code != null ? String(o.code) : ''
|
|
92
|
-
const name = o.name != null ? String(o.name) : ''
|
|
93
|
-
const qty = typeof o.qty === 'number' ? o.qty : Number(o.qty)
|
|
94
|
-
if (!Number.isFinite(qty)) continue
|
|
95
|
-
out.push({ id, code, name, qty })
|
|
96
|
-
}
|
|
97
|
-
return out.length ? out : null
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
/** opener 已预拉工单列表时(如父级合并的 `ticketList`),直接映射为表格行 */
|
|
101
|
-
function parseTicketListFromOpenerPayload (payload: unknown): DemoListRow[] | null {
|
|
102
|
-
if (payload == null || typeof payload !== 'object') return null
|
|
103
|
-
const list = (payload as { ticketList?: unknown }).ticketList
|
|
104
|
-
if (!Array.isArray(list) || list.length === 0) return null
|
|
105
|
-
const rows = list.map((t) => mapAgenticlabTicketToDemoRow(t)).filter((r): r is DemoListRow => r != null)
|
|
106
|
-
return rows.length ? rows : null
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
function numOr0 (v: unknown): number {
|
|
110
|
-
const n = typeof v === 'number' ? v : Number(v)
|
|
111
|
-
return Number.isFinite(n) ? n : 0
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
/** 将 `/api/ticket/list` 单项映射为示例表列(编码/名称/数量) */
|
|
115
|
-
function mapAgenticlabTicketToDemoRow (raw: unknown): DemoListRow | null {
|
|
116
|
-
if (raw == null || typeof raw !== 'object') return null
|
|
117
|
-
const t = raw as Record<string, unknown>
|
|
118
|
-
const id = t.id != null ? String(t.id) : ''
|
|
119
|
-
if (!id) return null
|
|
120
|
-
const detail = t.detail != null && typeof t.detail === 'object' ? (t.detail as Record<string, unknown>) : {}
|
|
121
|
-
const ticketId = detail.ticket_id != null ? String(detail.ticket_id) : id
|
|
122
|
-
const name =
|
|
123
|
-
detail.compound_id != null
|
|
124
|
-
? String(detail.compound_id)
|
|
125
|
-
: detail.smiles != null
|
|
126
|
-
? String(detail.smiles)
|
|
127
|
-
: String(t.uuid ?? ticketId)
|
|
128
|
-
const pp = detail.process_params != null && typeof detail.process_params === 'object'
|
|
129
|
-
? (detail.process_params as Record<string, unknown>)
|
|
130
|
-
: {}
|
|
131
|
-
const qty =
|
|
132
|
-
numOr0(pp['@requested_amount_mg']) ||
|
|
133
|
-
numOr0(detail.target_amount_1_mg) ||
|
|
134
|
-
numOr0(detail.target_amount_2_mg) ||
|
|
135
|
-
0
|
|
136
|
-
return { id, code: ticketId, name, qty }
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
export interface ListSubShape {
|
|
140
|
-
index: string
|
|
141
|
-
name: string
|
|
142
|
-
row: number
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
function handleBitable (bitable: Record<string, unknown>, sub: ListSubShape | undefined): void {
|
|
146
|
-
bitable.name = sub?.name
|
|
147
|
-
if (sub && Object.prototype.hasOwnProperty.call(sub, 'index')) bitable.index = sub.index
|
|
148
|
-
bitable.row = sub?.row ?? 200
|
|
149
|
-
bitable.xnb_show_sheetbar = true
|
|
150
|
-
bitable.xnb_hide_statisticbar = true
|
|
151
|
-
bitable.xnb_hide_toolbar = false
|
|
152
|
-
bitable.xnb_hide_formulaBar = false
|
|
153
|
-
bitable.xnb_excel_with_bg = true
|
|
154
|
-
bitable.xnb_height = `${Math.floor((window.innerHeight - 100) * 0.72)}px`
|
|
155
|
-
bitable.config = bitable.config ?? bitableConfig
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
function dataColumnsMismatch (bitable: Record<string, unknown>, cols: Array<Record<string, unknown>>): boolean {
|
|
159
|
-
const dc = (bitable.data_columns as Array<{ field?: string }> | undefined) ?? []
|
|
160
|
-
if (dc.length !== cols.length) return true
|
|
161
|
-
return cols.some((c, i) => c.field !== dc[i]?.field)
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
export interface UseSuperTableBitableLifecycleResult {
|
|
165
|
-
choreo: Ref<XNBBitableChoreo | null>
|
|
166
|
-
lastRowCount: Ref<number>
|
|
167
|
-
/** 首屏:ensure 列结构 + 拉数 + 写 items */
|
|
168
|
-
bootstrap: () => Promise<void>
|
|
169
|
-
/** 与 bitable-list-lifecycle.js 中 refreshTable 一致:列不齐则补建,再拉数,整表替换 items 后 update */
|
|
170
|
-
refreshTable: (opts?: { silentNotify?: boolean; skipLoading?: boolean }) => Promise<number>
|
|
171
|
-
/** 校验当前子表行:数量列为正数;`skipLoading` 为 true 时不套 loading(供提交前复用);`silentSuccess` 为 true 时通过不弹成功提示 */
|
|
172
|
-
checkRows: (opts?: { skipLoading?: boolean; silentSuccess?: boolean }) => Promise<boolean>
|
|
173
|
-
/** 先 `checkRows`,再 POST `/api/execution/complete`(不触发整书保存) */
|
|
174
|
-
submitRows: () => Promise<void>
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
/**
|
|
178
|
-
* Vue 版「超级表格列表」生命周期。
|
|
179
|
-
* 刷新使用全量 `items = mapped`(`applyRowsToBitable(..., { replaceAll: true })`);全量 list 接口须同此写法(见 orbit skill §8.5.2)。
|
|
180
|
-
*/
|
|
181
|
-
export function useSuperTableBitableLifecycle (
|
|
182
|
-
getContext: () => XnbSuperCellContext,
|
|
183
|
-
getOpenerPayload: () => unknown
|
|
184
|
-
): UseSuperTableBitableLifecycleResult {
|
|
185
|
-
const choreo = ref<XNBBitableChoreo | null>(null)
|
|
186
|
-
const lastRowCount = ref(0)
|
|
187
|
-
/** 最近一次从 AgenticLab `GET /api/ticket/list` 拉到的完整工单,用于提交时与表格合并为 `tickets` */
|
|
188
|
-
const lastAgenticRawTickets = ref<Record<string, unknown>[]>([])
|
|
189
|
-
const cellUid = computed(() => getContext().cellUid || '')
|
|
190
|
-
|
|
191
|
-
let _prevBookPath = ''
|
|
192
|
-
let _prevCellUid = ''
|
|
193
|
-
|
|
194
|
-
watch(
|
|
195
|
-
() => [getContext().bookPath, getContext().cellUid, getContext().XNBBitable] as const,
|
|
196
|
-
() => {
|
|
197
|
-
const ctx = getContext()
|
|
198
|
-
const bp = ctx.bookPath
|
|
199
|
-
const cu = ctx.cellUid
|
|
200
|
-
if (bp === _prevBookPath && cu === _prevCellUid && choreo.value != null) return
|
|
201
|
-
_prevBookPath = bp
|
|
202
|
-
_prevCellUid = cu
|
|
203
|
-
const Ctor = ctx.XNBBitable
|
|
204
|
-
choreo.value = Ctor != null ? new Ctor({ bookPath: bp, cellUid: cu }) : null
|
|
205
|
-
},
|
|
206
|
-
{ immediate: true }
|
|
207
|
-
)
|
|
208
|
-
|
|
209
|
-
function xnb (): XNBBitableChoreo {
|
|
210
|
-
const c = choreo.value
|
|
211
|
-
if (c == null) throw new Error('XNBBitable 未注入')
|
|
212
|
-
return c
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
const listSub: ListSubShape = {
|
|
216
|
-
index: DEMO_LIST_SUB.index,
|
|
217
|
-
name: DEMO_LIST_SUB.name,
|
|
218
|
-
row: DEMO_LIST_SUB.row
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
async function resolveBitableBySub (sub: ListSubShape): Promise<Record<string, unknown> | null> {
|
|
222
|
-
const x = xnb()
|
|
223
|
-
if (!(await x.getSubCell())) return null
|
|
224
|
-
const all = await x.getAllSubBitable(true)
|
|
225
|
-
if (sub.index) {
|
|
226
|
-
const hit = all.find((b) => b.index === sub.index)
|
|
227
|
-
if (hit) return hit
|
|
228
|
-
}
|
|
229
|
-
if (all.length === 1) return all[0] ?? null
|
|
230
|
-
const main = (await x.getSubBitable(true)) as Record<string, unknown> | null
|
|
231
|
-
if (main == null) return null
|
|
232
|
-
if (!sub.index || main.index === sub.index) return main
|
|
233
|
-
return null
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
async function rebuildSubBitableColumns (
|
|
237
|
-
bitable: Record<string, unknown>,
|
|
238
|
-
sub: ListSubShape,
|
|
239
|
-
cols: Array<Record<string, unknown>>
|
|
240
|
-
): Promise<void> {
|
|
241
|
-
const x = xnb()
|
|
242
|
-
bitable.items = []
|
|
243
|
-
bitable.data_columns = []
|
|
244
|
-
await x.addColumns(bitable, cols, [])
|
|
245
|
-
handleBitable(bitable, sub)
|
|
246
|
-
await x.updateSubBitable([bitable], { isMerge: false, name: DEMO_SUB_TABLE_NAME })
|
|
247
|
-
if (typeof x.delay === 'function') await x.delay(300)
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
async function createEmptyBitable (isMerge = false): Promise<void> {
|
|
251
|
-
const x = xnb()
|
|
252
|
-
if (await x.getSubCell()) {
|
|
253
|
-
const bitable = (await x.getSubBitable(true)) as Record<string, unknown>
|
|
254
|
-
bitable.items = []
|
|
255
|
-
bitable.data_columns = []
|
|
256
|
-
await x.addColumns(bitable, dataColumns, [])
|
|
257
|
-
handleBitable(bitable, listSub)
|
|
258
|
-
await x.updateSubBitable([bitable], { isMerge, name: DEMO_SUB_TABLE_NAME })
|
|
259
|
-
if (typeof x.delay === 'function') await x.delay(300)
|
|
260
|
-
return
|
|
261
|
-
}
|
|
262
|
-
const bitable: Record<string, unknown> = { items: [], data_columns: [] }
|
|
263
|
-
handleBitable(bitable, listSub)
|
|
264
|
-
await x.addColumns(bitable, dataColumns, [])
|
|
265
|
-
try {
|
|
266
|
-
await x.createSubBitable([bitable], DEMO_SUB_TABLE_NAME)
|
|
267
|
-
} catch {
|
|
268
|
-
if (typeof x.delay === 'function') await x.delay(500)
|
|
269
|
-
await x.createSubBitable([bitable], DEMO_SUB_TABLE_NAME)
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
async function fetchDemoList (): Promise<DemoListRow[]> {
|
|
274
|
-
const payload = getOpenerPayload()
|
|
275
|
-
const baseUrl = import.meta.env.VITE_AGENTICLAB_API_URL || ''
|
|
276
|
-
const token = import.meta.env.VITE_DEV_TOKEN || ''
|
|
277
|
-
const ids = extractTicketIdsFromPayload(payload)
|
|
278
|
-
if (baseUrl !== '' && ids.length > 0) {
|
|
279
|
-
const list = await fetchAgenticlabTicketList({
|
|
280
|
-
baseUrl,
|
|
281
|
-
token,
|
|
282
|
-
ticketIds: ids
|
|
283
|
-
})
|
|
284
|
-
const fromApi = list.map((t) => mapAgenticlabTicketToDemoRow(t)).filter((r): r is DemoListRow => r != null)
|
|
285
|
-
if (fromApi.length > 0) {
|
|
286
|
-
lastAgenticRawTickets.value = list.map((t) => {
|
|
287
|
-
return t != null && typeof t === 'object' ? (t as Record<string, unknown>) : {}
|
|
288
|
-
})
|
|
289
|
-
return fromApi
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
lastAgenticRawTickets.value = []
|
|
293
|
-
const fromTicketList = parseTicketListFromOpenerPayload(payload)
|
|
294
|
-
if (fromTicketList != null) return fromTicketList
|
|
295
|
-
const fromOpener = parseDemoRowsFromOpenerPayload(payload)
|
|
296
|
-
if (fromOpener != null) return fromOpener
|
|
297
|
-
/* 无 Agentic 列表、无 opener ticketList/demoRows 时不注入占位行 */
|
|
298
|
-
return []
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
function mapRowToItem (row: DemoListRow, i: number): { id: string; fields: Record<string, string> } {
|
|
302
|
-
const fields: Record<string, string> = {}
|
|
303
|
-
for (const { field, key } of DEMO_LIST_COLS) {
|
|
304
|
-
const raw = row[key as keyof DemoListRow]
|
|
305
|
-
fields[field] = raw == null ? '' : typeof raw === 'object' ? JSON.stringify(raw) : String(raw)
|
|
306
|
-
}
|
|
307
|
-
return {
|
|
308
|
-
id: String(row.id ?? `${cellUid.value}-${i}`),
|
|
309
|
-
fields
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
/**
|
|
314
|
-
* `replaceAll: true`(刷新列表):整表 `items = mapped`,与全量 list 接口写法一致,宿主表格易重绘。
|
|
315
|
-
* 默认 `replaceAll: false`:无可见行时整表替换,有行时按 id 合并 fields(询价式增量示例)。
|
|
316
|
-
*/
|
|
317
|
-
async function applyRowsToBitable (
|
|
318
|
-
bitable: Record<string, unknown>,
|
|
319
|
-
rows: DemoListRow[],
|
|
320
|
-
opts?: { replaceAll?: boolean }
|
|
321
|
-
): Promise<void> {
|
|
322
|
-
const x = xnb()
|
|
323
|
-
const mapped = rows.map((row, i) => mapRowToItem(row, i))
|
|
324
|
-
const items = (bitable.items as Array<{ id: string; fields: Record<string, string>; visible?: boolean }> | undefined) ?? []
|
|
325
|
-
const visible = items.filter((it) => it?.visible !== false)
|
|
326
|
-
if (opts?.replaceAll === true || !visible.length) {
|
|
327
|
-
bitable.items = mapped
|
|
328
|
-
} else {
|
|
329
|
-
const byId = new Map(mapped.map((m) => [m.id, m]))
|
|
330
|
-
for (const item of items) {
|
|
331
|
-
const hit = byId.get(item.id)
|
|
332
|
-
if (hit != null) item.fields = { ...item.fields, ...hit.fields }
|
|
333
|
-
}
|
|
334
|
-
const have = new Set(items.map((it) => it.id))
|
|
335
|
-
for (const m of mapped) {
|
|
336
|
-
if (!have.has(m.id)) items.push(m)
|
|
337
|
-
}
|
|
338
|
-
bitable.items = items
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
await x.updateSubBitable([bitable], { isMerge: true, name: DEMO_SUB_TABLE_NAME })
|
|
342
|
-
if (typeof x.delay === 'function') await x.delay(300)
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
async function ensureBitableStructure (): Promise<void> {
|
|
346
|
-
const x = xnb()
|
|
347
|
-
if (!(await x.getSubCell())) {
|
|
348
|
-
await createEmptyBitable()
|
|
349
|
-
return
|
|
350
|
-
}
|
|
351
|
-
const bitable = await resolveBitableBySub(listSub)
|
|
352
|
-
if (bitable == null) throw new Error('无法解析子表:请核对 listSub.index / name 与 Book')
|
|
353
|
-
if (dataColumnsMismatch(bitable, dataColumns) || !((bitable.data_columns as unknown[])?.length)) {
|
|
354
|
-
await rebuildSubBitableColumns(bitable, listSub, dataColumns)
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
async function refreshTable (opts: { silentNotify?: boolean; skipLoading?: boolean } = {}): Promise<number> {
|
|
359
|
-
const { silentNotify, skipLoading } = opts
|
|
360
|
-
const ctx = getContext()
|
|
361
|
-
const loadingShow = ctx.loadingShow
|
|
362
|
-
const loadingHide = ctx.loadingHide
|
|
363
|
-
const notifySuccess = ctx.notifySuccess
|
|
364
|
-
const notifyError = ctx.notifyError
|
|
365
|
-
const outer = !skipLoading
|
|
366
|
-
if (outer) loadingShow?.()
|
|
367
|
-
try {
|
|
368
|
-
const x = xnb()
|
|
369
|
-
let bitable = await resolveBitableBySub(listSub)
|
|
370
|
-
if (bitable == null && (await x.getSubCell())) {
|
|
371
|
-
bitable = (await x.getSubBitable(true)) as Record<string, unknown>
|
|
372
|
-
}
|
|
373
|
-
if (bitable == null) throw new Error('未解析到 bitable')
|
|
374
|
-
|
|
375
|
-
if (dataColumnsMismatch(bitable, dataColumns) || !((bitable.data_columns as unknown[])?.length)) {
|
|
376
|
-
await rebuildSubBitableColumns(bitable, listSub, dataColumns)
|
|
377
|
-
bitable = (await resolveBitableBySub(listSub)) ?? bitable
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
const rows = await fetchDemoList()
|
|
381
|
-
await applyRowsToBitable(bitable, rows, { replaceAll: true })
|
|
382
|
-
lastRowCount.value = rows.length
|
|
383
|
-
if (!silentNotify) notifySuccess?.(`已刷新,共 ${rows.length} 条`)
|
|
384
|
-
return rows.length
|
|
385
|
-
} catch (e) {
|
|
386
|
-
notifyError?.(e, '', '刷新失败')
|
|
387
|
-
throw e
|
|
388
|
-
} finally {
|
|
389
|
-
if (outer) loadingHide?.()
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
async function bootstrap (): Promise<void> {
|
|
394
|
-
const ctx = getContext()
|
|
395
|
-
const loadingShow = ctx.loadingShow
|
|
396
|
-
const loadingHide = ctx.loadingHide
|
|
397
|
-
const notifySuccess = ctx.notifySuccess
|
|
398
|
-
const notifyError = ctx.notifyError
|
|
399
|
-
loadingShow?.()
|
|
400
|
-
try {
|
|
401
|
-
await ensureBitableStructure()
|
|
402
|
-
await refreshTable({ silentNotify: true, skipLoading: true })
|
|
403
|
-
notifySuccess?.('演示表已加载')
|
|
404
|
-
} catch (e) {
|
|
405
|
-
notifyError?.(e, '', '首屏加载失败')
|
|
406
|
-
throw e
|
|
407
|
-
} finally {
|
|
408
|
-
loadingHide?.()
|
|
409
|
-
}
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
async function checkRows (opts: { skipLoading?: boolean; silentSuccess?: boolean } = {}): Promise<boolean> {
|
|
413
|
-
const { skipLoading = false, silentSuccess = false } = opts
|
|
414
|
-
const ctx = getContext()
|
|
415
|
-
const notifySuccess = ctx.notifySuccess
|
|
416
|
-
const notifyError = ctx.notifyError
|
|
417
|
-
const loadingShow = ctx.loadingShow
|
|
418
|
-
const loadingHide = ctx.loadingHide
|
|
419
|
-
const outer = !skipLoading
|
|
420
|
-
if (outer) loadingShow?.()
|
|
421
|
-
try {
|
|
422
|
-
const x = xnb()
|
|
423
|
-
const bitable = (await resolveBitableBySub(listSub)) ?? ((await x.getSubBitable(true)) as Record<string, unknown>)
|
|
424
|
-
const items = (bitable.items as Array<{ fields?: Record<string, string> }> | undefined) ?? []
|
|
425
|
-
const visible = items.filter((it) => (it as { visible?: boolean }).visible !== false)
|
|
426
|
-
const qtyField = DEMO_LIST_COLS.find((c) => c.key === 'qty')?.field ?? '数量'
|
|
427
|
-
const bad: string[] = []
|
|
428
|
-
for (const it of visible) {
|
|
429
|
-
const raw = it.fields?.[qtyField]
|
|
430
|
-
const n = Number(raw)
|
|
431
|
-
if (!Number.isFinite(n) || n <= 0) bad.push(String(it.fields?.['编码'] ?? it.fields?.['名称'] ?? '?'))
|
|
432
|
-
}
|
|
433
|
-
if (bad.length) {
|
|
434
|
-
notifyError?.(new Error(`数量须为正数,行:${bad.join(', ')}`), '', '数据检查未通过')
|
|
435
|
-
return false
|
|
436
|
-
}
|
|
437
|
-
if (!silentSuccess) {
|
|
438
|
-
notifySuccess?.(`数据检查通过,共 ${visible.length} 行`)
|
|
439
|
-
}
|
|
440
|
-
return true
|
|
441
|
-
} catch (e) {
|
|
442
|
-
notifyError?.(e, '', '数据检查失败')
|
|
443
|
-
throw e
|
|
444
|
-
} finally {
|
|
445
|
-
if (outer) loadingHide?.()
|
|
446
|
-
}
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
function resolveValveIdForSubmit (): number {
|
|
450
|
-
const fromPayload = extractValveIdFromPayload(getOpenerPayload())
|
|
451
|
-
if (fromPayload != null) return fromPayload
|
|
452
|
-
const env = import.meta.env.VITE_AGENTICLAB_VALVE_ID
|
|
453
|
-
if (env != null && env !== '') {
|
|
454
|
-
const n = Number(env)
|
|
455
|
-
if (Number.isFinite(n)) return n
|
|
456
|
-
}
|
|
457
|
-
return 0
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
/** 将表格行中的「数量」写回工单 `detail`(与 `mapAgenticlabTicketToDemoRow` 读取路径对齐) */
|
|
461
|
-
function mergeEditedRowIntoAgenticTicket (ticket: Record<string, unknown>, row: DemoListRow): Record<string, unknown> {
|
|
462
|
-
const out = JSON.parse(JSON.stringify(ticket)) as Record<string, unknown>
|
|
463
|
-
const detail = (out.detail != null && typeof out.detail === 'object' ? out.detail : {}) as Record<string, unknown>
|
|
464
|
-
const pp =
|
|
465
|
-
detail.process_params != null && typeof detail.process_params === 'object'
|
|
466
|
-
? ({ ...(detail.process_params as Record<string, unknown>) } as Record<string, unknown>)
|
|
467
|
-
: ({} as Record<string, unknown>)
|
|
468
|
-
pp['@requested_amount_mg'] = row.qty
|
|
469
|
-
detail.process_params = pp
|
|
470
|
-
detail.target_amount_1_mg = row.qty
|
|
471
|
-
out.detail = detail
|
|
472
|
-
return out
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
function buildTicketsForComplete (bitable: Record<string, unknown>): Record<string, unknown>[] {
|
|
476
|
-
const rows = itemsToRows(bitable)
|
|
477
|
-
const byId = new Map(lastAgenticRawTickets.value.map((t) => [String(t.id), t]))
|
|
478
|
-
const tickets: Record<string, unknown>[] = []
|
|
479
|
-
for (const row of rows) {
|
|
480
|
-
const raw = byId.get(String(row.id))
|
|
481
|
-
if (raw == null) continue
|
|
482
|
-
tickets.push(mergeEditedRowIntoAgenticTicket(raw, row))
|
|
483
|
-
}
|
|
484
|
-
return tickets
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
function itemsToRows (bitable: Record<string, unknown>): DemoListRow[] {
|
|
488
|
-
const items = (bitable.items as Array<{ id: string; fields?: Record<string, string> }> | undefined) ?? []
|
|
489
|
-
const qtyField = DEMO_LIST_COLS.find((c) => c.key === 'qty')?.field ?? '数量'
|
|
490
|
-
return items
|
|
491
|
-
.filter((it) => (it as { visible?: boolean }).visible !== false)
|
|
492
|
-
.map((it) => {
|
|
493
|
-
const f = it.fields ?? {}
|
|
494
|
-
return {
|
|
495
|
-
id: String(it.id),
|
|
496
|
-
code: f['编码'] ?? '',
|
|
497
|
-
name: f['名称'] ?? '',
|
|
498
|
-
qty: Number(f[qtyField])
|
|
499
|
-
}
|
|
500
|
-
})
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
async function submitRows (): Promise<void> {
|
|
504
|
-
const ctx = getContext()
|
|
505
|
-
const notifySuccess = ctx.notifySuccess
|
|
506
|
-
const notifyError = ctx.notifyError
|
|
507
|
-
const loadingShow = ctx.loadingShow
|
|
508
|
-
const loadingHide = ctx.loadingHide
|
|
509
|
-
loadingShow?.()
|
|
510
|
-
try {
|
|
511
|
-
if (!(await checkRows({ skipLoading: true, silentSuccess: true }))) {
|
|
512
|
-
return
|
|
513
|
-
}
|
|
514
|
-
const baseUrl = import.meta.env.VITE_AGENTICLAB_API_URL || ''
|
|
515
|
-
const token = import.meta.env.VITE_DEV_TOKEN || ''
|
|
516
|
-
if (baseUrl === '') {
|
|
517
|
-
notifyError?.(new Error('未配置 VITE_AGENTICLAB_API_URL(接口地址)'), '', '无法提交')
|
|
518
|
-
return
|
|
519
|
-
}
|
|
520
|
-
const x = xnb()
|
|
521
|
-
const bitable = (await resolveBitableBySub(listSub)) ?? ((await x.getSubBitable(true)) as Record<string, unknown>)
|
|
522
|
-
const tickets = buildTicketsForComplete(bitable)
|
|
523
|
-
if (tickets.length === 0) {
|
|
524
|
-
notifyError?.(
|
|
525
|
-
new Error('当前表格行无法对应到已拉取的工单;请通过 ticket_ids 拉取列表后再提交'),
|
|
526
|
-
'',
|
|
527
|
-
'提交失败'
|
|
528
|
-
)
|
|
529
|
-
return
|
|
530
|
-
}
|
|
531
|
-
const valveId = resolveValveIdForSubmit()
|
|
532
|
-
await postAgenticlabExecutionComplete({
|
|
533
|
-
baseUrl,
|
|
534
|
-
token,
|
|
535
|
-
valveId,
|
|
536
|
-
tickets
|
|
537
|
-
})
|
|
538
|
-
notifySuccess?.(`已提交 ${tickets.length} 条工单(execution/complete)`)
|
|
539
|
-
} catch (e) {
|
|
540
|
-
notifyError?.(e, '', '提交失败')
|
|
541
|
-
throw e
|
|
542
|
-
} finally {
|
|
543
|
-
loadingHide?.()
|
|
544
|
-
}
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
return {
|
|
548
|
-
choreo,
|
|
549
|
-
lastRowCount,
|
|
550
|
-
bootstrap,
|
|
551
|
-
refreshTable,
|
|
552
|
-
checkRows,
|
|
553
|
-
submitRows
|
|
554
|
-
}
|
|
555
|
-
}
|
|
1
|
+
/* eslint-disable @typescript-eslint/typedef, prefer-destructuring, no-continue, quote-props, operator-linebreak */
|
|
2
|
+
import { computed, ref, watch } from 'vue'
|
|
3
|
+
import type { Ref } from 'vue'
|
|
4
|
+
import { fetchAgenticlabTicketList, postAgenticlabExecutionComplete } from '@/api/agenticlabTickets'
|
|
5
|
+
import type { XNBBitableChoreo, XnbSuperCellContext } from '@/types/xnb-context'
|
|
6
|
+
import { extractTicketIdsFromPayload, extractValveIdFromPayload } from '@/utils/openerTicketIds'
|
|
7
|
+
|
|
8
|
+
/** 与 Book 子表、updateSubBitable 最后一参 `name` 对齐(示例名) */
|
|
9
|
+
export const DEMO_SUB_TABLE_NAME = '演示'
|
|
10
|
+
|
|
11
|
+
/** index 须与子表编辑器一致;单主表且编辑器为空时可试 `''`(多子表勿用空串) */
|
|
12
|
+
export const DEMO_LIST_SUB = { index: 'sheet-demo', name: DEMO_SUB_TABLE_NAME, row: 200 } as const
|
|
13
|
+
|
|
14
|
+
const HEADER_GRAY = '#9e9e9e'
|
|
15
|
+
|
|
16
|
+
/** 列定义:field 为表格中文列头,key 为接口/内存行字段 */
|
|
17
|
+
export const DEMO_LIST_COLS = [
|
|
18
|
+
{ field: '编码', key: 'code' },
|
|
19
|
+
{ field: '名称', key: 'name' },
|
|
20
|
+
{ field: '数量', key: 'qty' }
|
|
21
|
+
] as const
|
|
22
|
+
|
|
23
|
+
export interface DemoListRow {
|
|
24
|
+
id: string
|
|
25
|
+
code: string
|
|
26
|
+
name: string
|
|
27
|
+
qty: number
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const bitableConfig = {
|
|
31
|
+
columnlen: { '0': 120, '1': 160, '2': 100 },
|
|
32
|
+
rowlen: { 0: 34 }
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function buildDataColumns (): Array<Record<string, unknown>> {
|
|
36
|
+
const cols: Array<Record<string, unknown>> = [
|
|
37
|
+
{
|
|
38
|
+
w: 120,
|
|
39
|
+
dv: { type: 'text_length' },
|
|
40
|
+
field: '编码',
|
|
41
|
+
readonly: true,
|
|
42
|
+
visible: true,
|
|
43
|
+
dbRelated: false,
|
|
44
|
+
filters: null,
|
|
45
|
+
configurable: false,
|
|
46
|
+
headerColor: HEADER_GRAY
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
w: 160,
|
|
50
|
+
dv: { type: 'text_length' },
|
|
51
|
+
field: '名称',
|
|
52
|
+
readonly: true,
|
|
53
|
+
visible: true,
|
|
54
|
+
dbRelated: false,
|
|
55
|
+
filters: null,
|
|
56
|
+
configurable: false,
|
|
57
|
+
headerColor: HEADER_GRAY
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
w: 100,
|
|
61
|
+
dv: { type: 'number' },
|
|
62
|
+
ct: { fa: '0', t: 'n' },
|
|
63
|
+
field: '数量',
|
|
64
|
+
readonly: true,
|
|
65
|
+
visible: true,
|
|
66
|
+
dbRelated: false,
|
|
67
|
+
filters: null,
|
|
68
|
+
configurable: false,
|
|
69
|
+
headerColor: HEADER_GRAY
|
|
70
|
+
}
|
|
71
|
+
]
|
|
72
|
+
cols.forEach((dc, i) => {
|
|
73
|
+
dc.c = i
|
|
74
|
+
})
|
|
75
|
+
return cols
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const dataColumns = buildDataColumns()
|
|
79
|
+
|
|
80
|
+
/** opener `payload` 中带 `demoRows` 时用于首屏/刷新数据源(类型宽松便于对接) */
|
|
81
|
+
export function parseDemoRowsFromOpenerPayload (payload: unknown): DemoListRow[] | null {
|
|
82
|
+
if (payload == null || typeof payload !== 'object') return null
|
|
83
|
+
const rows = (payload as { demoRows?: unknown }).demoRows
|
|
84
|
+
if (!Array.isArray(rows) || rows.length === 0) return null
|
|
85
|
+
const out: DemoListRow[] = []
|
|
86
|
+
for (const r of rows) {
|
|
87
|
+
if (r == null || typeof r !== 'object') continue
|
|
88
|
+
const o = r as Record<string, unknown>
|
|
89
|
+
const id = o.id != null ? String(o.id) : ''
|
|
90
|
+
if (!id) continue
|
|
91
|
+
const code = o.code != null ? String(o.code) : ''
|
|
92
|
+
const name = o.name != null ? String(o.name) : ''
|
|
93
|
+
const qty = typeof o.qty === 'number' ? o.qty : Number(o.qty)
|
|
94
|
+
if (!Number.isFinite(qty)) continue
|
|
95
|
+
out.push({ id, code, name, qty })
|
|
96
|
+
}
|
|
97
|
+
return out.length ? out : null
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** opener 已预拉工单列表时(如父级合并的 `ticketList`),直接映射为表格行 */
|
|
101
|
+
function parseTicketListFromOpenerPayload (payload: unknown): DemoListRow[] | null {
|
|
102
|
+
if (payload == null || typeof payload !== 'object') return null
|
|
103
|
+
const list = (payload as { ticketList?: unknown }).ticketList
|
|
104
|
+
if (!Array.isArray(list) || list.length === 0) return null
|
|
105
|
+
const rows = list.map((t) => mapAgenticlabTicketToDemoRow(t)).filter((r): r is DemoListRow => r != null)
|
|
106
|
+
return rows.length ? rows : null
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function numOr0 (v: unknown): number {
|
|
110
|
+
const n = typeof v === 'number' ? v : Number(v)
|
|
111
|
+
return Number.isFinite(n) ? n : 0
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** 将 `/api/ticket/list` 单项映射为示例表列(编码/名称/数量) */
|
|
115
|
+
function mapAgenticlabTicketToDemoRow (raw: unknown): DemoListRow | null {
|
|
116
|
+
if (raw == null || typeof raw !== 'object') return null
|
|
117
|
+
const t = raw as Record<string, unknown>
|
|
118
|
+
const id = t.id != null ? String(t.id) : ''
|
|
119
|
+
if (!id) return null
|
|
120
|
+
const detail = t.detail != null && typeof t.detail === 'object' ? (t.detail as Record<string, unknown>) : {}
|
|
121
|
+
const ticketId = detail.ticket_id != null ? String(detail.ticket_id) : id
|
|
122
|
+
const name =
|
|
123
|
+
detail.compound_id != null
|
|
124
|
+
? String(detail.compound_id)
|
|
125
|
+
: detail.smiles != null
|
|
126
|
+
? String(detail.smiles)
|
|
127
|
+
: String(t.uuid ?? ticketId)
|
|
128
|
+
const pp = detail.process_params != null && typeof detail.process_params === 'object'
|
|
129
|
+
? (detail.process_params as Record<string, unknown>)
|
|
130
|
+
: {}
|
|
131
|
+
const qty =
|
|
132
|
+
numOr0(pp['@requested_amount_mg']) ||
|
|
133
|
+
numOr0(detail.target_amount_1_mg) ||
|
|
134
|
+
numOr0(detail.target_amount_2_mg) ||
|
|
135
|
+
0
|
|
136
|
+
return { id, code: ticketId, name, qty }
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export interface ListSubShape {
|
|
140
|
+
index: string
|
|
141
|
+
name: string
|
|
142
|
+
row: number
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function handleBitable (bitable: Record<string, unknown>, sub: ListSubShape | undefined): void {
|
|
146
|
+
bitable.name = sub?.name
|
|
147
|
+
if (sub && Object.prototype.hasOwnProperty.call(sub, 'index')) bitable.index = sub.index
|
|
148
|
+
bitable.row = sub?.row ?? 200
|
|
149
|
+
bitable.xnb_show_sheetbar = true
|
|
150
|
+
bitable.xnb_hide_statisticbar = true
|
|
151
|
+
bitable.xnb_hide_toolbar = false
|
|
152
|
+
bitable.xnb_hide_formulaBar = false
|
|
153
|
+
bitable.xnb_excel_with_bg = true
|
|
154
|
+
bitable.xnb_height = `${Math.floor((window.innerHeight - 100) * 0.72)}px`
|
|
155
|
+
bitable.config = bitable.config ?? bitableConfig
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function dataColumnsMismatch (bitable: Record<string, unknown>, cols: Array<Record<string, unknown>>): boolean {
|
|
159
|
+
const dc = (bitable.data_columns as Array<{ field?: string }> | undefined) ?? []
|
|
160
|
+
if (dc.length !== cols.length) return true
|
|
161
|
+
return cols.some((c, i) => c.field !== dc[i]?.field)
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export interface UseSuperTableBitableLifecycleResult {
|
|
165
|
+
choreo: Ref<XNBBitableChoreo | null>
|
|
166
|
+
lastRowCount: Ref<number>
|
|
167
|
+
/** 首屏:ensure 列结构 + 拉数 + 写 items */
|
|
168
|
+
bootstrap: () => Promise<void>
|
|
169
|
+
/** 与 bitable-list-lifecycle.js 中 refreshTable 一致:列不齐则补建,再拉数,整表替换 items 后 update */
|
|
170
|
+
refreshTable: (opts?: { silentNotify?: boolean; skipLoading?: boolean }) => Promise<number>
|
|
171
|
+
/** 校验当前子表行:数量列为正数;`skipLoading` 为 true 时不套 loading(供提交前复用);`silentSuccess` 为 true 时通过不弹成功提示 */
|
|
172
|
+
checkRows: (opts?: { skipLoading?: boolean; silentSuccess?: boolean }) => Promise<boolean>
|
|
173
|
+
/** 先 `checkRows`,再 POST `/api/execution/complete`(不触发整书保存) */
|
|
174
|
+
submitRows: () => Promise<void>
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Vue 版「超级表格列表」生命周期。
|
|
179
|
+
* 刷新使用全量 `items = mapped`(`applyRowsToBitable(..., { replaceAll: true })`);全量 list 接口须同此写法(见 orbit skill §8.5.2)。
|
|
180
|
+
*/
|
|
181
|
+
export function useSuperTableBitableLifecycle (
|
|
182
|
+
getContext: () => XnbSuperCellContext,
|
|
183
|
+
getOpenerPayload: () => unknown
|
|
184
|
+
): UseSuperTableBitableLifecycleResult {
|
|
185
|
+
const choreo = ref<XNBBitableChoreo | null>(null)
|
|
186
|
+
const lastRowCount = ref(0)
|
|
187
|
+
/** 最近一次从 AgenticLab `GET /api/ticket/list` 拉到的完整工单,用于提交时与表格合并为 `tickets` */
|
|
188
|
+
const lastAgenticRawTickets = ref<Record<string, unknown>[]>([])
|
|
189
|
+
const cellUid = computed(() => getContext().cellUid || '')
|
|
190
|
+
|
|
191
|
+
let _prevBookPath = ''
|
|
192
|
+
let _prevCellUid = ''
|
|
193
|
+
|
|
194
|
+
watch(
|
|
195
|
+
() => [getContext().bookPath, getContext().cellUid, getContext().XNBBitable] as const,
|
|
196
|
+
() => {
|
|
197
|
+
const ctx = getContext()
|
|
198
|
+
const bp = ctx.bookPath
|
|
199
|
+
const cu = ctx.cellUid
|
|
200
|
+
if (bp === _prevBookPath && cu === _prevCellUid && choreo.value != null) return
|
|
201
|
+
_prevBookPath = bp
|
|
202
|
+
_prevCellUid = cu
|
|
203
|
+
const Ctor = ctx.XNBBitable
|
|
204
|
+
choreo.value = Ctor != null ? new Ctor({ bookPath: bp, cellUid: cu }) : null
|
|
205
|
+
},
|
|
206
|
+
{ immediate: true }
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
function xnb (): XNBBitableChoreo {
|
|
210
|
+
const c = choreo.value
|
|
211
|
+
if (c == null) throw new Error('XNBBitable 未注入')
|
|
212
|
+
return c
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const listSub: ListSubShape = {
|
|
216
|
+
index: DEMO_LIST_SUB.index,
|
|
217
|
+
name: DEMO_LIST_SUB.name,
|
|
218
|
+
row: DEMO_LIST_SUB.row
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
async function resolveBitableBySub (sub: ListSubShape): Promise<Record<string, unknown> | null> {
|
|
222
|
+
const x = xnb()
|
|
223
|
+
if (!(await x.getSubCell())) return null
|
|
224
|
+
const all = await x.getAllSubBitable(true)
|
|
225
|
+
if (sub.index) {
|
|
226
|
+
const hit = all.find((b) => b.index === sub.index)
|
|
227
|
+
if (hit) return hit
|
|
228
|
+
}
|
|
229
|
+
if (all.length === 1) return all[0] ?? null
|
|
230
|
+
const main = (await x.getSubBitable(true)) as Record<string, unknown> | null
|
|
231
|
+
if (main == null) return null
|
|
232
|
+
if (!sub.index || main.index === sub.index) return main
|
|
233
|
+
return null
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
async function rebuildSubBitableColumns (
|
|
237
|
+
bitable: Record<string, unknown>,
|
|
238
|
+
sub: ListSubShape,
|
|
239
|
+
cols: Array<Record<string, unknown>>
|
|
240
|
+
): Promise<void> {
|
|
241
|
+
const x = xnb()
|
|
242
|
+
bitable.items = []
|
|
243
|
+
bitable.data_columns = []
|
|
244
|
+
await x.addColumns(bitable, cols, [])
|
|
245
|
+
handleBitable(bitable, sub)
|
|
246
|
+
await x.updateSubBitable([bitable], { isMerge: false, name: DEMO_SUB_TABLE_NAME })
|
|
247
|
+
if (typeof x.delay === 'function') await x.delay(300)
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
async function createEmptyBitable (isMerge = false): Promise<void> {
|
|
251
|
+
const x = xnb()
|
|
252
|
+
if (await x.getSubCell()) {
|
|
253
|
+
const bitable = (await x.getSubBitable(true)) as Record<string, unknown>
|
|
254
|
+
bitable.items = []
|
|
255
|
+
bitable.data_columns = []
|
|
256
|
+
await x.addColumns(bitable, dataColumns, [])
|
|
257
|
+
handleBitable(bitable, listSub)
|
|
258
|
+
await x.updateSubBitable([bitable], { isMerge, name: DEMO_SUB_TABLE_NAME })
|
|
259
|
+
if (typeof x.delay === 'function') await x.delay(300)
|
|
260
|
+
return
|
|
261
|
+
}
|
|
262
|
+
const bitable: Record<string, unknown> = { items: [], data_columns: [] }
|
|
263
|
+
handleBitable(bitable, listSub)
|
|
264
|
+
await x.addColumns(bitable, dataColumns, [])
|
|
265
|
+
try {
|
|
266
|
+
await x.createSubBitable([bitable], DEMO_SUB_TABLE_NAME)
|
|
267
|
+
} catch {
|
|
268
|
+
if (typeof x.delay === 'function') await x.delay(500)
|
|
269
|
+
await x.createSubBitable([bitable], DEMO_SUB_TABLE_NAME)
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
async function fetchDemoList (): Promise<DemoListRow[]> {
|
|
274
|
+
const payload = getOpenerPayload()
|
|
275
|
+
const baseUrl = import.meta.env.VITE_AGENTICLAB_API_URL || ''
|
|
276
|
+
const token = import.meta.env.VITE_DEV_TOKEN || ''
|
|
277
|
+
const ids = extractTicketIdsFromPayload(payload)
|
|
278
|
+
if (baseUrl !== '' && ids.length > 0) {
|
|
279
|
+
const list = await fetchAgenticlabTicketList({
|
|
280
|
+
baseUrl,
|
|
281
|
+
token,
|
|
282
|
+
ticketIds: ids
|
|
283
|
+
})
|
|
284
|
+
const fromApi = list.map((t) => mapAgenticlabTicketToDemoRow(t)).filter((r): r is DemoListRow => r != null)
|
|
285
|
+
if (fromApi.length > 0) {
|
|
286
|
+
lastAgenticRawTickets.value = list.map((t) => {
|
|
287
|
+
return t != null && typeof t === 'object' ? (t as Record<string, unknown>) : {}
|
|
288
|
+
})
|
|
289
|
+
return fromApi
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
lastAgenticRawTickets.value = []
|
|
293
|
+
const fromTicketList = parseTicketListFromOpenerPayload(payload)
|
|
294
|
+
if (fromTicketList != null) return fromTicketList
|
|
295
|
+
const fromOpener = parseDemoRowsFromOpenerPayload(payload)
|
|
296
|
+
if (fromOpener != null) return fromOpener
|
|
297
|
+
/* 无 Agentic 列表、无 opener ticketList/demoRows 时不注入占位行 */
|
|
298
|
+
return []
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function mapRowToItem (row: DemoListRow, i: number): { id: string; fields: Record<string, string> } {
|
|
302
|
+
const fields: Record<string, string> = {}
|
|
303
|
+
for (const { field, key } of DEMO_LIST_COLS) {
|
|
304
|
+
const raw = row[key as keyof DemoListRow]
|
|
305
|
+
fields[field] = raw == null ? '' : typeof raw === 'object' ? JSON.stringify(raw) : String(raw)
|
|
306
|
+
}
|
|
307
|
+
return {
|
|
308
|
+
id: String(row.id ?? `${cellUid.value}-${i}`),
|
|
309
|
+
fields
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* `replaceAll: true`(刷新列表):整表 `items = mapped`,与全量 list 接口写法一致,宿主表格易重绘。
|
|
315
|
+
* 默认 `replaceAll: false`:无可见行时整表替换,有行时按 id 合并 fields(询价式增量示例)。
|
|
316
|
+
*/
|
|
317
|
+
async function applyRowsToBitable (
|
|
318
|
+
bitable: Record<string, unknown>,
|
|
319
|
+
rows: DemoListRow[],
|
|
320
|
+
opts?: { replaceAll?: boolean }
|
|
321
|
+
): Promise<void> {
|
|
322
|
+
const x = xnb()
|
|
323
|
+
const mapped = rows.map((row, i) => mapRowToItem(row, i))
|
|
324
|
+
const items = (bitable.items as Array<{ id: string; fields: Record<string, string>; visible?: boolean }> | undefined) ?? []
|
|
325
|
+
const visible = items.filter((it) => it?.visible !== false)
|
|
326
|
+
if (opts?.replaceAll === true || !visible.length) {
|
|
327
|
+
bitable.items = mapped
|
|
328
|
+
} else {
|
|
329
|
+
const byId = new Map(mapped.map((m) => [m.id, m]))
|
|
330
|
+
for (const item of items) {
|
|
331
|
+
const hit = byId.get(item.id)
|
|
332
|
+
if (hit != null) item.fields = { ...item.fields, ...hit.fields }
|
|
333
|
+
}
|
|
334
|
+
const have = new Set(items.map((it) => it.id))
|
|
335
|
+
for (const m of mapped) {
|
|
336
|
+
if (!have.has(m.id)) items.push(m)
|
|
337
|
+
}
|
|
338
|
+
bitable.items = items
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
await x.updateSubBitable([bitable], { isMerge: true, name: DEMO_SUB_TABLE_NAME })
|
|
342
|
+
if (typeof x.delay === 'function') await x.delay(300)
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
async function ensureBitableStructure (): Promise<void> {
|
|
346
|
+
const x = xnb()
|
|
347
|
+
if (!(await x.getSubCell())) {
|
|
348
|
+
await createEmptyBitable()
|
|
349
|
+
return
|
|
350
|
+
}
|
|
351
|
+
const bitable = await resolveBitableBySub(listSub)
|
|
352
|
+
if (bitable == null) throw new Error('无法解析子表:请核对 listSub.index / name 与 Book')
|
|
353
|
+
if (dataColumnsMismatch(bitable, dataColumns) || !((bitable.data_columns as unknown[])?.length)) {
|
|
354
|
+
await rebuildSubBitableColumns(bitable, listSub, dataColumns)
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
async function refreshTable (opts: { silentNotify?: boolean; skipLoading?: boolean } = {}): Promise<number> {
|
|
359
|
+
const { silentNotify, skipLoading } = opts
|
|
360
|
+
const ctx = getContext()
|
|
361
|
+
const loadingShow = ctx.loadingShow
|
|
362
|
+
const loadingHide = ctx.loadingHide
|
|
363
|
+
const notifySuccess = ctx.notifySuccess
|
|
364
|
+
const notifyError = ctx.notifyError
|
|
365
|
+
const outer = !skipLoading
|
|
366
|
+
if (outer) loadingShow?.()
|
|
367
|
+
try {
|
|
368
|
+
const x = xnb()
|
|
369
|
+
let bitable = await resolveBitableBySub(listSub)
|
|
370
|
+
if (bitable == null && (await x.getSubCell())) {
|
|
371
|
+
bitable = (await x.getSubBitable(true)) as Record<string, unknown>
|
|
372
|
+
}
|
|
373
|
+
if (bitable == null) throw new Error('未解析到 bitable')
|
|
374
|
+
|
|
375
|
+
if (dataColumnsMismatch(bitable, dataColumns) || !((bitable.data_columns as unknown[])?.length)) {
|
|
376
|
+
await rebuildSubBitableColumns(bitable, listSub, dataColumns)
|
|
377
|
+
bitable = (await resolveBitableBySub(listSub)) ?? bitable
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
const rows = await fetchDemoList()
|
|
381
|
+
await applyRowsToBitable(bitable, rows, { replaceAll: true })
|
|
382
|
+
lastRowCount.value = rows.length
|
|
383
|
+
if (!silentNotify) notifySuccess?.(`已刷新,共 ${rows.length} 条`)
|
|
384
|
+
return rows.length
|
|
385
|
+
} catch (e) {
|
|
386
|
+
notifyError?.(e, '', '刷新失败')
|
|
387
|
+
throw e
|
|
388
|
+
} finally {
|
|
389
|
+
if (outer) loadingHide?.()
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
async function bootstrap (): Promise<void> {
|
|
394
|
+
const ctx = getContext()
|
|
395
|
+
const loadingShow = ctx.loadingShow
|
|
396
|
+
const loadingHide = ctx.loadingHide
|
|
397
|
+
const notifySuccess = ctx.notifySuccess
|
|
398
|
+
const notifyError = ctx.notifyError
|
|
399
|
+
loadingShow?.()
|
|
400
|
+
try {
|
|
401
|
+
await ensureBitableStructure()
|
|
402
|
+
await refreshTable({ silentNotify: true, skipLoading: true })
|
|
403
|
+
notifySuccess?.('演示表已加载')
|
|
404
|
+
} catch (e) {
|
|
405
|
+
notifyError?.(e, '', '首屏加载失败')
|
|
406
|
+
throw e
|
|
407
|
+
} finally {
|
|
408
|
+
loadingHide?.()
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
async function checkRows (opts: { skipLoading?: boolean; silentSuccess?: boolean } = {}): Promise<boolean> {
|
|
413
|
+
const { skipLoading = false, silentSuccess = false } = opts
|
|
414
|
+
const ctx = getContext()
|
|
415
|
+
const notifySuccess = ctx.notifySuccess
|
|
416
|
+
const notifyError = ctx.notifyError
|
|
417
|
+
const loadingShow = ctx.loadingShow
|
|
418
|
+
const loadingHide = ctx.loadingHide
|
|
419
|
+
const outer = !skipLoading
|
|
420
|
+
if (outer) loadingShow?.()
|
|
421
|
+
try {
|
|
422
|
+
const x = xnb()
|
|
423
|
+
const bitable = (await resolveBitableBySub(listSub)) ?? ((await x.getSubBitable(true)) as Record<string, unknown>)
|
|
424
|
+
const items = (bitable.items as Array<{ fields?: Record<string, string> }> | undefined) ?? []
|
|
425
|
+
const visible = items.filter((it) => (it as { visible?: boolean }).visible !== false)
|
|
426
|
+
const qtyField = DEMO_LIST_COLS.find((c) => c.key === 'qty')?.field ?? '数量'
|
|
427
|
+
const bad: string[] = []
|
|
428
|
+
for (const it of visible) {
|
|
429
|
+
const raw = it.fields?.[qtyField]
|
|
430
|
+
const n = Number(raw)
|
|
431
|
+
if (!Number.isFinite(n) || n <= 0) bad.push(String(it.fields?.['编码'] ?? it.fields?.['名称'] ?? '?'))
|
|
432
|
+
}
|
|
433
|
+
if (bad.length) {
|
|
434
|
+
notifyError?.(new Error(`数量须为正数,行:${bad.join(', ')}`), '', '数据检查未通过')
|
|
435
|
+
return false
|
|
436
|
+
}
|
|
437
|
+
if (!silentSuccess) {
|
|
438
|
+
notifySuccess?.(`数据检查通过,共 ${visible.length} 行`)
|
|
439
|
+
}
|
|
440
|
+
return true
|
|
441
|
+
} catch (e) {
|
|
442
|
+
notifyError?.(e, '', '数据检查失败')
|
|
443
|
+
throw e
|
|
444
|
+
} finally {
|
|
445
|
+
if (outer) loadingHide?.()
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
function resolveValveIdForSubmit (): number {
|
|
450
|
+
const fromPayload = extractValveIdFromPayload(getOpenerPayload())
|
|
451
|
+
if (fromPayload != null) return fromPayload
|
|
452
|
+
const env = import.meta.env.VITE_AGENTICLAB_VALVE_ID
|
|
453
|
+
if (env != null && env !== '') {
|
|
454
|
+
const n = Number(env)
|
|
455
|
+
if (Number.isFinite(n)) return n
|
|
456
|
+
}
|
|
457
|
+
return 0
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
/** 将表格行中的「数量」写回工单 `detail`(与 `mapAgenticlabTicketToDemoRow` 读取路径对齐) */
|
|
461
|
+
function mergeEditedRowIntoAgenticTicket (ticket: Record<string, unknown>, row: DemoListRow): Record<string, unknown> {
|
|
462
|
+
const out = JSON.parse(JSON.stringify(ticket)) as Record<string, unknown>
|
|
463
|
+
const detail = (out.detail != null && typeof out.detail === 'object' ? out.detail : {}) as Record<string, unknown>
|
|
464
|
+
const pp =
|
|
465
|
+
detail.process_params != null && typeof detail.process_params === 'object'
|
|
466
|
+
? ({ ...(detail.process_params as Record<string, unknown>) } as Record<string, unknown>)
|
|
467
|
+
: ({} as Record<string, unknown>)
|
|
468
|
+
pp['@requested_amount_mg'] = row.qty
|
|
469
|
+
detail.process_params = pp
|
|
470
|
+
detail.target_amount_1_mg = row.qty
|
|
471
|
+
out.detail = detail
|
|
472
|
+
return out
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
function buildTicketsForComplete (bitable: Record<string, unknown>): Record<string, unknown>[] {
|
|
476
|
+
const rows = itemsToRows(bitable)
|
|
477
|
+
const byId = new Map(lastAgenticRawTickets.value.map((t) => [String(t.id), t]))
|
|
478
|
+
const tickets: Record<string, unknown>[] = []
|
|
479
|
+
for (const row of rows) {
|
|
480
|
+
const raw = byId.get(String(row.id))
|
|
481
|
+
if (raw == null) continue
|
|
482
|
+
tickets.push(mergeEditedRowIntoAgenticTicket(raw, row))
|
|
483
|
+
}
|
|
484
|
+
return tickets
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
function itemsToRows (bitable: Record<string, unknown>): DemoListRow[] {
|
|
488
|
+
const items = (bitable.items as Array<{ id: string; fields?: Record<string, string> }> | undefined) ?? []
|
|
489
|
+
const qtyField = DEMO_LIST_COLS.find((c) => c.key === 'qty')?.field ?? '数量'
|
|
490
|
+
return items
|
|
491
|
+
.filter((it) => (it as { visible?: boolean }).visible !== false)
|
|
492
|
+
.map((it) => {
|
|
493
|
+
const f = it.fields ?? {}
|
|
494
|
+
return {
|
|
495
|
+
id: String(it.id),
|
|
496
|
+
code: f['编码'] ?? '',
|
|
497
|
+
name: f['名称'] ?? '',
|
|
498
|
+
qty: Number(f[qtyField])
|
|
499
|
+
}
|
|
500
|
+
})
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
async function submitRows (): Promise<void> {
|
|
504
|
+
const ctx = getContext()
|
|
505
|
+
const notifySuccess = ctx.notifySuccess
|
|
506
|
+
const notifyError = ctx.notifyError
|
|
507
|
+
const loadingShow = ctx.loadingShow
|
|
508
|
+
const loadingHide = ctx.loadingHide
|
|
509
|
+
loadingShow?.()
|
|
510
|
+
try {
|
|
511
|
+
if (!(await checkRows({ skipLoading: true, silentSuccess: true }))) {
|
|
512
|
+
return
|
|
513
|
+
}
|
|
514
|
+
const baseUrl = import.meta.env.VITE_AGENTICLAB_API_URL || ''
|
|
515
|
+
const token = import.meta.env.VITE_DEV_TOKEN || ''
|
|
516
|
+
if (baseUrl === '') {
|
|
517
|
+
notifyError?.(new Error('未配置 VITE_AGENTICLAB_API_URL(接口地址)'), '', '无法提交')
|
|
518
|
+
return
|
|
519
|
+
}
|
|
520
|
+
const x = xnb()
|
|
521
|
+
const bitable = (await resolveBitableBySub(listSub)) ?? ((await x.getSubBitable(true)) as Record<string, unknown>)
|
|
522
|
+
const tickets = buildTicketsForComplete(bitable)
|
|
523
|
+
if (tickets.length === 0) {
|
|
524
|
+
notifyError?.(
|
|
525
|
+
new Error('当前表格行无法对应到已拉取的工单;请通过 ticket_ids 拉取列表后再提交'),
|
|
526
|
+
'',
|
|
527
|
+
'提交失败'
|
|
528
|
+
)
|
|
529
|
+
return
|
|
530
|
+
}
|
|
531
|
+
const valveId = resolveValveIdForSubmit()
|
|
532
|
+
await postAgenticlabExecutionComplete({
|
|
533
|
+
baseUrl,
|
|
534
|
+
token,
|
|
535
|
+
valveId,
|
|
536
|
+
tickets
|
|
537
|
+
})
|
|
538
|
+
notifySuccess?.(`已提交 ${tickets.length} 条工单(execution/complete)`)
|
|
539
|
+
} catch (e) {
|
|
540
|
+
notifyError?.(e, '', '提交失败')
|
|
541
|
+
throw e
|
|
542
|
+
} finally {
|
|
543
|
+
loadingHide?.()
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
return {
|
|
548
|
+
choreo,
|
|
549
|
+
lastRowCount,
|
|
550
|
+
bootstrap,
|
|
551
|
+
refreshTable,
|
|
552
|
+
checkRows,
|
|
553
|
+
submitRows
|
|
554
|
+
}
|
|
555
|
+
}
|