@chenchaolong/plugin-trade-compliance-workbench 0.1.6 → 0.1.8
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' },
|
|
@@ -311,12 +340,13 @@
|
|
|
311
340
|
}
|
|
312
341
|
|
|
313
342
|
function renderProductsPage() {
|
|
314
|
-
const groups = groupBy(filteredProducts, (item) => value(item.supplierName, '未识别供应商'))
|
|
315
343
|
return h('div', { className: 'tcw-page products-page' },
|
|
316
|
-
|
|
344
|
+
businessPanel('供应商商品', '上传供应商合同后,识别结果在列表内完成审核、驳回和入库。', [
|
|
317
345
|
uploadButton('upload_supplier_contract', '上传供应商合同')
|
|
318
|
-
]
|
|
319
|
-
|
|
346
|
+
], [
|
|
347
|
+
renderListToolbar('供应商商品列表', supplierReviews.length),
|
|
348
|
+
h('div', { className: 'pending-supplier-products tcw-table-section' },
|
|
349
|
+
reviewTable(['供应商', '商品', '型号', '海关编码', '退税率', '英文品名', '管控状态'], supplierReviews, (item) => {
|
|
320
350
|
const row = readMerged(item)
|
|
321
351
|
return [
|
|
322
352
|
value(row.supplierName),
|
|
@@ -327,17 +357,21 @@
|
|
|
327
357
|
value(row.englishName),
|
|
328
358
|
status(controlStatusText(row.controlledStatus), controlStatusLevel(row.controlledStatus))
|
|
329
359
|
]
|
|
330
|
-
|
|
360
|
+
}, '暂无供应商商品识别结果。上传供应商合同并等待智能体识别后会显示在这里。')
|
|
361
|
+
)
|
|
362
|
+
])
|
|
331
363
|
)
|
|
332
364
|
}
|
|
333
365
|
|
|
334
366
|
function renderWorkbooksPage() {
|
|
335
367
|
return h('div', { className: 'tcw-page workbooks-page' },
|
|
336
|
-
|
|
368
|
+
businessPanel('销售发票', '上传购销合同后,先审核发票字段,再生成固定模板 Excel。', [
|
|
337
369
|
uploadButton('upload_sales_contract', '上传购销合同'),
|
|
338
370
|
h('button', { className: 'tcw-btn tcw-btn-primary', disabled: busy, onClick: generateWorkbook }, '生成销售发票')
|
|
339
|
-
]
|
|
340
|
-
|
|
371
|
+
], [
|
|
372
|
+
renderListToolbar('购销合同识别列表', salesReviews.length),
|
|
373
|
+
h('div', { className: 'pending-sales-contracts tcw-table-section' },
|
|
374
|
+
reviewTable(['文件/标题', '发票号', '合同号', '买方', '卖方'], salesReviews, (item) => {
|
|
341
375
|
const row = readMerged(item)
|
|
342
376
|
return [
|
|
343
377
|
value(item.title),
|
|
@@ -346,14 +380,21 @@
|
|
|
346
380
|
value(row.buyerName),
|
|
347
381
|
value(row.sellerName)
|
|
348
382
|
]
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
383
|
+
}, '暂无购销合同识别结果。')
|
|
384
|
+
),
|
|
385
|
+
h('section', { className: 'tcw-history-section' },
|
|
386
|
+
h('div', { className: 'tcw-subsection-head' },
|
|
387
|
+
h('div', null, h('h3', null, '已生成销售发票'), h('p', null, `${filteredWorkbooks.length} 条生成历史`))
|
|
388
|
+
),
|
|
389
|
+
table(['文件名', '发票号', '合同号', '模板工作表', '状态'], filteredWorkbooks, (item) => [
|
|
390
|
+
value(item.fileName),
|
|
391
|
+
value(item.invoiceNo),
|
|
392
|
+
value(item.contractNo),
|
|
393
|
+
Array.isArray(item.sheetNames) ? item.sheetNames.join('、') : '-',
|
|
394
|
+
status(value(item.status, 'generated'), 'ok')
|
|
395
|
+
], '暂无销售发票生成历史。')
|
|
396
|
+
)
|
|
397
|
+
])
|
|
357
398
|
)
|
|
358
399
|
}
|
|
359
400
|
|
|
@@ -380,7 +421,28 @@
|
|
|
380
421
|
|
|
381
422
|
function renderListPanel(title, className, rows, headers, mapRow, emptyText) {
|
|
382
423
|
return panel(title, h('div', { className },
|
|
383
|
-
|
|
424
|
+
renderListToolbar(null, rows.length),
|
|
425
|
+
reviewTable(headers, rows, mapRow, emptyText)
|
|
426
|
+
))
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
function businessPanel(title, subtitle, actions, content) {
|
|
430
|
+
return h('section', { className: 'tcw-business-panel' },
|
|
431
|
+
h('div', { className: 'tcw-business-head' },
|
|
432
|
+
h('div', null, h('h2', null, title), h('p', null, subtitle)),
|
|
433
|
+
h('div', { className: 'tcw-button-row' }, actions)
|
|
434
|
+
),
|
|
435
|
+
h('div', { className: 'tcw-business-body' }, content)
|
|
436
|
+
)
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
function renderListToolbar(title, total) {
|
|
440
|
+
return h('div', { className: 'tcw-list-toolbar' },
|
|
441
|
+
h('div', { className: 'tcw-list-title' },
|
|
442
|
+
title ? h('h3', null, title) : null,
|
|
443
|
+
h('span', null, `${total || 0} 条记录`)
|
|
444
|
+
),
|
|
445
|
+
h('div', { className: 'tcw-list-actions' },
|
|
384
446
|
h('label', { className: 'tcw-filter' },
|
|
385
447
|
h('span', null, '状态'),
|
|
386
448
|
h('select', { className: 'status-filter', value: statusFilter, onChange: (event) => { setStatusFilter(event.target.value); setSelectedIds([]) } },
|
|
@@ -391,9 +453,8 @@
|
|
|
391
453
|
)
|
|
392
454
|
),
|
|
393
455
|
h('button', { className: 'tcw-btn tcw-btn-primary', disabled: busy || selectedIds.length === 0, onClick: batchApproveSelected }, `批量审核${selectedIds.length ? ` (${selectedIds.length})` : ''}`)
|
|
394
|
-
)
|
|
395
|
-
|
|
396
|
-
))
|
|
456
|
+
)
|
|
457
|
+
)
|
|
397
458
|
}
|
|
398
459
|
|
|
399
460
|
function reviewTable(headers, rows, mapRow, emptyText) {
|
|
@@ -594,6 +655,18 @@
|
|
|
594
655
|
return items.filter((item) => (item.reviewStatus || 'pending') === statusFilter)
|
|
595
656
|
}
|
|
596
657
|
|
|
658
|
+
function reviewTypeForUploadAction(actionKey) {
|
|
659
|
+
if (actionKey === 'upload_controlled_goods_file') return 'controlled_goods'
|
|
660
|
+
if (actionKey === 'upload_supplier_contract') return 'supplier_product'
|
|
661
|
+
if (actionKey === 'upload_sales_contract') return 'customs_workbook'
|
|
662
|
+
return null
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
function countReviewItemsByType(items, type) {
|
|
666
|
+
if (!type || !Array.isArray(items)) return 0
|
|
667
|
+
return items.filter((item) => item.type === type && !isPlaceholderReviewItem(item)).length
|
|
668
|
+
}
|
|
669
|
+
|
|
597
670
|
function isPlaceholderReviewItem(item) {
|
|
598
671
|
const merged = readMerged(item || {})
|
|
599
672
|
if (!item || !item.type) return true
|
|
@@ -680,11 +753,11 @@
|
|
|
680
753
|
* { box-sizing: border-box; }
|
|
681
754
|
body { margin: 0; background: var(--tcw-bg); color: var(--tcw-text); font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; }
|
|
682
755
|
button, input { font: inherit; letter-spacing: 0; }
|
|
683
|
-
.tcw-shell { display: grid; gap:
|
|
684
|
-
.tcw-header { display: grid; grid-template-columns: minmax(
|
|
756
|
+
.tcw-shell { display: grid; gap: 10px; min-height: 100vh; padding: 12px 14px 18px; }
|
|
757
|
+
.tcw-header { display: grid; grid-template-columns: minmax(260px, .72fr) minmax(340px, 520px); gap: 12px; align-items: center; border: 1px solid var(--tcw-border); border-radius: 8px; background: var(--tcw-card); padding: 12px 14px; }
|
|
685
758
|
.tcw-title-block { display: grid; gap: 3px; }
|
|
686
759
|
.tcw-kicker { color: var(--tcw-primary); font-size: 12px; font-weight: 900; }
|
|
687
|
-
.tcw-title-block h1 { margin: 0; font-size:
|
|
760
|
+
.tcw-title-block h1 { margin: 0; font-size: 20px; line-height: 1.22; }
|
|
688
761
|
.tcw-title-block p, .tcw-page-head p, .tcw-drawer-head p { margin: 0; color: var(--tcw-muted); font-size: 13px; }
|
|
689
762
|
.tcw-header-actions { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 8px; }
|
|
690
763
|
.tcw-search, .tcw-field input { width: 100%; min-height: 36px; border: 1px solid var(--tcw-border); border-radius: 6px; background: var(--tcw-card); color: var(--tcw-text); padding: 8px 10px; }
|
|
@@ -697,18 +770,28 @@ button, input { font: inherit; letter-spacing: 0; }
|
|
|
697
770
|
.tcw-upload { border-style: dashed; border-color: color-mix(in srgb, var(--tcw-primary) 42%, var(--tcw-border)); color: var(--tcw-primary); font-weight: 800; }
|
|
698
771
|
.tcw-upload input { display: none; }
|
|
699
772
|
.tcw-notice { border: 1px solid color-mix(in srgb, var(--tcw-primary) 24%, var(--tcw-border)); border-radius: 6px; background: color-mix(in srgb, var(--tcw-primary) 7%, var(--tcw-card)); padding: 10px 12px; color: var(--tcw-primary-dark); font-size: 13px; }
|
|
700
|
-
.tcw-tabs { display: flex; gap: 6px; overflow-x: auto; border-bottom: 1px solid var(--tcw-border); }
|
|
701
|
-
.tcw-tab { min-height:
|
|
773
|
+
.tcw-tabs { display: flex; gap: 6px; overflow-x: auto; border-bottom: 1px solid var(--tcw-border); padding: 0 2px; }
|
|
774
|
+
.tcw-tab { min-height: 34px; border: 0; border-bottom: 3px solid transparent; background: transparent; color: var(--tcw-muted); padding: 7px 12px; cursor: pointer; font-weight: 900; }
|
|
702
775
|
.tcw-tab.active { border-color: var(--tcw-primary); color: var(--tcw-primary); }
|
|
703
776
|
.tcw-main { display: grid; grid-template-columns: minmax(0, 1fr); gap: 12px; align-items: start; }
|
|
704
777
|
.tcw-main.with-drawer { grid-template-columns: minmax(0, 1fr) minmax(340px, 410px); }
|
|
705
778
|
.tcw-content, .tcw-drawer, .tcw-panel, .tcw-supplier-block { border: 1px solid var(--tcw-border); border-radius: 8px; background: var(--tcw-card); }
|
|
706
|
-
.tcw-content { min-width: 0; padding:
|
|
779
|
+
.tcw-content { min-width: 0; border: 0; background: transparent; padding: 0; }
|
|
707
780
|
.tcw-page { display: grid; gap: 12px; }
|
|
708
781
|
.tcw-page-head, .tcw-drawer-head, .tcw-supplier-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; }
|
|
709
782
|
.tcw-page-head h2, .tcw-drawer-head h2, .tcw-panel h3 { margin: 0 0 4px; font-size: 16px; line-height: 1.35; }
|
|
710
783
|
.tcw-button-row { display: flex; flex-wrap: wrap; gap: 8px; justify-content: flex-end; }
|
|
711
784
|
.tcw-panel { display: grid; gap: 10px; padding: 12px; }
|
|
785
|
+
.tcw-business-panel { display: grid; gap: 0; border: 1px solid var(--tcw-border); border-radius: 8px; background: var(--tcw-card); overflow: hidden; }
|
|
786
|
+
.tcw-business-head { display: flex; align-items: center; justify-content: space-between; gap: 14px; border-bottom: 1px solid var(--tcw-border); background: var(--tcw-card); padding: 14px 16px; }
|
|
787
|
+
.tcw-business-head h2 { margin: 0 0 4px; font-size: 18px; line-height: 1.25; }
|
|
788
|
+
.tcw-business-head p { margin: 0; color: var(--tcw-muted); font-size: 13px; }
|
|
789
|
+
.tcw-business-body { display: grid; gap: 12px; padding: 14px 16px 16px; }
|
|
790
|
+
.tcw-table-section { display: grid; gap: 0; }
|
|
791
|
+
.tcw-history-section { display: grid; gap: 10px; border-top: 1px solid var(--tcw-border); padding-top: 14px; }
|
|
792
|
+
.tcw-subsection-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
|
|
793
|
+
.tcw-subsection-head h3, .tcw-list-title h3 { margin: 0; font-size: 15px; line-height: 1.3; }
|
|
794
|
+
.tcw-subsection-head p, .tcw-list-title span { margin: 0; color: var(--tcw-muted); font-size: 12px; }
|
|
712
795
|
.tcw-metrics { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 10px; }
|
|
713
796
|
.tcw-metric { display: grid; gap: 4px; border: 1px solid var(--tcw-border); border-radius: 8px; background: var(--tcw-card); padding: 12px; }
|
|
714
797
|
.tcw-metric span { color: var(--tcw-muted); font-size: 12px; }
|
|
@@ -724,7 +807,9 @@ button, input { font: inherit; letter-spacing: 0; }
|
|
|
724
807
|
.tcw-table tr:last-child td { border-bottom: 0; }
|
|
725
808
|
.tcw-table tr.clickable { cursor: pointer; }
|
|
726
809
|
.tcw-table tr.clickable:hover td { background: color-mix(in srgb, var(--tcw-primary) 5%, transparent); }
|
|
727
|
-
.tcw-list-toolbar { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 10px; }
|
|
810
|
+
.tcw-list-toolbar { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 10px; margin-bottom: 10px; }
|
|
811
|
+
.tcw-list-title { display: grid; gap: 2px; min-width: 160px; }
|
|
812
|
+
.tcw-list-actions { display: flex; flex-wrap: wrap; align-items: center; justify-content: flex-end; gap: 8px; }
|
|
728
813
|
.tcw-filter { display: inline-flex; align-items: center; gap: 8px; color: var(--tcw-muted); font-size: 13px; font-weight: 800; }
|
|
729
814
|
.tcw-filter select { min-height: 34px; border: 1px solid var(--tcw-border); border-radius: 6px; background: var(--tcw-card); color: var(--tcw-text); padding: 6px 28px 6px 9px; }
|
|
730
815
|
.tcw-row-actions { display: flex; flex-wrap: wrap; gap: 6px; }
|
|
@@ -732,7 +817,7 @@ button, input { font: inherit; letter-spacing: 0; }
|
|
|
732
817
|
.tcw-mini-btn.danger { border-color: color-mix(in srgb, var(--tcw-danger) 35%, var(--tcw-border)); background: color-mix(in srgb, var(--tcw-danger) 6%, var(--tcw-card)); color: var(--tcw-danger); }
|
|
733
818
|
.tcw-mini-btn:disabled { cursor: not-allowed; opacity: .5; }
|
|
734
819
|
.row-selection-checkbox { width: 16px; height: 16px; }
|
|
735
|
-
.tcw-empty { display: grid; place-items: center; min-height:
|
|
820
|
+
.tcw-empty { display: grid; place-items: center; min-height: 86px; border: 1px dashed var(--tcw-border); border-radius: 8px; background: var(--tcw-soft); color: var(--tcw-muted); padding: 16px; text-align: center; }
|
|
736
821
|
.tcw-supplier-block { display: grid; gap: 10px; padding: 12px; }
|
|
737
822
|
.tcw-supplier-head strong { font-size: 15px; }
|
|
738
823
|
.tcw-supplier-head span { color: var(--tcw-muted); font-size: 12px; }
|
|
@@ -758,6 +843,8 @@ button, input { font: inherit; letter-spacing: 0; }
|
|
|
758
843
|
.tcw-status.muted { background: #eef2f6; color: var(--tcw-muted); }
|
|
759
844
|
@media (max-width: 1040px) {
|
|
760
845
|
.tcw-header, .tcw-main.with-drawer, .tcw-form { grid-template-columns: 1fr; }
|
|
846
|
+
.tcw-business-head { align-items: stretch; flex-direction: column; }
|
|
847
|
+
.tcw-button-row, .tcw-list-actions { justify-content: flex-start; }
|
|
761
848
|
.tcw-metrics { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
|
762
849
|
.tcw-drawer { position: static; max-height: none; }
|
|
763
850
|
}
|
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.8",
|
|
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",
|