@svadmin/ui 0.73.0 → 0.74.0

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 (295) hide show
  1. package/README.md +53 -28
  2. package/dist/app.css +3733 -4119
  3. package/dist/app.theme.css +3733 -4119
  4. package/dist/component-registry.d.ts +14977 -0
  5. package/dist/component-registry.js +216 -0
  6. package/dist/components/AboutSettings.svelte +7 -1
  7. package/dist/components/AboutSettings.svelte.d.ts +4 -1
  8. package/dist/components/ActivityFeed.svelte +9 -3
  9. package/dist/components/ActivityFeed.svelte.d.ts +3 -0
  10. package/dist/components/AdminApp.svelte +19 -10
  11. package/dist/components/AdminApp.svelte.d.ts +6 -0
  12. package/dist/components/ApiSettings.svelte +41 -5
  13. package/dist/components/ApprovalCenter.svelte +283 -0
  14. package/dist/components/ApprovalCenter.svelte.d.ts +10 -0
  15. package/dist/components/AuditLogViewer.svelte +3 -1
  16. package/dist/components/AutoForm.svelte +33 -3
  17. package/dist/components/AutoForm.svelte.d.ts +1 -0
  18. package/dist/components/AutoTable.svelte +610 -47
  19. package/dist/components/AutoTable.svelte.d.ts +17 -1
  20. package/dist/components/BoundRecordDetailDrawer.svelte +9 -0
  21. package/dist/components/BoundRecordDetailDrawer.svelte.d.ts +2 -0
  22. package/dist/components/Breadcrumbs.svelte +11 -1
  23. package/dist/components/CancelTaskButton.svelte +12 -42
  24. package/dist/components/ColorPicker.svelte +73 -0
  25. package/dist/components/ColorPicker.svelte.d.ts +15 -0
  26. package/dist/components/ComboboxField.svelte +114 -27
  27. package/dist/components/ComboboxField.svelte.d.ts +1 -0
  28. package/dist/components/DashboardView.svelte +99 -0
  29. package/dist/components/DashboardView.svelte.d.ts +14 -0
  30. package/dist/components/DatePicker.svelte +10 -0
  31. package/dist/components/DatePicker.svelte.d.ts +6 -0
  32. package/dist/components/DateRangeInput.svelte +85 -5
  33. package/dist/components/DateRangeInput.svelte.d.ts +10 -0
  34. package/dist/components/DateRangePicker.svelte +10 -0
  35. package/dist/components/DateRangePicker.svelte.d.ts +6 -0
  36. package/dist/components/DateTimeInput.svelte +74 -5
  37. package/dist/components/DateTimeInput.svelte.d.ts +8 -0
  38. package/dist/components/DateTimePicker.svelte +10 -0
  39. package/dist/components/DateTimePicker.svelte.d.ts +6 -0
  40. package/dist/components/DevTools.svelte +34 -26
  41. package/dist/components/DevframePanel.svelte +123 -0
  42. package/dist/components/DevframePanel.svelte.d.ts +3 -0
  43. package/dist/components/ErrorSummary.svelte +52 -0
  44. package/dist/components/ErrorSummary.svelte.d.ts +14 -0
  45. package/dist/components/FieldRenderer.svelte +82 -81
  46. package/dist/components/FileUpload.svelte +214 -52
  47. package/dist/components/FileUpload.svelte.d.ts +11 -0
  48. package/dist/components/FilterBuilder.svelte +88 -66
  49. package/dist/components/FilterCollectionInput.svelte +80 -0
  50. package/dist/components/FilterCollectionInput.svelte.d.ts +10 -0
  51. package/dist/components/GanttChart.svelte +66 -14
  52. package/dist/components/GanttChart.svelte.d.ts +6 -0
  53. package/dist/components/Header.svelte +18 -2
  54. package/dist/components/ImportWizard.svelte +339 -17
  55. package/dist/components/ImportWizard.svelte.d.ts +11 -0
  56. package/dist/components/JsonSchemaForm.svelte +120 -52
  57. package/dist/components/JsonSchemaForm.svelte.d.ts +7 -1
  58. package/dist/components/KanbanBoard.svelte +195 -40
  59. package/dist/components/KanbanBoard.svelte.d.ts +9 -2
  60. package/dist/components/Layout.svelte +4 -2
  61. package/dist/components/LazyAutoForm.svelte +28 -0
  62. package/dist/components/LazyAutoForm.svelte.d.ts +8 -0
  63. package/dist/components/LazyAutoTable.svelte +28 -0
  64. package/dist/components/LazyAutoTable.svelte.d.ts +8 -0
  65. package/dist/components/LazySettingsPage.svelte +28 -0
  66. package/dist/components/LazySettingsPage.svelte.d.ts +8 -0
  67. package/dist/components/LazyShowPage.svelte +28 -0
  68. package/dist/components/LazyShowPage.svelte.d.ts +8 -0
  69. package/dist/components/ListPage.svelte +5 -1
  70. package/dist/components/ListPage.svelte.d.ts +3 -0
  71. package/dist/components/LoginPage.svelte +1 -1
  72. package/dist/components/MultiTabKeepAlive.svelte +11 -6
  73. package/dist/components/NotificationsSettings.svelte +69 -2
  74. package/dist/components/NotificationsSettings.svelte.d.ts +25 -1
  75. package/dist/components/PermissionMatrix.svelte +20 -1
  76. package/dist/components/PivotExportButton.svelte +157 -0
  77. package/dist/components/PivotExportButton.svelte.d.ts +16 -0
  78. package/dist/components/PivotTable.svelte +229 -74
  79. package/dist/components/PivotTable.svelte.d.ts +19 -2
  80. package/dist/components/RadioGroup.svelte +121 -0
  81. package/dist/components/RadioGroup.svelte.d.ts +43 -0
  82. package/dist/components/RangeSlider.svelte +127 -0
  83. package/dist/components/RangeSlider.svelte.d.ts +22 -0
  84. package/dist/components/Rate.svelte +121 -0
  85. package/dist/components/Rate.svelte.d.ts +16 -0
  86. package/dist/components/RecordDetailDrawer.svelte +4 -2
  87. package/dist/components/RecordDetailDrawer.svelte.d.ts +2 -0
  88. package/dist/components/RelationPicker.svelte +341 -0
  89. package/dist/components/RelationPicker.svelte.d.ts +26 -0
  90. package/dist/components/ResourceOperationsPage.svelte +18 -18
  91. package/dist/components/ResourceOperationsPage.svelte.d.ts +2 -2
  92. package/dist/components/RetryTaskButton.svelte +12 -42
  93. package/dist/components/SecuritySettings.svelte +29 -5
  94. package/dist/components/SegmentedControl.svelte +145 -0
  95. package/dist/components/SegmentedControl.svelte.d.ts +38 -0
  96. package/dist/components/SettingsPage.svelte +3 -1
  97. package/dist/components/SettingsPage.svelte.d.ts +1 -0
  98. package/dist/components/ShowPage.svelte +18 -3
  99. package/dist/components/ShowPage.svelte.d.ts +3 -0
  100. package/dist/components/Sidebar.svelte +2 -2
  101. package/dist/components/Slider.svelte +100 -0
  102. package/dist/components/Slider.svelte.d.ts +19 -0
  103. package/dist/components/SpreadsheetView.svelte +1213 -95
  104. package/dist/components/SpreadsheetView.svelte.d.ts +19 -0
  105. package/dist/components/TagsInput.svelte +178 -0
  106. package/dist/components/TagsInput.svelte.d.ts +17 -0
  107. package/dist/components/TimePicker.svelte +10 -0
  108. package/dist/components/TimePicker.svelte.d.ts +6 -0
  109. package/dist/components/TreeSelect.svelte +267 -53
  110. package/dist/components/TreeSelect.svelte.d.ts +3 -0
  111. package/dist/components/TreeTable.svelte +484 -82
  112. package/dist/components/TreeTable.svelte.d.ts +24 -1
  113. package/dist/components/TwoFactorAuthPage.svelte +85 -48
  114. package/dist/components/TwoFactorAuthPage.svelte.d.ts +12 -1
  115. package/dist/components/VirtualTable.svelte +116 -20
  116. package/dist/components/VirtualTable.svelte.d.ts +5 -0
  117. package/dist/components/account/CompanyProfilePage.svelte +74 -38
  118. package/dist/components/account/GetStartedPage.svelte +2 -1
  119. package/dist/components/account/ImportMembersPage.svelte +22 -167
  120. package/dist/components/account/ImportMembersPage.svelte.d.ts +4 -1
  121. package/dist/components/account/MemberDirectory.svelte +156 -0
  122. package/dist/components/account/MemberDirectory.svelte.d.ts +19 -0
  123. package/dist/components/account/MembersStarterPage.svelte +9 -55
  124. package/dist/components/account/MembersStarterPage.svelte.d.ts +5 -1
  125. package/dist/components/account/SecurityLogPage.svelte +4 -1
  126. package/dist/components/account/SettingsPlainPage.svelte +7 -38
  127. package/dist/components/account/SettingsPlainPage.svelte.d.ts +4 -1
  128. package/dist/components/account/SettingsSidebarPage.svelte +7 -20
  129. package/dist/components/account/TeamMembersPage.svelte +4 -20
  130. package/dist/components/account/TeamMembersPage.svelte.d.ts +5 -1
  131. package/dist/components/account/UserProfilePage.svelte +3 -25
  132. package/dist/components/account/UserProfilePage.svelte.d.ts +17 -2
  133. package/dist/components/buttons/ExportButton.svelte +56 -12
  134. package/dist/components/buttons/ExportButton.svelte.d.ts +11 -0
  135. package/dist/components/content/ContentPageHeader.svelte +1 -1
  136. package/dist/components/content/ContentPageShell.svelte +1 -1
  137. package/dist/components/content/MetricBlock.svelte +1 -1
  138. package/dist/components/content/PageToolbar.svelte +1 -1
  139. package/dist/components/content/ReferenceNotice.svelte +5 -0
  140. package/dist/components/content/ReferenceNotice.svelte.d.ts +18 -0
  141. package/dist/components/content/SectionHeader.svelte +1 -1
  142. package/dist/components/content/SettingsFieldRow.svelte +1 -1
  143. package/dist/components/content/SettingsGroup.svelte +1 -1
  144. package/dist/components/content/StatusBadge.svelte +1 -1
  145. package/dist/components/content/WorkspaceLayout.svelte +1 -1
  146. package/dist/components/export-reference.d.ts +2 -0
  147. package/dist/components/export-reference.js +9 -0
  148. package/dist/components/fieldComponentMap.js +2 -5
  149. package/dist/components/fields/DateField.svelte +12 -13
  150. package/dist/components/fields/date-display.d.ts +6 -0
  151. package/dist/components/fields/date-display.js +24 -0
  152. package/dist/components/network/TeamCrewTablePage.svelte +6 -1
  153. package/dist/components/network/UserCardsNFTPage.svelte +2 -0
  154. package/dist/components/profile/PublicProfilePage.svelte +2 -0
  155. package/dist/components/saved-list-views.d.ts +74 -1
  156. package/dist/components/saved-list-views.js +191 -3
  157. package/dist/components/table-contract.d.ts +12 -1
  158. package/dist/components/task-action.svelte.d.ts +13 -0
  159. package/dist/components/task-action.svelte.js +73 -0
  160. package/dist/components/ui/DesignSystemShowcase.svelte +164 -0
  161. package/dist/components/ui/DesignSystemShowcase.svelte.d.ts +3 -0
  162. package/dist/components/ui/badge/badge-variants.d.ts +3 -2
  163. package/dist/components/ui/badge/badge-variants.js +2 -3
  164. package/dist/components/ui/badge/badge.svelte +3 -0
  165. package/dist/components/ui/badge/badge.svelte.d.ts +1 -0
  166. package/dist/components/ui/button/button-variants.d.ts +5 -4
  167. package/dist/components/ui/button/button-variants.js +7 -5
  168. package/dist/components/ui/checkbox/checkbox.svelte +5 -1
  169. package/dist/components/ui/checkbox/checkbox.svelte.d.ts +5 -1
  170. package/dist/components/ui/input/input.svelte +7 -2
  171. package/dist/components/ui/input/input.svelte.d.ts +4 -1
  172. package/dist/components/ui/select/select.svelte +4 -1
  173. package/dist/components/ui/select/select.svelte.d.ts +2 -1
  174. package/dist/components/ui/textarea/textarea.svelte +2 -2
  175. package/dist/components.css +33 -2
  176. package/dist/date-input-policy.d.ts +4 -0
  177. package/dist/date-input-policy.js +35 -0
  178. package/dist/design-system-components.d.ts +100 -0
  179. package/dist/design-system-components.js +101 -0
  180. package/dist/design-system.js +1 -1
  181. package/dist/devtools-bridge.d.ts +80 -0
  182. package/dist/devtools-bridge.js +92 -0
  183. package/dist/index.d.ts +37 -2
  184. package/dist/index.js +23 -2
  185. package/dist/recipes.d.ts +960 -4
  186. package/dist/recipes.js +264 -4
  187. package/dist/tree-table-model.d.ts +24 -0
  188. package/dist/tree-table-model.js +111 -0
  189. package/package.json +22 -10
  190. package/dist/styled-system/css/conditions.js +0 -36
  191. package/dist/styled-system/css/css.d.ts +0 -22
  192. package/dist/styled-system/css/css.js +0 -45
  193. package/dist/styled-system/css/cva.d.ts +0 -6
  194. package/dist/styled-system/css/cva.js +0 -87
  195. package/dist/styled-system/css/cx.d.ts +0 -5
  196. package/dist/styled-system/css/cx.js +0 -15
  197. package/dist/styled-system/css/index.d.ts +0 -5
  198. package/dist/styled-system/css/index.js +0 -4
  199. package/dist/styled-system/css/sva.d.ts +0 -4
  200. package/dist/styled-system/css/sva.js +0 -46
  201. package/dist/styled-system/helpers.js +0 -316
  202. package/dist/styled-system/patterns/aspect-ratio.d.ts +0 -20
  203. package/dist/styled-system/patterns/aspect-ratio.js +0 -38
  204. package/dist/styled-system/patterns/bleed.d.ts +0 -21
  205. package/dist/styled-system/patterns/bleed.js +0 -24
  206. package/dist/styled-system/patterns/box.d.ts +0 -20
  207. package/dist/styled-system/patterns/box.js +0 -15
  208. package/dist/styled-system/patterns/center.d.ts +0 -20
  209. package/dist/styled-system/patterns/center.js +0 -21
  210. package/dist/styled-system/patterns/circle.d.ts +0 -20
  211. package/dist/styled-system/patterns/circle.js +0 -25
  212. package/dist/styled-system/patterns/container.d.ts +0 -20
  213. package/dist/styled-system/patterns/container.js +0 -21
  214. package/dist/styled-system/patterns/cq.d.ts +0 -21
  215. package/dist/styled-system/patterns/cq.js +0 -21
  216. package/dist/styled-system/patterns/divider.d.ts +0 -22
  217. package/dist/styled-system/patterns/divider.js +0 -25
  218. package/dist/styled-system/patterns/flex.d.ts +0 -26
  219. package/dist/styled-system/patterns/flex.js +0 -26
  220. package/dist/styled-system/patterns/float.d.ts +0 -23
  221. package/dist/styled-system/patterns/float.js +0 -52
  222. package/dist/styled-system/patterns/grid-item.d.ts +0 -25
  223. package/dist/styled-system/patterns/grid-item.js +0 -25
  224. package/dist/styled-system/patterns/grid.d.ts +0 -24
  225. package/dist/styled-system/patterns/grid.js +0 -27
  226. package/dist/styled-system/patterns/hstack.d.ts +0 -21
  227. package/dist/styled-system/patterns/hstack.js +0 -24
  228. package/dist/styled-system/patterns/index.d.ts +0 -21
  229. package/dist/styled-system/patterns/index.js +0 -20
  230. package/dist/styled-system/patterns/link-overlay.d.ts +0 -20
  231. package/dist/styled-system/patterns/link-overlay.js +0 -24
  232. package/dist/styled-system/patterns/spacer.d.ts +0 -20
  233. package/dist/styled-system/patterns/spacer.js +0 -25
  234. package/dist/styled-system/patterns/square.d.ts +0 -20
  235. package/dist/styled-system/patterns/square.js +0 -24
  236. package/dist/styled-system/patterns/stack.d.ts +0 -23
  237. package/dist/styled-system/patterns/stack.js +0 -24
  238. package/dist/styled-system/patterns/visually-hidden.d.ts +0 -20
  239. package/dist/styled-system/patterns/visually-hidden.js +0 -18
  240. package/dist/styled-system/patterns/vstack.d.ts +0 -21
  241. package/dist/styled-system/patterns/vstack.js +0 -24
  242. package/dist/styled-system/patterns/wrap.d.ts +0 -24
  243. package/dist/styled-system/patterns/wrap.js +0 -25
  244. package/dist/styled-system/recipes/content-header.d.ts +0 -31
  245. package/dist/styled-system/recipes/content-header.js +0 -65
  246. package/dist/styled-system/recipes/content-page.d.ts +0 -34
  247. package/dist/styled-system/recipes/content-page.js +0 -43
  248. package/dist/styled-system/recipes/create-recipe.js +0 -82
  249. package/dist/styled-system/recipes/index.d.ts +0 -17
  250. package/dist/styled-system/recipes/index.js +0 -16
  251. package/dist/styled-system/recipes/metric-block.d.ts +0 -34
  252. package/dist/styled-system/recipes/metric-block.js +0 -76
  253. package/dist/styled-system/recipes/product-list.d.ts +0 -31
  254. package/dist/styled-system/recipes/product-list.js +0 -73
  255. package/dist/styled-system/recipes/product-section.d.ts +0 -31
  256. package/dist/styled-system/recipes/product-section.js +0 -49
  257. package/dist/styled-system/recipes/product-settings-row.d.ts +0 -34
  258. package/dist/styled-system/recipes/product-settings-row.js +0 -58
  259. package/dist/styled-system/recipes/product-settings.d.ts +0 -31
  260. package/dist/styled-system/recipes/product-settings.js +0 -57
  261. package/dist/styled-system/recipes/product-status.d.ts +0 -34
  262. package/dist/styled-system/recipes/product-status.js +0 -45
  263. package/dist/styled-system/recipes/product-toolbar.d.ts +0 -31
  264. package/dist/styled-system/recipes/product-toolbar.js +0 -41
  265. package/dist/styled-system/recipes/product-workspace.d.ts +0 -34
  266. package/dist/styled-system/recipes/product-workspace.js +0 -58
  267. package/dist/styled-system/recipes/surface-metric.d.ts +0 -38
  268. package/dist/styled-system/recipes/surface-metric.js +0 -63
  269. package/dist/styled-system/recipes/surface-table.d.ts +0 -34
  270. package/dist/styled-system/recipes/surface-table.js +0 -62
  271. package/dist/styled-system/recipes/ui-badge.d.ts +0 -31
  272. package/dist/styled-system/recipes/ui-badge.js +0 -38
  273. package/dist/styled-system/recipes/ui-button.d.ts +0 -32
  274. package/dist/styled-system/recipes/ui-button.js +0 -43
  275. package/dist/styled-system/recipes/ui-input.d.ts +0 -31
  276. package/dist/styled-system/recipes/ui-input.js +0 -49
  277. package/dist/styled-system/recipes/ui-textarea.d.ts +0 -31
  278. package/dist/styled-system/recipes/ui-textarea.js +0 -24
  279. package/dist/styled-system/styles.css +0 -1257
  280. package/dist/styled-system/tokens/index.d.ts +0 -9
  281. package/dist/styled-system/tokens/index.js +0 -268
  282. package/dist/styled-system/tokens/tokens.d.ts +0 -36
  283. package/dist/styled-system/types/composition.d.ts +0 -227
  284. package/dist/styled-system/types/conditions.d.ts +0 -236
  285. package/dist/styled-system/types/csstype.d.ts +0 -22570
  286. package/dist/styled-system/types/index.d.ts +0 -6
  287. package/dist/styled-system/types/parts.d.ts +0 -8
  288. package/dist/styled-system/types/pattern.d.ts +0 -78
  289. package/dist/styled-system/types/prop-type.d.ts +0 -223
  290. package/dist/styled-system/types/recipe.d.ts +0 -181
  291. package/dist/styled-system/types/selectors.d.ts +0 -59
  292. package/dist/styled-system/types/static-css.d.ts +0 -56
  293. package/dist/styled-system/types/style-props.d.ts +0 -8088
  294. package/dist/styled-system/types/system-types.d.ts +0 -151
  295. package/dist/styles/recipes.css +0 -1262
