@chenchaolong/plugin-trade-compliance-workbench 0.1.48 → 0.1.50

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,8 @@
74
74
  const [busy, setBusy] = React.useState(false)
75
75
  const [query, setQuery] = React.useState('')
76
76
  const [pageByList, setPageByList] = React.useState({})
77
+ const [pageSizeByList, setPageSizeByList] = React.useState({})
78
+ const [pageJumpByList, setPageJumpByList] = React.useState({})
77
79
  const [detailItem, setDetailItem] = React.useState(null)
78
80
  const [formDialog, setFormDialog] = React.useState(null)
79
81
  const [deleteDialog, setDeleteDialog] = React.useState(null)
@@ -84,6 +86,7 @@
84
86
  loading: false,
85
87
  searched: false,
86
88
  error: '',
89
+ jumpPage: '',
87
90
  result: null
88
91
  })
89
92
  const pollingRef = React.useRef(null)
@@ -104,10 +107,11 @@
104
107
  }
105
108
  }, [])
106
109
 
107
- React.useEffect(reportResize, [data, activePage, selectedIds, statusFilter, overviewTab, busy, formDialog, deleteDialog, hsCodeSearch, hsCodeDetailDialog])
110
+ React.useEffect(reportResize, [data, activePage, selectedIds, statusFilter, overviewTab, pageByList, pageSizeByList, busy, formDialog, deleteDialog, hsCodeSearch, hsCodeDetailDialog])
108
111
 
109
112
  const reviewItems = data.reviewItems.filter((item) => !isPlaceholderReviewItem(item))
110
113
  const pendingItems = reviewItems.filter((item) => (item.reviewStatus || 'pending') === 'pending')
114
+ const filteredPendingItems = filterItems(pendingItems, query, overviewSearchKeys)
111
115
  const controlledReviewRows = reviewItems.filter((item) => item.type === 'controlled_goods' && item.reviewStatus !== 'confirmed')
112
116
  const controlledRows = controlledReviewRows.concat(data.controlledGoods.map(toControlledGoodsReviewRow))
113
117
  const controlledReviews = applyStatusFilter(filterItems(controlledRows, query, reviewSearchKeys), statusFilter)
