@cloudtower/eagle 490.0.5 → 490.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.
Files changed (56) hide show
  1. package/README.md +34 -0
  2. package/dist/cjs/core/Cascader/cascader.widget.js +12 -12
  3. package/dist/cjs/core/SearchInput/SearchInput.hook.js +124 -0
  4. package/dist/cjs/core/SearchInput/SearchInput.js +253 -0
  5. package/dist/cjs/core/SearchInput/SearchInput.style.js +13 -0
  6. package/dist/cjs/core/index.js +6 -6
  7. package/dist/cjs/index.js +172 -172
  8. package/dist/cjs/legacy-antd.js +89 -89
  9. package/dist/cjs/stats1.html +1 -1
  10. package/dist/components.css +3836 -3813
  11. package/dist/esm/core/Cascader/cascader.widget.js +1 -1
  12. package/dist/esm/core/SearchInput/SearchInput.hook.js +117 -0
  13. package/dist/esm/core/SearchInput/SearchInput.js +247 -0
  14. package/dist/esm/core/SearchInput/SearchInput.style.js +7 -0
  15. package/dist/esm/index.js +1 -1
  16. package/dist/esm/legacy-antd.js +1 -1
  17. package/dist/esm/stats1.html +1 -1
  18. package/dist/linaria.merged.scss +3611 -3582
  19. package/dist/src/core/Cascader/cascader.type.d.ts +1 -1
  20. package/dist/src/core/ImmersiveDialog/type.d.ts +127 -19
  21. package/dist/src/core/MediumDialog/MediumDialog.type.d.ts +49 -1
  22. package/dist/src/core/SearchInput/SearchInput.d.ts +2 -0
  23. package/dist/src/core/SearchInput/SearchInput.hook.d.ts +9 -0
  24. package/dist/src/core/SearchInput/SearchInput.style.d.ts +5 -0
  25. package/dist/src/core/SearchInput/SearchInput.type.d.ts +150 -0
  26. package/dist/src/core/SearchInput/__test__/SearchInput.hook.test.d.ts +1 -0
  27. package/dist/src/core/SearchInput/index.d.ts +2 -4
  28. package/dist/src/core/SmallDialog/SmallDialog.type.d.ts +150 -21
  29. package/dist/src/core/TableForm/types.d.ts +216 -68
  30. package/dist/src/core/WizardDialog/type.d.ts +147 -13
  31. package/dist/src/core/index.d.ts +0 -1
  32. package/dist/src/coreX/Dialogs/DeleteDialog/DeleteDialog.type.d.ts +100 -9
  33. package/dist/src/coreX/Dialogs/RejectDialog/RejectDialog.type.d.ts +126 -19
  34. package/dist/stories/docs/core/ImmersiveDialog.stories.d.ts +76 -8
  35. package/dist/stories/docs/core/MediumDialog.stories.d.ts +42 -8
  36. package/dist/stories/docs/core/SearchInput.stories.d.ts +6 -1
  37. package/dist/stories/docs/core/SmallDialog.stories.d.ts +86 -7
  38. package/dist/stories/docs/core/TableForm.stories.d.ts +40 -26
  39. package/dist/stories/docs/core/WizardDialog.stories.d.ts +65 -6
  40. package/dist/stories/docs/coreX/Dialogs/DeleteDialog.stories.d.ts +20 -19
  41. package/dist/stories/docs/coreX/Dialogs/RejectDialog.stories.d.ts +24 -23
  42. package/dist/style.css +3836 -3813
  43. package/docs/core/ImmersiveDialog/guide.md +298 -0
  44. package/docs/core/LegacyModal/migrate-guide.md +422 -0
  45. package/docs/core/MediumDialog/guide.md +263 -0
  46. package/docs/core/SearchInput/guide.md +125 -0
  47. package/docs/core/SmallDialog/guide.md +224 -0
  48. package/docs/core/TableForm/guide.md +195 -0
  49. package/docs/core/WizardDialog/guide.md +322 -0
  50. package/docs/coreX/DeleteDialog/guide.md +161 -0
  51. package/docs/coreX/RejectDialog/guide.md +185 -0
  52. package/docs/llms.txt +169 -0
  53. package/package.json +6 -5
  54. package/dist/cjs/core/SearchInput/index.js +0 -164
  55. package/dist/esm/core/SearchInput/index.js +0 -157
  56. package/dist/src/core/SearchInput/searchInput.type.d.ts +0 -63
