@chenchaolong/plugin-trade-compliance-workbench 1.0.5 → 1.0.7
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 +426 -169
- 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 +3 -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 +2 -0
- package/dist/lib/remote-ui/pages/analytics.d.ts.map +1 -1
- package/dist/lib/remote-ui/pages/analytics.js +11 -6
- package/dist/lib/remote-ui/pages/analytics.js.map +1 -1
- package/dist/lib/remote-ui/pages/catalogs.d.ts +37 -14
- package/dist/lib/remote-ui/pages/catalogs.d.ts.map +1 -1
- package/dist/lib/remote-ui/pages/catalogs.js +149 -91
- package/dist/lib/remote-ui/pages/catalogs.js.map +1 -1
- package/dist/lib/remote-ui/pages/contracts.d.ts +11 -6
- package/dist/lib/remote-ui/pages/contracts.d.ts.map +1 -1
- package/dist/lib/remote-ui/pages/contracts.js +45 -33
- package/dist/lib/remote-ui/pages/contracts.js.map +1 -1
- package/dist/lib/remote-ui/pages/files.d.ts +29 -3
- package/dist/lib/remote-ui/pages/files.d.ts.map +1 -1
- package/dist/lib/remote-ui/pages/files.js +60 -16
- package/dist/lib/remote-ui/pages/files.js.map +1 -1
- package/dist/lib/remote-ui/pages/home.d.ts.map +1 -1
- package/dist/lib/remote-ui/pages/home.js +18 -18
- package/dist/lib/remote-ui/pages/home.js.map +1 -1
- package/dist/lib/remote-ui/pages/procurement.d.ts +29 -4
- package/dist/lib/remote-ui/pages/procurement.d.ts.map +1 -1
- package/dist/lib/remote-ui/pages/procurement.js +58 -63
- package/dist/lib/remote-ui/pages/procurement.js.map +1 -1
- package/dist/lib/remote-ui/pages/sales.d.ts +47 -1
- package/dist/lib/remote-ui/pages/sales.d.ts.map +1 -1
- package/dist/lib/remote-ui/pages/sales.js +60 -20
- package/dist/lib/remote-ui/pages/sales.js.map +1 -1
- package/dist/lib/remote-ui/pages/settings.d.ts +34 -1
- package/dist/lib/remote-ui/pages/settings.d.ts.map +1 -1
- package/dist/lib/remote-ui/pages/settings.js +120 -94
- 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 +2 -2
- 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.map +1 -1
- package/dist/lib/sales-file.service.js +5 -5
- package/dist/lib/sales-file.service.js.map +1 -1
- package/dist/lib/types.d.ts +5 -0
- package/dist/lib/types.d.ts.map +1 -1
- package/dist/lib/types.js +3 -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 +89 -91
- 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,7 +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';
|
|
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';
|
|
6
12
|
const PAGE_SIZE = 20;
|
|
7
13
|
const ACCEPTED_CATALOG_FILES = '.pdf,.doc,.docx,.xls,.xlsx,.png,.jpg,.jpeg,.webp';
|
|
8
14
|
const formatNotice = () => `支持 PDF、DOC、DOCX、XLS、XLSX、PNG、JPEG、WEBP,单文件最大 ${getConfiguredUploadPolicy().maxUploadMb} MB。`;
|
|
@@ -44,7 +50,6 @@ export function createCatalogActionApi({ onAction, onFileAction, onRefresh }) {
|
|
|
44
50
|
const mutate = async (actionKey, input) => {
|
|
45
51
|
const response = await onAction(actionKey, input);
|
|
46
52
|
assertActionSucceeded(response);
|
|
47
|
-
await onRefresh();
|
|
48
53
|
return response;
|
|
49
54
|
};
|
|
50
55
|
const mutateFile = async (actionKey, file, input) => {
|
|
@@ -79,26 +84,22 @@ export class CatalogImportError extends Error {
|
|
|
79
84
|
this.name = 'CatalogImportError';
|
|
80
85
|
}
|
|
81
86
|
}
|
|
82
|
-
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 }) {
|
|
83
88
|
assertActionSucceeded(start);
|
|
84
89
|
if (!isCatalogImportStartData(start.data))
|
|
85
90
|
throw new Error('上传响应缺少解析任务信息');
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
const deadline = now() + timeoutMs;
|
|
99
|
-
while (now() < deadline) {
|
|
100
|
-
await wait(intervalMs);
|
|
101
|
-
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;
|
|
102
103
|
const status = completion.task.status;
|
|
103
104
|
if (status === 'SUCCEEDED') {
|
|
104
105
|
if (!completion.batch)
|
|
@@ -111,13 +112,16 @@ export async function runCatalogImportOrchestration({ start, invokeClientCommand
|
|
|
111
112
|
if (!ACTIVE_IMPORT_STATUSES.has(status))
|
|
112
113
|
throw new Error(`不支持的导入任务状态:${status}`);
|
|
113
114
|
}
|
|
114
|
-
throw
|
|
115
|
+
throw observation.expired('目录任务观察窗口已结束', previous);
|
|
115
116
|
}
|
|
116
117
|
export function findActiveCatalogImportTask(items) {
|
|
117
118
|
return items
|
|
118
|
-
.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))
|
|
119
120
|
.sort((left, right) => String(right.updatedAt ?? '').localeCompare(String(left.updatedAt ?? '')))[0] ?? null;
|
|
120
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
|
+
}
|
|
121
125
|
export function findLatestFailedCatalogImportTask(items, documentType) {
|
|
122
126
|
const retriedTaskIds = new Set(items.map((task) => task.retryOfTaskId).filter((id) => Boolean(id)));
|
|
123
127
|
return items
|
|
@@ -128,17 +132,19 @@ export function createCatalogQueryScheduler(query, debounceMs = 300) {
|
|
|
128
132
|
let timer = null;
|
|
129
133
|
const cancel = () => { if (timer)
|
|
130
134
|
clearTimeout(timer); timer = null; };
|
|
131
|
-
const
|
|
135
|
+
const run = (page, filters, pageSize = PAGE_SIZE) => { cancel(); query(page, filters, pageSize); };
|
|
132
136
|
return {
|
|
133
|
-
schedule(type, filters) {
|
|
137
|
+
schedule(type, filters, pageSize = PAGE_SIZE) {
|
|
134
138
|
cancel();
|
|
135
139
|
if (type !== 'text') {
|
|
136
|
-
|
|
140
|
+
run(1, filters, pageSize);
|
|
137
141
|
return;
|
|
138
142
|
}
|
|
139
|
-
timer = setTimeout(() => { timer = null; query(filters); }, debounceMs);
|
|
143
|
+
timer = setTimeout(() => { timer = null; query(1, filters, pageSize); }, debounceMs);
|
|
140
144
|
},
|
|
141
|
-
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),
|
|
142
148
|
cancel
|
|
143
149
|
};
|
|
144
150
|
}
|
|
@@ -216,41 +222,47 @@ export function ControlledGoodsPage(props) {
|
|
|
216
222
|
if (props.command)
|
|
217
223
|
props.onCommandHandled?.();
|
|
218
224
|
}, [props.command, props.onCommandHandled]);
|
|
219
|
-
const query = React.useCallback((page, nextFilters) => props.onQuery({ page, pageSize
|
|
220
|
-
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]);
|
|
221
227
|
React.useEffect(() => () => scheduler.cancel(), [scheduler]);
|
|
222
228
|
React.useEffect(() => { issueControllerRef.current?.mount(); return () => issueControllerRef.current?.dispose(); }, []);
|
|
223
229
|
const changeFilter = (key, value) => {
|
|
224
230
|
const nextFilters = { ...filters, [key]: value };
|
|
225
231
|
setFilters(nextFilters);
|
|
226
|
-
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);
|
|
227
233
|
};
|
|
228
|
-
const queryIssues = async (batchId, issueType, page = 1) => {
|
|
234
|
+
const queryIssues = async (batchId, issueType, page = 1, pageSize = PAGE_SIZE) => {
|
|
229
235
|
if (!props.onIssueQuery)
|
|
230
236
|
return;
|
|
231
|
-
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 });
|
|
232
238
|
};
|
|
233
239
|
const displayedSummary = summary ?? (props.importCompletion?.task.documentType === 'CONTROLLED_CATALOG' ? props.importCompletion.batch ?? null : null);
|
|
234
240
|
const columns = [
|
|
235
|
-
{ type: 'custom', key: 'productName', header: '商品名称', width: 180, render: (_, row) => _jsx(LinkButton, { icon: "ri-search-line", title:
|
|
236
|
-
{ type: 'custom', key: 'hsCode', header: '海关编码', width: 130, render: (_, row) => row.hsCode ? _jsx(LinkButton, { icon: "ri-search-line", title:
|
|
237
|
-
{ 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, { icon: "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, { icon: "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) },
|
|
238
244
|
{ type: 'ellipsis', key: 'controlDescription', header: '管控说明', width: 220, maxWidth: 220 },
|
|
239
|
-
{ 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) },
|
|
240
246
|
{ type: 'ellipsis', key: 'sourceFileName', header: '来源文件', width: 190, maxWidth: 190 },
|
|
241
|
-
{ type: 'text', key: 'updatedAt', header: '更新时间', width: 170 }
|
|
247
|
+
{ type: 'text', key: 'updatedAt', header: '更新时间', width: 170, truncate: false, format: formatBeijingDateTime }
|
|
242
248
|
];
|
|
243
|
-
return (_jsxs("section", { className: "tcw-catalog-page", "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", actions: (row) => _jsxs(_Fragment, { children: [_jsxs("button", { type: "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: "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) => {
|
|
244
250
|
const response = await api.uploadControlledCatalog(file, input);
|
|
245
251
|
const completion = asImportCompletion(response.data);
|
|
246
252
|
if (completion.batch)
|
|
247
253
|
setSummary(completion.batch);
|
|
248
254
|
setUploadOpen(false);
|
|
249
|
-
} }), _jsx(ControlledGoodsFormDialog, { open: Boolean(form), mode: form?.mode ?? 'create', item: form?.item ?? null, busy: props.busy, onClose: () => setForm(null), onSave: async (input) => {
|
|
250
|
-
|
|
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)
|
|
251
262
|
void queryIssues(issueState.batchId, issueType); }, onPageChange: (page) => { if (issueState)
|
|
252
|
-
void queryIssues(issueState.batchId, issueState.issueType, page); },
|
|
253
|
-
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); } })] }));
|
|
254
266
|
}
|
|
255
267
|
export function SanctionedCompaniesPage(props) {
|
|
256
268
|
const [filters, setFilters] = React.useState({});
|
|
@@ -273,47 +285,53 @@ export function SanctionedCompaniesPage(props) {
|
|
|
273
285
|
if (props.command)
|
|
274
286
|
props.onCommandHandled?.();
|
|
275
287
|
}, [props.command, props.onCommandHandled]);
|
|
276
|
-
const query = React.useCallback((page, nextFilters) => props.onQuery({ page, pageSize
|
|
277
|
-
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]);
|
|
278
290
|
React.useEffect(() => () => scheduler.cancel(), [scheduler]);
|
|
279
291
|
React.useEffect(() => { issueControllerRef.current?.mount(); return () => issueControllerRef.current?.dispose(); }, []);
|
|
280
292
|
const changeFilter = (key, value) => {
|
|
281
293
|
const nextFilters = { ...filters, [key]: value };
|
|
282
294
|
setFilters(nextFilters);
|
|
283
|
-
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);
|
|
284
296
|
};
|
|
285
|
-
const queryIssues = async (batchId, issueType, page = 1) => {
|
|
297
|
+
const queryIssues = async (batchId, issueType, page = 1, pageSize = PAGE_SIZE) => {
|
|
286
298
|
if (!props.onIssueQuery)
|
|
287
299
|
return;
|
|
288
|
-
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 });
|
|
289
301
|
};
|
|
290
302
|
const displayedSummary = summary ?? (props.importCompletion?.task.documentType === 'SANCTION_CATALOG' ? props.importCompletion.batch ?? null : null);
|
|
291
303
|
const columns = [
|
|
292
|
-
{ type: 'custom', key: 'companyName', header: '公司名称', width: 210, render: (_, row) => _jsx(LinkButton, { icon: "ri-eye-line", title:
|
|
304
|
+
{ type: 'custom', key: 'companyName', header: '公司名称', width: 210, title: (_, row) => row.companyName || undefined, render: (_, row) => _jsx(LinkButton, { icon: "ri-eye-line", title: row.companyName || '—', ariaLabel: `查看制裁公司详情:${row.companyName || '—'}`, onClick: () => setForm({ mode: 'detail', item: row }), children: row.companyName || '—' }) },
|
|
293
305
|
{ type: 'text', key: 'countryOrRegion', header: '国家或地区', width: 120 },
|
|
294
306
|
{ type: 'ellipsis', key: 'sanctionListSource', header: '制裁名单来源', width: 190, maxWidth: 190 },
|
|
295
|
-
{ type: 'text', key: 'effectiveDate', header: '生效日期', width: 120 },
|
|
307
|
+
{ type: 'text', key: 'effectiveDate', header: '生效日期', width: 120, truncate: false, format: formatBusinessDate },
|
|
296
308
|
{ type: 'ellipsis', key: 'sanctionReason', header: '制裁原因', width: 220, maxWidth: 220 },
|
|
297
|
-
{ 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) },
|
|
298
310
|
{ type: 'ellipsis', key: 'sourceFileName', header: '来源文件', width: 190, maxWidth: 190 },
|
|
299
|
-
{ type: 'text', key: 'updatedAt', header: '更新时间', width: 170 }
|
|
311
|
+
{ type: 'text', key: 'updatedAt', header: '更新时间', width: 170, truncate: false, format: formatBeijingDateTime }
|
|
300
312
|
];
|
|
301
|
-
return (_jsxs("section", { className: "tcw-catalog-page", "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", actions: (row) => _jsxs(_Fragment, { children: [_jsxs("button", { type: "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: "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) => {
|
|
302
314
|
const response = await api.uploadSanctionCatalog(file, input);
|
|
303
315
|
const completion = asImportCompletion(response.data);
|
|
304
316
|
if (completion.batch)
|
|
305
317
|
setSummary(completion.batch);
|
|
306
318
|
setUploadOpen(false);
|
|
307
|
-
} }), _jsx(SanctionedCompanyDialog, { open: Boolean(form), mode: form?.mode ?? 'create', item: form?.item ?? null, busy: props.busy, onClose: () => setForm(null), onSave: async (input) => {
|
|
308
|
-
|
|
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)
|
|
309
326
|
void queryIssues(issueState.batchId, issueType); }, onPageChange: (page) => { if (issueState)
|
|
310
|
-
void queryIssues(issueState.batchId, issueState.issueType, page); },
|
|
311
|
-
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); } })] }));
|
|
312
330
|
}
|
|
313
331
|
export function CatalogCommands({ busy, onUpload, onCreate }) {
|
|
314
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"] })] });
|
|
315
333
|
}
|
|
316
|
-
export function CatalogUploadDialog({ open, kind, batches, busy = false, onClose, onUpload }) {
|
|
334
|
+
export function CatalogUploadDialog({ open, kind, batches, busy = false, onClose, onUpload, onNotify }) {
|
|
317
335
|
const [state, dispatch] = React.useReducer(catalogUploadReducer, initialCatalogUploadState);
|
|
318
336
|
const disabled = busy || state.submitting;
|
|
319
337
|
React.useEffect(() => {
|
|
@@ -334,7 +352,7 @@ export function CatalogUploadDialog({ open, kind, batches, busy = false, onClose
|
|
|
334
352
|
dispatch({ type: 'reset' });
|
|
335
353
|
}
|
|
336
354
|
catch (cause) {
|
|
337
|
-
|
|
355
|
+
onNotify?.({ message: errorMessage(cause, '目录上传失败'), tone: 'error' });
|
|
338
356
|
dispatch({ type: 'cancel-replace' });
|
|
339
357
|
}
|
|
340
358
|
finally {
|
|
@@ -360,7 +378,7 @@ export function CatalogUploadDialog({ open, kind, batches, busy = false, onClose
|
|
|
360
378
|
}
|
|
361
379
|
export function CatalogReplaceConfirmDialog({ open, kind, batch, fileName, busy = false, onCancel, onConfirm }) {
|
|
362
380
|
const typeLabel = batch?.catalogType ? catalogTypeLabels[batch.catalogType] : '公司制裁目录';
|
|
363
|
-
return (_jsx(
|
|
381
|
+
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: [_jsxs("div", { className: "tcw-catalog-replace-warning tcw-notice--risk", role: "alert", children: [_jsx("i", { className: "ri-error-warning-fill", "aria-hidden": "true" }), _jsxs("span", { children: ["\u53EA\u6709\u65B0\u6587\u4EF6\u89E3\u6790\u6210\u529F\u5E76\u901A\u8FC7\u57FA\u7840\u6821\u9A8C\u540E\uFF0C\u65E7\u6279\u6B21\u53CA\u5176", kind === 'controlled' ? '文件导入商品' : '公司记录', "\u624D\u4F1A\u88AB\u903B\u8F91\u5220\u9664\u3002"] })] }), _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 || '—' })] })] }) }));
|
|
364
382
|
}
|
|
365
383
|
export function ControlledGoodsFormDialog({ open, mode, item, busy = false, onClose, onSave }) {
|
|
366
384
|
const [form, setForm] = React.useState(() => controlledForm(item));
|
|
@@ -383,10 +401,10 @@ export function ControlledGoodsFormDialog({ open, mode, item, busy = false, onCl
|
|
|
383
401
|
}
|
|
384
402
|
};
|
|
385
403
|
const withEvidence = mode === 'review' && item?.sourceType === 'FILE_IMPORT';
|
|
386
|
-
return (_jsx(Modal, { open: open, title: mode === 'create' ? '新增管控商品' : '审核管控商品', size: withEvidence ? 'wide' : 'normal', onClose: onClose, footer: _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"] })] }), 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" }) : _jsxs(_Fragment, { children: [mode === 'create' && _jsx(
|
|
404
|
+
return (_jsx(Modal, { open: open, title: mode === 'create' ? '新增管控商品' : '审核管控商品', size: withEvidence ? 'wide' : 'normal', onClose: onClose, footer: _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"] })] }), children: _jsx("div", { className: `tcw-catalog-dialog ${withEvidence ? 'tcw-catalog-dialog--review tcw-catalog-dialog--controlled-review' : 'tcw-catalog-dialog--controlled-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 })] }) }) }));
|
|
387
405
|
}
|
|
388
406
|
function ControlledGoodsFields({ value, onChange }) {
|
|
389
|
-
return _jsxs(FieldGrid, { columns: 1, children: [
|
|
407
|
+
return _jsxs(FieldGrid, { columns: 1, children: [_jsxs(FormField, { label: "\u76EE\u5F55\u7C7B\u578B", required: true, children: [_jsxs("select", { value: value.catalogType, onChange: (event) => onChange({ ...value, catalogType: event.target.value }), children: [_jsx("option", { value: "", children: "\u8BF7\u9009\u62E9\u76EE\u5F55\u7C7B\u578B" }), options(catalogTypeLabels).map((option) => _jsx("option", { value: option.value, children: option.label }, option.value))] }), _jsx("small", { children: "\u519B\u6C11\u4E24\u7528\u7269\u8D44 / \u6D77\u5916\u5236\u88C1" })] }), _jsx(FormField, { label: "\u5546\u54C1\u540D\u79F0", required: true, children: _jsx("input", { placeholder: "\u8BF7\u8F93\u5165\u5546\u54C1\u540D\u79F0", value: value.productName, onChange: (event) => onChange({ ...value, productName: event.target.value }) }) }), _jsx(FormField, { label: "\u6D77\u5173\u7F16\u7801", children: _jsx("input", { placeholder: "\u8BF7\u8F93\u5165\u6D77\u5173\u7F16\u7801\uFF08\u53EF\u9009\uFF09", value: value.hsCode ?? '', onChange: (event) => onChange({ ...value, hsCode: event.target.value }) }) }), _jsx(FormField, { label: "\u7BA1\u63A7\u8BF4\u660E", children: _jsxs("div", { className: "tcw-counted-textarea", children: [_jsx("textarea", { rows: 5, maxLength: 500, placeholder: "\u8BF7\u8F93\u5165\u7BA1\u63A7\u8BF4\u660E\uFF08\u53EF\u9009\uFF09", value: value.controlDescription ?? '', onChange: (event) => onChange({ ...value, controlDescription: event.target.value }) }), _jsxs("small", { className: "tcw-character-count", children: [value.controlDescription?.length ?? 0, "/500"] })] }) })] });
|
|
390
408
|
}
|
|
391
409
|
export function SanctionedCompanyDialog({ open, mode, item, busy = false, onClose, onSave }) {
|
|
392
410
|
const [form, setForm] = React.useState(() => sanctionedForm(item));
|
|
@@ -397,8 +415,9 @@ export function SanctionedCompanyDialog({ open, mode, item, busy = false, onClos
|
|
|
397
415
|
} }, [open, item]);
|
|
398
416
|
const editor = _jsx(SanctionedCompanyFields, { value: form, disabled: mode === 'detail', onChange: setForm });
|
|
399
417
|
const save = async () => {
|
|
400
|
-
|
|
401
|
-
|
|
418
|
+
const validationError = validateSanctionedCompanyInput(form);
|
|
419
|
+
if (validationError) {
|
|
420
|
+
setError(validationError);
|
|
402
421
|
return;
|
|
403
422
|
}
|
|
404
423
|
try {
|
|
@@ -413,42 +432,58 @@ export function SanctionedCompanyDialog({ open, mode, item, busy = false, onClos
|
|
|
413
432
|
? _jsxs("button", { type: "button", className: "tcw-button tcw-button--primary", onClick: onClose, children: [_jsx("i", { className: "ri-close-line", "aria-hidden": "true" }), "\u5173\u95ED"] })
|
|
414
433
|
: _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"] })] });
|
|
415
434
|
const withEvidence = mode !== 'create' && item?.sourceType === 'FILE_IMPORT';
|
|
416
|
-
return (_jsx(Modal, { open: open, title: title, size: withEvidence ? 'wide' : 'normal', onClose: onClose, footer: footer, 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" }) : _jsxs(_Fragment, { children: [mode === 'create' && _jsx(
|
|
435
|
+
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 })] }) }) }));
|
|
436
|
+
}
|
|
437
|
+
export function validateSanctionedCompanyInput(input) {
|
|
438
|
+
if (!input.companyName.trim() || !input.sanctionListSource?.trim() || !input.effectiveDate?.trim() || !input.sanctionReason?.trim()) {
|
|
439
|
+
return '公司名称、制裁名单来源、发布/生效日期和制裁原因为必填项';
|
|
440
|
+
}
|
|
441
|
+
return '';
|
|
417
442
|
}
|
|
418
443
|
function SanctionedCompanyFields({ value, disabled, onChange }) {
|
|
419
|
-
return _jsxs(FieldGrid, { columns: 1, children: [_jsx(FormField, { label: "\u516C\u53F8\u540D\u79F0", required: true, children: _jsx("input", { disabled: disabled, value: value.companyName, onChange: (event) => onChange({ ...value, companyName: event.target.value }) }) }), _jsx(FormField, { label: "\u56FD\u5BB6\u6216\u5730\u533A", children: _jsx("input", { disabled: disabled, value: value.countryOrRegion ?? '', onChange: (event) => onChange({ ...value, countryOrRegion: event.target.value }) }) }), _jsx(FormField, { label: "\u5236\u88C1\u540D\u5355\u6765\u6E90", children: _jsx("input", { disabled: disabled, value: value.sanctionListSource ?? '', onChange: (event) => onChange({ ...value, sanctionListSource: event.target.value }) }) }), _jsx(FormField, { label: "\u53D1\u5E03/\u751F\u6548\u65E5\u671F", 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", children: _jsx("textarea", { disabled: disabled, rows: 4, value: value.sanctionReason ?? '', onChange: (event) => onChange({ ...value, sanctionReason: event.target.value }) }) }), _jsx(FormField, { label: "\u5907\u6CE8", children: _jsx("textarea", { disabled: disabled, rows: 3, value: value.remarks ?? '', onChange: (event) => onChange({ ...value, remarks: event.target.value }) }) })] });
|
|
444
|
+
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"] })] }) })] });
|
|
445
|
+
}
|
|
446
|
+
function CatalogManualSource() {
|
|
447
|
+
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" })] });
|
|
420
448
|
}
|
|
421
449
|
function SourceEvidence({ item }) {
|
|
422
|
-
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 ?? '—' }) })
|
|
450
|
+
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 ?? '—' }) })] });
|
|
423
451
|
}
|
|
424
452
|
export function CatalogImportSummaryNotice({ documentType, batch, busy, onDownload, onView }) {
|
|
425
453
|
const issueCount = (batch.duplicateCount ?? 0) + (batch.invalidCount ?? 0);
|
|
454
|
+
if (issueCount === 0)
|
|
455
|
+
return null;
|
|
426
456
|
const initialIssueType = (batch.duplicateCount ?? 0) > 0 ? 'DUPLICATE' : 'INVALID';
|
|
427
|
-
return _jsxs(InfoNotice, { children: [_jsxs("span", { children: ["\u5BFC\u5165\
|
|
457
|
+
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"] })] });
|
|
428
458
|
}
|
|
429
459
|
export function CatalogImportStatusNotice({ task, busy, onRetry }) {
|
|
430
460
|
if (task.status !== 'FAILED' && task.status !== 'TIMED_OUT')
|
|
431
461
|
return null;
|
|
432
462
|
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"] })] });
|
|
433
463
|
}
|
|
434
|
-
export function CatalogImportIssuesDialog({ open, documentType, batchId, issueType, collection, loading, error, busy, onClose, onIssueTypeChange, onPageChange, onRetry }) {
|
|
464
|
+
export function CatalogImportIssuesDialog({ open, documentType, batchId, issueType, collection, loading, error, busy, onClose, onIssueTypeChange, onPageChange, onPageSizeChange, onRetry }) {
|
|
435
465
|
const columns = [
|
|
436
|
-
{ type: 'text', key: 'issueType', header: '问题类型', width: 100, format: (value) => value === 'DUPLICATE' ? '重复' : '无效' },
|
|
437
|
-
{ type: 'text', key: 'sourceSequence', header: '来源序号', width: 100 },
|
|
466
|
+
{ type: 'text', key: 'issueType', header: '问题类型', width: 100, truncate: false, format: (value) => value === 'DUPLICATE' ? '重复' : '无效' },
|
|
467
|
+
{ type: 'text', key: 'sourceSequence', header: '来源序号', width: 100, truncate: false },
|
|
438
468
|
{ type: 'ellipsis', key: 'sourceLocation', header: '来源位置', width: 160, maxWidth: 160 },
|
|
439
|
-
{ type: 'ellipsis', key: 'sourceContent', header: '来源原文', width: 260, maxWidth: 260 },
|
|
440
469
|
{ type: 'ellipsis', key: 'message', header: '问题说明', width: 220, maxWidth: 220 }
|
|
441
470
|
];
|
|
442
|
-
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' ? '暂无重复数据' : '暂无无效数据' })] });
|
|
471
|
+
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' ? '暂无重复数据' : '暂无无效数据' })] });
|
|
472
|
+
}
|
|
473
|
+
export function hsSearchPageSizeRequest(keywords, pageSize) {
|
|
474
|
+
return { keywords: keywords.trim(), page: 1, pageSize };
|
|
475
|
+
}
|
|
476
|
+
function emptyHsSearchData(page = 1, pageSize = 20) {
|
|
477
|
+
return { items: [], total: 0, page, pageSize, pageCount: 1 };
|
|
443
478
|
}
|
|
444
479
|
export function HsCodeLookupDialog({ open, initialQuery, onAction, onClose }) {
|
|
445
480
|
const [query, setQuery] = React.useState(initialQuery);
|
|
446
|
-
const [
|
|
447
|
-
const [results, setResults] = React.useState([]);
|
|
481
|
+
const [collection, setCollection] = React.useState(() => emptyHsSearchData());
|
|
448
482
|
const [detail, setDetail] = React.useState(null);
|
|
449
483
|
const [error, setError] = React.useState('');
|
|
450
484
|
const [loading, setLoading] = React.useState(false);
|
|
451
|
-
const
|
|
485
|
+
const hsRequestGateRef = React.useRef(createLatestRequestGate());
|
|
486
|
+
const search = React.useCallback(async (keywords, nextPage = 1, pageSize = 20) => {
|
|
452
487
|
const normalized = keywords.trim();
|
|
453
488
|
if (!normalized) {
|
|
454
489
|
setError('请输入商品名称或海关编码');
|
|
@@ -457,56 +492,73 @@ export function HsCodeLookupDialog({ open, initialQuery, onAction, onClose }) {
|
|
|
457
492
|
setLoading(true);
|
|
458
493
|
setError('');
|
|
459
494
|
setDetail(null);
|
|
495
|
+
const requestId = hsRequestGateRef.current.begin();
|
|
460
496
|
try {
|
|
461
|
-
const response = await onAction('search_hs_code', { keywords: normalized, page: nextPage });
|
|
497
|
+
const response = await onAction('search_hs_code', { keywords: normalized, page: nextPage, pageSize });
|
|
462
498
|
if (response.success === false)
|
|
463
499
|
throw new Error(response.message || '海关编码查询失败');
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
500
|
+
if (!hsRequestGateRef.current.commit(requestId))
|
|
501
|
+
return;
|
|
502
|
+
setCollection(isHsSearchData(response.data) ? response.data : emptyHsSearchData(nextPage, pageSize));
|
|
467
503
|
}
|
|
468
504
|
catch (cause) {
|
|
505
|
+
if (!hsRequestGateRef.current.isLatest(requestId))
|
|
506
|
+
return;
|
|
469
507
|
setError(errorMessage(cause, '海关编码查询失败'));
|
|
470
|
-
|
|
508
|
+
setCollection(emptyHsSearchData(nextPage, pageSize));
|
|
471
509
|
}
|
|
472
510
|
finally {
|
|
473
|
-
|
|
511
|
+
hsRequestGateRef.current.finish(requestId);
|
|
512
|
+
if (hsRequestGateRef.current.isLatest(requestId))
|
|
513
|
+
setLoading(false);
|
|
474
514
|
}
|
|
475
515
|
}, [onAction]);
|
|
476
516
|
React.useEffect(() => {
|
|
477
|
-
if (!open)
|
|
517
|
+
if (!open) {
|
|
518
|
+
hsRequestGateRef.current.invalidate();
|
|
519
|
+
setLoading(false);
|
|
478
520
|
return;
|
|
521
|
+
}
|
|
479
522
|
setQuery(initialQuery);
|
|
480
|
-
|
|
523
|
+
setCollection(emptyHsSearchData());
|
|
481
524
|
void search(initialQuery, 1);
|
|
525
|
+
return () => hsRequestGateRef.current.invalidate();
|
|
482
526
|
}, [open, initialQuery, search]);
|
|
483
527
|
const showDetail = async (result) => {
|
|
484
528
|
setLoading(true);
|
|
485
529
|
setError('');
|
|
530
|
+
const requestId = hsRequestGateRef.current.begin();
|
|
486
531
|
try {
|
|
487
532
|
const response = await onAction('get_hs_code_detail', { code: result.code, ...(result.detailUrl ? { detailUrl: result.detailUrl } : {}) });
|
|
488
533
|
if (response.success === false)
|
|
489
534
|
throw new Error(response.message || '海关编码详情查询失败');
|
|
535
|
+
if (!hsRequestGateRef.current.commit(requestId))
|
|
536
|
+
return;
|
|
490
537
|
setDetail(isRecord(response.data) ? response.data : {});
|
|
491
538
|
}
|
|
492
539
|
catch (cause) {
|
|
540
|
+
if (!hsRequestGateRef.current.isLatest(requestId))
|
|
541
|
+
return;
|
|
493
542
|
setError(errorMessage(cause, '海关编码详情查询失败'));
|
|
494
543
|
}
|
|
495
544
|
finally {
|
|
496
|
-
|
|
545
|
+
hsRequestGateRef.current.finish(requestId);
|
|
546
|
+
if (hsRequestGateRef.current.isLatest(requestId))
|
|
547
|
+
setLoading(false);
|
|
497
548
|
}
|
|
498
549
|
};
|
|
550
|
+
const close = () => { hsRequestGateRef.current.invalidate(); setLoading(false); onClose(); };
|
|
499
551
|
const columns = [
|
|
500
552
|
{ type: 'text', key: 'code', header: '海关编码', width: 130 },
|
|
501
553
|
{ type: 'ellipsis', key: 'name', header: '商品名称', width: 220, maxWidth: 220 },
|
|
502
554
|
{ type: 'ellipsis', key: 'englishName', header: '英文名称', width: 220, maxWidth: 220 },
|
|
503
|
-
{ type: 'text', key: 'unit', header: '单位', width: 90 },
|
|
504
|
-
{ type: 'text', key: 'taxRefundRate', header: '退税率', width: 90 }
|
|
555
|
+
{ type: 'text', key: 'unit', header: '单位', width: 90, truncate: false },
|
|
556
|
+
{ type: 'text', key: 'taxRefundRate', header: '退税率', width: 90, truncate: false }
|
|
505
557
|
];
|
|
506
|
-
return (_jsxs(Modal, { open: open, title: "\u6D77\u5173\u7F16\u7801\u67E5\u8BE2", size: "wide", onClose:
|
|
558
|
+
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 ? '查询中' : '暂无海关编码结果' }) })] }));
|
|
507
559
|
}
|
|
508
560
|
function HsCodeDetail({ detail, onBack }) {
|
|
509
|
-
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
|
|
561
|
+
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}`))] });
|
|
510
562
|
}
|
|
511
563
|
export function hsLookupQuery(row, field) {
|
|
512
564
|
return String(row[field] ?? '').trim();
|
|
@@ -529,8 +581,8 @@ function sanctionedForm(item) {
|
|
|
529
581
|
remarks: item?.remarks ?? ''
|
|
530
582
|
};
|
|
531
583
|
}
|
|
532
|
-
function LinkButton({ children, icon, title, onClick }) {
|
|
533
|
-
return _jsx("span", { className: "tcw-row-actions", children: _jsxs("button", { type: "button", title: title, onClick: onClick, children: [_jsx("i", { className: icon, "aria-hidden": "true" }), children] }) });
|
|
584
|
+
function LinkButton({ children, icon, title, ariaLabel, onClick }) {
|
|
585
|
+
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: icon, "aria-hidden": "true" }), children] }) });
|
|
534
586
|
}
|
|
535
587
|
function options(labels) {
|
|
536
588
|
return Object.entries(labels).map(([value, label]) => ({ value, label: String(label) }));
|
|
@@ -539,7 +591,13 @@ function labelFor(value, labels) {
|
|
|
539
591
|
return typeof value === 'string' && value in labels ? labels[value] : value ? String(value) : '—';
|
|
540
592
|
}
|
|
541
593
|
function isHsSearchData(value) {
|
|
542
|
-
return isRecord(value)
|
|
594
|
+
return isRecord(value)
|
|
595
|
+
&& Array.isArray(value.items)
|
|
596
|
+
&& value.items.every((item) => isRecord(item) && typeof item.code === 'string')
|
|
597
|
+
&& Number.isInteger(value.total) && Number(value.total) >= 0
|
|
598
|
+
&& Number.isInteger(value.page) && Number(value.page) >= 1
|
|
599
|
+
&& isPaginationPageSize(Number(value.pageSize))
|
|
600
|
+
&& Number.isInteger(value.pageCount) && Number(value.pageCount) >= 1;
|
|
543
601
|
}
|
|
544
602
|
function isRecord(value) {
|
|
545
603
|
return Boolean(value && typeof value === 'object' && !Array.isArray(value));
|
|
@@ -552,7 +610,7 @@ function assertActionSucceeded(response) {
|
|
|
552
610
|
throw new Error(typeof response.message === 'string' && response.message ? response.message : '操作失败');
|
|
553
611
|
}
|
|
554
612
|
function collectionPagination(collection) {
|
|
555
|
-
const pageSize =
|
|
613
|
+
const pageSize = collection.pageSize ?? PAGE_SIZE;
|
|
556
614
|
const total = Number(collection.total) >= 0 ? Number(collection.total) : collection.items.length;
|
|
557
615
|
const pageCount = Math.max(1, Math.ceil(total / pageSize));
|
|
558
616
|
const page = Math.min(pageCount, Math.max(1, Number(collection.page) || 1));
|
|
@@ -564,7 +622,7 @@ function asImportCompletion(value) {
|
|
|
564
622
|
return value;
|
|
565
623
|
}
|
|
566
624
|
function isCatalogImportStartData(value) {
|
|
567
|
-
return isRecord(value) && isRecord(value.task) && typeof value.task.id === 'string' && typeof value.task.documentType === 'string' && typeof value.task.status === 'string'
|
|
625
|
+
return isRecord(value) && isRecord(value.task) && typeof value.task.id === 'string' && typeof value.task.documentType === 'string' && typeof value.task.status === 'string';
|
|
568
626
|
}
|
|
569
627
|
function delay(milliseconds) {
|
|
570
628
|
return new Promise((resolve) => setTimeout(resolve, milliseconds));
|