@@ -1,19 +1,34 @@
1
1
  <script lang="ts">
2
- import { captureAdminContext, captureAuthSession, useCan, useImport, useResourceContract } from '@svadmin/core';
2
+ import { captureAdminContext, captureAuthSession, IMPORT_LIMITS, snapshotImportArtifact, snapshotImportTaskResult, useCan, useImport, useResourceContract, useSubmitTask, useTask } from '@svadmin/core';
3
3
  import { untrack } from 'svelte';
4
4
  import { definedReactiveOptions } from '@svadmin/core/options';
5
5
  import { useTranslation } from '@svadmin/core/i18n';
6
+ import { parseContractCreateInput } from '@svadmin/core/resource-contract';
7
+ import type { ImportArtifactProvider, TaskProvider } from '@svadmin/core';
8
+ import { normalizeTaskStatus, resolveTaskProgress, canCancelTask } from './task-utils.js';
9
+ import TaskStatusBadge from './TaskStatusBadge.svelte';
10
+ import CancelTaskButton from './CancelTaskButton.svelte';
6
11
  import { parseCSV, toCsv } from '@svadmin/core';
7
12
  import * as Dialog from './ui/dialog/index.js';
8
13
  import { Button } from './ui/button/index.js';
9
14
  import { Progress } from './ui/progress/index.js';
