@chenchaolong/plugin-trade-compliance-workbench 0.1.7 → 0.1.9
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.
|
@@ -74,6 +74,7 @@
|
|
|
74
74
|
const [query, setQuery] = React.useState('')
|
|
75
75
|
const [showManualForm, setShowManualForm] = React.useState(false)
|
|
76
76
|
const [form, setForm] = React.useState({ productName: '', hsCode: '', keywords: '', controlNote: '', enabled: true })
|
|
77
|
+
const pollingRef = React.useRef(null)
|
|
77
78
|
|
|
78
79
|
React.useEffect(() => {
|
|
79
80
|
window.__tradeComplianceSetContext = (nextContext) => {
|
|
@@ -86,6 +87,7 @@
|
|
|
86
87
|
return () => {
|
|
87
88
|
window.__tradeComplianceSetContext = null
|
|
88
89
|
window.__tradeComplianceHostEvent = null
|
|
90
|
+
stopRecognitionPolling()
|
|
89
91
|
}
|
|
90
92
|
}, [])
|
|
91
93
|
|
|
@@ -123,6 +125,8 @@
|
|
|
123
125
|
|
|
124
126
|
async function handleFile(actionKey, file) {
|
|
125
127
|
if (!file || busy) return
|
|
128
|
+
const expectedType = reviewTypeForUploadAction(actionKey)
|
|
129
|
+
const previousCount = countReviewItemsByType(data.reviewItems, expectedType)
|
|
126
130
|
setBusy(true)
|
|
127
131
|
try {
|
|
128
132
|
const response = await executeFileAction(actionKey, file, { name: file.name, fileName: file.name }, {})
|
|
@@ -130,6 +134,7 @@
|
|
|
130
134
|
const dispatched = await dispatchAssistantCommands(getActionDataPayload(response))
|
|
131
135
|
showNotice(dispatched ? '文件已发送给智能体解析,识别结果会进入待审核列表。' : '文件已登记,等待识别结果。', 'success')
|
|
132
136
|
await reload()
|
|
137
|
+
if (dispatched && expectedType) startRecognitionPolling(expectedType, previousCount)
|
|
133
138
|
} catch (error) {
|
|
134
139
|
showNotice(getErrorMessage(error), 'error')
|
|
135
140
|
} finally {
|
|
@@ -239,6 +244,30 @@
|
|
|
239
244
|
setTimeout(() => setNotice(''), 4500)
|
|
240
245
|
}
|
|
241
246
|
|
|
247
|
+
function startRecognitionPolling(expectedType, previousCount) {
|
|
248
|
+
stopRecognitionPolling()
|
|
249
|
+
let attempts = 0
|
|
250
|
+
pollingRef.current = setInterval(async () => {
|
|
251
|
+
attempts += 1
|
|
252
|
+
try {
|
|
253
|
+
const response = await request('requestData', { query: { page: 1, pageSize: 200, search: query, parameters: {} } })
|
|
254
|
+
const nextData = normalizeData(getResponsePayload(response))
|
|
255
|
+
setData(nextData)
|
|
256
|
+
if (countReviewItemsByType(nextData.reviewItems, expectedType) > previousCount || attempts >= 36) {
|
|
257
|
+
stopRecognitionPolling()
|
|
258
|
+
}
|
|
259
|
+
} catch (_error) {
|
|
260
|
+
if (attempts >= 36) stopRecognitionPolling()
|
|
261
|
+
}
|
|
262
|
+
}, 2500)
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function stopRecognitionPolling() {
|
|
266
|
+
if (!pollingRef.current) return
|
|
267
|
+
clearInterval(pollingRef.current)
|
|
268
|
+
pollingRef.current = null
|
|
269
|
+
}
|
|
270
|
+
|
|
242
271
|
return h('div', { className: 'tcw-shell' },
|
|
243
272
|
h('header', { className: 'tcw-header' },
|
|
244
273
|
h('div', { className: 'tcw-title-block' },
|
|
@@ -292,12 +321,14 @@
|
|
|
292
321
|
|
|
293
322
|
function renderControlledGoodsPage() {
|
|
294
323
|
return h('div', { className: 'tcw-page controlled-goods-page' },
|
|
295
|
-
|
|
324
|
+
businessPanel('管控商品', '上传管控目录后,识别结果在列表内完成审核、驳回和入库。', [
|
|
296
325
|
uploadButton('upload_controlled_goods_file', '上传管控商品文件'),
|
|
297
326
|
h('button', { className: 'tcw-btn tcw-btn-soft', onClick: () => setShowManualForm(!showManualForm) }, showManualForm ? '收起新增' : '手动新增')
|
|
298
|
-
]
|
|
299
|
-
|
|
300
|
-
|
|
327
|
+
], [
|
|
328
|
+
showManualForm ? renderControlledGoodsForm() : null,
|
|
329
|
+
renderListToolbar('管控商品列表', controlledReviews.length),
|
|
330
|
+
h('div', { className: 'pending-controlled-goods confirmed-controlled-goods tcw-table-section' },
|
|
331
|
+
reviewTable(['商品名称', '海关编码', '关键词', '管控说明', '来源'], controlledReviews, (item) => {
|
|
301
332
|
const row = readMerged(item)
|
|
302
333
|
return [
|
|
303
334
|
value(row.productName || item.title),
|
|
@@ -306,7 +337,9 @@
|
|
|
306
337
|
value(row.controlNote),
|
|
307
338
|
value(item.sourceLocation || row.sourceFileName)
|
|
308
339
|
]
|
|
309
|
-
|
|
340
|
+
}, '暂无管控商品识别结果。上传目录并等待智能体调用工具后会显示在这里。')
|
|
341
|
+
)
|
|
342
|
+
])
|
|
310
343
|
)
|
|
311
344
|
}
|
|
312
345
|
|
|
@@ -429,7 +462,26 @@
|
|
|
429
462
|
}
|
|
430
463
|
|
|
431
464
|
function reviewTable(headers, rows, mapRow, emptyText) {
|
|
432
|
-
|
|
465
|
+
const selectableIds = getSelectableRowIds(rows)
|
|
466
|
+
const selectedVisibleIds = selectableIds.filter((id) => selectedIds.includes(id))
|
|
467
|
+
const allVisibleSelected = selectableIds.length > 0 && selectedVisibleIds.length === selectableIds.length
|
|
468
|
+
const partiallySelected = selectedVisibleIds.length > 0 && selectedVisibleIds.length < selectableIds.length
|
|
469
|
+
return table([
|
|
470
|
+
h('input', {
|
|
471
|
+
className: 'row-selection-checkbox',
|
|
472
|
+
type: 'checkbox',
|
|
473
|
+
'aria-label': '全选当前列表',
|
|
474
|
+
checked: allVisibleSelected,
|
|
475
|
+
disabled: selectableIds.length === 0,
|
|
476
|
+
ref: (node) => {
|
|
477
|
+
if (node) node.indeterminate = partiallySelected
|
|
478
|
+
},
|
|
479
|
+
onChange: (event) => toggleAllVisibleRows(rows, event.target.checked)
|
|
480
|
+
}),
|
|
481
|
+
...headers,
|
|
482
|
+
'状态',
|
|
483
|
+
'操作'
|
|
484
|
+
], rows, (row) => [
|
|
433
485
|
h('input', {
|
|
434
486
|
className: 'row-selection-checkbox',
|
|
435
487
|
type: 'checkbox',
|
|
@@ -451,6 +503,21 @@
|
|
|
451
503
|
setSelectedIds(checked ? Array.from(new Set([...selectedIds, id])) : selectedIds.filter((item) => item !== id))
|
|
452
504
|
}
|
|
453
505
|
|
|
506
|
+
function toggleAllVisibleRows(rows, checked) {
|
|
507
|
+
const visibleIds = getSelectableRowIds(rows)
|
|
508
|
+
if (checked) {
|
|
509
|
+
setSelectedIds(Array.from(new Set([...selectedIds, ...visibleIds])))
|
|
510
|
+
return
|
|
511
|
+
}
|
|
512
|
+
setSelectedIds(selectedIds.filter((id) => !visibleIds.includes(id)))
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
function getSelectableRowIds(rows) {
|
|
516
|
+
return (rows || [])
|
|
517
|
+
.filter((row) => row.id && !row.materializedOnly && row.reviewStatus !== 'confirmed')
|
|
518
|
+
.map((row) => row.id)
|
|
519
|
+
}
|
|
520
|
+
|
|
454
521
|
function pageToolbar(title, subtitle, actions) {
|
|
455
522
|
return h('div', { className: 'tcw-page-head' },
|
|
456
523
|
h('div', null, h('h2', null, title), h('p', null, subtitle)),
|
|
@@ -494,7 +561,7 @@
|
|
|
494
561
|
if (!rows || rows.length === 0) return empty(emptyText || '暂无数据。')
|
|
495
562
|
return h('div', { className: 'tcw-table-wrap' },
|
|
496
563
|
h('table', { className: 'tcw-table' },
|
|
497
|
-
h('thead', null, h('tr', null, headers.map((item) => h('th', { key:
|
|
564
|
+
h('thead', null, h('tr', null, headers.map((item, index) => h('th', { key: index }, item)))),
|
|
498
565
|
h('tbody', null, rows.map((row, index) =>
|
|
499
566
|
h('tr', { key: row.id || index, className: onRowClick ? 'clickable' : '', onClick: onRowClick ? () => onRowClick(row) : undefined },
|
|
500
567
|
mapRow(row).map((cell, cellIndex) => h('td', { key: cellIndex }, cell))
|
|
@@ -626,6 +693,18 @@
|
|
|
626
693
|
return items.filter((item) => (item.reviewStatus || 'pending') === statusFilter)
|
|
627
694
|
}
|
|
628
695
|
|
|
696
|
+
function reviewTypeForUploadAction(actionKey) {
|
|
697
|
+
if (actionKey === 'upload_controlled_goods_file') return 'controlled_goods'
|
|
698
|
+
if (actionKey === 'upload_supplier_contract') return 'supplier_product'
|
|
699
|
+
if (actionKey === 'upload_sales_contract') return 'customs_workbook'
|
|
700
|
+
return null
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
function countReviewItemsByType(items, type) {
|
|
704
|
+
if (!type || !Array.isArray(items)) return 0
|
|
705
|
+
return items.filter((item) => item.type === type && !isPlaceholderReviewItem(item)).length
|
|
706
|
+
}
|
|
707
|
+
|
|
629
708
|
function isPlaceholderReviewItem(item) {
|
|
630
709
|
const merged = readMerged(item || {})
|
|
631
710
|
if (!item || !item.type) return true
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chenchaolong/plugin-trade-compliance-workbench",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.9",
|
|
4
4
|
"description": "Trade Compliance Workbench app plugin for controlled goods review, supplier product management, and customs workbook generation.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "XpertAI",
|