@@ -223,6 +227,7 @@
223
227
  setFormDialog((current) => current ? Object.assign({}, current, {
224
228
  hsCandidateKeyword: keyword,
225
229
  hsCandidatePage: nextPage,
230
+ hsCandidateLocalPage: 1,
226
231
  hsCandidateLoading: false,
227
232
  hsCandidateError: '',
228
233
  hsCandidateStatus: candidates.length ? 'pending_confirmation' : 'not_found',
@@ -471,6 +476,10 @@
471
476
  hsCandidates: Array.isArray(merged.hsCodeCandidates) ? merged.hsCodeCandidates : [],
472
477
  hsCandidateKeyword: merged.hsCodeLookupKeyword || buildSupplierHsCandidateKeyword(merged),
473
478
  hsCandidatePage: 1,
479
+ hsCandidateJumpPage: '',
480
+ hsCandidateLocalPage: 1,
481
+ hsCandidateLocalPageSize: 5,
482
+ hsCandidateLocalJumpPage: '',
474
483
  hsCandidatePagination: null,
475
484
  hsCandidateStatus: merged.hsCodeLookupStatus || '',
476
485
  hsCandidateError: merged.hsCodeLookupError || '',
@@ -487,6 +496,10 @@
487
496
  hsCandidates: [],
488
497
  hsCandidateKeyword: '',
489
498
  hsCandidatePage: 1,
499
+ hsCandidateJumpPage: '',
500
+ hsCandidateLocalPage: 1,
501
+ hsCandidateLocalPageSize: 5,
502
+ hsCandidateLocalJumpPage: '',
490
503
  hsCandidatePagination: null,
491
504
  hsCandidateStatus: '',
492
505
  hsCandidateError: '',
@@ -672,15 +685,20 @@
672
685
  }
673
686
 
674
687
  function renderOverviewPage() {
675
- const pendingOverviewItems = overviewTab === 'all'
676
- ? pendingItems.slice(0, 12)
677
- : pendingItems.filter((item) => item.type === overviewTab).slice(0, 12)
688
+ const filteredPendingControlledItems = filteredPendingItems.filter((item) => item.type === 'controlled_goods')
689
+ const filteredPendingSupplierItems = filteredPendingItems.filter((item) => item.type === 'supplier_product')
690
+ const filteredPendingSalesItems = filteredPendingItems.filter((item) => item.type === 'customs_workbook')
691
+ const overviewRows = overviewTab === 'all'
692
+ ? filteredPendingItems
693
+ : filteredPendingItems.filter((item) => item.type === overviewTab)
694
+ const overviewListKey = `overview-${overviewTab}`
695
+ const overviewPage = paginateRows(overviewListKey, overviewRows)
678
696
  return h('div', { className: 'tcw-page overview-page' },
679
697
  h('section', { className: 'tcw-metrics' },
680
- metric('待审核', pendingItems.length, 'dashboard', 'blue'),
681
- metric('管控识别待审', pendingControlledReviews.length, 'shield', 'green'),
682
- metric('供应商商品待审', pendingSupplierReviews.length, 'box', 'orange'),
683
- metric('销售合同待审', pendingSalesReviews.length, 'invoice', 'violet'),
698
+ metric('待审核', filteredPendingItems.length, 'dashboard', 'blue'),
699
+ metric('管控识别待审', filteredPendingControlledItems.length, 'shield', 'green'),
700
+ metric('供应商商品待审', filteredPendingSupplierItems.length, 'box', 'orange'),
701
+ metric('销售合同待审', filteredPendingSalesItems.length, 'invoice', 'violet'),
684
702
  metric('已入库管控商品', data.controlledGoods.length, 'archive', 'slate'),
685
703
  metric('已入库供应商商品', data.products.length, 'supplier', 'green'),
686
704
  metric('销售发票历史', data.workbookGenerations.length, 'document', 'violet'),
@@ -689,14 +707,15 @@
689
707
  panel('待办事项明细',
690
708
  h('div', { className: 'tcw-overview-panel' },
691
709
  h('div', { className: 'tcw-overview-tabs' }, [
692
- overviewTabButton('all', '全部', pendingItems.length),
693
- overviewTabButton('controlled_goods', '管控商品', pendingItems.filter((item) => item.type === 'controlled_goods').length),
694
- overviewTabButton('supplier_product', '供应商商品', pendingItems.filter((item) => item.type === 'supplier_product').length),
695
- overviewTabButton('customs_workbook', '销售发票', pendingItems.filter((item) => item.type === 'customs_workbook').length)
710
+ overviewTabButton('all', '全部', filteredPendingItems.length),
711
+ overviewTabButton('controlled_goods', '管控商品', filteredPendingControlledItems.length),
712
+ overviewTabButton('supplier_product', '供应商商品', filteredPendingSupplierItems.length),
713
+ overviewTabButton('customs_workbook', '销售发票', filteredPendingSalesItems.length)
696
714
  ]),
697
- pendingOverviewItems.length
698
- ? compactReviewList(pendingOverviewItems)
699
- : empty('暂无待审核记录。智能体识别结果会先出现在这里。')
715
+ overviewPage.rows.length
716
+ ? compactReviewList(overviewPage.rows)
717
+ : empty('暂无待审核记录。智能体识别结果会先出现在这里。'),
718
+ pagination(overviewListKey, overviewRows.length)
700
719
  )
701
720
  )
702
721
  )
@@ -892,20 +911,26 @@
892
911
  function renderHsCodePagination(pagination) {
893
912
  if (!pagination || (!pagination.hasPrevious && !pagination.hasNext && (!pagination.pages || pagination.pages.length <= 1))) return null
894
913
  const pages = Array.isArray(pagination.pages) ? pagination.pages.filter((item) => item.page) : []
895
- const visiblePages = pages.filter((item, index, arr) => arr.findIndex((candidate) => candidate.page === item.page) === index).slice(0, 10)
896
- return h('div', { className: 'tcw-pagination hs-code-pagination' },
897
- h('span', null, `第 ${pagination.currentPage || hsCodeSearch.page} 页`),
898
- h('div', { className: 'tcw-page-buttons' },
899
- h('button', { className: 'tcw-mini-btn', disabled: hsCodeSearch.loading || !pagination.hasPrevious, onClick: () => searchHsCodePage((pagination.currentPage || hsCodeSearch.page) - 1) }, '上一页'),
900
- visiblePages.map((item) => h('button', {
901
- key: item.page,
902
- className: item.current ? 'tcw-mini-btn active' : 'tcw-mini-btn',
903
- disabled: hsCodeSearch.loading || item.current,
904
- onClick: () => searchHsCodePage(item.page)
905
- }, String(item.page))),
906
- h('button', { className: 'tcw-mini-btn', disabled: hsCodeSearch.loading || !pagination.hasNext, onClick: () => searchHsCodePage((pagination.currentPage || hsCodeSearch.page) + 1) }, '下一页')
907
- )
908
- )
914
+ const maxPage = Number(pagination.maxVisiblePage) || Math.max(...pages.map((item) => Number(item.page) || 0), pagination.currentPage || hsCodeSearch.page || 1)
915
+ const currentPage = pagination.currentPage || hsCodeSearch.page || 1
916
+ return renderUnifiedPagination({
917
+ page: currentPage,
918
+ totalPages: Math.max(1, maxPage),
919
+ pageSize: 10,
920
+ fixedPageSize: true,
921
+ loading: hsCodeSearch.loading,
922
+ hasPrevious: pagination.hasPrevious,
923
+ hasNext: pagination.hasNext,
924
+ jumpValue: hsCodeSearch.jumpPage || '',
925
+ onPageChange: searchHsCodePage,
926
+ onJumpInput: (nextValue) => setHsCodeSearch(Object.assign({}, hsCodeSearch, { jumpPage: nextValue })),
927
+ onJump: () => {
928
+ const nextPage = Number(hsCodeSearch.jumpPage)
929
+ if (!Number.isFinite(nextPage) || nextPage <= 0) return
930
+ searchHsCodePage(nextPage)
931
+ setHsCodeSearch((current) => Object.assign({}, current, { jumpPage: '' }))
932
+ }
933
+ })
909
934
  }
910
935
 
911
936
  function compactReviewList(items) {
@@ -1050,7 +1075,7 @@
1050
1075
  }
1051
1076
 
1052
1077
  function paginateRows(listKey, rows) {
1053
- const pageSize = 10
1078
+ const pageSize = getPageSize(listKey)
1054
1079
  const total = rows.length
1055
1080
  const totalPages = Math.max(1, Math.ceil(total / pageSize))
1056
1081
  const current = Math.min(Math.max(pageByList[listKey] || 1, 1), totalPages)
@@ -1058,15 +1083,44 @@
1058
1083
  }
1059
1084
 
1060
1085
  function pagination(listKey, total) {
1061
- if (total <= 10) return null
1086
+ if (total <= 0) return null
1062
1087
  const page = paginateRows(listKey, Array.from({ length: total }))
1063
- return h('div', { className: 'tcw-pagination' },
1064
- h('span', null, `共 ${total} 条,第 ${page.page} / ${page.totalPages} 页`),
1065
- h('div', { className: 'tcw-page-buttons' },
1066
- h('button', { className: 'tcw-mini-btn', disabled: page.page <= 1, onClick: () => setPageByList(Object.assign({}, pageByList, { [listKey]: page.page - 1 })) }, '上一页'),
1067
- h('button', { className: 'tcw-mini-btn', disabled: page.page >= page.totalPages, onClick: () => setPageByList(Object.assign({}, pageByList, { [listKey]: page.page + 1 })) }, '下一页')
1068
- )
1069
- )
1088
+ return renderUnifiedPagination({
1089
+ total,
1090
+ page: page.page,
1091
+ totalPages: page.totalPages,
1092
+ pageSize: page.pageSize,
1093
+ pageSizeOptions: [10, 20, 50],
1094
+ jumpValue: pageJumpByList[listKey] || '',
1095
+ onPageChange: (nextPage) => setLocalPage(listKey, nextPage, page.totalPages),
1096
+ onPageSizeChange: (nextSize) => setLocalPageSize(listKey, nextSize),
1097
+ onJumpInput: (nextValue) => setPageJumpByList(Object.assign({}, pageJumpByList, { [listKey]: nextValue })),
1098
+ onJump: () => jumpLocalPage(listKey, page.totalPages)
1099
+ })
1100
+ }
1101
+
1102
+ function getPageSize(listKey) {
1103
+ const value = Number(pageSizeByList[listKey])
1104
+ return Number.isFinite(value) && value > 0 ? value : 10
1105
+ }
1106
+
1107
+ function setLocalPage(listKey, nextPage, totalPages) {
1108
+ const page = Math.min(Math.max(Number(nextPage) || 1, 1), totalPages || 1)
1109
+ setPageByList(Object.assign({}, pageByList, { [listKey]: page }))
1110
+ }
1111
+
1112
+ function setLocalPageSize(listKey, nextSize) {
1113
+ const pageSize = Number(nextSize) || 10
1114
+ setPageSizeByList(Object.assign({}, pageSizeByList, { [listKey]: pageSize }))
1115
+ setPageByList(Object.assign({}, pageByList, { [listKey]: 1 }))
1116
+ setPageJumpByList(Object.assign({}, pageJumpByList, { [listKey]: '' }))
1117
+ }
1118
+
1119
+ function jumpLocalPage(listKey, totalPages) {
1120
+ const value = Number(pageJumpByList[listKey])
1121
+ if (!Number.isFinite(value) || value <= 0) return
1122
+ setLocalPage(listKey, value, totalPages)
1123
+ setPageJumpByList(Object.assign({}, pageJumpByList, { [listKey]: '' }))
1070
1124
  }
1071
1125
 
1072
1126
  function renderDetailModal() {
@@ -1128,6 +1182,13 @@
1128
1182
  function renderSupplierHsCandidatePanel() {
1129
1183
  const candidates = Array.isArray(formDialog.hsCandidates) ? formDialog.hsCandidates : []
1130
1184
  const pagination = formDialog.hsCandidatePagination || null
1185
+ const hasSourcePagination = pagination && (pagination.hasPrevious || pagination.hasNext || (pagination.pages && pagination.pages.length > 1))
1186
+ const localPageSize = Math.max(1, Number(formDialog.hsCandidateLocalPageSize) || 5)
1187
+ const localTotalPages = Math.max(1, Math.ceil(candidates.length / localPageSize))
1188
+ const localPage = Math.min(Math.max(Number(formDialog.hsCandidateLocalPage) || 1, 1), localTotalPages)
1189
+ const visibleCandidates = hasSourcePagination
1190
+ ? candidates
1191
+ : candidates.slice((localPage - 1) * localPageSize, localPage * localPageSize)
1131
1192
  return h('section', { className: 'tcw-hs-candidate-panel' },
1132
1193
  h('div', { className: 'tcw-subsection-head' },
1133
1194
  h('div', null,
@@ -1146,7 +1207,7 @@
1146
1207
  ),
1147
1208
  formDialog.hsCandidateError ? h('div', { className: 'tcw-error' }, formDialog.hsCandidateError) : null,
1148
1209
  candidates.length ? h('div', { className: 'tcw-hs-candidate-result' },
1149
- table(['编码', '商品名称', '英文名称', '单位', '退税率', '监管', '检验检疫', '操作'], candidates, (candidate) => [
1210
+ table(['编码', '商品名称', '英文名称', '单位', '退税率', '监管', '检验检疫', '操作'], visibleCandidates, (candidate) => [
1150
1211
  h('strong', { className: 'tcw-hs-code-text' }, value(candidate.code)),
1151
1212
  value(candidate.name),
1152
1213
  value(candidate.englishName),
@@ -1159,28 +1220,100 @@
1159
1220
  h('button', { type: 'button', className: 'tcw-mini-btn', disabled: !candidate.code && !candidate.detailUrl, onClick: () => loadHsCodeDetail(candidate) }, '详情')
1160
1221
  )
1161
1222
  ], '暂无候选编码。'),
1162
- renderSupplierHsCandidatePagination(pagination)
1223
+ hasSourcePagination
1224
+ ? renderSupplierHsCandidatePagination(pagination)
1225
+ : renderSupplierHsCandidateLocalPagination(candidates.length, localPage, localTotalPages, localPageSize)
1163
1226
  ) : empty('暂无候选编码,可调整关键词后查询。')
1164
1227
  )
1165
1228
  }
1166
1229
 
1230
+ function renderSupplierHsCandidateLocalPagination(total, page, totalPages, pageSize) {
1231
+ if (total <= pageSize) return null
1232
+ return renderUnifiedPagination({
1233
+ total,
1234
+ page,
1235
+ totalPages,
1236
+ pageSize,
1237
+ pageSizeOptions: [5, 10, 20],
1238
+ jumpValue: formDialog.hsCandidateLocalJumpPage || '',
1239
+ onPageChange: (nextPage) => setFormDialog(Object.assign({}, formDialog, { hsCandidateLocalPage: Math.min(Math.max(Number(nextPage) || 1, 1), totalPages) })),
1240
+ onPageSizeChange: (nextSize) => setFormDialog(Object.assign({}, formDialog, { hsCandidateLocalPageSize: Number(nextSize) || 5, hsCandidateLocalPage: 1, hsCandidateLocalJumpPage: '' })),
1241
+ onJumpInput: (nextValue) => setFormDialog(Object.assign({}, formDialog, { hsCandidateLocalJumpPage: nextValue })),
1242
+ onJump: () => {
1243
+ const nextPage = Number(formDialog.hsCandidateLocalJumpPage)
1244
+ if (!Number.isFinite(nextPage) || nextPage <= 0) return
1245
+ setFormDialog(Object.assign({}, formDialog, {
1246
+ hsCandidateLocalPage: Math.min(Math.max(nextPage, 1), totalPages),
1247
+ hsCandidateLocalJumpPage: ''
1248
+ }))
1249
+ }
1250
+ })
1251
+ }
1252
+
1167
1253
  function renderSupplierHsCandidatePagination(pagination) {
1168
1254
  if (!pagination || (!pagination.hasPrevious && !pagination.hasNext && (!pagination.pages || pagination.pages.length <= 1))) return null
1169
1255
  const pages = Array.isArray(pagination.pages) ? pagination.pages.filter((item) => item.page) : []
1170
- const visiblePages = pages.filter((item, index, arr) => arr.findIndex((candidate) => candidate.page === item.page) === index).slice(0, 10)
1171
1256
  const currentPage = pagination.currentPage || formDialog.hsCandidatePage || 1
1172
- return h('div', { className: 'tcw-pagination hs-code-pagination' },
1173
- h('span', null, `第 ${currentPage} 页`),
1174
- h('div', { className: 'tcw-page-buttons' },
1175
- h('button', { className: 'tcw-mini-btn', disabled: formDialog.hsCandidateLoading || !pagination.hasPrevious, onClick: () => searchSupplierHsCandidatesForForm(currentPage - 1) }, '上一页'),
1176
- visiblePages.map((item) => h('button', {
1177
- key: item.page,
1178
- className: item.current ? 'tcw-mini-btn active' : 'tcw-mini-btn',
1179
- disabled: formDialog.hsCandidateLoading || item.current,
1180
- onClick: () => searchSupplierHsCandidatesForForm(item.page)
1181
- }, String(item.page))),
1182
- h('button', { className: 'tcw-mini-btn', disabled: formDialog.hsCandidateLoading || !pagination.hasNext, onClick: () => searchSupplierHsCandidatesForForm(currentPage + 1) }, '下一页')
1183
- )
1257
+ const maxPage = Number(pagination.maxVisiblePage) || Math.max(...pages.map((item) => Number(item.page) || 0), currentPage)
1258
+ return renderUnifiedPagination({
1259
+ page: currentPage,
1260
+ totalPages: Math.max(1, maxPage),
1261
+ pageSize: 10,
1262
+ fixedPageSize: true,
1263
+ loading: formDialog.hsCandidateLoading,
1264
+ hasPrevious: pagination.hasPrevious,
1265
+ hasNext: pagination.hasNext,
1266
+ jumpValue: formDialog.hsCandidateJumpPage || '',
1267
+ onPageChange: searchSupplierHsCandidatesForForm,
1268
+ onJumpInput: (nextValue) => setFormDialog(Object.assign({}, formDialog, { hsCandidateJumpPage: nextValue })),
1269
+ onJump: () => {
1270
+ const nextPage = Number(formDialog.hsCandidateJumpPage)
1271
+ if (!Number.isFinite(nextPage) || nextPage <= 0) return
1272
+ searchSupplierHsCandidatesForForm(nextPage)
1273
+ setFormDialog((current) => current ? Object.assign({}, current, { hsCandidateJumpPage: '' }) : current)
1274
+ }
1275
+ })
1276
+ }
1277
+
1278
+ function renderUnifiedPagination(config) {
1279
+ const totalPages = Math.max(1, Number(config.totalPages) || 1)
1280
+ const page = Math.min(Math.max(Number(config.page) || 1, 1), totalPages)
1281
+ const canPrevious = config.hasPrevious === undefined ? page > 1 : Boolean(config.hasPrevious)
1282
+ const canNext = config.hasNext === undefined ? page < totalPages : Boolean(config.hasNext)
1283
+ const loading = Boolean(config.loading)
1284
+ const jumpValue = config.jumpValue || ''
1285
+ const pageSize = Number(config.pageSize) || 10
1286
+ return h('div', { className: 'tcw-pagination tcw-pagination-unified' },
1287
+ config.total == null ? null : h('span', { className: 'tcw-pagination-total' }, `共 ${config.total} 条`),
1288
+ h('button', { className: 'tcw-page-btn', disabled: loading || !canPrevious, onClick: () => config.onPageChange(1) }, '首页'),
1289
+ h('button', { className: 'tcw-page-btn', disabled: loading || !canPrevious, onClick: () => config.onPageChange(page - 1) }, '上一页'),
1290
+ h('span', { className: 'tcw-page-current' }, `第 ${page} / ${totalPages} 页`),
1291
+ h('button', { className: 'tcw-page-btn', disabled: loading || !canNext, onClick: () => config.onPageChange(page + 1) }, '下一页'),
1292
+ h('button', { className: 'tcw-page-btn', disabled: loading || !canNext, onClick: () => config.onPageChange(totalPages) }, '末页'),
1293
+ h('span', { className: 'tcw-page-size-label' }, '每页'),
1294
+ config.fixedPageSize
1295
+ ? h('span', { className: 'tcw-page-size-fixed' }, `${pageSize} 条/页`)
1296
+ : h('select', {
1297
+ className: 'tcw-page-size-select',
1298
+ value: pageSize,
1299
+ onChange: (event) => config.onPageSizeChange(Number(event.target.value))
1300
+ }, (config.pageSizeOptions || [10, 20, 50]).map((size) => h('option', { key: size, value: size }, `${size} 条/页`))),
1301
+ h('input', {
1302
+ className: 'tcw-page-jump-input',
1303
+ type: 'number',
1304
+ min: 1,
1305
+ max: totalPages,
1306
+ value: jumpValue,
1307
+ placeholder: String(page),
1308
+ onChange: (event) => config.onJumpInput(event.target.value),
1309
+ onKeyDown: (event) => {
1310
+ if (event.key === 'Enter') {
1311
+ event.preventDefault()
1312
+ config.onJump()
1313
+ }
1314
+ }
1315
+ }),
1316
+ h('button', { className: 'tcw-page-btn', disabled: loading, onClick: config.onJump }, '跳转')
1184
1317
  )
1185
1318
  }
1186
1319
 
@@ -1372,6 +1505,27 @@
1372
1505
  }
1373
1506
 
1374
1507
  const reviewSearchKeys = ['title', 'sourceLocation']
1508
+ const overviewSearchKeys = [
1509
+ 'title',
1510
+ 'sourceLocation',
1511
+ 'sourceFileName',
1512
+ 'fileName',
1513
+ 'supplierName',
1514
+ 'supplierCreditCode',
1515
+ 'productName',
1516
+ 'model',
1517
+ 'description',
1518
+ 'hsCode',
1519
+ 'contractHsCode',
1520
+ 'enrichedHsCode',
1521
+ 'englishName',
1522
+ 'invoiceNo',
1523
+ 'contractNo',
1524
+ 'buyerName',
1525
+ 'sellerName',
1526
+ 'controlNote',
1527
+ 'keywords'
1528
+ ]
1375
1529
  const controlledGoodsFormFields = [
1376
1530
  { key: 'productName', label: '商品名称', placeholder: '例如:高性能服务器' },
1377
1531
  { key: 'hsCode', label: '海关编码', placeholder: '例如:8471501010' },
@@ -1977,8 +2131,74 @@ button, input, textarea, select { font: inherit; letter-spacing: 0; }
1977
2131
  .tcw-error { border: 1px solid color-mix(in srgb, var(--tcw-danger) 35%, var(--tcw-border)); border-radius: 8px; background: color-mix(in srgb, var(--tcw-danger) 6%, var(--tcw-card)); color: var(--tcw-danger); padding: 10px 12px; font-size: 13px; font-weight: 800; }
1978
2132
  .row-selection-checkbox { width: 16px; height: 16px; }
1979
2133
  .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; }
1980
- .tcw-pagination { display: flex; align-items: center; justify-content: space-between; gap: 10px; border: 1px solid var(--tcw-border); border-top: 0; border-radius: 0 0 8px 8px; background: var(--tcw-card); padding: 10px 12px; color: var(--tcw-muted); font-size: 12px; }
2134
+ .tcw-pagination {
2135
+ display: flex;
2136
+ flex-wrap: wrap;
2137
+ align-items: center;
2138
+ gap: 6px;
2139
+ border: 1px solid var(--tcw-border);
2140
+ border-radius: 8px;
2141
+ background: var(--tcw-card);
2142
+ padding: 8px 10px;
2143
+ color: var(--tcw-muted);
2144
+ font-size: 12px;
2145
+ }
2146
+ .tcw-pagination-unified { justify-content: flex-start; }
1981
2147
  .tcw-page-buttons { display: flex; gap: 8px; }
2148
+ .tcw-pagination-total {
2149
+ display: inline-flex;
2150
+ align-items: center;
2151
+ min-height: 28px;
2152
+ color: var(--tcw-primary);
2153
+ }
2154
+ .tcw-page-btn {
2155
+ display: inline-flex;
2156
+ align-items: center;
2157
+ justify-content: center;
2158
+ min-width: 48px;
2159
+ min-height: 28px;
2160
+ border: 1px solid var(--tcw-border);
2161
+ border-radius: 6px;
2162
+ background: #fff;
2163
+ color: var(--tcw-primary);
2164
+ padding: 4px 9px;
2165
+ cursor: pointer;
2166
+ font-size: 12px;
2167
+ font-weight: 700;
2168
+ }
2169
+ .tcw-page-btn:disabled {
2170
+ cursor: not-allowed;
2171
+ background: var(--tcw-soft);
2172
+ color: #9aa7b8;
2173
+ }
2174
+ .tcw-page-current {
2175
+ display: inline-flex;
2176
+ align-items: center;
2177
+ min-height: 28px;
2178
+ border-radius: 6px;
2179
+ background: var(--tcw-primary);
2180
+ color: #fff;
2181
+ padding: 4px 12px;
2182
+ font-weight: 800;
2183
+ }
2184
+ .tcw-page-size-label {
2185
+ color: var(--tcw-primary);
2186
+ font-weight: 800;
2187
+ }
2188
+ .tcw-page-size-select,
2189
+ .tcw-page-jump-input,
2190
+ .tcw-page-size-fixed {
2191
+ min-height: 28px;
2192
+ border: 1px solid var(--tcw-border);
2193
+ border-radius: 6px;
2194
+ background: #fff;
2195
+ color: var(--tcw-text);
2196
+ padding: 4px 8px;
2197
+ font-size: 12px;
2198
+ }
2199
+ .tcw-page-size-select { min-width: 92px; }
2200
+ .tcw-page-size-fixed { display: inline-flex; align-items: center; }
2201
+ .tcw-page-jump-input { width: 56px; }
1982
2202
  .tcw-modal-backdrop { position: fixed; inset: 0; z-index: 10; display: grid; place-items: center; background: rgba(15, 23, 42, .32); padding: 20px; }
1983
2203
  .tcw-modal { display: grid; gap: 12px; width: min(900px, 100%); max-height: calc(100vh - 48px); overflow: auto; border: 1px solid var(--tcw-border); border-radius: 8px; background: var(--tcw-card); padding: 16px; box-shadow: 0 18px 48px rgba(15, 23, 42, .18); }
1984
2204
  .tcw-form-modal { width: min(960px, 100%); }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chenchaolong/plugin-trade-compliance-workbench",
3
- "version": "0.1.48",
3
+ "version": "0.1.50",
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",