10
15
  import { Badge } from './ui/badge/index.js';
11
- import { Upload, ArrowRight, ArrowLeft, AlertCircle, Download, FileSpreadsheet, Loader2 } from '@lucide/svelte';
16
+ import { Upload, ArrowRight, ArrowLeft, AlertCircle, Download, FileSpreadsheet, Loader2, RotateCw } from '@lucide/svelte';
12
17
 
13
18
  interface Props {
14
19
  resourceName: string;
15
20
  open?: boolean;
16
21
  batchSize?: number;
22
+ maxRows?: number;
23
+ maxBytes?: number;
24
+ taskName?: string;
25
+ retryTaskName?: string;
26
+ taskProvider?: TaskProvider;
27
+ /** 上传原文件并以 artifact 引用提交任务,避免任务请求重复携带 records。 */
28
+ taskArtifactProvider?: ImportArtifactProvider;
29
+ taskIdempotencyKey?: string;
30
+ initialTaskId?: string;
31
+ onTaskSubmitted?: (taskId: string) => void;
17
32
  onSuccess?: (result: { succeeded: number; failed: number }) => void;
18
33
  }
19
34
 
@@ -21,6 +36,15 @@
21
36
  resourceName,
22
37
  open = $bindable(false),
23
38
  batchSize = 20,
