@bit-sun/business-component 4.2.0-alpha.3 → 4.2.0-alpha.30

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.
Files changed (138) hide show
  1. package/.fatherrc.ts +1 -1
  2. package/.umirc.ts +13 -9
  3. package/dist/components/Business/AddSelectBusiness/index.d.ts +3 -4
  4. package/dist/components/Business/BsLayouts/Components/AllFunc/drawContent.d.ts +1 -2
  5. package/dist/components/Business/BsLayouts/Components/ChooseStore/index.d.ts +1 -2
  6. package/dist/components/Business/BsLayouts/Components/CustomerMenu/MenuSetting/index.d.ts +1 -1
  7. package/dist/components/Business/BsLayouts/Components/CustomerMenu/MenuSetting/leftTree.d.ts +1 -1
  8. package/dist/components/Business/BsLayouts/Components/CustomerMenu/MenuSetting/rightTree.d.ts +2 -2
  9. package/dist/components/Business/BsLayouts/Components/CustomerMenu/globalMenu/DrawContent.d.ts +1 -2
  10. package/dist/components/Business/BsLayouts/Components/CustomerMenu/globalMenu/customMenuHeader.d.ts +1 -2
  11. package/dist/components/Business/BsLayouts/Components/CustomerMenu/index.d.ts +1 -1
  12. package/dist/components/Business/BsLayouts/Components/GlobalHeader/index.d.ts +1 -2
  13. package/dist/components/Business/BsLayouts/Components/RightContent/LoginModal.d.ts +1 -2
  14. package/dist/components/Business/BsLayouts/index.d.ts +1 -1
  15. package/dist/components/Business/BsSulaQueryTable/SearchItemSetting.d.ts +5 -5
  16. package/dist/components/Business/BsSulaQueryTable/index.d.ts +1 -2
  17. package/dist/components/Business/BsSulaQueryTable/setting.d.ts +6 -6
  18. package/dist/components/Business/BsSulaQueryTable/utils.d.ts +14 -15
  19. package/dist/components/Business/CommodityEntry/index.d.ts +1 -2
  20. package/dist/components/Business/CommonAlert/index.d.ts +1 -2
  21. package/dist/components/Business/CommonGuideWrapper/index.d.ts +3 -3
  22. package/dist/components/Business/DetailPageWrapper/index.d.ts +12 -12
  23. package/dist/components/Business/HomePageWrapper/index.d.ts +1 -2
  24. package/dist/components/Business/ImportExport/ExportButton/ModalTitle.d.ts +11 -0
  25. package/dist/components/Business/ImportExport/ExportButton/TemplatePicker.d.ts +13 -0
  26. package/dist/components/Business/ImportExport/ExportButton/index.d.ts +15 -0
  27. package/dist/components/Business/ImportExport/ImportWizard/Preview.d.ts +11 -0
  28. package/dist/components/Business/ImportExport/ImportWizard/index.d.ts +15 -0
  29. package/dist/components/Business/ImportExport/TaskDrawer/index.d.ts +15 -0
  30. package/dist/components/Business/ImportExport/TaskProgress/index.d.ts +15 -0
  31. package/dist/components/Business/ImportExport/businessFunction.d.ts +15 -0
  32. package/dist/components/Business/ImportExport/index.d.ts +14 -0
  33. package/dist/components/Business/ImportExport/sceneCapability.d.ts +20 -0
  34. package/dist/components/Business/ImportExport/services/importExport.d.ts +22 -0
  35. package/dist/components/Business/ImportExport/services/routing.d.ts +8 -0
  36. package/dist/components/Business/ImportExport/types.d.ts +134 -0
  37. package/dist/components/Business/ItemPropertySelector/index.d.ts +1 -2
  38. package/dist/components/Business/JsonQueryTable/components/FieldsModifyModal.d.ts +1 -2
  39. package/dist/components/Business/JsonQueryTable/components/FieldsSettingsTable.d.ts +1 -2
  40. package/dist/components/Business/JsonQueryTable/components/Formula.d.ts +1 -2
  41. package/dist/components/Business/JsonQueryTable/components/MaintainOptions.d.ts +1 -2
  42. package/dist/components/Business/JsonQueryTable/drawer/index.d.ts +1 -2
  43. package/dist/components/Business/PropertyModal/index.d.ts +1 -2
  44. package/dist/components/Business/PropertyModal/propertyGroup.d.ts +1 -1
  45. package/dist/components/Business/SearchSelect/index.d.ts +1 -1
  46. package/dist/components/Business/StateFlow/index.d.ts +1 -2
  47. package/dist/components/Business/SystemLog/index.d.ts +78 -0
  48. package/dist/components/Business/TreeSearchSelect/index.d.ts +1 -1
  49. package/dist/components/Business/TrialCalculation/index.d.ts +15 -0
  50. package/dist/components/Business/columnSettingTable/columnSetting.d.ts +6 -6
  51. package/dist/components/Business/columnSettingTable/components/TableSumComponent.d.ts +1 -2
  52. package/dist/components/Business/columnSettingTable/index.d.ts +3 -3
  53. package/dist/components/Business/columnSettingTable/sulaSettingTable.d.ts +3 -3
  54. package/dist/components/Business/columnSettingTable/utils.d.ts +1 -2
  55. package/dist/components/Business/moreTreeTable/FixedScrollBar.d.ts +1 -1
  56. package/dist/components/Common/ParagraphCopier/index.d.ts +1 -1
  57. package/dist/components/Common/Section/index.d.ts +1 -1
  58. package/dist/components/Functional/AddSelect/index.d.ts +1 -2
  59. package/dist/components/Functional/AuthButton/index.d.ts +1 -2
  60. package/dist/components/Functional/DataImport/index.d.ts +3 -3
  61. package/dist/components/Functional/DataValidation/index.d.ts +5 -5
  62. package/dist/components/Functional/ExportFunctions/ExportIcon/index.d.ts +1 -2
  63. package/dist/components/Functional/QueryMutipleInput/index.d.ts +1 -2
  64. package/dist/components/Functional/QueryMutipleSelect/index.d.ts +1 -2
  65. package/dist/components/Functional/SearchSelect/index.d.ts +1 -1
  66. package/dist/components/Functional/SearchSelect/pagination.d.ts +22 -0
  67. package/dist/components/Functional/SearchSelect/utils.d.ts +2 -3
  68. package/dist/components/Functional/TreeSearchSelect/index.d.ts +1 -2
  69. package/dist/components/Solution/RuleComponent/CustomPlugin/CustomSelector/CustomSelectorModal.d.ts +1 -1
  70. package/dist/components/Solution/RuleComponent/CustomPlugin/CustomSelector/index.d.ts +1 -2
  71. package/dist/components/Solution/RuleComponent/Formula.d.ts +1 -2
  72. package/dist/components/Solution/RuleComponent/InnerSelect.d.ts +1 -2
  73. package/dist/components/Solution/RuleComponent/RenderCompItem.d.ts +1 -2
  74. package/dist/components/Solution/RuleSetter/RuleInstance.d.ts +1 -2
  75. package/dist/components/Solution/RuleSetter/baseRule.d.ts +1 -1
  76. package/dist/components/Solution/RuleSetter/index.d.ts +1 -1
  77. package/dist/index.d.ts +5 -0
  78. package/dist/index.esm.js +5413 -1215
  79. package/dist/index.js +5433 -1208
  80. package/dist/plugin/TableColumnSetting/index.d.ts +5 -5
  81. package/dist/utils/TableUtils.d.ts +18 -19
  82. package/dist/utils/auth.d.ts +4 -0
  83. package/dist/utils/utils.d.ts +41 -0
  84. package/docs/error-record.md +52 -0
  85. package/docs/flow-control-readme.md +283 -0
  86. package/docs/task-records/2026-07-31-import-export-extraction.md +81 -0
  87. package/package.json +1 -1
  88. package/src/components/Business/AddSelectBusiness/index.tsx +3 -2
  89. package/src/components/Business/BsSulaQueryTable/index.md +57 -4
  90. package/src/components/Business/BsSulaQueryTable/index.tsx +174 -10
  91. package/src/components/Business/BsSulaQueryTable/setting.tsx +6 -0
  92. package/src/components/Business/DetailPageWrapper/index.tsx +81 -1
  93. package/src/components/Business/DetailPageWrapper/readme.md +89 -0
  94. package/src/components/Business/ImportExport/ExportButton/ModalTitle.tsx +27 -0
  95. package/src/components/Business/ImportExport/ExportButton/TemplatePicker.tsx +424 -0
  96. package/src/components/Business/ImportExport/ExportButton/index.module.less +382 -0
  97. package/src/components/Business/ImportExport/ExportButton/index.tsx +273 -0
  98. package/src/components/Business/ImportExport/ImportWizard/Preview.tsx +129 -0
  99. package/src/components/Business/ImportExport/ImportWizard/index.module.less +360 -0
  100. package/src/components/Business/ImportExport/ImportWizard/index.tsx +679 -0
  101. package/src/components/Business/ImportExport/TaskDrawer/index.tsx +285 -0
  102. package/src/components/Business/ImportExport/TaskProgress/index.tsx +267 -0
  103. package/src/components/Business/ImportExport/businessFunction.ts +29 -0
  104. package/src/components/Business/ImportExport/index.md +104 -0
  105. package/src/components/Business/ImportExport/index.tsx +208 -0
  106. package/src/components/Business/ImportExport/sceneCapability.ts +28 -0
  107. package/src/components/Business/ImportExport/services/importExport.ts +208 -0
  108. package/src/components/Business/ImportExport/services/routing.ts +21 -0
  109. package/src/components/Business/ImportExport/types.ts +180 -0
  110. package/src/components/Business/SearchSelect/BusinessUtils.tsx +713 -43
  111. package/src/components/Business/SearchSelect/index.md +2 -2
  112. package/src/components/Business/SystemLog/index.md +37 -0
  113. package/src/components/Business/SystemLog/index.tsx +87 -0
  114. package/src/components/Business/TrialCalculation/PathDisplay.less +93 -0
  115. package/src/components/Business/TrialCalculation/PathDisplay_README.md +77 -0
  116. package/src/components/Business/TrialCalculation/index.tsx +322 -0
  117. package/src/components/Business/columnSettingTable/index.tsx +7 -6
  118. package/src/components/Business/columnSettingTable/sulaSettingTable.tsx +23 -22
  119. package/src/components/Functional/AddSelect/index.tsx +95 -1
  120. package/src/components/Functional/DataValidation/index.tsx +31 -9
  121. package/src/components/Functional/QueryMutipleSelect/index.tsx +2 -2
  122. package/src/components/Functional/SearchSelect/index.md +109 -0
  123. package/src/components/Functional/SearchSelect/index.tsx +99 -17
  124. package/src/components/Functional/SearchSelect/pagination.ts +57 -0
  125. package/src/components/Functional/SearchSelect/utils.tsx +28 -18
  126. package/src/components/Functional/TreeSearchSelect/index.tsx +2 -1
  127. package/src/components/Solution/RuleComponent/README.md +187 -0
  128. package/src/components/Solution/RuleComponent/RenderCompItem.tsx +9 -1
  129. package/src/components/Solution/RuleComponent/RenderCompItem_README.md +189 -0
  130. package/src/components/Solution/RuleComponent/RuleField.md +63 -0
  131. package/src/components/Solution/RuleComponent/index.js +237 -8
  132. package/src/components/Solution/RuleComponent/ruleFiled.js +46 -6
  133. package/src/components/Solution/RuleSetter/README.md +97 -0
  134. package/src/index.ts +9 -0
  135. package/src/typings/umi.d.ts +9 -0
  136. package/src/utils/LocalstorageUtils.ts +1 -1
  137. package/src/utils/auth.ts +39 -2
  138. package/src/utils/utils.ts +41 -1