@@ -0,0 +1,422 @@
1
+ # LegacyModal 迁移指南
2
+
3
+ > **给 Coding Agent 的说明:**
4
+ > 1. 阅读本文档了解 LegacyModal 到新 Dialog 组件的完整迁移映射
5
+ > 2. 阅读替代组件的文档(如 `../SmallDialog/guide.md`)了解新 API
6
+ > 3. 如果遇到本文档未覆盖的边界情况,标注为"建议方案,请确认后再执行",等待开发者确认
7
+ > 4. 不要在没有逐个确认的情况下直接执行批量迁移
8
+
9
+ `LegacyModal` 已被标记为 `@deprecated`,将在未来版本中移除。请根据以下指南将其迁移到对应的新 Dialog 组件。
10
+
11
+ ## 选择替代组件
12
+
13
+ | LegacyModal 使用模式 | 替代组件 | 判断条件 |
14
+ |---|---|---|
15
+ | `normal={true}` 或默认模式 | SmallDialog / MediumDialog | 内容较少(如确认提示、简单表单)使用 SmallDialog(默认宽度 492px);内容较多(如复杂表单、详情展示)使用 MediumDialog(默认宽度 720px) |
16
+ | `fullscreen={true}` | ImmersiveDialog | 需要全屏展示的场景,如大表单、数据编辑 |
17
+ | `wizard={...}` | WizardDialog | 多步骤向导流程,如创建向导、配置引导 |
18
+
19
+ ## Props 映射表
20
+
21
+ ### 通用 Props 对照
22
+
23
+ | LegacyModal | SmallDialog / MediumDialog | ImmersiveDialog | WizardDialog | 说明 |
24
+ |---|---|---|---|---|
25
+ | `title` | `title` | `title` | `title` | 标题,用法不变 |
26
+ | `width` | `width` | 不需要(自动全屏) | 不需要(自动全屏) | SmallDialog 默认 492px,MediumDialog 默认 720px |
27
+ | `confirmLoading` | `confirmLoading` | `confirmLoading` | `confirmLoading` | 确认按钮加载状态,用法不变 |
28
+ | `okText` | `okText` | `okText` | `okText` | 确认按钮文案,用法不变 |
29
+ | `cancelText` | `cancelText` | `cancelText` | `cancelText` | 取消按钮文案,用法不变 |
30
+ | `okButtonProps` | `okButtonProps` | `okButtonProps` | `okButtonProps` | 确认按钮属性,用法不变 |
31
+ | `cancelButtonProps` | `cancelButtonProps` | `cancelButtonProps` | `cancelButtonProps` | 取消按钮属性,用法不变 |
32
+ | `showOk` | `showOk` | `showOk` | `showOk` | 是否显示确认按钮,用法不变 |
33
+ | `showCancel` | 无(始终显示) | `showCancel` | `showCancel` | SmallDialog 始终显示取消按钮 |
34
+ | `error` | `error` | `error` | `error` | 错误信息,用法不变 |
35
+ | `maskClosable` | `maskClosable` | 继承 antd Modal | 继承 antd Modal | 新组件默认值有变化,见下方说明 |
36
+ | `className` | `className` | `className` | `className` | 自定义类名,用法不变 |
37
+ | `closable` | `closable` | 继承 antd Modal | 继承 antd Modal | 用法不变 |
38
+ | `closeIcon` | 无(内置样式) | `closeIcon` | `closeIcon` | SmallDialog 内置了关闭图标样式 |
39
+ | `hideFooterButtonBorder` | 无 | 无 | 无 | 新组件已移除此设计,无需迁移 |
40
+ | `normal` | 无 | 无 | 无 | 新组件无需此参数,通过选择不同组件来区分 |
41
+ | `fullscreen` | 无 | 无 | 无 | 新组件无需此参数,使用 ImmersiveDialog 即为全屏 |
42
+
43
+ ### 新增 Props
44
+
45
+ | Props | 可用组件 | 说明 |
46
+ |---|---|---|
47
+ | `hideFooter` | SmallDialog / MediumDialog / ImmersiveDialog | 是否隐藏底部按钮区域 |
48
+ | `showFooterErrorIcon` | SmallDialog / MediumDialog / ImmersiveDialog | 是否显示错误图标(默认 true) |
49
+ | `initializing` | SmallDialog / MediumDialog / ImmersiveDialog | 初始化加载状态,显示骨架屏 |
50
+ | `initializingError` | SmallDialog / MediumDialog / ImmersiveDialog | 初始化错误内容 |
51
+ | `initializingSkeletonRows` | SmallDialog / MediumDialog | 初始化骨架屏行数 |
52
+ | `isContentFull` | MediumDialog / ImmersiveDialog | 内容是否占满视窗 |
53
+ | `footerLeftAction` | ImmersiveDialog / WizardDialog | 底部左侧操作区域 |
54
+ | `left` / `right` | ImmersiveDialog / WizardDialog | 左侧/右侧自定义内容 |
55
+ | `TitleRender` | SmallDialog / MediumDialog | 自定义标题渲染组件 |
56
+ | `footerClassName` | SmallDialog / MediumDialog | 自定义 footer 类名 |
57
+
58
+ ## 回调签名变化
59
+
60
+ 这是迁移中最关键的变化。新 Dialog 组件的 `onOk` 和 `onCancel` 签名与 LegacyModal 不同:
61
+
62
+ ### LegacyModal(旧)
63
+
64
+ ```typescript
65
+ // onOk 和 onCancel 都接收 MouseEvent,弹窗关闭由内部 redux 状态控制
66
+ onOk?: (e: React.MouseEvent<HTMLElement>) => void;
67
+ onCancel?: (e: React.MouseEvent<HTMLElement>) => void;
68
+ ```
69
+
70
+ ### SmallDialog / MediumDialog(新)
71
+
72
+ ```typescript
73
+ // onOk 和 onCancel 都接收 popModal 函数作为参数
74
+ // 调用 popModal() 关闭弹窗,不调用则弹窗保持打开
75
+ onOk?: (popModal: () => void) => void;
76
+ onCancel?: (popModal: () => void) => void;
77
+ ```
78
+
79
+ SmallDialog / MediumDialog 内部已调用 `usePopModal()`,并将 `popModal` 作为参数传递给回调。这意味着:
80
+
81
+ - 如果不传 `onOk`,点击确认按钮会自动关闭弹窗
82
+ - 如果不传 `onCancel`,点击取消按钮或关闭图标会自动关闭弹窗
83
+ - 如果传了回调,需要在合适的时机手动调用 `popModal()` 来关闭弹窗
84
+
85
+ ### ImmersiveDialog / WizardDialog(新)
86
+
87
+ ```typescript
88
+ // onOk 接收 MouseEvent,需要手动使用 usePopModal() 关闭弹窗
89
+ onOk?: (e: React.MouseEvent<HTMLElement>) => void;
90
+ // onCancel 接收 MouseEvent,组件内部会自动调用 popModal() 关闭
91
+ onCancel?: (e: React.MouseEvent<HTMLElement>) => void;
92
+ ```
93
+
94
+ ImmersiveDialog / WizardDialog 内部也调用了 `usePopModal()`,但行为不同:
95
+
96
+ - 点击取消按钮或关闭图标时,组件内部会先调用 `popModal()` 再触发 `onCancel`
97
+ - 点击确认按钮时,组件内部不会自动关闭弹窗,需要在 `onOk` 中使用 `usePopModal()` 手动关闭
98
+
99
+ ## 迁移前后代码对比
100
+
101
+ ### 场景一:普通弹窗
102
+
103
+ 迁移前(LegacyModal):
104
+
105
+ ```tsx
106
+ import { LegacyModal } from "@cloudtower/eagle";
107
+ import { usePushModal } from "@cloudtower/eagle";
108
+
109
+ function MyPage() {
110
+ const pushModal = usePushModal();
111
+
112
+ const handleOpen = () => {
113
+ pushModal({
114
+ component: () => (
115
+ <LegacyModal
116
+ title="确认删除"
117
+ normal={true}
118
+ onOk={(e) => {
119
+ // 执行删除操作
120
+ deleteItem();
121
+ }}
122
+ onCancel={(e) => {
123
+ // LegacyModal 通过内部 redux 状态控制关闭
124
+ }}
125
+ >
126
+ <p>确定要删除此项吗?</p>
127
+ </LegacyModal>
128
+ ),
129
+ props: {},
130
+ });
131
+ };
132
+
133
+ return <button onClick={handleOpen}>删除</button>;
134
+ }
135
+ ```
136
+
137
+ 迁移后(SmallDialog):
138
+
139
+ ```tsx
140
+ import { SmallDialog } from "@cloudtower/eagle";
141
+ import { usePushModal } from "@cloudtower/eagle";
142
+
143
+ function MyPage() {
144
+ const pushModal = usePushModal();
145
+
146
+ const handleOpen = () => {
147
+ pushModal({
148
+ component: () => (
149
+ <SmallDialog
150
+ title="确认删除"
151
+ onOk={(popModal) => {
152
+ // 执行删除操作
153
+ deleteItem();
154
+ // 手动调用 popModal 关闭弹窗
155
+ popModal();
156
+ }}
157
+ // 不传 onCancel,点击取消时自动关闭
158
+ >
159
+ <p>确定要删除此项吗?</p>
160
+ </SmallDialog>
161
+ ),
162
+ props: {},
163
+ });
164
+ };
165
+
166
+ return <button onClick={handleOpen}>删除</button>;
167
+ }
168
+ ```
169
+
170
+ ### 场景二:全屏弹窗
171
+
172
+ 迁移前(LegacyModal fullscreen):
173
+
174
+ ```tsx
175
+ import { LegacyModal } from "@cloudtower/eagle";
176
+ import { usePushModal } from "@cloudtower/eagle";
177
+
178
+ function MyPage() {
179
+ const pushModal = usePushModal();
180
+
181
+ const handleOpen = () => {
182
+ pushModal({
183
+ component: () => (
184
+ <LegacyModal
185
+ title="编辑配置"
186
+ fullscreen={true}
187
+ confirmLoading={loading}
188
+ onOk={(e) => {
189
+ saveConfig();
190
+ }}
191
+ onCancel={(e) => {
192
+ // 关闭
193
+ }}
194
+ >
195
+ <ConfigForm />
196
+ </LegacyModal>
197
+ ),
198
+ props: {},
199
+ });
200
+ };
201
+
202
+ return <button onClick={handleOpen}>编辑</button>;
203
+ }
204
+ ```
205
+
206
+ 迁移后(ImmersiveDialog):
207
+
208
+ ```tsx
209
+ import { ImmersiveDialog } from "@cloudtower/eagle";
210
+ import { usePushModal, usePopModal } from "@cloudtower/eagle";
211
+
212
+ function ConfigDialog() {
213
+ const popModal = usePopModal();
214
+
215
+ return (
216
+ <ImmersiveDialog
217
+ title="编辑配置"
218
+ confirmLoading={loading}
219
+ onOk={(e) => {
220
+ saveConfig().then(() => {
221
+ // ImmersiveDialog 的 onOk 不会自动关闭弹窗
222
+ // 需要手动调用 popModal()
223
+ popModal();
224
+ });
225
+ }}
226
+ // onCancel 不需要手动关闭,组件内部会自动调用 popModal()
227
+ >
228
+ <ConfigForm />
229
+ </ImmersiveDialog>
230
+ );
231
+ }
232
+
233
+ function MyPage() {
234
+ const pushModal = usePushModal();
235
+
236
+ const handleOpen = () => {
237
+ pushModal({
238
+ component: () => <ConfigDialog />,
239
+ props: {},
240
+ });
241
+ };
242
+
243
+ return <button onClick={handleOpen}>编辑</button>;
244
+ }
245
+ ```
246
+
247
+ ### 场景三:向导弹窗
248
+
249
+ 迁移前(LegacyModal wizard):
250
+
251
+ ```tsx
252
+ import { LegacyModal } from "@cloudtower/eagle";
253
+ import { usePushModal } from "@cloudtower/eagle";
254
+
255
+ function MyPage() {
256
+ const pushModal = usePushModal();
257
+ const [step, setStep] = useState(0);
258
+
259
+ const handleOpen = () => {
260
+ pushModal({
261
+ component: () => (
262
+ <LegacyModal
263
+ title="创建虚拟机"
264
+ fullscreen={true}
265
+ wizard={{
266
+ step: step,
267
+ onStepChange: (s) => setStep(s),
268
+ steps: [
269
+ {
270
+ title: "基本信息",
271
+ render: <BasicInfoStep />,
272
+ onOk: (e) => {
273
+ // 校验后进入下一步
274
+ },
275
+ },
276
+ {
277
+ title: "网络配置",
278
+ render: <NetworkStep />,
279
+ onOk: (e) => {
280
+ // 提交创建
281
+ createVM();
282
+ },
283
+ },
284
+ ],
285
+ }}
286
+ >
287
+ {/* 公共内容 */}
288
+ </LegacyModal>
289
+ ),
290
+ props: {},
291
+ });
292
+ };
293
+
294
+ return <button onClick={handleOpen}>创建</button>;
295
+ }
296
+ ```
297
+
298
+ 迁移后(WizardDialog):
299
+
300
+ ```tsx
301
+ import { WizardDialog } from "@cloudtower/eagle";
302
+ import { usePushModal, usePopModal } from "@cloudtower/eagle";
303
+
304
+ function CreateVMDialog() {
305
+ const popModal = usePopModal();
306
+
307
+ return (
308
+ <WizardDialog
309
+ title="创建虚拟机"
310
+ steps={[
311
+ {
312
+ title: "基本信息",
313
+ children: <BasicInfoStep />,
314
+ },
315
+ {
316
+ title: "网络配置",
317
+ children: <NetworkStep />,
318
+ },
319
+ ]}
320
+ onNextStep={(step) => {
321
+ // 返回 false 可阻止进入下一步(用于校验)
322
+ if (!validate()) return false;
323
+ }}
324
+ onStepChange={(step) => {
325
+ // 步骤变化回调
326
+ }}
327
+ onOk={(e) => {
328
+ // 最后一步点击确认时触发
329
+ createVM().then(() => {
330
+ popModal();
331
+ });
332
+ }}
333
+ >
334
+ {/* 公共内容(可选) */}
335
+ </WizardDialog>
336
+ );
337
+ }
338
+
339
+ function MyPage() {
340
+ const pushModal = usePushModal();
341
+
342
+ const handleOpen = () => {
343
+ pushModal({
344
+ component: () => <CreateVMDialog />,
345
+ props: {},
346
+ });
347
+ };
348
+
349
+ return <button onClick={handleOpen}>创建</button>;
350
+ }
351
+ ```
352
+
353
+ ## 关键差异说明
354
+
355
+ ### 1. 向导步骤配置
356
+
357
+ | 对比项 | LegacyModal wizard | WizardDialog |
358
+ |---|---|---|
359
+ | 步骤内容字段 | `render: React.ReactNode` | `children: React.ReactNode` |
360
+ | 步骤级别的 onOk | 支持(每个 step 可配 `onOk`) | 不支持(统一使用顶层 `onOk`) |
361
+ | 步骤级别的 onPrev | 支持(每个 step 可配 `onPrev`) | 不支持(统一使用 `onPrevStep`) |
362
+ | 步骤级别的 okText | 支持(每个 step 可配 `okText`) | 不支持(统一使用 `nextText` / `okText`) |
363
+ | 步骤级别的 disabled | 支持 | 不支持 |
364
+ | 步骤管理 | 需要外部维护 `step` 状态 | 内部管理(也可通过 `step` prop 受控) |
365
+ | 阻止下一步 | 不支持 | `onNextStep` 返回 `false` 可阻止 |
366
+ | 隐藏左侧步骤 | `hideLeft` | `hideSteps` |
367
+ | 禁用上一步 | `disablePrevStep` | 无(由 `onPrevStep` 自行控制) |
368
+
369
+ ### 2. maskClosable 默认值
370
+
371
+ | 组件 | 默认值 |
372
+ |---|---|
373
+ | LegacyModal | `false` |
374
+ | SmallDialog | `true` |
375
+ | MediumDialog | `true` |
376
+ | ImmersiveDialog | 继承 antd Modal 默认值 |
377
+ | WizardDialog | 继承 antd Modal 默认值 |
378
+
379
+ 如果你的业务场景要求点击遮罩层不关闭弹窗,需要显式设置 `maskClosable={false}`。
380
+
381
+ ### 3. 弹窗关闭机制
382
+
383
+ LegacyModal 通过内部 redux 状态(`closeId`)控制关闭动画和移除,开发者只需在回调中执行业务逻辑。
384
+
385
+ 新 Dialog 组件的关闭行为:
386
+
387
+ - **SmallDialog / MediumDialog**:`onOk` 和 `onCancel` 接收 `popModal` 参数,开发者决定何时关闭
388
+ - **ImmersiveDialog / WizardDialog**:取消操作自动关闭,确认操作需通过 `usePopModal()` 手动关闭
389
+
390
+ ## 迁移步骤
391
+
392
+ 1. **确定 LegacyModal 使用模式** -- 检查是否使用了 `fullscreen`、`wizard` 等 prop,对照上方表格选择替代组件
393
+
394
+ 2. **替换 import**
395
+ ```tsx
396
+ // 迁移前
397
+ import { LegacyModal } from "@cloudtower/eagle";
398
+ // 迁移后(根据场景选择)
399
+ import { SmallDialog } from "@cloudtower/eagle";
400
+ import { MediumDialog } from "@cloudtower/eagle";
401
+ import { ImmersiveDialog } from "@cloudtower/eagle";
402
+ import { WizardDialog } from "@cloudtower/eagle";
403
+ ```
404
+
405
+ 3. **调整 Props** -- 对照 Props 映射表,移除不再支持的 prop(`normal`、`fullscreen`、`hideFooterButtonBorder`),添加必要的新 prop
406
+
407
+ 4. **改写 onOk / onCancel 回调**
408
+ - 如果迁移到 SmallDialog / MediumDialog:将回调签名从 `(e) => {}` 改为 `(popModal) => {}`,在需要关闭弹窗时调用 `popModal()`
409
+ - 如果迁移到 ImmersiveDialog / WizardDialog:在组件内部使用 `usePopModal()` 获取 `popModal`,在 `onOk` 中手动调用关闭。注意,对于 ImmersiveDialog / WizardDialog,需要将弹窗内容抽取为独立组件以便使用 hook
410
+
411
+ 5. **处理向导模式迁移**(仅 wizard 场景)
412
+ - 将 `wizard.steps[].render` 改为 `steps[].children`
413
+ - 移除步骤级别的 `onOk`、`onPrev`、`okText`,统一使用顶层回调
414
+ - 利用 `onNextStep` 返回 `false` 的机制替代步骤级别的校验逻辑
415
+ - 如果之前依赖外部 `step` 状态,可以改为让 WizardDialog 内部管理,通过 `onStepChange` 监听变化
416
+
417
+ 6. **运行类型检查**
418
+ ```bash
419
+ cd packages/eagle && yarn typings
420
+ ```
421
+
422
+ 7. **手动验证功能** -- 检查弹窗的打开、关闭、确认、取消、遮罩层点击等行为是否符合预期
@@ -0,0 +1,263 @@
1
+ # MediumDialog
2
+
3
+ ## 简介
4
+
5
+ MediumDialog 是一个固定 720px 宽度的中型对话框组件,继承 SmallDialog 的全部功能,在此基础上提供更大的显示区域(720px 宽度、60px 水平内边距)和更突出的标题排版(d1s_bold_title)。适用于包含表单输入、内容较多的确认等场景。通过 `isContentFull` 属性可开启全屏模式,使弹窗尺寸变为 `width: calc(100vw - 160px)`、`height: calc(100vh - 80px)`,适合需要大面积展示内容的场景。
6
+
7
+ ## 何时使用
8
+
9
+ - 包含表单输入的弹窗(如编辑虚拟机配置、创建网络策略)
10
+ - 内容较多的确认弹窗,492px 宽度不够展示时
11
+ - 需要大面积展示内容(如表格、拓扑图)的场景,配合 `isContentFull` 使用
12
+
13
+ 不要使用:
14
+
15
+ - 简单确认或信息提示 --> 请用 `SmallDialog`
16
+ - 多步骤向导流程 --> 请用 `WizardDialog`
17
+ - 全屏沉浸式操作 --> 请用 `ImmersiveDialog`
18
+ - 删除确认场景 --> 请用 `DeleteDialog`(已封装删除确认样式和交互)
19
+ - 操作被拒绝 / 不可执行的反馈 --> 请用 `RejectDialog`(支持 Single / All / Part / Custom 四种模式)
20
+
21
+ ## 基础用法
22
+
23
+ ```tsx
24
+ import React from "react";
25
+ import { MediumDialog, Button, KitStoreProvider, ModalStack } from "@cloudtower/eagle";
26
+ import { usePushModal } from "@cloudtower/eagle";
27
+
28
+ const App = () => {
29
+ const pushModal = usePushModal();
30
+
31
+ const openDialog = () => {
32
+ pushModal({
33
+ component: () => (
34
+ <MediumDialog
35
+ title="编辑虚拟机"
36
+ onOk={(popModal) => {
37
+ console.log("保存配置");
38
+ popModal();
39
+ }}
40
+ onCancel={(popModal) => {
41
+ popModal();
42
+ }}
43
+ >
44
+ <form>
45
+ <label>虚拟机名称</label>
46
+ <input defaultValue="vm-prod-01" />
47
+ <label>CPU (核)</label>
48
+ <input type="number" defaultValue={4} />
49
+ <label>内存 (GB)</label>
50
+ <input type="number" defaultValue={8} />
51
+ </form>
52
+ </MediumDialog>
53
+ ),
54
+ props: {},
55
+ });
56
+ };
57
+
58
+ return (
59
+ <Button type="primary" onClick={openDialog}>
60
+ 编辑虚拟机
61
+ </Button>
62
+ );
63
+ };
64
+ ```
65
+
66
+ ## 常见模式
67
+
68
+ ### 模式一:表单提交弹窗
69
+
70
+ 适用于在弹窗中包含表单输入的场景。通过 `confirmLoading` 控制提交按钮加载状态,提交成功后再关闭弹窗。
71
+
72
+ ```tsx
73
+ import React, { useState } from "react";
74
+ import { MediumDialog, Button } from "@cloudtower/eagle";
75
+ import { usePushModal } from "@cloudtower/eagle";
76
+
77
+ const EditVmDialog: React.FC = () => {
78
+ const [loading, setLoading] = useState(false);
79
+ const [error, setError] = useState<string>();
80
+
81
+ return (
82
+ <MediumDialog
83
+ title="编辑虚拟机"
84
+ confirmLoading={loading}
85
+ error={error}
86
+ onOk={async (popModal) => {
87
+ setLoading(true);
88
+ setError(undefined);
89
+ try {
90
+ await saveVmConfig({ name: "vm-prod-01", cpu: 4, memory: 8 });
91
+ popModal();
92
+ } catch (e) {
93
+ setError("保存配置失败,请重试");
94
+ } finally {
95
+ setLoading(false);
96
+ }
97
+ }}
98
+ >
99
+ <form>
100
+ <label>虚拟机名称</label>
101
+ <input defaultValue="vm-prod-01" />
102
+ <label>CPU (核)</label>
103
+ <input type="number" defaultValue={4} />
104
+ <label>内存 (GB)</label>
105
+ <input type="number" defaultValue={8} />
106
+ </form>
107
+ </MediumDialog>
108
+ );
109
+ };
110
+
111
+ // 使用方式
112
+ const App = () => {
113
+ const pushModal = usePushModal();
114
+
115
+ return (
116
+ <Button
117
+ type="primary"
118
+ onClick={() =>
119
+ pushModal({ component: () => <EditVmDialog />, props: {} })
120
+ }
121
+ >
122
+ 编辑虚拟机
123
+ </Button>
124
+ );
125
+ };
126
+ ```
127
+
128
+ ### 模式二:数据展示弹窗
129
+
130
+ 适用于在弹窗中展示较多详情信息的场景。利用 `initializing`、`initializingError` 控制加载状态,加载完成后展示数据。
131
+
132
+ ```tsx
133
+ import React, { useEffect, useMemo, useState } from "react";
134
+ import { MediumDialog, Button } from "@cloudtower/eagle";
135
+ import type { MediumDialogProps } from "@cloudtower/eagle";
136
+ import { usePushModal } from "@cloudtower/eagle";
137
+
138
+ const VmDetailDialog: React.FC = () => {
139
+ const [loading, setLoading] = useState(true);
140
+ const [error, setError] = useState<string>();
141
+ const [data, setData] = useState<{
142
+ name: string;
143
+ cpu: number;
144
+ memory: number;
145
+ disks: string[];
146
+ nics: string[];
147
+ }>();
148
+
149
+ const fetchData = async () => {
150
+ setLoading(true);
151
+ setError(undefined);
152
+ try {
153
+ const result = await fetchVmDetail("vm-prod-01");
154
+ setData(result);
155
+ } catch (e) {
156
+ setError("加载虚拟机详情失败");
157
+ } finally {
158
+ setLoading(false);
159
+ }
160
+ };
161
+
162
+ useEffect(() => {
163
+ fetchData();
164
+ }, []);
165
+
166
+ const modalProps: Partial<MediumDialogProps> = useMemo(() => {
167
+ if (loading) {
168
+ return { title: "", initializing: true, initializingSkeletonRows: 5 };
169
+ }
170
+ if (error) {
171
+ return { title: "", initializingError: error, onOk: fetchData };
172
+ }
173
+ return { title: `虚拟机详情 - ${data?.name}` };
174
+ }, [loading, error, data]);
175
+
176
+ return (
177
+ <MediumDialog showOk={false} cancelText="关闭" {...modalProps}>
178
+ <p>虚拟机名称:{data?.name}</p>
179
+ <p>CPU:{data?.cpu} 核</p>
180
+ <p>内存:{data?.memory} GB</p>
181
+ <p>磁盘:{data?.disks?.join(", ")}</p>
182
+ <p>网卡:{data?.nics?.join(", ")}</p>
183
+ </MediumDialog>
184
+ );
185
+ };
186
+
187
+ // 使用方式
188
+ const App = () => {
189
+ const pushModal = usePushModal();
190
+
191
+ return (
192
+ <Button
193
+ onClick={() =>
194
+ pushModal({ component: () => <VmDetailDialog />, props: {} })
195
+ }
196
+ >
197
+ 查看虚拟机详情
198
+ </Button>
199
+ );
200
+ };
201
+ ```
202
+
203
+ ### 模式三:全屏内容弹窗(isContentFull)
204
+
205
+ 适用于需要大面积展示内容(如表格、拓扑图、日志)的场景。开启 `isContentFull` 后弹窗尺寸变为 `width: calc(100vw - 160px)`、`height: calc(100vh - 80px)`,内容区域自动 flex 填充剩余高度。
206
+
207
+ ```tsx
208
+ import React from "react";
209
+ import { MediumDialog, Button } from "@cloudtower/eagle";
210
+ import { usePushModal } from "@cloudtower/eagle";
211
+
212
+ const App = () => {
213
+ const pushModal = usePushModal();
214
+
215
+ return (
216
+ <Button
217
+ onClick={() =>
218
+ pushModal({
219
+ component: () => (
220
+ <MediumDialog
221
+ title="虚拟机任务日志"
222
+ isContentFull
223
+ showOk={false}
224
+ cancelText="关闭"
225
+ >
226
+ <div style={{ flex: 1, overflow: "auto" }}>
227
+ <pre>
228
+ {`[2024-01-15 10:00:01] 开始迁移虚拟机 vm-prod-01
229
+ [2024-01-15 10:00:05] 正在同步内存页...
230
+ [2024-01-15 10:01:30] 内存同步完成,开始切换
231
+ [2024-01-15 10:01:32] 迁移完成,虚拟机已在目标主机上运行`}
232
+ </pre>
233
+ </div>
234
+ </MediumDialog>
235
+ ),
236
+ props: {},
237
+ })
238
+ }
239
+ >
240
+ 查看任务日志
241
+ </Button>
242
+ );
243
+ };
244
+ ```
245
+
246
+ ## 与 SmallDialog 的差异对比
247
+
248
+ | 特性 | SmallDialog | MediumDialog |
249
+ |------|-------------|--------------|
250
+ | 默认宽度 | 492px | 720px |
251
+ | 水平内边距 | 40px | 60px |
252
+ | 标题排版 | `d2_bold_title` | `d1s_bold_title` |
253
+ | 骨架屏默认行数 | 2 | 3 |
254
+ | 全屏模式 | 不支持 | 支持(`isContentFull`) |
255
+ | 适用场景 | 简单确认、信息提示 | 表单弹窗、内容较多的确认 |
256
+
257
+ ## 相关组件
258
+
259
+ - `SmallDialog` -- 小尺寸对话框(492px),适用于简单确认和信息提示
260
+ - `ImmersiveDialog` -- 全屏沉浸式对话框,适用于需要大量操作空间的场景
261
+ - `WizardDialog` -- 向导式对话框,适用于多步骤流程
262
+ - `DeleteDialog` -- 删除确认对话框,封装了删除场景的标准样式和交互(位于 `@cloudtower/eagle` 的 coreX 模块)
263
+ - `RejectDialog` -- 操作拒绝反馈对话框,支持 Single(单条拒绝)、All(全部拒绝)、Part(部分拒绝)、Custom(自定义内容)四种模式(位于 `@cloudtower/eagle` 的 coreX 模块)