@chenchaolong/plugin-trade-compliance-workbench 0.1.8 → 0.1.10

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.
@@ -269,7 +269,7 @@
269
269
  }
270
270
 
271
271
  return h('div', { className: 'tcw-shell' },
272
- h('header', { className: 'tcw-header' },
272
+ h('header', { className: 'tcw-header tcw-frame' },
273
273
  h('div', { className: 'tcw-title-block' },
274
274
  h('span', { className: 'tcw-kicker' }, '外贸合规工作台'),
275
275
  h('h1', null, pageTitle(activePage)),
@@ -281,13 +281,13 @@
281
281
  )
282
282
  ),
283
283
  notice ? h('div', { className: 'tcw-notice' }, notice) : null,
284
- h('nav', { className: 'tcw-tabs' }, [
284
+ h('nav', { className: 'tcw-tabs tcw-frame' }, [
285
285
  tab('overview-page', '工作台总览'),
286
286
  tab('controlled-goods-page', '管控商品'),
287
287
  tab('products-page', '供应商商品'),
288
288
  tab('workbooks-page', '销售发票')
289
289
  ]),
290
- h('main', { className: 'tcw-main' },
290
+ h('main', { className: 'tcw-main tcw-frame' },
291
291
  h('section', { className: 'tcw-content' },
292
292
  activePage === 'overview-page' ? renderOverviewPage() : null,
293
293
  activePage === 'controlled-goods-page' ? renderControlledGoodsPage() : null,
@@ -321,12 +321,14 @@
321
321
 
322
322
  function renderControlledGoodsPage() {
323
323
  return h('div', { className: 'tcw-page controlled-goods-page' },
324
- pageToolbar('管控商品', '智能体识别结果先进入待审核,确认后写入正式管控商品库。', [
324
+ businessPanel('管控商品', '上传管控目录后,识别结果在列表内完成审核、驳回和入库。', [
325
325
  uploadButton('upload_controlled_goods_file', '上传管控商品文件'),
326
326
  h('button', { className: 'tcw-btn tcw-btn-soft', onClick: () => setShowManualForm(!showManualForm) }, showManualForm ? '收起新增' : '手动新增')
327
- ]),
328
- showManualForm ? renderControlledGoodsForm() : null,
329
- renderListPanel('管控商品列表', 'pending-controlled-goods confirmed-controlled-goods', controlledReviews, ['商品名称', '海关编码', '关键词', '管控说明', '来源'], (item) => {
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) => {
330
332
  const row = readMerged(item)
331
333
  return [
332
334
  value(row.productName || item.title),
@@ -335,7 +337,9 @@
335
337
  value(row.controlNote),
336
338
  value(item.sourceLocation || row.sourceFileName)
337
339
  ]
338
- }, '暂无管控商品识别结果。上传目录并等待智能体调用工具后会显示在这里。')
340
+ }, '暂无管控商品识别结果。上传目录并等待智能体调用工具后会显示在这里。')
341
+ )
342
+ ])
339
343
  )
340
344
  }
341
345
 
@@ -458,7 +462,26 @@
458
462
  }
459
463
 
460
464
  function reviewTable(headers, rows, mapRow, emptyText) {
461
- return table(['', ...headers, '状态', '操作'], rows, (row) => [
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) => [
462
485
  h('input', {
463
486
  className: 'row-selection-checkbox',
464
487
  type: 'checkbox',
@@ -480,6 +503,21 @@
480
503
  setSelectedIds(checked ? Array.from(new Set([...selectedIds, id])) : selectedIds.filter((item) => item !== id))
481
504
  }
482
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
+
483
521
  function pageToolbar(title, subtitle, actions) {
484
522
  return h('div', { className: 'tcw-page-head' },
485
523
  h('div', null, h('h2', null, title), h('p', null, subtitle)),
@@ -523,7 +561,7 @@
523
561
  if (!rows || rows.length === 0) return empty(emptyText || '暂无数据。')
524
562
  return h('div', { className: 'tcw-table-wrap' },
525
563
  h('table', { className: 'tcw-table' },
526
- h('thead', null, h('tr', null, headers.map((item) => h('th', { key: item }, item)))),
564
+ h('thead', null, h('tr', null, headers.map((item, index) => h('th', { key: index }, item)))),
527
565
  h('tbody', null, rows.map((row, index) =>
528
566
  h('tr', { key: row.id || index, className: onRowClick ? 'clickable' : '', onClick: onRowClick ? () => onRowClick(row) : undefined },
529
567
  mapRow(row).map((cell, cellIndex) => h('td', { key: cellIndex }, cell))
@@ -753,8 +791,9 @@
753
791
  * { box-sizing: border-box; }
754
792
  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; }
755
793
  button, input { font: inherit; letter-spacing: 0; }
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; }
794
+ .tcw-shell { display: grid; grid-template-rows: auto auto auto 1fr; gap: 10px; min-height: 100vh; padding: 12px 8px 18px; }
795
+ .tcw-frame { width: 100%; max-width: none; margin: 0; justify-self: stretch; }
796
+ .tcw-header { display: grid; grid-template-columns: minmax(260px, 1fr) minmax(340px, 520px); gap: 12px; align-items: center; border: 1px solid var(--tcw-border); border-radius: 8px; background: var(--tcw-card); padding: 12px 14px; }
758
797
  .tcw-title-block { display: grid; gap: 3px; }
759
798
  .tcw-kicker { color: var(--tcw-primary); font-size: 12px; font-weight: 900; }
760
799
  .tcw-title-block h1 { margin: 0; font-size: 20px; line-height: 1.22; }
@@ -777,16 +816,16 @@ button, input { font: inherit; letter-spacing: 0; }
777
816
  .tcw-main.with-drawer { grid-template-columns: minmax(0, 1fr) minmax(340px, 410px); }
778
817
  .tcw-content, .tcw-drawer, .tcw-panel, .tcw-supplier-block { border: 1px solid var(--tcw-border); border-radius: 8px; background: var(--tcw-card); }
779
818
  .tcw-content { min-width: 0; border: 0; background: transparent; padding: 0; }
780
- .tcw-page { display: grid; gap: 12px; }
819
+ .tcw-page { display: grid; gap: 12px; width: 100%; }
781
820
  .tcw-page-head, .tcw-drawer-head, .tcw-supplier-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; }
782
821
  .tcw-page-head h2, .tcw-drawer-head h2, .tcw-panel h3 { margin: 0 0 4px; font-size: 16px; line-height: 1.35; }
783
822
  .tcw-button-row { display: flex; flex-wrap: wrap; gap: 8px; justify-content: flex-end; }
784
823
  .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; }
824
+ .tcw-business-panel { display: grid; width: 100%; gap: 0; border: 1px solid var(--tcw-border); border-radius: 8px; background: var(--tcw-card); overflow: hidden; }
825
+ .tcw-business-head { display: flex; min-height: 118px; align-items: flex-start; justify-content: space-between; gap: 14px; border-bottom: 1px solid var(--tcw-border); background: var(--tcw-card); padding: 22px 28px; }
787
826
  .tcw-business-head h2 { margin: 0 0 4px; font-size: 18px; line-height: 1.25; }
788
827
  .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; }
828
+ .tcw-business-body { display: grid; gap: 12px; padding: 20px 28px 24px; }
790
829
  .tcw-table-section { display: grid; gap: 0; }
791
830
  .tcw-history-section { display: grid; gap: 10px; border-top: 1px solid var(--tcw-border); padding-top: 14px; }
792
831
  .tcw-subsection-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chenchaolong/plugin-trade-compliance-workbench",
3
- "version": "0.1.8",
3
+ "version": "0.1.10",
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",