@@ -0,0 +1,285 @@
1
+ import React, { useEffect, useMemo, useState } from 'react';
2
+ import {
3
+ Badge,
4
+ Button,
5
+ Descriptions,
6
+ Drawer,
7
+ Empty,
8
+ Input,
9
+ List,
10
+ Modal,
11
+ Progress,
12
+ Space,
13
+ Spin,
14
+ Tag,
15
+ message,
16
+ } from 'antd';
17
+ import {
18
+ getItems,
19
+ normalizeTask,
20
+ taskTypeName,
21
+ } from '../services/importExport';
22
+ import type { FileTask, ImportExportServices } from '../types';
23
+
24
+ export interface TaskDrawerProps {
25
+ /** 最近任务对应的业务功能编码集合。 */
26
+ businessFunctionCodes: string[];
27
+ services: ImportExportServices;
28
+ /** 任务中心路由。 */
29
+ taskCenterPath?: string;
30
+ onNavigateTaskCenter?: (path: string) => void;
31
+ }
32
+
33
+ const statusNames: Record<string, string> = {
34
+ CREATED: '等待执行',
35
+ RUNNING: '执行中',
36
+ WAITING_EXECUTE: '等待确认',
37
+ VERIFYING: '结果核对中',
38
+ SUCCESS: '成功',
39
+ PARTIAL_SUCCESS: '部分成功',
40
+ PASSED: '校验通过',
41
+ COMPLETED_WITH_ERRORS: '校验完成(含异常)',
42
+ FAILED: '失败',
43
+ CANCELLED: '已取消',
44
+ };
45
+
46
+ /**
47
+ * 展示当前业务功能最近的文件任务。
48
+ */
49
+ const TaskDrawer: React.FC<TaskDrawerProps> = ({
50
+ businessFunctionCodes,
51
+ services,
52
+ taskCenterPath,
53
+ onNavigateTaskCenter,
54
+ }) => {
55
+ const [visible, setVisible] = useState(false);
56
+ const [keyword, setKeyword] = useState('');
57
+ const [tasks, setTasks] = useState<FileTask[]>([]);
58
+ const [loading, setLoading] = useState(false);
59
+ const [detail, setDetail] = useState<FileTask>();
60
+ const [unreadFailure, setUnreadFailure] = useState(false);
61
+ const businessFunctionCodeKey = businessFunctionCodes.join('|');
62
+
63
+ useEffect(() => {
64
+ if (!visible) return;
65
+ setLoading(true);
66
+ const currentBusinessFunctionCodes = businessFunctionCodeKey
67
+ .split('|')
68
+ .filter(Boolean);
69
+ Promise.all(
70
+ currentBusinessFunctionCodes.map((businessFunctionCode) =>
71
+ services.queryRecentTasks({
72
+ businessFunctionCode,
73
+ days: 3,
74
+ }),
75
+ ),
76
+ )
77
+ .then((responses) => {
78
+ const taskIds = new Set<string>();
79
+ const recent = responses
80
+ .reduce<FileTask[]>((items, response) => {
81
+ return [...items, ...getItems<FileTask>(response)];
82
+ }, [])
83
+ .map(normalizeTask)
84
+ .filter((task) => {
85
+ if (!task.taskId || taskIds.has(task.taskId)) return false;
86
+ taskIds.add(task.taskId);
87
+ return true;
88
+ });
89
+ setTasks(recent);
90
+ setUnreadFailure(false);
91
+ })
92
+ .catch(() => message.error('最近文件任务加载失败,请稍后重试'))
93
+ .finally(() => setLoading(false));
94
+ }, [visible, businessFunctionCodeKey, services]);
95
+
96
+ useEffect(() => {
97
+ if (
98
+ !visible &&
99
+ tasks.some(
100
+ (item) =>
101
+ item.taskStatus === 'FAILED' ||
102
+ item.taskStatus === 'PARTIAL_SUCCESS' ||
103
+ item.taskStatus === 'COMPLETED_WITH_ERRORS',
104
+ )
105
+ ) {
106
+ setUnreadFailure(true);
107
+ }
108
+ }, [visible, tasks]);
109
+
110
+ const items = useMemo(
111
+ () =>
112
+ tasks.filter(
113
+ (item) =>
114
+ !keyword ||
115
+ item.templateName?.includes(keyword) ||
116
+ item.taskId?.includes(keyword),
117
+ ),
118
+ [tasks, keyword],
119
+ );
120
+
121
+ const openFile = (url: string, fileName: string) => {
122
+ const link = document.createElement('a');
123
+ link.href = url;
124
+ link.download = fileName;
125
+ document.body.appendChild(link);
126
+ link.click();
127
+ document.body.removeChild(link);
128
+ message.success('文件下载已开始');
129
+ };
130
+
131
+ return (
132
+ <>
133
+ <Badge dot={unreadFailure}>
134
+ <Button
135
+ onClick={() => {
136
+ setUnreadFailure(false);
137
+ setVisible(true);
138
+ }}
139
+ >
140
+ 文件任务
141
+ </Button>
142
+ </Badge>
143
+ <Drawer
144
+ width={400}
145
+ title="最近文件任务"
146
+ visible={visible}
147
+ onClose={() => setVisible(false)}
148
+ >
149
+ <Input.Search
150
+ allowClear
151
+ placeholder="搜索模板名称或任务编号"
152
+ value={keyword}
153
+ onChange={(event) => setKeyword(event.target.value)}
154
+ />
155
+ <Spin spinning={loading}>
156
+ <List
157
+ dataSource={items}
158
+ locale={{
159
+ emptyText: (
160
+ <Empty
161
+ image={Empty.PRESENTED_IMAGE_SIMPLE}
162
+ description="最近3天暂无导入导出任务"
163
+ />
164
+ ),
165
+ }}
166
+ renderItem={(item) => (
167
+ <List.Item
168
+ style={{ cursor: 'pointer' }}
169
+ onClick={() => setDetail(item)}
170
+ actions={[
171
+ item.resultDownloadUrl ? (
172
+ <Button
173
+ type="link"
174
+ size="small"
175
+ onClick={(event) => {
176
+ event.stopPropagation();
177
+ openFile(
178
+ item.resultDownloadUrl!,
179
+ `${item.taskId}-结果文件`,
180
+ );
181
+ }}
182
+ >
183
+ 下载
184
+ </Button>
185
+ ) : null,
186
+ item.errorDownloadUrl ? (
187
+ <Button
188
+ type="link"
189
+ size="small"
190
+ danger
191
+ onClick={(event) => {
192
+ event.stopPropagation();
193
+ openFile(
194
+ item.errorDownloadUrl!,
195
+ `${item.taskId}-错误文件`,
196
+ );
197
+ }}
198
+ >
199
+ 导出错误
200
+ </Button>
201
+ ) : null,
202
+ ].filter(Boolean)}
203
+ >
204
+ <List.Item.Meta
205
+ title={
206
+ <Space>
207
+ {taskTypeName(item.taskType)}
208
+ <Tag>
209
+ {statusNames[item.taskStatus] || item.taskStatus}
210
+ </Tag>
211
+ </Space>
212
+ }
213
+ description={
214
+ <>
215
+ <div>{item.taskId}</div>
216
+ <Progress percent={item.progress || 0} size="small" />
217
+ </>
218
+ }
219
+ />
220
+ </List.Item>
221
+ )}
222
+ />
223
+ </Spin>
224
+ {taskCenterPath && onNavigateTaskCenter && (
225
+ <Button
226
+ type="link"
227
+ onClick={() => {
228
+ setVisible(false);
229
+ onNavigateTaskCenter(taskCenterPath);
230
+ }}
231
+ >
232
+ 查看全部
233
+ </Button>
234
+ )}
235
+ </Drawer>
236
+ <Modal
237
+ width={720}
238
+ title={`任务详情 · ${detail?.taskId || ''}`}
239
+ visible={!!detail}
240
+ footer={null}
241
+ onCancel={() => setDetail(undefined)}
242
+ destroyOnClose
243
+ >
244
+ {detail && (
245
+ <Descriptions bordered size="small" column={2}>
246
+ <Descriptions.Item label="类型">
247
+ {taskTypeName(detail.taskType)}
248
+ </Descriptions.Item>
249
+ <Descriptions.Item label="状态">
250
+ {statusNames[detail.taskStatus] || detail.taskStatus}
251
+ </Descriptions.Item>
252
+ <Descriptions.Item label="模板">
253
+ {detail.templateName || '-'}
254
+ </Descriptions.Item>
255
+ <Descriptions.Item label="操作人">
256
+ {detail.operatorName || '-'}
257
+ </Descriptions.Item>
258
+ <Descriptions.Item label="文件名">
259
+ {detail.fileName || '-'}
260
+ </Descriptions.Item>
261
+ <Descriptions.Item label="创建时间">
262
+ {detail.createdTime || '-'}
263
+ </Descriptions.Item>
264
+ <Descriptions.Item label="成功行数">
265
+ {detail.successCount ?? '-'}
266
+ </Descriptions.Item>
267
+ <Descriptions.Item label="失败行数">
268
+ {detail.failedCount ?? '-'}
269
+ </Descriptions.Item>
270
+ <Descriptions.Item label="跳过行数">
271
+ {detail.skippedCount ?? '-'}
272
+ </Descriptions.Item>
273
+ <Descriptions.Item label="耗时">
274
+ {detail.elapsedSeconds == null
275
+ ? '-'
276
+ : `${detail.elapsedSeconds}s`}
277
+ </Descriptions.Item>
278
+ </Descriptions>
279
+ )}
280
+ </Modal>
281
+ </>
282
+ );
283
+ };
284
+
285
+ export default TaskDrawer;
@@ -0,0 +1,267 @@
1
+ import React, { useEffect, useMemo, useState } from 'react';
2
+ import {
3
+ Badge,
4
+ Button,
5
+ Card,
6
+ Progress,
7
+ Space,
8
+ Tag,
9
+ Tooltip,
10
+ message,
11
+ } from 'antd';
12
+ import { DownOutlined, UpOutlined } from '@ant-design/icons';
13
+ import {
14
+ normalizeTask,
15
+ taskTypeName,
16
+ unwrapResponse,
17
+ } from '../services/importExport';
18
+ import type { FileTask, ImportExportServices } from '../types';
19
+
20
+ const POLL_INTERVAL_MS = 2000;
21
+ const terminalStatuses = [
22
+ 'SUCCESS',
23
+ 'PARTIAL_SUCCESS',
24
+ 'PASSED',
25
+ 'COMPLETED_WITH_ERRORS',
26
+ 'FAILED',
27
+ 'CANCELLED',
28
+ ];
29
+
30
+ const statusView: Record<string, { color: string; text: string }> = {
31
+ PENDING: { color: 'default', text: '等待执行' },
32
+ CREATED: { color: 'default', text: '等待执行' },
33
+ RUNNING: { color: 'processing', text: '执行中' },
34
+ WAITING_EXECUTE: { color: 'warning', text: '等待确认' },
35
+ VERIFYING: { color: 'processing', text: '结果核对中' },
36
+ SUCCESS: { color: 'success', text: '成功' },
37
+ PARTIAL_SUCCESS: { color: 'warning', text: '部分成功' },
38
+ PASSED: { color: 'success', text: '校验通过' },
39
+ COMPLETED_WITH_ERRORS: { color: 'warning', text: '校验完成(含异常)' },
40
+ FAILED: { color: 'error', text: '失败' },
41
+ CANCELLED: { color: 'default', text: '已取消' },
42
+ };
43
+
44
+ const taskTypeColors: Record<string, string> = {
45
+ IMPORT_VALIDATION: 'blue',
46
+ IMPORT_EXECUTION: 'geekblue',
47
+ IMPORT: 'geekblue',
48
+ EXPORT: 'purple',
49
+ TEMPLATE_DOWNLOAD: 'cyan',
50
+ };
51
+
52
+ export interface TaskProgressProps {
53
+ tasks: FileTask[];
54
+ services: ImportExportServices;
55
+ taskCenterPath?: string;
56
+ onNavigateTaskCenter?: (path: string) => void;
57
+ onTaskUpdated: (task: FileTask) => void;
58
+ onClose: () => void;
59
+ }
60
+
61
+ const triggerDownload = (url: string, fileName: string) => {
62
+ const link = document.createElement('a');
63
+ link.href = url;
64
+ link.download = fileName;
65
+ document.body.appendChild(link);
66
+ link.click();
67
+ document.body.removeChild(link);
68
+ message.success('文件下载已开始');
69
+ };
70
+
71
+ /**
72
+ * 轮询并展示当前页面创建的文件任务进度。
73
+ */
74
+ const TaskProgress: React.FC<TaskProgressProps> = ({
75
+ tasks,
76
+ services,
77
+ taskCenterPath,
78
+ onNavigateTaskCenter,
79
+ onTaskUpdated,
80
+ onClose,
81
+ }) => {
82
+ const [collapsed, setCollapsed] = useState(false);
83
+ const runningTasks = useMemo(
84
+ () =>
85
+ tasks.filter(
86
+ (task) =>
87
+ Boolean(task.taskId) && !terminalStatuses.includes(task.taskStatus),
88
+ ),
89
+ [tasks],
90
+ );
91
+ const pollingKey = runningTasks
92
+ .map((task) => `${task.taskId}:${task.serviceContext || ''}`)
93
+ .join('|');
94
+
95
+ useEffect(() => {
96
+ if (runningTasks.length === 0) return undefined;
97
+ let stopped = false;
98
+ let timer: number | undefined;
99
+ const poll = async () => {
100
+ await Promise.all(
101
+ runningTasks.map(async (task) => {
102
+ if (!task.serviceContext) return;
103
+ try {
104
+ const response = await services.queryTask(
105
+ task.serviceContext,
106
+ task.taskId,
107
+ );
108
+ if (!stopped) {
109
+ onTaskUpdated({
110
+ ...task,
111
+ ...normalizeTask(unwrapResponse<FileTask>(response)),
112
+ serviceContext: task.serviceContext,
113
+ });
114
+ }
115
+ } catch (error) {
116
+ // A transient progress failure is retried on the next polling cycle.
117
+ }
118
+ }),
119
+ );
120
+ if (!stopped) {
121
+ timer = window.setTimeout(poll, POLL_INTERVAL_MS);
122
+ }
123
+ };
124
+ poll();
125
+ return () => {
126
+ stopped = true;
127
+ if (timer != null) window.clearTimeout(timer);
128
+ };
129
+ }, [pollingKey, services, onTaskUpdated]);
130
+
131
+ if (tasks.length === 0) return null;
132
+
133
+ const primaryTask = runningTasks[0] || tasks[0];
134
+
135
+ return (
136
+ <Card
137
+ size="small"
138
+ title={
139
+ <Space>
140
+ 文件任务
141
+ <Badge count={tasks.length} />
142
+ </Space>
143
+ }
144
+ extra={
145
+ <Space>
146
+ {taskCenterPath && onNavigateTaskCenter && (
147
+ <Button
148
+ type="link"
149
+ size="small"
150
+ onClick={() => onNavigateTaskCenter(taskCenterPath)}
151
+ >
152
+ 全部
153
+ </Button>
154
+ )}
155
+ <Button type="link" size="small" onClick={onClose}>
156
+ 关闭
157
+ </Button>
158
+ <Button
159
+ type="text"
160
+ size="small"
161
+ aria-label={collapsed ? '展开文件任务' : '收起文件任务'}
162
+ icon={collapsed ? <UpOutlined /> : <DownOutlined />}
163
+ onClick={() => setCollapsed((value) => !value)}
164
+ />
165
+ </Space>
166
+ }
167
+ style={{
168
+ position: 'fixed',
169
+ right: 24,
170
+ bottom: 24,
171
+ width: 360,
172
+ zIndex: 40,
173
+ }}
174
+ bodyStyle={{
175
+ maxHeight: 'min(520px, calc(100vh - 180px))',
176
+ overflowY: 'auto',
177
+ }}
178
+ >
179
+ {collapsed ? (
180
+ <Space direction="vertical" style={{ width: '100%' }}>
181
+ <Tag color={taskTypeColors[primaryTask.taskType] || 'default'}>
182
+ {taskTypeName(primaryTask.taskType)}
183
+ </Tag>
184
+ <Progress percent={primaryTask.progress || 0} size="small" />
185
+ </Space>
186
+ ) : (
187
+ tasks.map((task) => {
188
+ const status = statusView[task.taskStatus] || {
189
+ color: 'default',
190
+ text: task.taskStatus,
191
+ };
192
+ const operationLabel = taskTypeName(task.taskType);
193
+ return (
194
+ <div
195
+ key={task.taskId}
196
+ style={{ padding: '10px 0', borderBottom: '1px solid #f0f0f0' }}
197
+ >
198
+ <Tag color={taskTypeColors[task.taskType] || 'default'}>
199
+ {operationLabel}
200
+ </Tag>
201
+ <Space style={{ marginTop: 6 }}>
202
+ <Tag color={status.color}>{status.text}</Tag>
203
+ <span>
204
+ 已处理 {task.processedCount || 0}
205
+ {task.totalCount == null ? ' 行' : ` / ${task.totalCount} 行`}
206
+ </span>
207
+ {task.elapsedSeconds != null && (
208
+ <span>耗时 {task.elapsedSeconds}s</span>
209
+ )}
210
+ </Space>
211
+ <Progress percent={task.progress || 0} size="small" />
212
+ {task.taskStatus === 'FAILED' && task.errorMessage && (
213
+ <Tooltip title={task.errorMessage}>
214
+ <div
215
+ aria-label={task.errorMessage}
216
+ style={{
217
+ color: '#e82d1e',
218
+ marginBottom: 4,
219
+ overflow: 'hidden',
220
+ textOverflow: 'ellipsis',
221
+ whiteSpace: 'nowrap',
222
+ }}
223
+ >
224
+ {task.errorMessage}
225
+ </div>
226
+ </Tooltip>
227
+ )}
228
+ <Space>
229
+ {task.resultDownloadUrl && (
230
+ <Button
231
+ type="link"
232
+ size="small"
233
+ onClick={() =>
234
+ triggerDownload(
235
+ task.resultDownloadUrl!,
236
+ `${task.templateName || task.taskId}-结果文件`,
237
+ )
238
+ }
239
+ >
240
+ 下载结果文件
241
+ </Button>
242
+ )}
243
+ {task.errorDownloadUrl && (
244
+ <Button
245
+ type="link"
246
+ size="small"
247
+ danger
248
+ onClick={() =>
249
+ triggerDownload(
250
+ task.errorDownloadUrl!,
251
+ `${task.templateName || task.taskId}-错误文件`,
252
+ )
253
+ }
254
+ >
255
+ 下载错误文件
256
+ </Button>
257
+ )}
258
+ </Space>
259
+ </div>
260
+ );
261
+ })
262
+ )}
263
+ </Card>
264
+ );
265
+ };
266
+
267
+ export default TaskProgress;
@@ -0,0 +1,29 @@
1
+ import { getRelationTemplateCode } from '../../../utils/auth';
2
+ import type { ImportExportOperationType } from './types';
3
+
4
+ /**
5
+ * 导入导出按钮权限编码集合。
6
+ */
7
+ export interface ImportExportAuthCodes {
8
+ importCode: string;
9
+ quickExportCode: string;
10
+ templateExportCode: string;
11
+ }
12
+
13
+ /**
14
+ * 根据操作类型和按钮权限编码解析关联的业务功能编码。
15
+ *
16
+ * 导入使用独立入口;快速导出和模板导出共用任一已配置的导出入口。
17
+ */
18
+ export const resolveBusinessFunctionCode = (
19
+ operationType: ImportExportOperationType,
20
+ authCodes: ImportExportAuthCodes,
21
+ ): string => {
22
+ if (operationType === 'IMPORT') {
23
+ return getRelationTemplateCode(authCodes.importCode);
24
+ }
25
+ return (
26
+ getRelationTemplateCode(authCodes.quickExportCode) ||
27
+ getRelationTemplateCode(authCodes.templateExportCode)
28
+ );
29
+ };
@@ -0,0 +1,104 @@
1
+ ---
2
+ nav:
3
+ title: 组件
4
+ order: 1
5
+ group:
6
+ title: 业务
7
+ order: 2
8
+ title: 导入导出
9
+ order: 20
10
+ ---
11
+
12
+ # 导入导出
13
+
14
+ 统一提供导入、快速导出、按模板导出、任务进度和最近任务入口。组件只负责界面与流程,鉴权、租户、业务查询条件和跨应用路由由业务项目注入。
15
+
16
+ ## 基本使用
17
+
18
+ ```tsx
19
+ import React from 'react';
20
+ import { ImportExportActions } from '@bit-sun/business-component';
21
+
22
+ export default () => (
23
+ <ImportExportActions
24
+ config={{
25
+ documentName: '销售单',
26
+ getQueryParams: () => ({ status: 'CREATED' }),
27
+ getResultCount: () => 128,
28
+ }}
29
+ importCode="SALES_ORDER_IMPORT"
30
+ quickExportCode="SALES_ORDER_QUICK_EXPORT"
31
+ templateExportCode="SALES_ORDER_TEMPLATE_EXPORT"
32
+ showTaskDrawer={false}
33
+ />
34
+ );
35
+ ```
36
+
37
+ ## 业务项目接入
38
+
39
+ 组件默认复用宿主 Umi 项目的 `request`,并根据运行环境跳转任务中心,业务项目不需要重复创建服务或路由适配器:
40
+
41
+ ```tsx | pure
42
+ import { ImportExportActions } from '@bit-sun/business-component';
43
+
44
+ export default () => (
45
+ <ImportExportActions
46
+ config={{
47
+ documentName: '销售单',
48
+ getQueryParams: () => ({ status: 'CREATED' }),
49
+ getResultCount: () => 128,
50
+ }}
51
+ importCode="SALES_ORDER_IMPORT"
52
+ quickExportCode="SALES_ORDER_QUICK_EXPORT"
53
+ templateExportCode="SALES_ORDER_TEMPLATE_EXPORT"
54
+ taskCenterPath="/file-task"
55
+ />
56
+ );
57
+ ```
58
+
59
+ 默认跳转在无界子应用中调用主应用注入的 `jump`,独立运行时打开主应用页面。如果宿主有其他路由机制,可通过 `onNavigateTaskCenter` 覆盖默认行为。
60
+
61
+ 如果个别项目的接口不同,可以只覆盖差异服务,其余接口继续使用组件默认实现:
62
+
63
+ ```tsx | pure
64
+ const serviceOverrides = {
65
+ queryTemplates: (data) =>
66
+ request('/custom/import-export/templates', {
67
+ method: 'POST',
68
+ data,
69
+ }),
70
+ };
71
+
72
+ <ImportExportActions {...props} services={serviceOverrides} />;
73
+ ```
74
+
75
+ 组件会从权限菜单数据中读取按钮的 `relationExcelTemplateCode` 作为业务功能编码:导入使用 `importCode`;快速导出和模板导出使用 `quickExportCode`、`templateExportCode` 查询结果的“或”值,共用同一个导出入口。
76
+
77
+ ## API
78
+
79
+ ### ImportExportActions
80
+
81
+ | 参数 | 说明 | 类型 | 默认值 |
82
+ | -------------------- | ---------------------------- | ------------------------------- | ------------------------------------ |
83
+ | config | 单据名称、查询条件等业务配置 | `ImportExportConfig` | - |
84
+ | services | 仅覆盖存在差异的服务 | `Partial<ImportExportServices>` | 内置 Umi 请求服务 |
85
+ | importCode | 导入按钮权限编码 | `string` | - |
86
+ | quickExportCode | 快速导出按钮权限编码 | `string` | - |
87
+ | templateExportCode | 模板导出按钮权限编码 | `string` | - |
88
+ | showTaskDrawer | 是否显示最近任务入口 | `boolean` | `true` |
89
+ | taskCenterPath | 任务中心路由 | `string` | `/configure/importExport/taskCenter` |
90
+ | onNavigateTaskCenter | 覆盖默认任务中心跳转行为 | `(path: string) => void` | 内置跨应用跳转 |
91
+
92
+ ### ImportExportConfig
93
+
94
+ | 参数 | 说明 | 类型 | 默认值 |
95
+ | -------------- | -------------------- | --------------------------- | ------ |
96
+ | documentName | 单据名称 | `string` | - |
97
+ | getQueryParams | 获取当前列表查询条件 | `() => Record<string, any>` | - |
98
+ | getResultCount | 获取当前查询结果数量 | `() => number \| undefined` | - |
99
+
100
+ ## 抽取边界
101
+
102
+ - 组件库:交互流程、弹窗、字段预览、任务进度、任务抽屉、公共类型和接口适配工厂。
103
+ - 业务项目:按钮权限及其关联业务功能编码、当前查询条件、请求拦截器,以及特殊场景下对默认路由跳转的覆盖。
104
+ - 后端差异:通过可选 `services` 属性按服务覆盖,不在组件内部判断项目名称。