39
+ maxRows = IMPORT_LIMITS.maxRows,
40
+ maxBytes = IMPORT_LIMITS.maxBytes,
41
+ taskName,
42
+ retryTaskName,
43
+ taskProvider,
44
+ taskArtifactProvider,
45
+ taskIdempotencyKey,
46
+ initialTaskId,
47
+ onTaskSubmitted,
24
48
  onSuccess,
25
49
  }: Props = $props();
26
50
 
@@ -31,10 +55,13 @@
31
55
  const can = useCan(() => ({ resource: resourceName, action: 'import' }));
32
56
  const session = $derived(captureAuthSession(context.authProvider));
33
57
  const allowed = $derived(can.allowed === true && resource.canCreate !== false && session.available);
58
+ const activeTaskProvider = $derived(taskProvider ?? context.taskProvider);
34
59
  const scope = $derived({
35
60
  contract: binding.resource, provider: context.providers?.[binding.dataProviderName],
36
61
  tenant: context.tenantCacheKey?.__svadminTenant, meta: binding.meta,
37
62
  auth: context.authProvider, router: context.routerProvider, session, allowed,
63
+ taskProvider: activeTaskProvider, taskName, retryTaskName, taskIdempotencyKey, taskArtifactProvider,
64
+ access: context.accessControlProvider,
38
65
  });
