@chenchaolong/plugin-trade-compliance-workbench 1.0.6 → 1.0.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.
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +20 -1
- package/dist/index.js.map +1 -1
- package/dist/lib/adapters/hsbianma.provider.d.ts +6 -8
- package/dist/lib/adapters/hsbianma.provider.d.ts.map +1 -1
- package/dist/lib/adapters/hsbianma.provider.js +159 -53
- package/dist/lib/adapters/hsbianma.provider.js.map +1 -1
- package/dist/lib/adapters/translation.provider.d.ts +7 -1
- package/dist/lib/adapters/translation.provider.d.ts.map +1 -1
- package/dist/lib/adapters/translation.provider.js +15 -5
- package/dist/lib/adapters/translation.provider.js.map +1 -1
- package/dist/lib/domain/catalog-localization.d.ts +14 -0
- package/dist/lib/domain/catalog-localization.d.ts.map +1 -0
- package/dist/lib/domain/catalog-localization.js +19 -0
- package/dist/lib/domain/catalog-localization.js.map +1 -0
- package/dist/lib/domain/file-name.d.ts +8 -0
- package/dist/lib/domain/file-name.d.ts.map +1 -0
- package/dist/lib/domain/file-name.js +111 -0
- package/dist/lib/domain/file-name.js.map +1 -0
- package/dist/lib/domain/import-issues.d.ts +4 -0
- package/dist/lib/domain/import-issues.d.ts.map +1 -1
- package/dist/lib/domain/import-issues.js +14 -3
- package/dist/lib/domain/import-issues.js.map +1 -1
- package/dist/lib/entities/catalog.entity.d.ts +5 -1
- package/dist/lib/entities/catalog.entity.d.ts.map +1 -1
- package/dist/lib/entities/catalog.entity.js +16 -0
- package/dist/lib/entities/catalog.entity.js.map +1 -1
- package/dist/lib/entities/customer.entity.d.ts +3 -0
- package/dist/lib/entities/customer.entity.d.ts.map +1 -1
- package/dist/lib/entities/customer.entity.js +12 -0
- package/dist/lib/entities/customer.entity.js.map +1 -1
- package/dist/lib/entities/shared.entity.d.ts +9 -0
- package/dist/lib/entities/shared.entity.d.ts.map +1 -1
- package/dist/lib/entities/shared.entity.js +33 -0
- package/dist/lib/entities/shared.entity.js.map +1 -1
- package/dist/lib/import-jobs/agent-handoff.dispatcher.d.ts +15 -0
- package/dist/lib/import-jobs/agent-handoff.dispatcher.d.ts.map +1 -0
- package/dist/lib/import-jobs/agent-handoff.dispatcher.js +114 -0
- package/dist/lib/import-jobs/agent-handoff.dispatcher.js.map +1 -0
- package/dist/lib/import-jobs/contracts.d.ts +76 -0
- package/dist/lib/import-jobs/contracts.d.ts.map +1 -0
- package/dist/lib/import-jobs/contracts.js +31 -0
- package/dist/lib/import-jobs/contracts.js.map +1 -0
- package/dist/lib/import-jobs/import-command.builder.d.ts +30 -0
- package/dist/lib/import-jobs/import-command.builder.d.ts.map +1 -0
- package/dist/lib/import-jobs/import-command.builder.js +97 -0
- package/dist/lib/import-jobs/import-command.builder.js.map +1 -0
- package/dist/lib/import-jobs/import-job-callback.processor.d.ts +15 -0
- package/dist/lib/import-jobs/import-job-callback.processor.d.ts.map +1 -0
- package/dist/lib/import-jobs/import-job-callback.processor.js +107 -0
- package/dist/lib/import-jobs/import-job-callback.processor.js.map +1 -0
- package/dist/lib/import-jobs/import-job.coordinator.d.ts +17 -0
- package/dist/lib/import-jobs/import-job.coordinator.d.ts.map +1 -0
- package/dist/lib/import-jobs/import-job.coordinator.js +256 -0
- package/dist/lib/import-jobs/import-job.coordinator.js.map +1 -0
- package/dist/lib/import-jobs/import-job.processor.d.ts +24 -0
- package/dist/lib/import-jobs/import-job.processor.d.ts.map +1 -0
- package/dist/lib/import-jobs/import-job.processor.js +213 -0
- package/dist/lib/import-jobs/import-job.processor.js.map +1 -0
- package/dist/lib/import-jobs/import-timeout.processor.d.ts +12 -0
- package/dist/lib/import-jobs/import-timeout.processor.d.ts.map +1 -0
- package/dist/lib/import-jobs/import-timeout.processor.js +37 -0
- package/dist/lib/import-jobs/import-timeout.processor.js.map +1 -0
- package/dist/lib/import-jobs/organization-lease.d.ts +39 -0
- package/dist/lib/import-jobs/organization-lease.d.ts.map +1 -0
- package/dist/lib/import-jobs/organization-lease.js +136 -0
- package/dist/lib/import-jobs/organization-lease.js.map +1 -0
- package/dist/lib/plugin-context.token.d.ts +4 -0
- package/dist/lib/plugin-context.token.d.ts.map +1 -0
- package/dist/lib/plugin-context.token.js +2 -0
- package/dist/lib/plugin-context.token.js.map +1 -0
- package/dist/lib/public-dto.d.ts +6 -0
- package/dist/lib/public-dto.d.ts.map +1 -0
- package/dist/lib/public-dto.js +33 -0
- package/dist/lib/public-dto.js.map +1 -0
- package/dist/lib/remote-components/trade-compliance-workbench/app.css +1 -1
- package/dist/lib/remote-components/trade-compliance-workbench/app.js +5 -5
- package/dist/lib/remote-ui/action-execution.d.ts +26 -0
- package/dist/lib/remote-ui/action-execution.d.ts.map +1 -0
- package/dist/lib/remote-ui/action-execution.js +85 -0
- package/dist/lib/remote-ui/action-execution.js.map +1 -0
- package/dist/lib/remote-ui/analytics-contracts.d.ts +3 -2
- package/dist/lib/remote-ui/analytics-contracts.d.ts.map +1 -1
- package/dist/lib/remote-ui/analytics-contracts.js.map +1 -1
- package/dist/lib/remote-ui/app-source.d.ts.map +1 -1
- package/dist/lib/remote-ui/app-source.js +462 -176
- package/dist/lib/remote-ui/app-source.js.map +1 -1
- package/dist/lib/remote-ui/company-logo-lifecycle.d.ts +14 -0
- package/dist/lib/remote-ui/company-logo-lifecycle.d.ts.map +1 -0
- package/dist/lib/remote-ui/company-logo-lifecycle.js +52 -0
- package/dist/lib/remote-ui/company-logo-lifecycle.js.map +1 -0
- package/dist/lib/remote-ui/components/data-grid.d.ts +9 -4
- package/dist/lib/remote-ui/components/data-grid.d.ts.map +1 -1
- package/dist/lib/remote-ui/components/data-grid.js +41 -9
- package/dist/lib/remote-ui/components/data-grid.js.map +1 -1
- package/dist/lib/remote-ui/components/forms.d.ts +6 -1
- package/dist/lib/remote-ui/components/forms.d.ts.map +1 -1
- package/dist/lib/remote-ui/components/forms.js +6 -1
- package/dist/lib/remote-ui/components/forms.js.map +1 -1
- package/dist/lib/remote-ui/components/layout.d.ts.map +1 -1
- package/dist/lib/remote-ui/components/layout.js +4 -3
- package/dist/lib/remote-ui/components/layout.js.map +1 -1
- package/dist/lib/remote-ui/contract-import.d.ts +37 -32
- package/dist/lib/remote-ui/contract-import.d.ts.map +1 -1
- package/dist/lib/remote-ui/contract-import.js +51 -59
- package/dist/lib/remote-ui/contract-import.js.map +1 -1
- package/dist/lib/remote-ui/contracts.d.ts +20 -27
- package/dist/lib/remote-ui/contracts.d.ts.map +1 -1
- package/dist/lib/remote-ui/contracts.js +8 -0
- package/dist/lib/remote-ui/contracts.js.map +1 -1
- package/dist/lib/remote-ui/customer-contract-contracts.d.ts +5 -11
- package/dist/lib/remote-ui/customer-contract-contracts.d.ts.map +1 -1
- package/dist/lib/remote-ui/customer-contract-contracts.js.map +1 -1
- package/dist/lib/remote-ui/date-time.d.ts +3 -0
- package/dist/lib/remote-ui/date-time.d.ts.map +1 -0
- package/dist/lib/remote-ui/date-time.js +58 -0
- package/dist/lib/remote-ui/date-time.js.map +1 -0
- package/dist/lib/remote-ui/model.d.ts +26 -1
- package/dist/lib/remote-ui/model.d.ts.map +1 -1
- package/dist/lib/remote-ui/model.js +151 -15
- package/dist/lib/remote-ui/model.js.map +1 -1
- package/dist/lib/remote-ui/pages/analytics.d.ts +6 -0
- package/dist/lib/remote-ui/pages/analytics.d.ts.map +1 -1
- package/dist/lib/remote-ui/pages/analytics.js +56 -6
- package/dist/lib/remote-ui/pages/analytics.js.map +1 -1
- package/dist/lib/remote-ui/pages/catalogs.d.ts +35 -14
- package/dist/lib/remote-ui/pages/catalogs.d.ts.map +1 -1
- package/dist/lib/remote-ui/pages/catalogs.js +139 -89
- package/dist/lib/remote-ui/pages/catalogs.js.map +1 -1
- package/dist/lib/remote-ui/pages/contracts.d.ts +10 -5
- package/dist/lib/remote-ui/pages/contracts.d.ts.map +1 -1
- package/dist/lib/remote-ui/pages/contracts.js +29 -26
- package/dist/lib/remote-ui/pages/contracts.js.map +1 -1
- package/dist/lib/remote-ui/pages/files.d.ts +55 -4
- package/dist/lib/remote-ui/pages/files.d.ts.map +1 -1
- package/dist/lib/remote-ui/pages/files.js +121 -14
- package/dist/lib/remote-ui/pages/files.js.map +1 -1
- package/dist/lib/remote-ui/pages/home.js +2 -2
- package/dist/lib/remote-ui/pages/home.js.map +1 -1
- package/dist/lib/remote-ui/pages/procurement.d.ts +6 -4
- package/dist/lib/remote-ui/pages/procurement.d.ts.map +1 -1
- package/dist/lib/remote-ui/pages/procurement.js +69 -66
- package/dist/lib/remote-ui/pages/procurement.js.map +1 -1
- package/dist/lib/remote-ui/pages/sales.d.ts +51 -2
- package/dist/lib/remote-ui/pages/sales.d.ts.map +1 -1
- package/dist/lib/remote-ui/pages/sales.js +106 -19
- package/dist/lib/remote-ui/pages/sales.js.map +1 -1
- package/dist/lib/remote-ui/pages/settings.d.ts +29 -14
- package/dist/lib/remote-ui/pages/settings.d.ts.map +1 -1
- package/dist/lib/remote-ui/pages/settings.js +76 -69
- package/dist/lib/remote-ui/pages/settings.js.map +1 -1
- package/dist/lib/remote-ui/polling.d.ts +41 -0
- package/dist/lib/remote-ui/polling.d.ts.map +1 -0
- package/dist/lib/remote-ui/polling.js +148 -0
- package/dist/lib/remote-ui/polling.js.map +1 -0
- package/dist/lib/remote-ui/procurement-contracts.d.ts +4 -9
- package/dist/lib/remote-ui/procurement-contracts.d.ts.map +1 -1
- package/dist/lib/remote-ui/sales-contracts.d.ts +5 -3
- package/dist/lib/remote-ui/sales-contracts.d.ts.map +1 -1
- package/dist/lib/remote-ui/settings-contracts.d.ts +9 -1
- package/dist/lib/remote-ui/settings-contracts.d.ts.map +1 -1
- package/dist/lib/remote-ui/settings-contracts.js.map +1 -1
- package/dist/lib/remote-ui/toast-queue.d.ts +29 -0
- package/dist/lib/remote-ui/toast-queue.d.ts.map +1 -0
- package/dist/lib/remote-ui/toast-queue.js +71 -0
- package/dist/lib/remote-ui/toast-queue.js.map +1 -0
- package/dist/lib/sales-file.service.d.ts +13 -35
- package/dist/lib/sales-file.service.d.ts.map +1 -1
- package/dist/lib/sales-file.service.js +39 -21
- package/dist/lib/sales-file.service.js.map +1 -1
- package/dist/lib/types.d.ts +22 -3
- package/dist/lib/types.d.ts.map +1 -1
- package/dist/lib/types.js +5 -0
- package/dist/lib/types.js.map +1 -1
- package/dist/lib/workbench-action-contract.d.ts +4 -0
- package/dist/lib/workbench-action-contract.d.ts.map +1 -0
- package/dist/lib/workbench-action-contract.js +43 -0
- package/dist/lib/workbench-action-contract.js.map +1 -0
- package/dist/lib/workbench-view.provider.d.ts +57 -78
- package/dist/lib/workbench-view.provider.d.ts.map +1 -1
- package/dist/lib/workbench-view.provider.js +104 -94
- package/dist/lib/workbench-view.provider.js.map +1 -1
- package/dist/lib/workbench.middleware.d.ts +129 -72
- package/dist/lib/workbench.middleware.d.ts.map +1 -1
- package/dist/lib/workbench.middleware.js +16 -3
- package/dist/lib/workbench.middleware.js.map +1 -1
- package/dist/lib/workbench.plugin.d.ts +3 -1
- package/dist/lib/workbench.plugin.d.ts.map +1 -1
- package/dist/lib/workbench.plugin.js +13 -4
- package/dist/lib/workbench.plugin.js.map +1 -1
- package/dist/lib/workbench.service.d.ts +136 -40
- package/dist/lib/workbench.service.d.ts.map +1 -1
- package/dist/lib/workbench.service.js +985 -71
- package/dist/lib/workbench.service.js.map +1 -1
- package/package.json +6 -5
|
@@ -2,8 +2,13 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
2
2
|
import React from 'react';
|
|
3
3
|
import { DataGrid, FilterPanel, InfoNotice } from '../components/data-grid.js';
|
|
4
4
|
import { ConfirmDialog, FieldGrid, FormField, Modal, ReviewSplit, UploadZone } from '../components/forms.js';
|
|
5
|
+
import { isPaginationPageSize } from '../contracts.js';
|
|
5
6
|
import { getConfiguredUploadPolicy } from '../upload-policy.js';
|
|
7
|
+
import { formatBeijingDateTime, formatBusinessDate } from '../date-time.js';
|
|
6
8
|
import './catalogs.css';
|
|
9
|
+
import { createLatestRequestGate } from '../model.js';
|
|
10
|
+
import { abortablePollingDelay, createPollingObservation, pollWithRetry } from '../polling.js';
|
|
11
|
+
import { runNotifiedOperation } from '../toast-queue.js';
|
|
7
12
|
const PAGE_SIZE = 20;
|
|
8
13
|
const ACCEPTED_CATALOG_FILES = '.pdf,.doc,.docx,.xls,.xlsx,.png,.jpg,.jpeg,.webp';
|
|
9
14
|
const formatNotice = () => `支持 PDF、DOC、DOCX、XLS、XLSX、PNG、JPEG、WEBP,单文件最大 ${getConfiguredUploadPolicy().maxUploadMb} MB。`;
|
|
@@ -45,7 +50,6 @@ export function createCatalogActionApi({ onAction, onFileAction, onRefresh }) {
|
|
|
45
50
|
const mutate = async (actionKey, input) => {
|
|
46
51
|
const response = await onAction(actionKey, input);
|
|
47
52
|
assertActionSucceeded(response);
|
|
48
|
-
await onRefresh();
|
|
49
53
|
return response;
|
|
50
54
|
};
|
|
51
55
|
const mutateFile = async (actionKey, file, input) => {
|
|
@@ -80,26 +84,22 @@ export class CatalogImportError extends Error {
|
|
|
80
84
|
this.name = 'CatalogImportError';
|
|
81
85
|
}
|
|
82
86
|
}
|
|
83
|
-
export async function runCatalogImportOrchestration({ start,
|
|
87
|
+
export async function runCatalogImportOrchestration({ start, pollTask, refresh, wait = abortablePollingDelay, now = Date.now, intervalMs = 2000, timeoutMs = MAX_IMPORT_TIMEOUT_MS, onTransition, onPollError, signal }) {
|
|
84
88
|
assertActionSucceeded(start);
|
|
85
89
|
if (!isCatalogImportStartData(start.data))
|
|
86
90
|
throw new Error('上传响应缺少解析任务信息');
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
const deadline = now() + timeoutMs;
|
|
100
|
-
while (now() < deadline) {
|
|
101
|
-
await wait(intervalMs);
|
|
102
|
-
const completion = await pollTask(start.data.task.id);
|
|
91
|
+
const observation = createPollingObservation({ now, timeoutMs, maxTimeoutMs: MAX_IMPORT_TIMEOUT_MS });
|
|
92
|
+
intervalMs = Math.max(1, intervalMs);
|
|
93
|
+
let previous = start.data.task;
|
|
94
|
+
const taskId = previous.id;
|
|
95
|
+
while (observation.active()) {
|
|
96
|
+
await wait(intervalMs, signal);
|
|
97
|
+
const attempt = await pollWithRetry(requestSignal => requestSignal ? pollTask(taskId, requestSignal) : pollTask(taskId), onPollError, signal);
|
|
98
|
+
if (!attempt.ok)
|
|
99
|
+
continue;
|
|
100
|
+
const completion = attempt.value;
|
|
101
|
+
onTransition?.(previous, completion.task);
|
|
102
|
+
previous = completion.task;
|
|
103
103
|
const status = completion.task.status;
|
|
104
104
|
if (status === 'SUCCEEDED') {
|
|
105
105
|
if (!completion.batch)
|
|
@@ -112,13 +112,16 @@ export async function runCatalogImportOrchestration({ start, invokeClientCommand
|
|
|
112
112
|
if (!ACTIVE_IMPORT_STATUSES.has(status))
|
|
113
113
|
throw new Error(`不支持的导入任务状态:${status}`);
|
|
114
114
|
}
|
|
115
|
-
throw
|
|
115
|
+
throw observation.expired('目录任务观察窗口已结束', previous);
|
|
116
116
|
}
|
|
117
117
|
export function findActiveCatalogImportTask(items) {
|
|
118
118
|
return items
|
|
119
|
-
.filter((task) => (task.documentType === 'CONTROLLED_CATALOG' || task.documentType === 'SANCTION_CATALOG') && ACTIVE_IMPORT_STATUSES.has(task.status)
|
|
119
|
+
.filter((task) => (task.documentType === 'CONTROLLED_CATALOG' || task.documentType === 'SANCTION_CATALOG') && ACTIVE_IMPORT_STATUSES.has(task.status))
|
|
120
120
|
.sort((left, right) => String(right.updatedAt ?? '').localeCompare(String(left.updatedAt ?? '')))[0] ?? null;
|
|
121
121
|
}
|
|
122
|
+
export function findActiveCatalogImportTasks(items) {
|
|
123
|
+
return items.filter((task) => (task.documentType === 'CONTROLLED_CATALOG' || task.documentType === 'SANCTION_CATALOG') && ACTIVE_IMPORT_STATUSES.has(task.status));
|
|
124
|
+
}
|
|
122
125
|
export function findLatestFailedCatalogImportTask(items, documentType) {
|
|
123
126
|
const retriedTaskIds = new Set(items.map((task) => task.retryOfTaskId).filter((id) => Boolean(id)));
|
|
124
127
|
return items
|
|
@@ -129,17 +132,19 @@ export function createCatalogQueryScheduler(query, debounceMs = 300) {
|
|
|
129
132
|
let timer = null;
|
|
130
133
|
const cancel = () => { if (timer)
|
|
131
134
|
clearTimeout(timer); timer = null; };
|
|
132
|
-
const
|
|
135
|
+
const run = (page, filters, pageSize = PAGE_SIZE) => { cancel(); query(page, filters, pageSize); };
|
|
133
136
|
return {
|
|
134
|
-
schedule(type, filters) {
|
|
137
|
+
schedule(type, filters, pageSize = PAGE_SIZE) {
|
|
135
138
|
cancel();
|
|
136
139
|
if (type !== 'text') {
|
|
137
|
-
|
|
140
|
+
run(1, filters, pageSize);
|
|
138
141
|
return;
|
|
139
142
|
}
|
|
140
|
-
timer = setTimeout(() => { timer = null; query(filters); }, debounceMs);
|
|
143
|
+
timer = setTimeout(() => { timer = null; query(1, filters, pageSize); }, debounceMs);
|
|
141
144
|
},
|
|
142
|
-
flush,
|
|
145
|
+
flush: (filters, pageSize = PAGE_SIZE) => run(1, filters, pageSize),
|
|
146
|
+
page: (page, filters, pageSize = PAGE_SIZE) => run(page, filters, pageSize),
|
|
147
|
+
pageSize: (pageSize, filters) => run(1, filters, pageSize),
|
|
143
148
|
cancel
|
|
144
149
|
};
|
|
145
150
|
}
|
|
@@ -217,41 +222,47 @@ export function ControlledGoodsPage(props) {
|
|
|
217
222
|
if (props.command)
|
|
218
223
|
props.onCommandHandled?.();
|
|
219
224
|
}, [props.command, props.onCommandHandled]);
|
|
220
|
-
const query = React.useCallback((page, nextFilters) => props.onQuery({ page, pageSize
|
|
221
|
-
const scheduler = React.useMemo(() => createCatalogQueryScheduler((nextFilters) => void query(
|
|
225
|
+
const query = React.useCallback((page, nextFilters, pageSize) => props.onQuery({ page, pageSize, filters: nextFilters }), [props.onQuery]);
|
|
226
|
+
const scheduler = React.useMemo(() => createCatalogQueryScheduler((page, nextFilters, pageSize) => void query(page, nextFilters, pageSize)), [query]);
|
|
222
227
|
React.useEffect(() => () => scheduler.cancel(), [scheduler]);
|
|
223
228
|
React.useEffect(() => { issueControllerRef.current?.mount(); return () => issueControllerRef.current?.dispose(); }, []);
|
|
224
229
|
const changeFilter = (key, value) => {
|
|
225
230
|
const nextFilters = { ...filters, [key]: value };
|
|
226
231
|
setFilters(nextFilters);
|
|
227
|
-
scheduler.schedule(controlledGoodsFilters.find((definition) => definition.key === key)?.type ?? 'text', nextFilters);
|
|
232
|
+
scheduler.schedule(controlledGoodsFilters.find((definition) => definition.key === key)?.type ?? 'text', nextFilters, pagination.pageSize);
|
|
228
233
|
};
|
|
229
|
-
const queryIssues = async (batchId, issueType, page = 1) => {
|
|
234
|
+
const queryIssues = async (batchId, issueType, page = 1, pageSize = PAGE_SIZE) => {
|
|
230
235
|
if (!props.onIssueQuery)
|
|
231
236
|
return;
|
|
232
|
-
await issueControllerRef.current?.load(props.onIssueQuery, { documentType: 'CONTROLLED_CATALOG', batchId, issueType, page, pageSize
|
|
237
|
+
await issueControllerRef.current?.load(props.onIssueQuery, { documentType: 'CONTROLLED_CATALOG', batchId, issueType, page, pageSize });
|
|
233
238
|
};
|
|
234
239
|
const displayedSummary = summary ?? (props.importCompletion?.task.documentType === 'CONTROLLED_CATALOG' ? props.importCompletion.batch ?? null : null);
|
|
235
240
|
const columns = [
|
|
236
|
-
{ type: 'custom', key: 'productName', header: '商品名称', width: 180, render: (_, row) => _jsx(LinkButton, {
|
|
237
|
-
{ type: 'custom', key: 'hsCode', header: '海关编码', width: 130, render: (_, row) => row.hsCode ? _jsx(LinkButton, {
|
|
238
|
-
{ type: 'text', key: 'catalogType', header: '目录类型', width: 130, format: (value) => labelFor(value, catalogTypeLabels) },
|
|
241
|
+
{ type: 'custom', key: 'productName', header: '商品名称', width: 180, title: (_, row) => row.productName || undefined, render: (_, row) => _jsx(LinkButton, { iconClass: "ri-search-line", title: row.productName || '—', ariaLabel: `按商品名称查询海关编码:${row.productName || '—'}`, onClick: () => setHsQuery(hsLookupQuery(row, 'productName')), children: row.productName || '—' }) },
|
|
242
|
+
{ type: 'custom', key: 'hsCode', header: '海关编码', width: 130, title: (_, row) => row.hsCode || undefined, render: (_, row) => row.hsCode ? _jsx(LinkButton, { iconClass: "ri-search-line", title: row.hsCode, ariaLabel: `按编码查询海关编码:${row.hsCode}`, onClick: () => setHsQuery(hsLookupQuery(row, 'hsCode')), children: row.hsCode }) : '—' },
|
|
243
|
+
{ type: 'text', key: 'catalogType', header: '目录类型', width: 130, truncate: false, format: (value) => labelFor(value, catalogTypeLabels) },
|
|
239
244
|
{ type: 'ellipsis', key: 'controlDescription', header: '管控说明', width: 220, maxWidth: 220 },
|
|
240
|
-
{ type: 'text', key: 'sourceType', header: '来源类型', width: 110, format: (value) => labelFor(value, sourceTypeLabels) },
|
|
245
|
+
{ type: 'text', key: 'sourceType', header: '来源类型', width: 110, truncate: false, format: (value) => labelFor(value, sourceTypeLabels) },
|
|
241
246
|
{ type: 'ellipsis', key: 'sourceFileName', header: '来源文件', width: 190, maxWidth: 190 },
|
|
242
|
-
{ type: 'text', key: 'updatedAt', header: '更新时间', width: 170 }
|
|
247
|
+
{ type: 'text', key: 'updatedAt', header: '更新时间', width: 170, truncate: false, format: formatBeijingDateTime }
|
|
243
248
|
];
|
|
244
|
-
return (_jsxs("section", { className: "tcw-catalog-page tcw-catalog-page--controlled", "aria-label": "\u7BA1\u63A7\u5546\u54C1\u76EE\u5F55", children: [_jsx(FilterPanel, { definitions: controlledGoodsFilters, values: filters, onChange: changeFilter, onQuery: () => scheduler.flush(filters), onReset: () => { setFilters({}); scheduler.flush({}); } }), _jsx(InfoNotice, { children: formatNotice() }), props.importFailure?.documentType === 'CONTROLLED_CATALOG' && _jsx(CatalogImportStatusNotice, { task: props.importFailure, busy: props.busy, onRetry: (taskId) => props.onRetryImport?.(taskId) ?? Promise.resolve() }), displayedSummary && _jsx(CatalogImportSummaryNotice, { documentType: "CONTROLLED_CATALOG", batch: displayedSummary, busy: props.busy, onDownload: (documentType, batchId) => api.downloadImportIssues(documentType, batchId), onView: (batchId, issueType) => queryIssues(batchId, issueType) }), _jsx(DataGrid, { columns: columns, rows: rows, rowKey: "id", actionWidth: 160, actions: (row) => _jsxs(_Fragment, { children: [_jsxs("button", { type: "button", className: "tcw-catalog-operation-button", disabled: props.busy, onClick: () => setForm({ mode: 'review', item: row }), children: [_jsx("i", { className: "ri-edit-line", "aria-hidden": "true" }), "\u5BA1\u6838"] }), _jsxs("button", { type: "button", className: "tcw-catalog-operation-button is-danger", disabled: props.busy, onClick: () => setDeleteItem(row), children: [_jsx("i", { className: "ri-delete-bin-line", "aria-hidden": "true" }), "\u5220\u9664"] })] }), pagination: pagination, onPageChange: (nextPage) =>
|
|
249
|
+
return (_jsxs("section", { className: "tcw-catalog-page tcw-catalog-page--controlled", "aria-label": "\u7BA1\u63A7\u5546\u54C1\u76EE\u5F55", children: [_jsx(FilterPanel, { definitions: controlledGoodsFilters, values: filters, onChange: changeFilter, onQuery: () => scheduler.flush(filters, pagination.pageSize), onReset: () => { setFilters({}); scheduler.flush({}, pagination.pageSize); } }), _jsx(InfoNotice, { children: formatNotice() }), props.importFailure?.documentType === 'CONTROLLED_CATALOG' && _jsx(CatalogImportStatusNotice, { task: props.importFailure, busy: props.busy, onRetry: (taskId) => props.onRetryImport?.(taskId) ?? Promise.resolve() }), displayedSummary && _jsx(CatalogImportSummaryNotice, { documentType: "CONTROLLED_CATALOG", batch: displayedSummary, busy: props.busy, onDownload: (documentType, batchId) => api.downloadImportIssues(documentType, batchId), onView: (batchId, issueType) => queryIssues(batchId, issueType) }), _jsx(DataGrid, { columns: columns, rows: rows, rowKey: "id", actionWidth: 160, actions: (row) => _jsxs(_Fragment, { children: [_jsxs("button", { type: "button", className: "tcw-catalog-operation-button", disabled: props.busy, onClick: () => setForm({ mode: 'review', item: row }), children: [_jsx("i", { className: "ri-edit-line", "aria-hidden": "true" }), "\u5BA1\u6838"] }), _jsxs("button", { type: "button", className: "tcw-catalog-operation-button is-danger", disabled: props.busy, onClick: () => setDeleteItem(row), children: [_jsx("i", { className: "ri-delete-bin-line", "aria-hidden": "true" }), "\u5220\u9664"] })] }), pagination: pagination, onPageChange: (nextPage) => scheduler.page(nextPage, filters, pagination.pageSize), onPageSizeChange: (pageSize) => scheduler.pageSize(pageSize, filters), emptyText: "\u6682\u65E0\u7BA1\u63A7\u5546\u54C1\u6570\u636E" }), _jsx(CatalogUploadDialog, { open: uploadOpen, kind: "controlled", batches: props.batches, busy: props.busy, onClose: () => setUploadOpen(false), onNotify: props.onNotify, onUpload: async (file, input) => {
|
|
245
250
|
const response = await api.uploadControlledCatalog(file, input);
|
|
246
251
|
const completion = asImportCompletion(response.data);
|
|
247
252
|
if (completion.batch)
|
|
248
253
|
setSummary(completion.batch);
|
|
249
254
|
setUploadOpen(false);
|
|
250
|
-
} }), _jsx(ControlledGoodsFormDialog, { open: Boolean(form), mode: form?.mode ?? 'create', item: form?.item ?? null, busy: props.busy, onClose: () => setForm(null), onSave: async (input) => {
|
|
251
|
-
|
|
255
|
+
} }), _jsx(ControlledGoodsFormDialog, { open: Boolean(form), mode: form?.mode ?? 'create', item: form?.item ?? null, busy: props.busy, onClose: () => setForm(null), onSave: async (input) => {
|
|
256
|
+
const saved = await runNotifiedOperation(async () => { form?.mode === 'review' && form.item ? await api.updateControlledGoods({ ...input, id: form.item.id }) : await api.saveControlledGoods(input); }, props.onNotify, { success: form?.mode === 'review' ? '管控商品保存成功' : '管控商品新增成功', error: '管控商品保存失败' });
|
|
257
|
+
if (saved)
|
|
258
|
+
setForm(null);
|
|
259
|
+
} }), _jsx(ConfirmDialog, { open: Boolean(deleteItem), title: "\u786E\u8BA4\u5220\u9664\u7BA1\u63A7\u5546\u54C1", message: `删除“${deleteItem?.productName ?? ''}”后,该记录将被逻辑删除。`, confirmText: "\u786E\u8BA4\u5220\u9664", danger: true, onCancel: () => setDeleteItem(null), onConfirm: () => { if (deleteItem)
|
|
260
|
+
void runNotifiedOperation(() => api.deleteEntity('controlledGoods', deleteItem.id).then(() => undefined), props.onNotify, { success: '管控商品删除成功', error: '管控商品删除失败' }).then((deleted) => { if (deleted)
|
|
261
|
+
setDeleteItem(null); }); } }), _jsx(HsCodeLookupDialog, { open: hsQuery !== null, initialQuery: hsQuery ?? '', onAction: props.onAction, onClose: () => setHsQuery(null) }), _jsx(CatalogImportIssuesDialog, { open: Boolean(issueState), documentType: "CONTROLLED_CATALOG", batchId: issueState?.batchId ?? '', issueType: issueState?.issueType ?? 'DUPLICATE', collection: issueState?.collection ?? { items: [] }, loading: issueState?.loading ?? false, error: issueState?.error ?? '', busy: props.busy, onClose: () => { issueControllerRef.current?.close(); setIssueState(null); }, onIssueTypeChange: (issueType) => { if (issueState)
|
|
252
262
|
void queryIssues(issueState.batchId, issueType); }, onPageChange: (page) => { if (issueState)
|
|
253
|
-
void queryIssues(issueState.batchId, issueState.issueType, page); },
|
|
254
|
-
void queryIssues(issueState.batchId, issueState.issueType,
|
|
263
|
+
void queryIssues(issueState.batchId, issueState.issueType, page, collectionPagination(issueState.collection).pageSize); }, onPageSizeChange: (pageSize) => { if (issueState)
|
|
264
|
+
void queryIssues(issueState.batchId, issueState.issueType, 1, pageSize); }, onRetry: () => { if (issueState)
|
|
265
|
+
void queryIssues(issueState.batchId, issueState.issueType, Number(issueState.collection.page) || 1, collectionPagination(issueState.collection).pageSize); } })] }));
|
|
255
266
|
}
|
|
256
267
|
export function SanctionedCompaniesPage(props) {
|
|
257
268
|
const [filters, setFilters] = React.useState({});
|
|
@@ -274,47 +285,53 @@ export function SanctionedCompaniesPage(props) {
|
|
|
274
285
|
if (props.command)
|
|
275
286
|
props.onCommandHandled?.();
|
|
276
287
|
}, [props.command, props.onCommandHandled]);
|
|
277
|
-
const query = React.useCallback((page, nextFilters) => props.onQuery({ page, pageSize
|
|
278
|
-
const scheduler = React.useMemo(() => createCatalogQueryScheduler((nextFilters) => void query(
|
|
288
|
+
const query = React.useCallback((page, nextFilters, pageSize) => props.onQuery({ page, pageSize, filters: nextFilters }), [props.onQuery]);
|
|
289
|
+
const scheduler = React.useMemo(() => createCatalogQueryScheduler((page, nextFilters, pageSize) => void query(page, nextFilters, pageSize)), [query]);
|
|
279
290
|
React.useEffect(() => () => scheduler.cancel(), [scheduler]);
|
|
280
291
|
React.useEffect(() => { issueControllerRef.current?.mount(); return () => issueControllerRef.current?.dispose(); }, []);
|
|
281
292
|
const changeFilter = (key, value) => {
|
|
282
293
|
const nextFilters = { ...filters, [key]: value };
|
|
283
294
|
setFilters(nextFilters);
|
|
284
|
-
scheduler.schedule(sanctionedCompanyFilters.find((definition) => definition.key === key)?.type ?? 'text', nextFilters);
|
|
295
|
+
scheduler.schedule(sanctionedCompanyFilters.find((definition) => definition.key === key)?.type ?? 'text', nextFilters, pagination.pageSize);
|
|
285
296
|
};
|
|
286
|
-
const queryIssues = async (batchId, issueType, page = 1) => {
|
|
297
|
+
const queryIssues = async (batchId, issueType, page = 1, pageSize = PAGE_SIZE) => {
|
|
287
298
|
if (!props.onIssueQuery)
|
|
288
299
|
return;
|
|
289
|
-
await issueControllerRef.current?.load(props.onIssueQuery, { documentType: 'SANCTION_CATALOG', batchId, issueType, page, pageSize
|
|
300
|
+
await issueControllerRef.current?.load(props.onIssueQuery, { documentType: 'SANCTION_CATALOG', batchId, issueType, page, pageSize });
|
|
290
301
|
};
|
|
291
302
|
const displayedSummary = summary ?? (props.importCompletion?.task.documentType === 'SANCTION_CATALOG' ? props.importCompletion.batch ?? null : null);
|
|
292
303
|
const columns = [
|
|
293
|
-
{ type: 'custom', key: 'companyName', header: '公司名称', width: 210, render: (_, row) => _jsx(LinkButton, {
|
|
304
|
+
{ type: 'custom', key: 'companyName', header: '公司名称', width: 210, title: (_, row) => row.companyName || undefined, render: (_, row) => _jsx(LinkButton, { iconClass: "ri-eye-line", title: row.companyName || '—', ariaLabel: `查看制裁公司详情:${row.companyName || '—'}`, onClick: () => setForm({ mode: 'detail', item: row }), children: row.companyName || '—' }) },
|
|
294
305
|
{ type: 'text', key: 'countryOrRegion', header: '国家或地区', width: 120 },
|
|
295
306
|
{ type: 'ellipsis', key: 'sanctionListSource', header: '制裁名单来源', width: 190, maxWidth: 190 },
|
|
296
|
-
{ type: 'text', key: 'effectiveDate', header: '生效日期', width: 120 },
|
|
307
|
+
{ type: 'text', key: 'effectiveDate', header: '生效日期', width: 120, truncate: false, format: formatBusinessDate },
|
|
297
308
|
{ type: 'ellipsis', key: 'sanctionReason', header: '制裁原因', width: 220, maxWidth: 220 },
|
|
298
|
-
{ type: 'text', key: 'sourceType', header: '来源类型', width: 110, format: (value) => labelFor(value, sourceTypeLabels) },
|
|
309
|
+
{ type: 'text', key: 'sourceType', header: '来源类型', width: 110, truncate: false, format: (value) => labelFor(value, sourceTypeLabels) },
|
|
299
310
|
{ type: 'ellipsis', key: 'sourceFileName', header: '来源文件', width: 190, maxWidth: 190 },
|
|
300
|
-
{ type: 'text', key: 'updatedAt', header: '更新时间', width: 170 }
|
|
311
|
+
{ type: 'text', key: 'updatedAt', header: '更新时间', width: 170, truncate: false, format: formatBeijingDateTime }
|
|
301
312
|
];
|
|
302
|
-
return (_jsxs("section", { className: "tcw-catalog-page tcw-catalog-page--sanction", "aria-label": "\u516C\u53F8\u5236\u88C1\u76EE\u5F55", children: [_jsx(FilterPanel, { definitions: sanctionedCompanyFilters, values: filters, onChange: changeFilter, onQuery: () => scheduler.flush(filters), onReset: () => { setFilters({}); scheduler.flush({}); } }), _jsx(InfoNotice, { children: formatNotice() }), props.importFailure?.documentType === 'SANCTION_CATALOG' && _jsx(CatalogImportStatusNotice, { task: props.importFailure, busy: props.busy, onRetry: (taskId) => props.onRetryImport?.(taskId) ?? Promise.resolve() }), displayedSummary && _jsx(CatalogImportSummaryNotice, { documentType: "SANCTION_CATALOG", batch: displayedSummary, busy: props.busy, onDownload: (documentType, batchId) => api.downloadImportIssues(documentType, batchId), onView: (batchId, issueType) => queryIssues(batchId, issueType) }), _jsx(DataGrid, { columns: columns, rows: rows, rowKey: "id", actionWidth: 160, actions: (row) => _jsxs(_Fragment, { children: [_jsxs("button", { type: "button", className: "tcw-catalog-operation-button", disabled: props.busy, onClick: () => setForm({ mode: 'review', item: row }), children: [_jsx("i", { className: "ri-edit-line", "aria-hidden": "true" }), "\u5BA1\u6838"] }), _jsxs("button", { type: "button", className: "tcw-catalog-operation-button is-danger", disabled: props.busy, onClick: () => setDeleteItem(row), children: [_jsx("i", { className: "ri-delete-bin-line", "aria-hidden": "true" }), "\u5220\u9664"] })] }), pagination: pagination, onPageChange: (nextPage) =>
|
|
313
|
+
return (_jsxs("section", { className: "tcw-catalog-page tcw-catalog-page--sanction", "aria-label": "\u516C\u53F8\u5236\u88C1\u76EE\u5F55", children: [_jsx(FilterPanel, { definitions: sanctionedCompanyFilters, values: filters, onChange: changeFilter, onQuery: () => scheduler.flush(filters, pagination.pageSize), onReset: () => { setFilters({}); scheduler.flush({}, pagination.pageSize); } }), _jsx(InfoNotice, { children: formatNotice() }), props.importFailure?.documentType === 'SANCTION_CATALOG' && _jsx(CatalogImportStatusNotice, { task: props.importFailure, busy: props.busy, onRetry: (taskId) => props.onRetryImport?.(taskId) ?? Promise.resolve() }), displayedSummary && _jsx(CatalogImportSummaryNotice, { documentType: "SANCTION_CATALOG", batch: displayedSummary, busy: props.busy, onDownload: (documentType, batchId) => api.downloadImportIssues(documentType, batchId), onView: (batchId, issueType) => queryIssues(batchId, issueType) }), _jsx(DataGrid, { columns: columns, rows: rows, rowKey: "id", actionWidth: 160, actions: (row) => _jsxs(_Fragment, { children: [_jsxs("button", { type: "button", className: "tcw-catalog-operation-button", disabled: props.busy, onClick: () => setForm({ mode: 'review', item: row }), children: [_jsx("i", { className: "ri-edit-line", "aria-hidden": "true" }), "\u5BA1\u6838"] }), _jsxs("button", { type: "button", className: "tcw-catalog-operation-button is-danger", disabled: props.busy, onClick: () => setDeleteItem(row), children: [_jsx("i", { className: "ri-delete-bin-line", "aria-hidden": "true" }), "\u5220\u9664"] })] }), pagination: pagination, onPageChange: (nextPage) => scheduler.page(nextPage, filters, pagination.pageSize), onPageSizeChange: (pageSize) => scheduler.pageSize(pageSize, filters), emptyText: "\u6682\u65E0\u5236\u88C1\u516C\u53F8\u6570\u636E" }), _jsx(CatalogUploadDialog, { open: uploadOpen, kind: "sanction", batches: props.batches, busy: props.busy, onClose: () => setUploadOpen(false), onNotify: props.onNotify, onUpload: async (file, input) => {
|
|
303
314
|
const response = await api.uploadSanctionCatalog(file, input);
|
|
304
315
|
const completion = asImportCompletion(response.data);
|
|
305
316
|
if (completion.batch)
|
|
306
317
|
setSummary(completion.batch);
|
|
307
318
|
setUploadOpen(false);
|
|
308
|
-
} }), _jsx(SanctionedCompanyDialog, { open: Boolean(form), mode: form?.mode ?? 'create', item: form?.item ?? null, busy: props.busy, onClose: () => setForm(null), onSave: async (input) => {
|
|
309
|
-
|
|
319
|
+
} }), _jsx(SanctionedCompanyDialog, { open: Boolean(form), mode: form?.mode ?? 'create', item: form?.item ?? null, busy: props.busy, onClose: () => setForm(null), onSave: async (input) => {
|
|
320
|
+
const saved = await runNotifiedOperation(async () => { form?.mode === 'review' && form.item ? await api.updateSanctionedCompany({ ...input, id: form.item.id }) : await api.saveSanctionedCompany(input); }, props.onNotify, { success: form?.mode === 'review' ? '制裁公司保存成功' : '制裁公司新增成功', error: '制裁公司保存失败' });
|
|
321
|
+
if (saved)
|
|
322
|
+
setForm(null);
|
|
323
|
+
} }), _jsx(ConfirmDialog, { open: Boolean(deleteItem), title: "\u786E\u8BA4\u5220\u9664\u5236\u88C1\u516C\u53F8", message: `删除“${deleteItem?.companyName ?? ''}”后,该记录将被逻辑删除。`, confirmText: "\u786E\u8BA4\u5220\u9664", danger: true, onCancel: () => setDeleteItem(null), onConfirm: () => { if (deleteItem)
|
|
324
|
+
void runNotifiedOperation(() => api.deleteEntity('sanctionedCompany', deleteItem.id).then(() => undefined), props.onNotify, { success: '制裁公司删除成功', error: '制裁公司删除失败' }).then((deleted) => { if (deleted)
|
|
325
|
+
setDeleteItem(null); }); } }), _jsx(CatalogImportIssuesDialog, { open: Boolean(issueState), documentType: "SANCTION_CATALOG", batchId: issueState?.batchId ?? '', issueType: issueState?.issueType ?? 'DUPLICATE', collection: issueState?.collection ?? { items: [] }, loading: issueState?.loading ?? false, error: issueState?.error ?? '', busy: props.busy, onClose: () => { issueControllerRef.current?.close(); setIssueState(null); }, onIssueTypeChange: (issueType) => { if (issueState)
|
|
310
326
|
void queryIssues(issueState.batchId, issueType); }, onPageChange: (page) => { if (issueState)
|
|
311
|
-
void queryIssues(issueState.batchId, issueState.issueType, page); },
|
|
312
|
-
void queryIssues(issueState.batchId, issueState.issueType,
|
|
327
|
+
void queryIssues(issueState.batchId, issueState.issueType, page, collectionPagination(issueState.collection).pageSize); }, onPageSizeChange: (pageSize) => { if (issueState)
|
|
328
|
+
void queryIssues(issueState.batchId, issueState.issueType, 1, pageSize); }, onRetry: () => { if (issueState)
|
|
329
|
+
void queryIssues(issueState.batchId, issueState.issueType, Number(issueState.collection.page) || 1, collectionPagination(issueState.collection).pageSize); } })] }));
|
|
313
330
|
}
|
|
314
331
|
export function CatalogCommands({ busy, onUpload, onCreate }) {
|
|
315
332
|
return _jsxs(_Fragment, { children: [_jsxs("button", { type: "button", className: "tcw-button tcw-button--primary", disabled: busy, onClick: onUpload, children: [_jsx("i", { className: "ri-upload-cloud-2-line", "aria-hidden": "true" }), "\u4E0A\u4F20\u76EE\u5F55\u6587\u4EF6"] }), _jsxs("button", { type: "button", className: "tcw-button tcw-button--secondary", disabled: busy, onClick: onCreate, children: [_jsx("i", { className: "ri-add-line", "aria-hidden": "true" }), "\u624B\u5DE5\u65B0\u589E"] })] });
|
|
316
333
|
}
|
|
317
|
-
export function CatalogUploadDialog({ open, kind, batches, busy = false, onClose, onUpload }) {
|
|
334
|
+
export function CatalogUploadDialog({ open, kind, batches, busy = false, onClose, onUpload, onNotify }) {
|
|
318
335
|
const [state, dispatch] = React.useReducer(catalogUploadReducer, initialCatalogUploadState);
|
|
319
336
|
const disabled = busy || state.submitting;
|
|
320
337
|
React.useEffect(() => {
|
|
@@ -335,7 +352,7 @@ export function CatalogUploadDialog({ open, kind, batches, busy = false, onClose
|
|
|
335
352
|
dispatch({ type: 'reset' });
|
|
336
353
|
}
|
|
337
354
|
catch (cause) {
|
|
338
|
-
|
|
355
|
+
onNotify?.({ message: errorMessage(cause, '目录上传失败'), tone: 'error' });
|
|
339
356
|
dispatch({ type: 'cancel-replace' });
|
|
340
357
|
}
|
|
341
358
|
finally {
|
|
@@ -361,7 +378,10 @@ export function CatalogUploadDialog({ open, kind, batches, busy = false, onClose
|
|
|
361
378
|
}
|
|
362
379
|
export function CatalogReplaceConfirmDialog({ open, kind, batch, fileName, busy = false, onCancel, onConfirm }) {
|
|
363
380
|
const typeLabel = batch?.catalogType ? catalogTypeLabels[batch.catalogType] : '公司制裁目录';
|
|
364
|
-
|
|
381
|
+
const warningMessage = kind === 'controlled'
|
|
382
|
+
? '覆盖后,新文件只有在解析成功并通过基础校验后,旧批次及其文件导入商品才会被逻辑删除。'
|
|
383
|
+
: '只有新文件解析成功并通过基础校验后,旧批次及其公司记录才会被逻辑删除。';
|
|
384
|
+
return (_jsx(Modal, { open: open, title: kind === 'controlled' ? '确认覆盖历史目录?' : '确认覆盖历史制裁目录?', onClose: onCancel, footer: _jsxs(_Fragment, { children: [_jsxs("button", { type: "button", className: "tcw-button tcw-button--secondary", onClick: onCancel, children: [_jsx("i", { className: "ri-close-line", "aria-hidden": "true" }), "\u53D6\u6D88"] }), _jsxs("button", { type: "button", className: "tcw-button tcw-button--danger", disabled: busy, onClick: onConfirm, children: [_jsx("i", { className: "ri-upload-cloud-2-line", "aria-hidden": "true" }), "\u786E\u8BA4\u4E0A\u4F20\u5E76\u8986\u76D6"] })] }), children: _jsxs("div", { className: `tcw-catalog-replace-dialog tcw-catalog-replace-dialog--${kind}`, children: [_jsx("div", { className: "tcw-catalog-replace-warning tcw-notice--risk", role: "alert", children: _jsx("span", { children: warningMessage }) }), _jsxs("dl", { className: "tcw-catalog-replace-summary", children: [kind === 'controlled' && _jsxs(_Fragment, { children: [_jsx("dt", { children: "\u76EE\u5F55\u7C7B\u578B\uFF1A" }), _jsx("dd", { children: _jsx("span", { className: "tcw-catalog-type-badge", children: typeLabel }) })] }), _jsx("dt", { children: "\u88AB\u8986\u76D6\u6587\u4EF6\uFF1A" }), _jsx("dd", { children: batch?.sourceFileName || '—' }), _jsx("dt", { children: kind === 'controlled' ? '现有商品数量:' : '现有记录数量:' }), _jsx("dd", { className: "tcw-catalog-replace-count", children: batch?.totalCount ?? 0 }), _jsx("dt", { children: "\u6700\u8FD1\u66F4\u65B0\u65F6\u95F4\uFF1A" }), _jsx("dd", { children: formatBeijingDateTime(batch?.updatedAt || batch?.createdAt) }), _jsx("dt", { children: "\u65B0\u4E0A\u4F20\u6587\u4EF6\uFF1A" }), _jsx("dd", { children: fileName || '—' })] })] }) }));
|
|
365
385
|
}
|
|
366
386
|
export function ControlledGoodsFormDialog({ open, mode, item, busy = false, onClose, onSave }) {
|
|
367
387
|
const [form, setForm] = React.useState(() => controlledForm(item));
|
|
@@ -396,7 +416,7 @@ export function SanctionedCompanyDialog({ open, mode, item, busy = false, onClos
|
|
|
396
416
|
setForm(sanctionedForm(item));
|
|
397
417
|
setError('');
|
|
398
418
|
} }, [open, item]);
|
|
399
|
-
const editor = _jsx(SanctionedCompanyFields, { value: form, disabled: mode === 'detail', onChange: setForm });
|
|
419
|
+
const editor = _jsx(SanctionedCompanyFields, { value: form, disabled: mode === 'detail', singleLineReason: mode === 'create', onChange: setForm });
|
|
400
420
|
const save = async () => {
|
|
401
421
|
const validationError = validateSanctionedCompanyInput(form);
|
|
402
422
|
if (validationError) {
|
|
@@ -415,7 +435,7 @@ export function SanctionedCompanyDialog({ open, mode, item, busy = false, onClos
|
|
|
415
435
|
? _jsxs("button", { type: "button", className: "tcw-button tcw-button--primary", onClick: onClose, children: [_jsx("i", { className: "ri-close-line", "aria-hidden": "true" }), "\u5173\u95ED"] })
|
|
416
436
|
: _jsxs(_Fragment, { children: [_jsxs("button", { type: "button", className: "tcw-button tcw-button--secondary", onClick: onClose, children: [_jsx("i", { className: "ri-close-line", "aria-hidden": "true" }), "\u53D6\u6D88"] }), _jsxs("button", { type: "button", className: "tcw-button tcw-button--primary", disabled: busy, onClick: () => void save(), children: [_jsx("i", { className: "ri-save-line", "aria-hidden": "true" }), "\u4FDD\u5B58"] })] });
|
|
417
437
|
const withEvidence = mode !== 'create' && item?.sourceType === 'FILE_IMPORT';
|
|
418
|
-
return (_jsx(Modal, { open: open, title: title, size: withEvidence ? 'wide' : 'normal', onClose: onClose, footer: footer, children: _jsx("div", { className: `tcw-catalog-dialog ${withEvidence ? 'tcw-catalog-dialog--review tcw-catalog-dialog--sanction-review' : 'tcw-catalog-dialog--sanction-create'}`, children: withEvidence ? _jsx(ReviewSplit, { editor: _jsxs(_Fragment, { children: [editor, error && _jsx("p", { role: "alert", children: error })] }), source: _jsx(SourceEvidence, { item: item }), sourceTitle: "\u6765\u6E90\u8BC1\u636E\uFF08\u53EA\u8BFB\uFF09" }) : _jsxs(_Fragment, { children: [mode === 'create' && _jsx(CatalogManualSource, {}), editor, error && _jsx("p", { role: "alert", children: error })] }) }) }));
|
|
438
|
+
return (_jsx(Modal, { open: open, title: title, size: withEvidence ? 'wide' : 'normal', onClose: onClose, footer: footer, children: _jsx("div", { className: `tcw-catalog-dialog ${withEvidence ? 'tcw-catalog-dialog--review tcw-catalog-dialog--sanction-review' : 'tcw-catalog-dialog--sanction-create'}`, children: withEvidence ? _jsx(ReviewSplit, { editor: _jsxs(_Fragment, { children: [editor, error && _jsx("p", { role: "alert", children: error })] }), source: _jsx(SourceEvidence, { item: item }), editorTitle: "\u7F16\u8F91\u4FE1\u606F", sourceTitle: "\u6765\u6E90\u8BC1\u636E\uFF08\u53EA\u8BFB\uFF09" }) : _jsxs(_Fragment, { children: [mode === 'create' && _jsx(CatalogManualSource, {}), editor, error && _jsx("p", { role: "alert", children: error })] }) }) }));
|
|
419
439
|
}
|
|
420
440
|
export function validateSanctionedCompanyInput(input) {
|
|
421
441
|
if (!input.companyName.trim() || !input.sanctionListSource?.trim() || !input.effectiveDate?.trim() || !input.sanctionReason?.trim()) {
|
|
@@ -423,43 +443,50 @@ export function validateSanctionedCompanyInput(input) {
|
|
|
423
443
|
}
|
|
424
444
|
return '';
|
|
425
445
|
}
|
|
426
|
-
function SanctionedCompanyFields({ value, disabled, onChange }) {
|
|
427
|
-
return _jsxs(FieldGrid, { columns: 1, children: [_jsx(FormField, { label: "\u516C\u53F8\u540D\u79F0", required: true, children: _jsx("input", { disabled: disabled, placeholder: "\u8BF7\u8F93\u5165\u516C\u53F8\u540D\u79F0", value: value.companyName, onChange: (event) => onChange({ ...value, companyName: event.target.value }) }) }), _jsx(FormField, { label: "\u56FD\u5BB6\u6216\u5730\u533A", children: _jsx("input", { disabled: disabled, placeholder: "\u8BF7\u8F93\u5165\u56FD\u5BB6\u6216\u5730\u533A", value: value.countryOrRegion ?? '', onChange: (event) => onChange({ ...value, countryOrRegion: event.target.value }) }) }), _jsx(FormField, { label: "\u5236\u88C1\u540D\u5355\u6765\u6E90", required: true, children: _jsx("input", { disabled: disabled, placeholder: "\u8BF7\u8F93\u5165\u5236\u88C1\u540D\u5355\u6765\u6E90", value: value.sanctionListSource ?? '', onChange: (event) => onChange({ ...value, sanctionListSource: event.target.value }) }) }), _jsx(FormField, { label: "\u53D1\u5E03/\u751F\u6548\u65E5\u671F", required: true, children: _jsx("input", { type: "date", disabled: disabled, value: value.effectiveDate ?? '', onChange: (event) => onChange({ ...value, effectiveDate: event.target.value }) }) }), _jsx(FormField, { label: "\u5236\u88C1\u539F\u56E0", required: true, children: _jsxs("div", { className: "tcw-counted-textarea", children: [_jsx("textarea", { disabled: disabled, rows: 4, maxLength: 500, placeholder: "\u8BF7\u8F93\u5165\u5236\u88C1\u539F\u56E0", value: value.sanctionReason ?? '', onChange: (event) => onChange({ ...value, sanctionReason: event.target.value }) }), _jsxs("small", { className: "tcw-character-count", children: [value.sanctionReason?.length ?? 0, "/500"] })] }) }), _jsx(FormField, { label: "\u5907\u6CE8", children: _jsxs("div", { className: "tcw-counted-textarea", children: [_jsx("textarea", { disabled: disabled, rows: 3, maxLength: 500, placeholder: "\u8BF7\u8F93\u5165\u5907\u6CE8\uFF08\u53EF\u9009\uFF09", value: value.remarks ?? '', onChange: (event) => onChange({ ...value, remarks: event.target.value }) }), _jsxs("small", { className: "tcw-character-count", children: [value.remarks?.length ?? 0, "/500"] })] }) })] });
|
|
446
|
+
function SanctionedCompanyFields({ value, disabled, singleLineReason, onChange }) {
|
|
447
|
+
return _jsxs(FieldGrid, { columns: 1, children: [_jsx(FormField, { label: "\u516C\u53F8\u540D\u79F0", required: true, children: _jsx("input", { disabled: disabled, placeholder: "\u8BF7\u8F93\u5165\u516C\u53F8\u540D\u79F0", value: value.companyName, onChange: (event) => onChange({ ...value, companyName: event.target.value }) }) }), _jsx(FormField, { label: "\u56FD\u5BB6\u6216\u5730\u533A", children: _jsx("input", { disabled: disabled, placeholder: "\u8BF7\u8F93\u5165\u56FD\u5BB6\u6216\u5730\u533A", value: value.countryOrRegion ?? '', onChange: (event) => onChange({ ...value, countryOrRegion: event.target.value }) }) }), _jsx(FormField, { label: "\u5236\u88C1\u540D\u5355\u6765\u6E90", required: true, children: _jsx("input", { disabled: disabled, placeholder: "\u8BF7\u8F93\u5165\u5236\u88C1\u540D\u5355\u6765\u6E90", value: value.sanctionListSource ?? '', onChange: (event) => onChange({ ...value, sanctionListSource: event.target.value }) }) }), _jsx(FormField, { label: "\u53D1\u5E03/\u751F\u6548\u65E5\u671F", required: true, children: _jsx("input", { type: "date", disabled: disabled, value: value.effectiveDate ?? '', onChange: (event) => onChange({ ...value, effectiveDate: event.target.value }) }) }), _jsx(FormField, { label: "\u5236\u88C1\u539F\u56E0", required: true, children: singleLineReason ? _jsx("input", { disabled: disabled, maxLength: 500, placeholder: "\u8BF7\u8F93\u5165\u5236\u88C1\u539F\u56E0", value: value.sanctionReason ?? '', onChange: (event) => onChange({ ...value, sanctionReason: event.target.value }) }) : _jsxs("div", { className: "tcw-counted-textarea", children: [_jsx("textarea", { disabled: disabled, rows: 4, maxLength: 500, placeholder: "\u8BF7\u8F93\u5165\u5236\u88C1\u539F\u56E0", value: value.sanctionReason ?? '', onChange: (event) => onChange({ ...value, sanctionReason: event.target.value }) }), _jsxs("small", { className: "tcw-character-count", children: [value.sanctionReason?.length ?? 0, "/500"] })] }) }), _jsx(FormField, { label: "\u5907\u6CE8", children: _jsxs("div", { className: "tcw-counted-textarea", children: [_jsx("textarea", { disabled: disabled, rows: 3, maxLength: 500, placeholder: "\u8BF7\u8F93\u5165\u5907\u6CE8\uFF08\u53EF\u9009\uFF09", value: value.remarks ?? '', onChange: (event) => onChange({ ...value, remarks: event.target.value }) }), _jsxs("small", { className: "tcw-character-count", children: [value.remarks?.length ?? 0, "/500"] })] }) })] });
|
|
428
448
|
}
|
|
429
449
|
function CatalogManualSource() {
|
|
430
450
|
return _jsxs("div", { className: "tcw-catalog-manual-source", "aria-label": "\u6765\u6E90\u7C7B\u578B\uFF1A\u624B\u5DE5\u65B0\u589E", children: [_jsx("span", { children: "\u6765\u6E90\u7C7B\u578B\uFF1A" }), _jsx("strong", { className: "tcw-catalog-source-badge", children: "\u624B\u5DE5\u65B0\u589E" })] });
|
|
431
451
|
}
|
|
432
452
|
function SourceEvidence({ item }) {
|
|
433
|
-
return _jsxs(FieldGrid, { columns: 1, children: [_jsx(FormField, { label: "\u6765\u6E90\u7C7B\u578B", children: _jsx("input", { disabled: true, value: labelFor(item?.sourceType, sourceTypeLabels) }) }), _jsx(FormField, { label: "\u6765\u6E90\u6587\u4EF6", children: _jsx("input", { disabled: true, value: item?.sourceFileName ?? '—' }) }), _jsx(FormField, { label: "\u6765\u6E90\u4F4D\u7F6E", children: _jsx("input", { disabled: true, value: item?.sourceLocation ?? '—' }) }), _jsx(FormField, { label: "\u6765\u6E90\u539F\u6587", children: _jsx("textarea", { disabled: true, rows:
|
|
453
|
+
return _jsxs(FieldGrid, { columns: 1, children: [_jsx(FormField, { label: "\u6765\u6E90\u7C7B\u578B", children: _jsx("input", { disabled: true, value: labelFor(item?.sourceType, sourceTypeLabels) }) }), _jsx(FormField, { label: "\u6765\u6E90\u6587\u4EF6", children: _jsx("input", { disabled: true, value: item?.sourceFileName ?? '—' }) }), _jsx(FormField, { label: "\u6765\u6E90\u4F4D\u7F6E", children: _jsx("input", { disabled: true, value: item?.sourceLocation ?? '—' }) }), _jsx(FormField, { label: "\u6765\u6E90\u539F\u6587", children: _jsx("textarea", { disabled: true, rows: 5, value: item?.sourceContent ?? '—' }) })] });
|
|
434
454
|
}
|
|
435
455
|
export function CatalogImportSummaryNotice({ documentType, batch, busy, onDownload, onView }) {
|
|
436
456
|
const issueCount = (batch.duplicateCount ?? 0) + (batch.invalidCount ?? 0);
|
|
457
|
+
if (issueCount === 0)
|
|
458
|
+
return null;
|
|
437
459
|
const initialIssueType = (batch.duplicateCount ?? 0) > 0 ? 'DUPLICATE' : 'INVALID';
|
|
438
|
-
return _jsxs(InfoNotice, { children: [_jsxs("span", { children: ["\u5BFC\u5165\
|
|
460
|
+
return _jsxs(InfoNotice, { children: [_jsxs("span", { children: ["\u5BFC\u5165\u95EE\u9898\uFF1A\u91CD\u590D ", batch.duplicateCount ?? 0, "\uFF0C\u65E0\u6548 ", batch.invalidCount ?? 0, "\u3002"] }), _jsxs("button", { type: "button", className: "tcw-button tcw-button--secondary", disabled: busy || !onView, onClick: () => void onView?.(batch.id, initialIssueType), children: [_jsx("i", { className: "ri-list-check-2", "aria-hidden": "true" }), "\u67E5\u770B\u660E\u7EC6"] }), _jsxs("button", { type: "button", className: "tcw-button tcw-button--secondary", disabled: busy, onClick: () => void onDownload(documentType, batch.id).catch(() => undefined), children: [_jsx("i", { className: "ri-download-line", "aria-hidden": "true" }), "\u4E0B\u8F7D Excel"] })] });
|
|
439
461
|
}
|
|
440
462
|
export function CatalogImportStatusNotice({ task, busy, onRetry }) {
|
|
441
463
|
if (task.status !== 'FAILED' && task.status !== 'TIMED_OUT')
|
|
442
464
|
return null;
|
|
443
465
|
return _jsxs("div", { className: "tcw-catalog-import-failure", role: "alert", children: [_jsxs("div", { children: [_jsx("strong", { children: task.status === 'TIMED_OUT' ? '目录解析超时' : '目录解析失败' }), _jsx("p", { children: task.errorMessage || '解析任务未完成,请重新解析。' })] }), _jsxs("button", { type: "button", className: "tcw-button tcw-button--primary", disabled: busy, onClick: () => void onRetry(task.id).catch(() => undefined), children: [_jsx("i", { className: "ri-restart-line", "aria-hidden": "true" }), "\u91CD\u65B0\u89E3\u6790"] })] });
|
|
444
466
|
}
|
|
445
|
-
export function CatalogImportIssuesDialog({ open, documentType, batchId, issueType, collection, loading, error, busy, onClose, onIssueTypeChange, onPageChange, onRetry }) {
|
|
467
|
+
export function CatalogImportIssuesDialog({ open, documentType, batchId, issueType, collection, loading, error, busy, onClose, onIssueTypeChange, onPageChange, onPageSizeChange, onRetry }) {
|
|
446
468
|
const columns = [
|
|
447
|
-
{ type: 'text', key: 'issueType', header: '问题类型', width: 100, format: (value) => value === 'DUPLICATE' ? '重复' : '无效' },
|
|
448
|
-
{ type: 'text', key: 'sourceSequence', header: '来源序号', width: 100 },
|
|
469
|
+
{ type: 'text', key: 'issueType', header: '问题类型', width: 100, truncate: false, format: (value) => value === 'DUPLICATE' ? '重复' : '无效' },
|
|
470
|
+
{ type: 'text', key: 'sourceSequence', header: '来源序号', width: 100, truncate: false },
|
|
449
471
|
{ type: 'ellipsis', key: 'sourceLocation', header: '来源位置', width: 160, maxWidth: 160 },
|
|
450
|
-
{ type: 'ellipsis', key: 'sourceContent', header: '来源原文', width: 260, maxWidth: 260 },
|
|
451
472
|
{ type: 'ellipsis', key: 'message', header: '问题说明', width: 220, maxWidth: 220 }
|
|
452
473
|
];
|
|
453
|
-
return _jsxs(Modal, { open: open, title: "\u5BFC\u5165\u95EE\u9898\u660E\u7EC6", size: "wide", onClose: onClose, footer: _jsxs("button", { type: "button", className: "tcw-button tcw-button--primary", onClick: onClose, children: [_jsx("i", { className: "ri-close-line", "aria-hidden": "true" }), "\u5173\u95ED"] }), children: [_jsxs("div", { className: "tcw-catalog-issue-toolbar", "data-document-type": documentType, "data-batch-id": batchId, children: [_jsxs("button", { type: "button", className: issueType === 'DUPLICATE' ? 'is-active' : '', disabled: busy || loading, onClick: () => onIssueTypeChange('DUPLICATE'), children: [_jsx("i", { className: "ri-file-copy-line", "aria-hidden": "true" }), "\u91CD\u590D"] }), _jsxs("button", { type: "button", className: issueType === 'INVALID' ? 'is-active' : '', disabled: busy || loading, onClick: () => onIssueTypeChange('INVALID'), children: [_jsx("i", { className: "ri-error-warning-line", "aria-hidden": "true" }), "\u65E0\u6548"] })] }), loading && _jsx("p", { className: "tcw-catalog-issue-status", role: "status", children: "\u6B63\u5728\u52A0\u8F7D\u5BFC\u5165\u95EE\u9898\u660E\u7EC6..." }), error && _jsxs("div", { className: "tcw-catalog-issue-error", role: "alert", children: [_jsx("span", { children: error }), _jsxs("button", { type: "button", className: "tcw-button tcw-button--secondary", disabled: busy || loading, onClick: onRetry, children: [_jsx("i", { className: "ri-refresh-line", "aria-hidden": "true" }), "\u91CD\u65B0\u52A0\u8F7D"] })] }), !loading && !error && _jsx(DataGrid, { columns: columns, rows: collection.items, rowKey: "id", pagination: collectionPagination(collection), onPageChange: onPageChange, emptyText: issueType === 'DUPLICATE' ? '暂无重复数据' : '暂无无效数据' })] });
|
|
474
|
+
return _jsxs(Modal, { open: open, title: "\u5BFC\u5165\u95EE\u9898\u660E\u7EC6", size: "wide", onClose: onClose, footer: _jsxs("button", { type: "button", className: "tcw-button tcw-button--primary", onClick: onClose, children: [_jsx("i", { className: "ri-close-line", "aria-hidden": "true" }), "\u5173\u95ED"] }), children: [_jsxs("div", { className: "tcw-catalog-issue-toolbar", "data-document-type": documentType, "data-batch-id": batchId, children: [_jsxs("button", { type: "button", className: issueType === 'DUPLICATE' ? 'is-active' : '', disabled: busy || loading, onClick: () => onIssueTypeChange('DUPLICATE'), children: [_jsx("i", { className: "ri-file-copy-line", "aria-hidden": "true" }), "\u91CD\u590D"] }), _jsxs("button", { type: "button", className: issueType === 'INVALID' ? 'is-active' : '', disabled: busy || loading, onClick: () => onIssueTypeChange('INVALID'), children: [_jsx("i", { className: "ri-error-warning-line", "aria-hidden": "true" }), "\u65E0\u6548"] })] }), loading && _jsx("p", { className: "tcw-catalog-issue-status", role: "status", children: "\u6B63\u5728\u52A0\u8F7D\u5BFC\u5165\u95EE\u9898\u660E\u7EC6..." }), error && _jsxs("div", { className: "tcw-catalog-issue-error", role: "alert", children: [_jsx("span", { children: error }), _jsxs("button", { type: "button", className: "tcw-button tcw-button--secondary", disabled: busy || loading, onClick: onRetry, children: [_jsx("i", { className: "ri-refresh-line", "aria-hidden": "true" }), "\u91CD\u65B0\u52A0\u8F7D"] })] }), !loading && !error && _jsx(DataGrid, { columns: columns, rows: collection.items, rowKey: "id", pagination: collectionPagination(collection), onPageChange: onPageChange, onPageSizeChange: onPageSizeChange, emptyText: issueType === 'DUPLICATE' ? '暂无重复数据' : '暂无无效数据' })] });
|
|
475
|
+
}
|
|
476
|
+
export function hsSearchPageSizeRequest(keywords, pageSize) {
|
|
477
|
+
return { keywords: keywords.trim(), page: 1, pageSize };
|
|
478
|
+
}
|
|
479
|
+
function emptyHsSearchData(page = 1, pageSize = 20) {
|
|
480
|
+
return { items: [], total: 0, page, pageSize, pageCount: 1 };
|
|
454
481
|
}
|
|
455
482
|
export function HsCodeLookupDialog({ open, initialQuery, onAction, onClose }) {
|
|
456
483
|
const [query, setQuery] = React.useState(initialQuery);
|
|
457
|
-
const [
|
|
458
|
-
const [results, setResults] = React.useState([]);
|
|
484
|
+
const [collection, setCollection] = React.useState(() => emptyHsSearchData());
|
|
459
485
|
const [detail, setDetail] = React.useState(null);
|
|
460
486
|
const [error, setError] = React.useState('');
|
|
461
487
|
const [loading, setLoading] = React.useState(false);
|
|
462
|
-
const
|
|
488
|
+
const hsRequestGateRef = React.useRef(createLatestRequestGate());
|
|
489
|
+
const search = React.useCallback(async (keywords, nextPage = 1, pageSize = 20) => {
|
|
463
490
|
const normalized = keywords.trim();
|
|
464
491
|
if (!normalized) {
|
|
465
492
|
setError('请输入商品名称或海关编码');
|
|
@@ -468,56 +495,73 @@ export function HsCodeLookupDialog({ open, initialQuery, onAction, onClose }) {
|
|
|
468
495
|
setLoading(true);
|
|
469
496
|
setError('');
|
|
470
497
|
setDetail(null);
|
|
498
|
+
const requestId = hsRequestGateRef.current.begin();
|
|
471
499
|
try {
|
|
472
|
-
const response = await onAction('search_hs_code', { keywords: normalized, page: nextPage });
|
|
500
|
+
const response = await onAction('search_hs_code', { keywords: normalized, page: nextPage, pageSize });
|
|
473
501
|
if (response.success === false)
|
|
474
502
|
throw new Error(response.message || '海关编码查询失败');
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
503
|
+
if (!hsRequestGateRef.current.commit(requestId))
|
|
504
|
+
return;
|
|
505
|
+
setCollection(isHsSearchData(response.data) ? response.data : emptyHsSearchData(nextPage, pageSize));
|
|
478
506
|
}
|
|
479
507
|
catch (cause) {
|
|
508
|
+
if (!hsRequestGateRef.current.isLatest(requestId))
|
|
509
|
+
return;
|
|
480
510
|
setError(errorMessage(cause, '海关编码查询失败'));
|
|
481
|
-
|
|
511
|
+
setCollection(emptyHsSearchData(nextPage, pageSize));
|
|
482
512
|
}
|
|
483
513
|
finally {
|
|
484
|
-
|
|
514
|
+
hsRequestGateRef.current.finish(requestId);
|
|
515
|
+
if (hsRequestGateRef.current.isLatest(requestId))
|
|
516
|
+
setLoading(false);
|
|
485
517
|
}
|
|
486
518
|
}, [onAction]);
|
|
487
519
|
React.useEffect(() => {
|
|
488
|
-
if (!open)
|
|
520
|
+
if (!open) {
|
|
521
|
+
hsRequestGateRef.current.invalidate();
|
|
522
|
+
setLoading(false);
|
|
489
523
|
return;
|
|
524
|
+
}
|
|
490
525
|
setQuery(initialQuery);
|
|
491
|
-
|
|
526
|
+
setCollection(emptyHsSearchData());
|
|
492
527
|
void search(initialQuery, 1);
|
|
528
|
+
return () => hsRequestGateRef.current.invalidate();
|
|
493
529
|
}, [open, initialQuery, search]);
|
|
494
530
|
const showDetail = async (result) => {
|
|
495
531
|
setLoading(true);
|
|
496
532
|
setError('');
|
|
533
|
+
const requestId = hsRequestGateRef.current.begin();
|
|
497
534
|
try {
|
|
498
535
|
const response = await onAction('get_hs_code_detail', { code: result.code, ...(result.detailUrl ? { detailUrl: result.detailUrl } : {}) });
|
|
499
536
|
if (response.success === false)
|
|
500
537
|
throw new Error(response.message || '海关编码详情查询失败');
|
|
538
|
+
if (!hsRequestGateRef.current.commit(requestId))
|
|
539
|
+
return;
|
|
501
540
|
setDetail(isRecord(response.data) ? response.data : {});
|
|
502
541
|
}
|
|
503
542
|
catch (cause) {
|
|
543
|
+
if (!hsRequestGateRef.current.isLatest(requestId))
|
|
544
|
+
return;
|
|
504
545
|
setError(errorMessage(cause, '海关编码详情查询失败'));
|
|
505
546
|
}
|
|
506
547
|
finally {
|
|
507
|
-
|
|
548
|
+
hsRequestGateRef.current.finish(requestId);
|
|
549
|
+
if (hsRequestGateRef.current.isLatest(requestId))
|
|
550
|
+
setLoading(false);
|
|
508
551
|
}
|
|
509
552
|
};
|
|
553
|
+
const close = () => { hsRequestGateRef.current.invalidate(); setLoading(false); onClose(); };
|
|
510
554
|
const columns = [
|
|
511
555
|
{ type: 'text', key: 'code', header: '海关编码', width: 130 },
|
|
512
556
|
{ type: 'ellipsis', key: 'name', header: '商品名称', width: 220, maxWidth: 220 },
|
|
513
557
|
{ type: 'ellipsis', key: 'englishName', header: '英文名称', width: 220, maxWidth: 220 },
|
|
514
|
-
{ type: 'text', key: 'unit', header: '单位', width: 90 },
|
|
515
|
-
{ type: 'text', key: 'taxRefundRate', header: '退税率', width: 90 }
|
|
558
|
+
{ type: 'text', key: 'unit', header: '单位', width: 90, truncate: false },
|
|
559
|
+
{ type: 'text', key: 'taxRefundRate', header: '退税率', width: 90, truncate: false }
|
|
516
560
|
];
|
|
517
|
-
return (_jsxs(Modal, { open: open, title: "\u6D77\u5173\u7F16\u7801\u67E5\u8BE2", size: "wide", onClose:
|
|
561
|
+
return (_jsxs(Modal, { open: open, title: "\u6D77\u5173\u7F16\u7801\u67E5\u8BE2", size: "wide", onClose: close, footer: _jsxs("button", { type: "button", className: "tcw-button tcw-button--primary", onClick: close, children: [_jsx("i", { className: "ri-close-line", "aria-hidden": "true" }), "\u5173\u95ED"] }), children: [_jsx("form", { onSubmit: (event) => { event.preventDefault(); void search(query, 1); }, children: _jsxs(FieldGrid, { columns: 2, children: [_jsx(FormField, { label: "\u5546\u54C1\u540D\u79F0\u6216\u6D77\u5173\u7F16\u7801", required: true, children: _jsx("input", { value: query, onChange: (event) => setQuery(event.target.value) }) }), _jsx("div", { children: _jsxs("button", { type: "submit", className: "tcw-button tcw-button--primary", disabled: loading, children: [_jsx("i", { className: "ri-search-line", "aria-hidden": "true" }), "\u67E5\u8BE2"] }) })] }) }), error && _jsx("p", { role: "alert", children: error }), detail ? _jsx(HsCodeDetail, { detail: detail, onBack: () => setDetail(null) }) : _jsx(_Fragment, { children: _jsx(DataGrid, { columns: columns, rows: collection.items, rowKey: (row) => row.code, pagination: collection, onPageChange: (page) => void search(query, page, collection.pageSize), onPageSizeChange: (pageSize) => { const request = hsSearchPageSizeRequest(query, pageSize); void search(request.keywords, request.page, request.pageSize); }, actions: (row) => _jsxs("button", { type: "button", onClick: () => void showDetail(row), children: [_jsx("i", { className: "ri-eye-line", "aria-hidden": "true" }), "\u8BE6\u60C5"] }), emptyText: loading ? '查询中' : '暂无海关编码结果' }) })] }));
|
|
518
562
|
}
|
|
519
563
|
function HsCodeDetail({ detail, onBack }) {
|
|
520
|
-
return _jsxs("section", { children: [_jsxs("button", { type: "button", className: "tcw-button tcw-button--secondary", onClick: onBack, children: [_jsx("i", { className: "ri-arrow-left-line", "aria-hidden": "true" }), "\u8FD4\u56DE\u67E5\u8BE2\u7ED3\u679C"] }), _jsxs("h3", { children: [detail.code || '—', " ", detail.name || detail.title || ''] }), _jsxs("p", { children: [_jsx("strong", { children: "\u67E5\u8BE2\u65F6\u95F4\uFF1A" }), detail.queriedAt
|
|
564
|
+
return _jsxs("section", { children: [_jsxs("button", { type: "button", className: "tcw-button tcw-button--secondary", onClick: onBack, children: [_jsx("i", { className: "ri-arrow-left-line", "aria-hidden": "true" }), "\u8FD4\u56DE\u67E5\u8BE2\u7ED3\u679C"] }), _jsxs("h3", { children: [detail.code || '—', " ", detail.name || detail.title || ''] }), _jsxs("p", { children: [_jsx("strong", { children: "\u67E5\u8BE2\u65F6\u95F4\uFF1A" }), formatBeijingDateTime(detail.queriedAt)] }), detail.sourceUrl && _jsxs("p", { children: [_jsx("strong", { children: "\u6570\u636E\u6765\u6E90\uFF1A" }), _jsx("a", { href: detail.sourceUrl, target: "_blank", rel: "noreferrer", children: detail.sourceUrl })] }), detail.sections?.map((section, index) => _jsxs("section", { children: [_jsx("h4", { children: section.title || '详情' }), section.rows?.map((row, rowIndex) => _jsxs("p", { children: [_jsxs("strong", { children: [row.label || '字段', "\uFF1A"] }), row.value || '—'] }, `${row.label ?? 'row'}-${rowIndex}`))] }, `${section.title ?? 'detail'}-${index}`))] });
|
|
521
565
|
}
|
|
522
566
|
export function hsLookupQuery(row, field) {
|
|
523
567
|
return String(row[field] ?? '').trim();
|
|
@@ -540,8 +584,8 @@ function sanctionedForm(item) {
|
|
|
540
584
|
remarks: item?.remarks ?? ''
|
|
541
585
|
};
|
|
542
586
|
}
|
|
543
|
-
function LinkButton({ children,
|
|
544
|
-
return _jsx("span", { className: "tcw-row-actions tcw-catalog-table-link", children: _jsxs("button", { type: "button", title: title, onClick: onClick, children: [_jsx("i", { className:
|
|
587
|
+
function LinkButton({ children, iconClass, title, ariaLabel, onClick }) {
|
|
588
|
+
return _jsx("span", { className: "tcw-row-actions tcw-catalog-table-link", children: _jsxs("button", { type: "button", title: title, "aria-label": ariaLabel, onClick: onClick, children: [_jsx("i", { className: iconClass, "aria-hidden": "true" }), _jsx("span", { children: children })] }) });
|
|
545
589
|
}
|
|
546
590
|
function options(labels) {
|
|
547
591
|
return Object.entries(labels).map(([value, label]) => ({ value, label: String(label) }));
|
|
@@ -550,7 +594,13 @@ function labelFor(value, labels) {
|
|
|
550
594
|
return typeof value === 'string' && value in labels ? labels[value] : value ? String(value) : '—';
|
|
551
595
|
}
|
|
552
596
|
function isHsSearchData(value) {
|
|
553
|
-
return isRecord(value)
|
|
597
|
+
return isRecord(value)
|
|
598
|
+
&& Array.isArray(value.items)
|
|
599
|
+
&& value.items.every((item) => isRecord(item) && typeof item.code === 'string')
|
|
600
|
+
&& Number.isInteger(value.total) && Number(value.total) >= 0
|
|
601
|
+
&& Number.isInteger(value.page) && Number(value.page) >= 1
|
|
602
|
+
&& isPaginationPageSize(Number(value.pageSize))
|
|
603
|
+
&& Number.isInteger(value.pageCount) && Number(value.pageCount) >= 1;
|
|
554
604
|
}
|
|
555
605
|
function isRecord(value) {
|
|
556
606
|
return Boolean(value && typeof value === 'object' && !Array.isArray(value));
|
|
@@ -563,7 +613,7 @@ function assertActionSucceeded(response) {
|
|
|
563
613
|
throw new Error(typeof response.message === 'string' && response.message ? response.message : '操作失败');
|
|
564
614
|
}
|
|
565
615
|
function collectionPagination(collection) {
|
|
566
|
-
const pageSize =
|
|
616
|
+
const pageSize = collection.pageSize ?? PAGE_SIZE;
|
|
567
617
|
const total = Number(collection.total) >= 0 ? Number(collection.total) : collection.items.length;
|
|
568
618
|
const pageCount = Math.max(1, Math.ceil(total / pageSize));
|
|
569
619
|
const page = Math.min(pageCount, Math.max(1, Number(collection.page) || 1));
|
|
@@ -575,7 +625,7 @@ function asImportCompletion(value) {
|
|
|
575
625
|
return value;
|
|
576
626
|
}
|
|
577
627
|
function isCatalogImportStartData(value) {
|
|
578
|
-
return isRecord(value) && isRecord(value.task) && typeof value.task.id === 'string' && typeof value.task.documentType === 'string' && typeof value.task.status === 'string'
|
|
628
|
+
return isRecord(value) && isRecord(value.task) && typeof value.task.id === 'string' && typeof value.task.documentType === 'string' && typeof value.task.status === 'string';
|
|
579
629
|
}
|
|
580
630
|
function delay(milliseconds) {
|
|
581
631
|
return new Promise((resolve) => setTimeout(resolve, milliseconds));
|