39
66
  const availableFields = $derived(
40
67
  resource.fields.filter((f) => f.key !== (resource.primaryKey ?? 'id') && f.showInForm !== false)
@@ -50,6 +77,32 @@
50
77
  let importError = $state<string | null>(null);
51
78
  let parseToken = 0;
52
79
  let mounted = true;
80
+ let cancelled = $state(false);
81
+ let durableTaskId = $state<string | undefined>(untrack(() => initialTaskId));
82
+ let durableTaskScope = $state.raw<typeof scope | undefined>(untrack(() => scope));
83
+ const ownedTaskId = $derived(durableTaskScope && sameScope(durableTaskScope, scope) ? durableTaskId : undefined);
84
+ let durableHandledTaskId = $state<string | undefined>();
85
+ let durableSubmitAttempt = $state.raw<object | undefined>();
86
+ let artifactUploadController: AbortController | undefined;
87
+ let durablePolling = $state(true);
88
+ const durableMode = $derived(taskName !== undefined);
89
+ const submitTask = useSubmitTask();
90
+ const taskQuery = useTask(definedReactiveOptions({
91
+ get taskId() { return ownedTaskId; },
92
+ get taskProvider() { return activeTaskProvider; },
93
+ get queryOptions() {
94
+ return {
95
+ enabled: open && allowed && durableMode && !!activeTaskProvider && !!ownedTaskId,
96
+ refetchInterval: open && allowed && durableMode && ownedTaskId && durablePolling ? 2000 : false as const,
97
+ refetchOnWindowFocus: true,
98
+ };
99
+ },
100
+ }));
101
+ const durableStatus = $derived(normalizeTaskStatus(taskQuery.data?.status));
102
+ const retryReceipt = $derived(durableStatus === 'completed'
103
+ ? snapshotImportTaskResult(taskQuery.data?.result ?? taskQuery.data?.result_data)?.retry : undefined);
104
+ const durableActive = $derived(durableStatus === 'queued' || durableStatus === 'processing'
105
+ || !!durableSubmitAttempt);
53
106
 
54
107
  let importResult = $state<{ succeeded: number; failed: number } | null>(null);
55
108
  let succeededCount = $state(0);
@@ -59,10 +112,12 @@
59
112
  get resource() { return binding.resource; },
60
113
  get dataProviderName() { return binding.dataProviderName; },
61
114
  get batchSize() { return batchSize; },
115
+ get maxRows() { return maxRows; },
116
+ get maxBytes() { return maxBytes; },
62
117
  get enabled() { return open && allowed; },
63
118
  mapData: mapRow,
64
119
  }));
65
- const isImporting = $derived(importer.isLoading);
120
+ const isImporting = $derived(importer.isLoading || durableActive);
66
121
  const processedCount = $derived(importer.progress.processedAmount);
67
122
  const totalCount = $derived(importer.progress.totalAmount || rawRows.length);
68
123
  const progressPercent = $derived(
@@ -75,24 +130,35 @@
75
130
  const token = ++parseToken;
76
131
  fileError = null;
77
132
  importError = null;
133
+ durableTaskId = undefined;
134
+ durableHandledTaskId = undefined;
135
+ durableSubmitAttempt = undefined;
78
136
  selectedFile = null;
79
137
  fileName = file.name;
80
138
  rawHeaders = [];
81
139
  rawRows = [];
82
140
  columnMapping = {};
83
141
  try {
142
+ if (!Number.isSafeInteger(maxRows) || maxRows < 1 || maxRows > IMPORT_LIMITS.maxRows
143
+ || !Number.isSafeInteger(maxBytes) || maxBytes < 1 || maxBytes > IMPORT_LIMITS.maxBytes) {
144
+ throw new Error(i18n.t('import.invalidLimits'));
145
+ }
146
+ if (file.size > maxBytes) throw new Error(i18n.t('import.limit'));
84
147
  const text = await file.text();
85
148
  if (!current(origin, token)) return;
149
+ if (text.length > maxBytes) throw new Error(i18n.t('import.limit'));
86
150
  const cleanText = text.charCodeAt(0) === 0xfeff ? text.slice(1) : text;
87
151
 
88
152
  if (file.name.toLowerCase().endsWith('.json')) {
89
153
  const json: unknown = JSON.parse(cleanText);
154
+ if (Array.isArray(json) && json.length > maxRows) throw new Error(i18n.t('import.limit'));
90
155
  if (!Array.isArray(json) || json.length === 0 || json.some((item) => (
91
156
  typeof item !== 'object' || item === null || Array.isArray(item)
92
157
  ))) {
93
158
  throw new Error('The JSON file must contain a non-empty array of records.');
94
159
  }
95
160
  const records = json as Record<string, unknown>[];
161
+ if (records.length > maxRows) throw new Error(i18n.t('import.limit'));
96
162
  const keys = [...new Set(records.flatMap(item => Object.keys(item)))];
97
163
  if (keys.length === 0) throw new Error('The import file has no columns.');
98
164
  rawHeaders = keys;
@@ -109,6 +175,7 @@
109
175
  const records = rows.slice(1).filter((r: string[]) =>
110
176
  r.some((cell: string) => cell.trim().length > 0));
111
177
  if (records.length === 0) throw new Error('The import file has no records.');
178
+ if (records.length > maxRows) throw new Error(i18n.t('import.limit'));
112
179
  if (records.some((row) => row.length !== firstRow.length)) {
113
180
  throw new Error('The CSV file contains rows with inconsistent column counts.');
114
181
  }
@@ -161,6 +228,13 @@
161
228
  const normalized = val.trim().toLowerCase();
162
229
  if (['true', '1', 'yes', '是'].includes(normalized)) val = true;
163
230
  else if (['false', '0', 'no', '否'].includes(normalized)) val = false;
231
+ } else if (fieldDef?.type === 'select' && fieldDef.options && selectedFile?.name.toLowerCase().endsWith('.csv')) {
232
+ const matches = fieldDef.options.filter(option => String(option.value) === val);
233
+ const match = matches[0];
234
+ if (matches.length !== 1 || !match || match.disabled) throw new Error('Invalid import option.');
235
+ val = match.value;
236
+ } else if (fieldDef?.type === 'json' && selectedFile?.name.toLowerCase().endsWith('.csv')) {
237
+ val = JSON.parse(val);
164
238
  }
165
239
  }
166
240
  if (val !== undefined) {
@@ -170,13 +244,34 @@
170
244
  return record;
171
245
  }
172
246
 
247
+ function validateMappedRows(): string | null {
248
+ for (const [index, source] of rawRows.entries()) {
249
+ try {
250
+ const mapped = mapRow(Object.fromEntries(rawHeaders.map((header, column) => [header, source[column]])));
251
+ parseContractCreateInput(binding.resource, mapped);
252
+ } catch {
253
+ return i18n.t('import.invalidRow', { row: index + 1 });
254
+ }
255
+ }
256
+ return null;
257
+ }
258
+
173
259
  async function startImport() {
174
260
  if (!selectedFile || rawRows.length === 0 || isImporting || !open || !allowed) return;
261
+ if (durableMode && (!activeTaskProvider || !taskName?.trim() || !taskIdempotencyKey?.trim())) {
262
+ fileError = i18n.t('import.taskConfiguration');
263
+ return;
264
+ }
175
265
  const targets = Object.values(columnMapping).filter(Boolean);
176
266
  if (targets.length === 0 || new Set(targets).size !== targets.length) {
177
267
  fileError = 'Map at least one column, and use each target field only once.';
178
268
  return;
179
269
  }
270
+ const mappingError = validateMappedRows();
271
+ if (mappingError) {
272
+ fileError = mappingError;
273
+ return;
274
+ }
180
275
  const origin = scope;
181
276
  const token = parseToken;
182
277
  const notifySuccess = onSuccess;
@@ -185,6 +280,83 @@
185
280
  failedRecords = [];
186
281
  importResult = null;
187
282
  importError = null;
283
+ cancelled = false;
284
+ if (durableMode) {
285
+ if (durableTaskId || durableSubmitAttempt) return;
286
+ const attempt = {};
287
+ durableSubmitAttempt = attempt;
288
+ const uploadController = new AbortController();
289
+ artifactUploadController = uploadController;
290
+ try {
291
+ if (!activeTaskProvider || !taskName?.trim() || !taskIdempotencyKey?.trim()) {
292
+ throw new Error(i18n.t('import.taskConfiguration'));
293
+ }
294
+ const records = taskArtifactProvider ? undefined : rawRows.map((source, index) => {
295
+ const mapped = mapRow(Object.fromEntries(rawHeaders.map((header, column) => [header, source[column]])));
296
+ try { return parseContractCreateInput(binding.resource, mapped); }
297
+ catch { throw new Error(i18n.t('import.invalidRow', { row: index + 1 })); }
298
+ });
299
+ const uploaded = taskArtifactProvider
300
+ ? snapshotImportArtifact(await taskArtifactProvider.upload({
301
+ file: selectedFile,
302
+ fileName,
303
+ size: selectedFile.size,
304
+ resource: binding.resource.name,
305
+ idempotencyKey: JSON.stringify(['import-artifact', taskIdempotencyKey]),
306
+ signal: uploadController.signal,
307
+ }))
308
+ : undefined;
309
+ if (durableSubmitAttempt !== attempt || !current(origin, token) || uploadController.signal.aborted) return;
310
+ if (taskArtifactProvider && !uploaded) throw new Error(i18n.t('import.taskPayloadLimit'));
311
+ if (uploaded && (uploaded.size !== selectedFile.size || uploaded.fileName !== fileName)) {
312
+ throw new Error(i18n.t('import.taskPayloadLimit'));
313
+ }
314
+ const body = uploaded ? {
315
+ protocolVersion: 2,
316
+ resource: binding.resource.name,
317
+ fileName,
318
+ mapping: { ...columnMapping },
319
+ artifact: uploaded,
320
+ } : {
321
+ protocolVersion: 1,
322
+ resource: binding.resource.name,
323
+ fileName,
324
+ mapping: { ...columnMapping },
325
+ records,
326
+ };
327
+ const encoded = JSON.stringify(body);
328
+ if (new TextEncoder().encode(encoded).byteLength > maxBytes) {
329
+ fileError = i18n.t('import.taskPayloadLimit');
330
+ currentStep = 2;
331
+ return;
332
+ }
333
+ const handle = await submitTask.mutation.mutateAsync({
334
+ taskName: taskName.trim(),
335
+ taskProvider: activeTaskProvider,
336
+ successNotification: false,
337
+ errorNotification: false,
338
+ options: {
339
+ body,
340
+ idempotencyKey: taskIdempotencyKey,
341
+ ...(binding.meta === undefined ? {} : { meta: binding.meta }),
342
+ },
343
+ });
344
+ if (durableSubmitAttempt !== attempt || !current(origin, token)) return;
345
+ durableTaskId = handle.id;
346
+ durableTaskScope = scope;
347
+ durableHandledTaskId = undefined;
348
+ currentStep = 3;
349
+ try { void Promise.resolve(onTaskSubmitted?.(handle.id)).catch(() => {}); }
350
+ catch { /* 观察者失败不重发任务。 */ }
351
+ } catch {
352
+ if (durableSubmitAttempt !== attempt || !current(origin, token)) return;
353
+ importError = i18n.t('import.taskUnconfirmed');
354
+ } finally {
355
+ if (artifactUploadController === uploadController) artifactUploadController = undefined;
356
+ if (durableSubmitAttempt === attempt) durableSubmitAttempt = undefined;
357
+ }
358
+ return;
359
+ }
188
360
  try {
189
361
  const result = await importer.handleChange({ file: selectedFile });
190
362
  if (!current(origin, token)) return;
@@ -206,7 +378,63 @@
206
378
  }
207
379
 
208
380
  function current(origin: typeof scope, token: number): boolean {
209
- return mounted && open && allowed && origin === scope && origin.session.isCurrent() && token === parseToken;
381
+ return mounted && open && allowed && sameScope(origin, scope) && origin.session.isCurrent() && token === parseToken;
382
+ }
383
+
384
+ function sameScope(left: typeof scope, right: typeof scope): boolean {
385
+ return left.contract === right.contract && left.provider === right.provider &&
386
+ left.tenant === right.tenant && JSON.stringify(left.meta) === JSON.stringify(right.meta) &&
387
+ left.auth === right.auth && left.session.cacheKey === right.session.cacheKey &&
388
+ left.router === right.router && left.access === right.access &&
389
+ left.taskProvider === right.taskProvider && left.taskArtifactProvider === right.taskArtifactProvider &&
390
+ left.taskName === right.taskName && left.retryTaskName === right.retryTaskName &&
391
+ left.taskIdempotencyKey === right.taskIdempotencyKey;
392
+ }
393
+
394
+ async function retryFailedRows(): Promise<void> {
395
+ const receipt = retryReceipt;
396
+ const parentTaskId = ownedTaskId;
397
+ if (!open || !allowed || !receipt || !parentTaskId || !retryTaskName?.trim()
398
+ || !activeTaskProvider || durableSubmitAttempt || taskQuery.isError) return;
399
+ const origin = scope;
400
+ const token = parseToken;
401
+ const attempt = {};
402
+ durableSubmitAttempt = attempt;
403
+ importError = null;
404
+ try {
405
+ // 同一父任务和不可变回执始终使用同一键;未知提交结果不得转成新写入。
406
+ const handle = await submitTask.mutation.mutateAsync({
407
+ taskName: retryTaskName.trim(),
408
+ taskProvider: activeTaskProvider,
409
+ successNotification: false,
410
+ errorNotification: false,
411
+ options: {
412
+ idempotencyKey: JSON.stringify(['import-failed-rows', parentTaskId, receipt.receiptId]),
413
+ body: {
414
+ protocolVersion: 1,
415
+ operation: 'retry-failed-rows',
416
+ resource: binding.resource.name,
417
+ parentTaskId,
418
+ receiptId: receipt.receiptId,
419
+ rows: [...receipt.rows].sort((a, b) => a - b),
420
+ },
421
+ ...(binding.meta === undefined ? {} : { meta: binding.meta }),
422
+ },
423
+ });
424
+ if (durableSubmitAttempt !== attempt || !current(origin, token)) return;
425
+ if (handle.id === parentTaskId) throw new Error('Retry must create a child task');
426
+ durableTaskId = handle.id;
427
+ durableTaskScope = scope;
428
+ durableHandledTaskId = undefined;
429
+ durablePolling = true;
430
+ importResult = null;
431
+ try { void Promise.resolve(onTaskSubmitted?.(handle.id)).catch(() => {}); }
432
+ catch { /* 通知失败不重复提交。 */ }
433
+ } catch {
434
+ if (durableSubmitAttempt === attempt && current(origin, token)) importError = i18n.t('import.taskUnconfirmed');
435
+ } finally {
436
+ if (durableSubmitAttempt === attempt) durableSubmitAttempt = undefined;
437
+ }
210
438
  }
211
439
 
212
440
  function downloadErrors() {
@@ -220,12 +448,20 @@
220
448
  const url = URL.createObjectURL(blob);
221
449
  const link = document.createElement('a');
222
450
  link.href = url;
223
- link.download = `${resourceName}-import-errors.csv`;
224
- link.click();
225
- URL.revokeObjectURL(url);
451
+ const safeResourceName = resourceName.replace(/[^A-Za-z0-9._-]+/g, '-')
452
+ .replace(/^[._-]+/, '').slice(0, 100).replace(/[._-]+$/, '') || 'resource';
453
+ link.download = `${safeResourceName}-import-errors.csv`;
454
+ try {
455
+ link.click();
456
+ } finally {
457
+ // 浏览器需要在 click 返回后仍能读取对象 URL,延迟回收避免空下载。
458
+ setTimeout(() => URL.revokeObjectURL(url), 10_000);
459
+ }
226
460
  }
227
461
 
228
462
  function reset() {
463
+ artifactUploadController?.abort();
464
+ artifactUploadController = undefined;
229
465
  parseToken += 1;
230
466
  currentStep = 1;
231
467
  fileName = '';
@@ -238,15 +474,62 @@
238
474
  importResult = null;
239
475
  fileError = null;
240
476
  importError = null;
477
+ cancelled = false;
478
+ durableSubmitAttempt = undefined;
479
+ durableHandledTaskId = undefined;
480
+ durablePolling = true;
241
481
  importer.reset();
242
482
  }
243
483
 
484
+ function cancelImport() {
485
+ if (durableMode && durableTaskId && durableActive) return;
486
+ if (!isImporting) return;
487
+ // 废弃 UI 回执与核心执行令牌;已经发出的请求并不因此回滚。
488
+ parseToken++;
489
+ artifactUploadController?.abort();
490
+ artifactUploadController = undefined;
491
+ durableSubmitAttempt = undefined;
492
+ importer.reset();
493
+ cancelled = true;
494
+ importResult = null;
495
+ importError = null;
496
+ }
497
+
498
+ let observedScope = untrack(() => scope);
499
+ let observedOpen = untrack(() => open);
244
500
  $effect.pre(() => {
245
- void scope;
246
- void open;
247
- untrack(reset);
501
+ const changed = !sameScope(observedScope, scope) || (observedScope.allowed && !allowed);
502
+ if (changed) {
503
+ durableTaskId = undefined;
504
+ durableTaskScope = undefined;
505
+ durableHandledTaskId = undefined;
506
+ }
507
+ if (changed || observedOpen !== open) untrack(reset);
508
+ observedScope = scope;
509
+ observedOpen = open;
248
510
  });
249
- $effect(() => () => { mounted = false; parseToken += 1; });
511
+ $effect(() => {
512
+ const task = taskQuery.data;
513
+ if (!open || !allowed || !durableTaskId || !task || durableHandledTaskId === durableTaskId) return;
514
+ if (!['completed', 'failed', 'cancelled'].includes(durableStatus)) return;
515
+ durableHandledTaskId = durableTaskId;
516
+ durablePolling = false;
517
+ if (durableStatus === 'completed') {
518
+ const result = snapshotImportTaskResult(task.result ?? task.result_data);
519
+ if (result) {
520
+ succeededCount = result.succeeded;
521
+ importResult = { succeeded: result.succeeded, failed: result.failed };
522
+ try { void Promise.resolve(onSuccess?.(importResult)).catch(() => {}); }
523
+ catch { /* 任务完成通知失败不改变服务端任务结果。 */ }
524
+ } else {
525
+ importError = i18n.t('import.invalidTaskResult');
526
+ }
527
+ } else if (durableStatus === 'failed' || durableStatus === 'cancelled') {
528
+ importError = i18n.t('common.operationFailed');
529
+ importResult = null;
530
+ }
531
+ });
532
+ $effect(() => () => { mounted = false; parseToken += 1; artifactUploadController?.abort(); });
250
533
  </script>
251
534
 
252
535
  {#if open}
@@ -272,7 +555,41 @@
272
555
  {#if !allowed}
273
556
  <p role="status">{i18n.t('common.operationFailed', { defaultValue: 'Import is unavailable.' })}</p>
274
557
  {/if}
275
- {#if currentStep === 1}
558
+ {#if durableMode && (durableTaskId || durableSubmitAttempt || currentStep === 3)}
559
+ {#if durableTaskId}
560
+ <p>{durableTaskId}</p>
561
+ {#if taskQuery.isError}
562
+ <p role="alert">{i18n.t('task.fetchFailed')}</p>
563
+ <Button type="button" onclick={() => taskQuery.refetch()}>{i18n.t('common.retry')}</Button>
564
+ {:else if taskQuery.data && allowed}
565
+ <TaskStatusBadge status={taskQuery.data.status ?? 'pending'} />
566
+ {@const progress = resolveTaskProgress(taskQuery.data)}
567
+ {#if progress !== undefined}
568
+ <Progress value={progress} />
569
+ {/if}
570
+ {#if importResult}
571
+ <p role="status">{i18n.t('common.succeeded')}: {importResult.succeeded};
572
+ {i18n.t('common.failed')}: {importResult.failed}</p>
573
+ {/if}
574
+ {#if retryReceipt && retryTaskName?.trim()}
575
+ <Button type="button" disabled={!allowed || !!durableSubmitAttempt}
576
+ onclick={retryFailedRows}><RotateCw aria-hidden="true" />{i18n.t('import.retryFailedRows')}</Button>
577
+ {/if}
578
+ {#if activeTaskProvider?.cancel && canCancelTask(taskQuery.data)}
579
+ <CancelTaskButton taskId={durableTaskId} taskProvider={activeTaskProvider}
580
+ disabled={!allowed || !open}
581
+ onSuccess={() => { void taskQuery.refetch(); }}
582
+ onError={() => { importError = i18n.t('common.operationFailed'); }} />
583
+ {/if}
584
+ {:else}
585
+ <p role="status">{i18n.t('common.loading')}</p>
586
+ {/if}
587
+ {:else if durableSubmitAttempt}
588
+ <p role="status">{i18n.t('common.processing')}</p>
589
+ {/if}
590
+ {#if importError}<p role="alert">{importError}</p>{/if}
591
+ <Button type="button" onclick={() => { open = false; }}>{i18n.t('common.close')}</Button>
592
+ {:else if currentStep === 1}
276
593
  <div
277
594
  class="svadmin-u-65935df577ba svadmin-u-a29b7a649c77 svadmin-u-c9ed8c5f79ae svadmin-u-8b9d5d768973 svadmin-u-a217b4eaa918 svadmin-u-845f53365c8d svadmin-u-ca6bf63030aa svadmin-u-ceb69a6b0e5f svadmin-u-34516836730d svadmin-u-967d113a1451"
278
595
  ondragover={(e) => e.preventDefault()}
@@ -309,7 +626,7 @@
309
626
  {/if}
310
627
 
311
628
  <!-- Step 2: Column Mapping -->
312
- {#if currentStep === 2}
629
+ {#if currentStep === 2 && !durableTaskId}
313
630
  <div class="svadmin-u-3e7ce58d64fa">
314
631
  {#if fileError}<p role="alert">{fileError}</p>{/if}
315
632
  <div class="svadmin-u-60fbb7713999 svadmin-u-3960ffc248d9 svadmin-u-8ef2268efbbc svadmin-u-359090c2d529 svadmin-u-bfa603190748 svadmin-u-2859c861d7de svadmin-u-9fe52d5d506c svadmin-u-5f22e64f2282 svadmin-u-ca6bcd4b6f3f svadmin-u-05faf5c801ff">
@@ -364,16 +681,19 @@
364
681
  {/if}
365
682
 
366
683
  <!-- Step 3: Progress & Summary -->
367
- {#if currentStep === 3}
684
+ {#if currentStep === 3 && !durableMode}
368
685
  <div class="svadmin-u-b43b4c086d9a svadmin-u-03b4dd7f172b">
369
686
  <div class="svadmin-u-6f7e013d6499">
370
687
  <div class="svadmin-u-60fbb7713999 svadmin-u-3960ffc248d9 svadmin-u-8ef2268efbbc svadmin-u-359090c2d529">
371
- <span class="svadmin-u-bfa603190748">{isImporting ? i18n.t('common.importing', { defaultValue: 'Importing...' }) : importError ? i18n.t('common.operationFailed', { defaultValue: 'Import failed' }) : i18n.t('common.completed', { defaultValue: 'Completed' })}</span>
372
- <span class="svadmin-u-e83a7042bc91 svadmin-u-d4108abe6359 svadmin-u-3032cae0badb">{progressPercent}%</span>
688
+ <span class="svadmin-u-bfa603190748">{cancelled ? i18n.t('import.stopped') : isImporting ? i18n.t('common.importing', { defaultValue: 'Importing...' }) : importError ? i18n.t('common.operationFailed', { defaultValue: 'Import failed' }) : i18n.t('common.completed', { defaultValue: 'Completed' })}</span>
689
+ {#if !cancelled}<span class="svadmin-u-e83a7042bc91 svadmin-u-d4108abe6359 svadmin-u-3032cae0badb">{progressPercent}%</span>{/if}
373
690
  </div>
374
- <Progress value={progressPercent} class="svadmin-u-2f2a842e50fa" />
691
+ {#if !cancelled}<Progress value={progressPercent} class="svadmin-u-2f2a842e50fa" />{/if}
375
692
  </div>
376
693
 
694
+ {#if cancelled}
695
+ <p role="status">{i18n.t('import.stoppedHint')}</p>
696
+ {:else}
377
697
  <div class="svadmin-u-f3c543ad5fe9 svadmin-u-8e75e3db482b svadmin-u-1004c0c3954c svadmin-u-ca6bf63030aa">
378
698
  <div class="svadmin-u-eb6e8b881acd svadmin-u-5f22e64f2282 svadmin-u-17a9f7af2265 svadmin-u-ca6bcd4b6f3f svadmin-u-95b7dea5a67f">
379
699
  <span class="svadmin-u-359090c2d529 svadmin-u-bfa603190748 svadmin-u-0214b4b355d1">{i18n.t('common.succeeded', { defaultValue: 'Succeeded' })}</span>
@@ -384,6 +704,7 @@
384
704
  <span class="svadmin-u-d5c9b0001e7e svadmin-u-69450ef1487e svadmin-u-811148b13d1e svadmin-u-3032cae0badb">{failedRecords.length}</span>
385
705
  </div>
386
706
  </div>
707
+ {/if}
387
708
 
388
709
  {#if importError}
389
710
  <p role="alert">{importError}</p>
@@ -411,6 +732,7 @@
411
732
  {i18n.t('common.close', { defaultValue: 'Close' })}
412
733
  </Button>
413
734
  {:else}
735
+ <Button variant="outline" size="sm" onclick={cancelImport}>{i18n.t('common.cancel')}</Button>
414
736
  <Button size="sm" disabled class="svadmin-u-77a2a20e90d4">
415
737
  <Loader2 class="svadmin-u-7fc7f732bf7e svadmin-u-bf600f8e029c svadmin-u-afbdd13a380e" />
416
738
  {i18n.t('common.processing', { defaultValue: 'Processing...' })}
@@ -1,7 +1,18 @@
1
+ import type { ImportArtifactProvider, TaskProvider } from '@svadmin/core';
1
2
  interface Props {
2
3
  resourceName: string;
3
4
  open?: boolean;
4
5
  batchSize?: number;
6
+ maxRows?: number;
7
+ maxBytes?: number;
8
+ taskName?: string;
9
+ retryTaskName?: string;
10
+ taskProvider?: TaskProvider;
11
+ /** 上传原文件并以 artifact 引用提交任务,避免任务请求重复携带 records。 */
12
+ taskArtifactProvider?: ImportArtifactProvider;
13
+ taskIdempotencyKey?: string;
14
+ initialTaskId?: string;
15
+ onTaskSubmitted?: (taskId: string) => void;
5
16
  onSuccess?: (result: {
6
17
  succeeded: number;
7
18
  failed: number;