@ucloud-fe/udesign-cli 0.1.3 → 0.2.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.
- package/package.json +1 -1
- package/skills/cpn-basic-box/SKILL.md +40 -2
- package/skills/cpn-basic-breadcrumb/SKILL.md +17 -2
- package/skills/cpn-basic-button/SKILL.md +64 -2
- package/skills/cpn-basic-card/SKILL.md +34 -2
- package/skills/cpn-basic-checkbox/SKILL.md +20 -2
- package/skills/cpn-basic-combine/SKILL.md +27 -2
- package/skills/cpn-basic-date-picker/SKILL.md +21 -2
- package/skills/cpn-basic-editable-table/SKILL.md +6 -2
- package/skills/cpn-basic-icon/SKILL.md +10 -2
- package/skills/cpn-basic-input/SKILL.md +39 -2
- package/skills/cpn-basic-loading/SKILL.md +41 -2
- package/skills/cpn-basic-message/SKILL.md +48 -2
- package/skills/cpn-basic-modal/SKILL.md +73 -2
- package/skills/cpn-basic-notice/SKILL.md +45 -2
- package/skills/cpn-basic-number-input/SKILL.md +42 -2
- package/skills/cpn-basic-popover/SKILL.md +30 -2
- package/skills/cpn-basic-radio/SKILL.md +40 -2
- package/skills/cpn-basic-select/SKILL.md +46 -2
- package/skills/cpn-basic-slider/SKILL.md +45 -2
- package/skills/cpn-basic-switch/SKILL.md +37 -2
- package/skills/cpn-basic-table/SKILL.md +48 -2
- package/skills/cpn-basic-tabs/SKILL.md +46 -2
- package/skills/cpn-basic-tag/SKILL.md +37 -2
- package/skills/cpn-basic-textarea/SKILL.md +40 -2
- package/skills/cpn-basic-tooltip/SKILL.md +39 -2
- package/skills/cpn-basic-tree/SKILL.md +40 -2
- package/skills/udesign/SKILL.md +2 -2
package/package.json
CHANGED
|
@@ -729,13 +729,51 @@ const Demo = () => (
|
|
|
729
729
|
<!-- MANUAL_START: best-practices -->
|
|
730
730
|
## 最佳实践
|
|
731
731
|
|
|
732
|
-
|
|
732
|
+
1. **容器必须设置 `container` 属性**:只有 `container` 为 `true` 时,flex 布局属性才会生效
|
|
733
|
+
2. **优先使用预设间距**:使用 `sm`、`md`、`lg` 等预设值保持间距一致性
|
|
734
|
+
3. **滚动场景使用 `cleanMargin`**:当使用 `spacing` 且需要滚动时,设置 `cleanMargin` 避免外边距影响滚动容器
|
|
735
|
+
4. **栅格布局使用 `span`**:使用 12 栅格系统进行响应式布局,需要更精细的可使用小数
|
|
736
|
+
|
|
737
|
+
### 常见场景
|
|
738
|
+
|
|
739
|
+
#### 水平垂直居中
|
|
740
|
+
|
|
741
|
+
```jsx
|
|
742
|
+
<Box container alignItems="center" justifyContent="center" height="300px">
|
|
743
|
+
<Box>居中内容</Box>
|
|
744
|
+
</Box>
|
|
745
|
+
```
|
|
746
|
+
|
|
747
|
+
#### 模拟浮动布局
|
|
748
|
+
|
|
749
|
+
```jsx
|
|
750
|
+
<Box container justifyContent="space-between">
|
|
751
|
+
<Box>左侧内容</Box>
|
|
752
|
+
<Box>右侧内容</Box>
|
|
753
|
+
</Box>
|
|
754
|
+
```
|
|
755
|
+
|
|
756
|
+
#### 卡片布局
|
|
757
|
+
|
|
758
|
+
```jsx
|
|
759
|
+
<Box container spacing="md" wrap="wrap">
|
|
760
|
+
<Box span={4}><Card>卡片1</Card></Box>
|
|
761
|
+
<Box span={4}><Card>卡片2</Card></Box>
|
|
762
|
+
<Box span={4}><Card>卡片3</Card></Box>
|
|
763
|
+
</Box>
|
|
764
|
+
```
|
|
733
765
|
<!-- MANUAL_END: best-practices -->
|
|
734
766
|
|
|
735
767
|
<!-- MANUAL_START: faq -->
|
|
736
768
|
## 常见问题
|
|
737
769
|
|
|
738
|
-
|
|
770
|
+
### Q: 子元素的 flex、order 等属性不生效?
|
|
771
|
+
|
|
772
|
+
A: 确保父级 Box 设置了 `container` 为 `true`,否则子组件的 flex 相关属性不会生效。
|
|
773
|
+
|
|
774
|
+
### Q: 滚动时间距导致布局异常?
|
|
775
|
+
|
|
776
|
+
A: 使用了 `spacing` 后,内部通过负 margin 实现间距,在滚动容器中可能有影响。设置 `cleanMargin` 即可修复。
|
|
739
777
|
<!-- MANUAL_END: faq -->
|
|
740
778
|
|
|
741
779
|
<!-- MANUAL_START: critical -->
|
|
@@ -207,13 +207,28 @@ class Demo extends React.Component {
|
|
|
207
207
|
<!-- MANUAL_START: best-practices -->
|
|
208
208
|
## 最佳实践
|
|
209
209
|
|
|
210
|
-
|
|
210
|
+
1. **最后一项使用 `current` 或 `noAction`**:当前页面的面包屑项不应可点击
|
|
211
|
+
2. **层级不宜过深**:建议面包屑层级控制在 2-4 级
|
|
212
|
+
3. **首项为首页或产品入口**:保持导航一致性
|
|
213
|
+
|
|
214
|
+
### 常见场景
|
|
215
|
+
|
|
216
|
+
#### 资源详情页面包屑
|
|
217
|
+
|
|
218
|
+
```jsx
|
|
219
|
+
<Breadcrumb>
|
|
220
|
+
<Breadcrumb.Item onClick={() => navigate('/uhost')}>云主机</Breadcrumb.Item>
|
|
221
|
+
<Breadcrumb.Item current>uhost-xxxx</Breadcrumb.Item>
|
|
222
|
+
</Breadcrumb>
|
|
223
|
+
```
|
|
211
224
|
<!-- MANUAL_END: best-practices -->
|
|
212
225
|
|
|
213
226
|
<!-- MANUAL_START: faq -->
|
|
214
227
|
## 常见问题
|
|
215
228
|
|
|
216
|
-
|
|
229
|
+
### Q: 面包屑项不可点击怎么设置?
|
|
230
|
+
|
|
231
|
+
A: 使用 `noAction` 属性标记无需跳转的项目,或使用 `current` 标记当前页。
|
|
217
232
|
<!-- MANUAL_END: faq -->
|
|
218
233
|
|
|
219
234
|
<!-- MANUAL_START: critical -->
|
|
@@ -362,13 +362,75 @@ const Demo = () => {
|
|
|
362
362
|
<!-- MANUAL_START: best-practices -->
|
|
363
363
|
## 最佳实践
|
|
364
364
|
|
|
365
|
-
|
|
365
|
+
1. **主操作使用 `primary`**:每个视觉区域内应只有一个主按钮,避免多个 `primary` 按钮并列
|
|
366
|
+
2. **异步操作务必加 `loading`**:提交表单、发送请求时使用 `loading` 防止重复点击,并在操作完成后关闭
|
|
367
|
+
3. **Tooltip 包裹禁用按钮时使用 `fakeDisabled`**:避免原生 `disabled` 屏蔽所有事件导致 Tooltip 失效
|
|
368
|
+
4. **表单提交按钮设置 `type="submit"`**:如果不想触发表单提交,保持默认的 `type="button"`
|
|
369
|
+
5. **图标按钮建议搭配 `shape`**:纯图标按钮使用 `circle` 或 `square` 形状,视觉效果更好
|
|
370
|
+
6. **按钮排列顺序**:主操作在前(左),辅助操作在后(右)
|
|
371
|
+
|
|
372
|
+
### 常见场景
|
|
373
|
+
|
|
374
|
+
#### 表单操作按钮
|
|
375
|
+
|
|
376
|
+
```jsx
|
|
377
|
+
<div>
|
|
378
|
+
<Button styleType="primary" onClick={handleSubmit}>提交</Button>
|
|
379
|
+
<Button styleType="border" onClick={handleCancel}>取消</Button>
|
|
380
|
+
</div>
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
#### 加载态提交按钮
|
|
384
|
+
|
|
385
|
+
```jsx
|
|
386
|
+
const [loading, setLoading] = useState(false);
|
|
387
|
+
|
|
388
|
+
const handleSubmit = async () => {
|
|
389
|
+
setLoading(true);
|
|
390
|
+
try {
|
|
391
|
+
await submitForm();
|
|
392
|
+
} finally {
|
|
393
|
+
setLoading(false);
|
|
394
|
+
}
|
|
395
|
+
};
|
|
396
|
+
|
|
397
|
+
<Button styleType="primary" loading={loading} onClick={handleSubmit}>
|
|
398
|
+
提交
|
|
399
|
+
</Button>
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
#### 图标操作按钮
|
|
403
|
+
|
|
404
|
+
```jsx
|
|
405
|
+
<Button shape="circle" styleType="primary" icon="plus" onClick={handleAdd} />
|
|
406
|
+
<Button shape="circle" styleType="border" icon="edit" onClick={handleEdit} />
|
|
407
|
+
```
|
|
366
408
|
<!-- MANUAL_END: best-practices -->
|
|
367
409
|
|
|
368
410
|
<!-- MANUAL_START: faq -->
|
|
369
411
|
## 常见问题
|
|
370
412
|
|
|
371
|
-
|
|
413
|
+
### Q: 禁用按钮外包裹 Tooltip 不显示?
|
|
414
|
+
|
|
415
|
+
A: 原生 `disabled` 会阻止所有事件,导致 Tooltip 无法获取鼠标事件。解决方案是同时添加 `disabled` 和 `fakeDisabled`:
|
|
416
|
+
|
|
417
|
+
```jsx
|
|
418
|
+
<Tooltip popup="该操作暂不可用">
|
|
419
|
+
<Button disabled fakeDisabled>操作</Button>
|
|
420
|
+
</Tooltip>
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
### Q: loading 状态下按钮的图标会怎样?
|
|
424
|
+
|
|
425
|
+
A: `loading` 为 `true` 时,原始 `icon` 会被替换为旋转的加载图标。
|
|
426
|
+
|
|
427
|
+
### Q: 如何创建只有图标没有文字的按钮?
|
|
428
|
+
|
|
429
|
+
A: 不传 `children`,只传 `icon` 和 `shape`:
|
|
430
|
+
|
|
431
|
+
```jsx
|
|
432
|
+
<Button shape="circle" icon="plus" styleType="primary" />
|
|
433
|
+
```
|
|
372
434
|
<!-- MANUAL_END: faq -->
|
|
373
435
|
|
|
374
436
|
<!-- MANUAL_START: critical -->
|
|
@@ -323,13 +323,45 @@ const Demo = () => (
|
|
|
323
323
|
<!-- MANUAL_START: best-practices -->
|
|
324
324
|
## 最佳实践
|
|
325
325
|
|
|
326
|
-
|
|
326
|
+
1. **子组件顺序灵活**:Header、Action、Content、Footer 可按需使用和组合
|
|
327
|
+
2. **弹层自动适配**:Card 内部的 Popover、Select 等弹出层会自动使用 Card 的父节点作为容器
|
|
328
|
+
3. **配合 Grid 布局**:多卡片场景使用 Grid 栅格组件排列
|
|
329
|
+
|
|
330
|
+
### 常见场景
|
|
331
|
+
|
|
332
|
+
#### 信息展示卡片
|
|
333
|
+
|
|
334
|
+
```jsx
|
|
335
|
+
<Card>
|
|
336
|
+
<Card.Header>基本信息</Card.Header>
|
|
337
|
+
<Card.Action>
|
|
338
|
+
<Button styleType="primary" onClick={handleEdit}>编辑</Button>
|
|
339
|
+
</Card.Action>
|
|
340
|
+
<Card.Content>
|
|
341
|
+
<p>名称:example-instance</p>
|
|
342
|
+
<p>状态:运行中</p>
|
|
343
|
+
</Card.Content>
|
|
344
|
+
</Card>
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
#### 列表卡片
|
|
348
|
+
|
|
349
|
+
```jsx
|
|
350
|
+
{items.map(item => (
|
|
351
|
+
<Card key={item.id}>
|
|
352
|
+
<Card.Header>{item.title}</Card.Header>
|
|
353
|
+
<Card.Content>{item.description}</Card.Content>
|
|
354
|
+
</Card>
|
|
355
|
+
))}
|
|
356
|
+
```
|
|
327
357
|
<!-- MANUAL_END: best-practices -->
|
|
328
358
|
|
|
329
359
|
<!-- MANUAL_START: faq -->
|
|
330
360
|
## 常见问题
|
|
331
361
|
|
|
332
|
-
|
|
362
|
+
### Q: Card 内部的弹层被遮挡?
|
|
363
|
+
|
|
364
|
+
A: Card 已通过 Context 自动处理,弹层会使用 Card 的 parentNode 作为容器。如果仍有问题,检查是否有其他外层 overflow 容器影响。
|
|
333
365
|
<!-- MANUAL_END: faq -->
|
|
334
366
|
|
|
335
367
|
<!-- MANUAL_START: critical -->
|
|
@@ -303,13 +303,31 @@ class Demo extends React.Component {
|
|
|
303
303
|
<!-- MANUAL_START: best-practices -->
|
|
304
304
|
## 最佳实践
|
|
305
305
|
|
|
306
|
-
|
|
306
|
+
1. **Group 中使用 value/defaultValue**:不要在 Group 内的 Checkbox 上使用 checked
|
|
307
|
+
2. **全选使用 indeterminate**:全选控制时使用 `indeterminate` 展示部分选中状态
|
|
308
|
+
3. **数据量大时使用 options**:比逐个写 Checkbox 更简洁
|
|
309
|
+
|
|
310
|
+
### 常见场景
|
|
311
|
+
|
|
312
|
+
#### 表单多选
|
|
313
|
+
|
|
314
|
+
```jsx
|
|
315
|
+
<Form.Item label="兴趣">
|
|
316
|
+
<Checkbox.Group value={interests} onChange={setInterests}>
|
|
317
|
+
<Checkbox value="reading">阅读</Checkbox>
|
|
318
|
+
<Checkbox value="sports">运动</Checkbox>
|
|
319
|
+
<Checkbox value="music">音乐</Checkbox>
|
|
320
|
+
</Checkbox.Group>
|
|
321
|
+
</Form.Item>
|
|
322
|
+
```
|
|
307
323
|
<!-- MANUAL_END: best-practices -->
|
|
308
324
|
|
|
309
325
|
<!-- MANUAL_START: faq -->
|
|
310
326
|
## 常见问题
|
|
311
327
|
|
|
312
|
-
|
|
328
|
+
### Q: Group 中 Checkbox 的 checked 不生效?
|
|
329
|
+
|
|
330
|
+
A: 在 Group 中应使用 `value`/`defaultValue` 控制选中,不要在子 Checkbox 上使用 `checked`。
|
|
313
331
|
<!-- MANUAL_END: faq -->
|
|
314
332
|
|
|
315
333
|
<!-- MANUAL_START: critical -->
|
|
@@ -326,13 +326,38 @@ const Demo = () => (
|
|
|
326
326
|
<!-- MANUAL_START: best-practices -->
|
|
327
327
|
## 最佳实践
|
|
328
328
|
|
|
329
|
-
|
|
329
|
+
1. **使用 `sharedProps` 统一尺寸**:避免每个子组件重复设置 `size`
|
|
330
|
+
2. **默认 `spacing="smart"` 即可**:会根据 size 自动选择合适间距
|
|
331
|
+
3. **注意 sharedProps 与 Popover 包裹的兼容性**:如果子组件外层包裹了 Popover,sharedProps 可能会被 Popover 接收,此时需手动传递
|
|
332
|
+
|
|
333
|
+
### 常见场景
|
|
334
|
+
|
|
335
|
+
#### 搜索栏
|
|
336
|
+
|
|
337
|
+
```jsx
|
|
338
|
+
<Combine sharedProps={{ size: 'md' }}>
|
|
339
|
+
<Select value={type} onChange={setType} options={typeOptions} />
|
|
340
|
+
<Input value={keyword} onChange={e => setKeyword(e.target.value)} placeholder="请输入关键词" />
|
|
341
|
+
<Button styleType="primary" onClick={handleSearch}>搜索</Button>
|
|
342
|
+
</Combine>
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
#### 日期范围
|
|
346
|
+
|
|
347
|
+
```jsx
|
|
348
|
+
<Combine separator="~">
|
|
349
|
+
<DatePicker value={startDate} onChange={setStartDate} />
|
|
350
|
+
<DatePicker value={endDate} onChange={setEndDate} />
|
|
351
|
+
</Combine>
|
|
352
|
+
```
|
|
330
353
|
<!-- MANUAL_END: best-practices -->
|
|
331
354
|
|
|
332
355
|
<!-- MANUAL_START: faq -->
|
|
333
356
|
## 常见问题
|
|
334
357
|
|
|
335
|
-
|
|
358
|
+
### Q: sharedProps 传的 size 没生效?
|
|
359
|
+
|
|
360
|
+
A: 如果子组件被 Popover、Tooltip 等包裹,sharedProps 会被外层组件拿到。解决方案是在目标组件上直接传入 props。
|
|
336
361
|
<!-- MANUAL_END: faq -->
|
|
337
362
|
|
|
338
363
|
<!-- MANUAL_START: critical -->
|
|
@@ -789,13 +789,32 @@ const Demo = () => (
|
|
|
789
789
|
<!-- MANUAL_START: best-practices -->
|
|
790
790
|
## 最佳实践
|
|
791
791
|
|
|
792
|
-
|
|
792
|
+
1. **明确时区和语言包**:使用前确保导入了 moment 语言包和设置了时区
|
|
793
|
+
2. **使用 format 控制精度**:不需要时间时使用 `YYYY-MM-DD` 格式
|
|
794
|
+
3. **表单场景使用 nullable**:允许用户清空已选日期
|
|
795
|
+
|
|
796
|
+
### 常见场景
|
|
797
|
+
|
|
798
|
+
#### 表单中的日期选择
|
|
799
|
+
|
|
800
|
+
```jsx
|
|
801
|
+
<Form.Item label="开始时间">
|
|
802
|
+
<DatePicker
|
|
803
|
+
value={startDate}
|
|
804
|
+
onChange={setStartDate}
|
|
805
|
+
format="YYYY-MM-DD HH:mm:ss"
|
|
806
|
+
size="md"
|
|
807
|
+
/>
|
|
808
|
+
</Form.Item>
|
|
809
|
+
```
|
|
793
810
|
<!-- MANUAL_END: best-practices -->
|
|
794
811
|
|
|
795
812
|
<!-- MANUAL_START: faq -->
|
|
796
813
|
## 常见问题
|
|
797
814
|
|
|
798
|
-
|
|
815
|
+
### Q: 日期选择器默认显示当前时间?
|
|
816
|
+
|
|
817
|
+
A: 当 `nullable` 为 false(默认)时,空值会默认为当前时刻。设置 `nullable` 允许空值。
|
|
799
818
|
<!-- MANUAL_END: faq -->
|
|
800
819
|
|
|
801
820
|
<!-- MANUAL_START: critical -->
|
|
@@ -792,13 +792,17 @@ const Demo = () => {
|
|
|
792
792
|
<!-- MANUAL_START: best-practices -->
|
|
793
793
|
## 最佳实践
|
|
794
794
|
|
|
795
|
-
|
|
795
|
+
1. **确保每行有唯一 key**:数据中必须有唯一的 key 字段
|
|
796
|
+
2. **添加行时生成唯一 key**:使用时间戳或 UUID 作为新行的 key
|
|
797
|
+
3. **条件禁用删除**:使用 `getDisabledOfRow` 保护不可删除的行
|
|
796
798
|
<!-- MANUAL_END: best-practices -->
|
|
797
799
|
|
|
798
800
|
<!-- MANUAL_START: faq -->
|
|
799
801
|
## 常见问题
|
|
800
802
|
|
|
801
|
-
|
|
803
|
+
### Q: 如何限制最大行数?
|
|
804
|
+
|
|
805
|
+
A: 通过 `addition.disabled` 控制,当达到最大行数时禁用添加按钮。
|
|
802
806
|
<!-- MANUAL_END: faq -->
|
|
803
807
|
|
|
804
808
|
<!-- MANUAL_START: critical -->
|
|
@@ -198,13 +198,21 @@ const Demo = () => (
|
|
|
198
198
|
<!-- MANUAL_START: best-practices -->
|
|
199
199
|
## 最佳实践
|
|
200
200
|
|
|
201
|
-
|
|
201
|
+
1. **按需导入字体样式**:使用前确保导入了 `icon.min.css`
|
|
202
|
+
2. **使用 ConfigProvider 全局修改前缀**:如果使用自定义图标库
|
|
203
|
+
3. **加载态使用 spin**:`<Icon type="loading" spin />` 表示加载中
|
|
202
204
|
<!-- MANUAL_END: best-practices -->
|
|
203
205
|
|
|
204
206
|
<!-- MANUAL_START: faq -->
|
|
205
207
|
## 常见问题
|
|
206
208
|
|
|
207
|
-
|
|
209
|
+
### Q: 图标不显示?
|
|
210
|
+
|
|
211
|
+
A: 确保已正确导入字体样式文件 `@ucloud-fe/react-components/dist/icon.min.css`,且 `type` 值与图标库中的名称一致。
|
|
212
|
+
|
|
213
|
+
### Q: 如何使用自定义图标库?
|
|
214
|
+
|
|
215
|
+
A: 通过 `prefix` 属性或使用 `ConfigProvider` 全局修改图标类名前缀,配合自定义字体图标库使用。
|
|
208
216
|
<!-- MANUAL_END: faq -->
|
|
209
217
|
|
|
210
218
|
<!-- MANUAL_START: critical -->
|
|
@@ -407,13 +407,50 @@ const Demo = () => (
|
|
|
407
407
|
<!-- MANUAL_START: best-practices -->
|
|
408
408
|
## 最佳实践
|
|
409
409
|
|
|
410
|
-
|
|
410
|
+
1. **使用 clearable 提升体验**:搜索框等场景建议开启 clearable
|
|
411
|
+
2. **prefix/suffix 增强语义**:使用图标前缀提示用户输入类型
|
|
412
|
+
3. **数字输入使用 NumberInput**:不要用 Input 做数字输入,请使用 `Input.Number` 或 `NumberInput`
|
|
413
|
+
4. **多行文本使用 Textarea**:不要用 Input 做多行输入,请使用 `Input.Textarea` 或 `Textarea`
|
|
414
|
+
5. **受控模式使用 onChange**:`onChange` 返回原生 event,取值用 `e.target.value`
|
|
415
|
+
|
|
416
|
+
### 常见场景
|
|
417
|
+
|
|
418
|
+
#### 搜索输入框
|
|
419
|
+
|
|
420
|
+
```jsx
|
|
421
|
+
<Input
|
|
422
|
+
prefix={<Icon type="search" />}
|
|
423
|
+
clearable
|
|
424
|
+
placeholder="请输入搜索关键词"
|
|
425
|
+
value={keyword}
|
|
426
|
+
onChange={e => setKeyword(e.target.value)}
|
|
427
|
+
/>
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
#### 表单中的输入框
|
|
431
|
+
|
|
432
|
+
```jsx
|
|
433
|
+
<Form.Item label="名称" required>
|
|
434
|
+
<Input
|
|
435
|
+
value={name}
|
|
436
|
+
onChange={e => setName(e.target.value)}
|
|
437
|
+
placeholder="请输入名称"
|
|
438
|
+
size="md"
|
|
439
|
+
/>
|
|
440
|
+
</Form.Item>
|
|
441
|
+
```
|
|
411
442
|
<!-- MANUAL_END: best-practices -->
|
|
412
443
|
|
|
413
444
|
<!-- MANUAL_START: faq -->
|
|
414
445
|
## 常见问题
|
|
415
446
|
|
|
416
|
-
|
|
447
|
+
### Q: onChange 的参数是什么?
|
|
448
|
+
|
|
449
|
+
A: `onChange` 接收的是原生的 `ChangeEvent`,需要通过 `e.target.value` 获取输入值,而不是直接的值。
|
|
450
|
+
|
|
451
|
+
### Q: 如何获取 Input 的焦点?
|
|
452
|
+
|
|
453
|
+
A: 使用 ref:`<Input ref={inputRef} />`,然后调用 `inputRef.current.focus()`。
|
|
417
454
|
<!-- MANUAL_END: faq -->
|
|
418
455
|
|
|
419
456
|
<!-- MANUAL_START: critical -->
|
|
@@ -319,13 +319,52 @@ class Demo extends React.Component {
|
|
|
319
319
|
<!-- MANUAL_START: best-practices -->
|
|
320
320
|
## 最佳实践
|
|
321
321
|
|
|
322
|
-
|
|
322
|
+
1. **包裹具体区域而非整个页面**:Loading 应包裹实际需要加载的区域,而不是整个页面
|
|
323
|
+
2. **使用 tip 提示操作**:长时间加载时添加文字提示提升用户体验
|
|
324
|
+
3. **异步操作必须 finally 关闭 loading**:确保异常情况下也能关闭加载状态
|
|
325
|
+
4. **inline-block 元素注意**:Loading 内部使用 block 布局,包裹 inline-block 元素时注意样式影响
|
|
326
|
+
|
|
327
|
+
### 常见场景
|
|
328
|
+
|
|
329
|
+
#### 表格数据加载
|
|
330
|
+
|
|
331
|
+
```jsx
|
|
332
|
+
const [loading, setLoading] = useState(true);
|
|
333
|
+
const [data, setData] = useState([]);
|
|
334
|
+
|
|
335
|
+
useEffect(() => {
|
|
336
|
+
fetchTableData().then(res => {
|
|
337
|
+
setData(res);
|
|
338
|
+
setLoading(false);
|
|
339
|
+
});
|
|
340
|
+
}, []);
|
|
341
|
+
|
|
342
|
+
<Loading loading={loading}>
|
|
343
|
+
<Table dataSource={data} columns={columns} />
|
|
344
|
+
</Loading>
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
#### 页面区域加载
|
|
348
|
+
|
|
349
|
+
```jsx
|
|
350
|
+
<Loading loading={pageLoading} tip="正在加载页面数据...">
|
|
351
|
+
<Card>
|
|
352
|
+
<div>页面内容</div>
|
|
353
|
+
</Card>
|
|
354
|
+
</Loading>
|
|
355
|
+
```
|
|
323
356
|
<!-- MANUAL_END: best-practices -->
|
|
324
357
|
|
|
325
358
|
<!-- MANUAL_START: faq -->
|
|
326
359
|
## 常见问题
|
|
327
360
|
|
|
328
|
-
|
|
361
|
+
### Q: Loading 默认就显示吗?
|
|
362
|
+
|
|
363
|
+
A: 不会,`loading` 属性默认为 `false`,需要显式设置为 `true` 才会显示。
|
|
364
|
+
|
|
365
|
+
### Q: Loading 没有子元素时能用吗?
|
|
366
|
+
|
|
367
|
+
A: 可以,但通常建议包裹目标区域,这样遮罩会覆盖在内容上方,体验更好。
|
|
329
368
|
<!-- MANUAL_END: faq -->
|
|
330
369
|
|
|
331
370
|
<!-- MANUAL_START: critical -->
|
|
@@ -369,13 +369,59 @@ const Demo = () => {
|
|
|
369
369
|
<!-- MANUAL_START: best-practices -->
|
|
370
370
|
## 最佳实践
|
|
371
371
|
|
|
372
|
-
|
|
372
|
+
1. **操作反馈优先用 Message**:轻量级的操作成功/失败反馈使用 Message,重要操作使用 Modal
|
|
373
|
+
2. **loading 方法要手动关闭**:`Message.loading()` 返回的关闭函数需要在操作完成后调用
|
|
374
|
+
3. **避免传入复杂内容**:如果需要在 Message 中传入依赖 Context 的内容,需要自行处理依赖
|
|
375
|
+
4. **慎用 config 全局配置**:它会影响所有后续 Message
|
|
376
|
+
|
|
377
|
+
### 常见场景
|
|
378
|
+
|
|
379
|
+
#### 操作反馈
|
|
380
|
+
|
|
381
|
+
```jsx
|
|
382
|
+
const handleDelete = async () => {
|
|
383
|
+
try {
|
|
384
|
+
await api.deleteResource(id);
|
|
385
|
+
Message.success('删除成功');
|
|
386
|
+
} catch (error) {
|
|
387
|
+
Message.error('删除失败:' + error.message);
|
|
388
|
+
}
|
|
389
|
+
};
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
#### 表单提交反馈
|
|
393
|
+
|
|
394
|
+
```jsx
|
|
395
|
+
const handleSubmit = async (values) => {
|
|
396
|
+
const hide = Message.loading('提交中...');
|
|
397
|
+
try {
|
|
398
|
+
await api.submitForm(values);
|
|
399
|
+
Message.success('提交成功');
|
|
400
|
+
} catch (error) {
|
|
401
|
+
Message.error('提交失败');
|
|
402
|
+
} finally {
|
|
403
|
+
hide();
|
|
404
|
+
}
|
|
405
|
+
};
|
|
406
|
+
```
|
|
373
407
|
<!-- MANUAL_END: best-practices -->
|
|
374
408
|
|
|
375
409
|
<!-- MANUAL_START: faq -->
|
|
376
410
|
## 常见问题
|
|
377
411
|
|
|
378
|
-
|
|
412
|
+
### Q: Message.loading 如何关闭?
|
|
413
|
+
|
|
414
|
+
A: `Message.loading()` 返回一个关闭函数,调用即可关闭:
|
|
415
|
+
|
|
416
|
+
```jsx
|
|
417
|
+
const hide = Message.loading('加载中...');
|
|
418
|
+
// 操作完成后
|
|
419
|
+
hide();
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
### Q: Message 能否不自动关闭?
|
|
423
|
+
|
|
424
|
+
A: 可以,将 `duration` 设为 `null`:`Message.success('内容', null)`。
|
|
379
425
|
<!-- MANUAL_END: faq -->
|
|
380
426
|
|
|
381
427
|
<!-- MANUAL_START: critical -->
|
|
@@ -821,13 +821,84 @@ class Demo extends React.Component {
|
|
|
821
821
|
<!-- MANUAL_START: best-practices -->
|
|
822
822
|
## 最佳实践
|
|
823
823
|
|
|
824
|
-
|
|
824
|
+
1. **优先使用声明式**:避免命令式调用带来的上下文丢失和生命周期问题
|
|
825
|
+
2. **重要操作需要二次确认**:删除等不可逆操作使用 Modal 确认
|
|
826
|
+
3. **表单弹窗自定义 footer**:使用自定义 footer 控制提交按钮的 loading 状态
|
|
827
|
+
4. **maskClosable 慎重关闭**:表单弹窗建议设置 `maskClosable={false}` 防止误关闭
|
|
828
|
+
5. **使用 destroyOnClose 清理状态**:表单弹窗关闭后需要重置状态时使用
|
|
829
|
+
|
|
830
|
+
### 常见场景
|
|
831
|
+
|
|
832
|
+
#### 确认删除弹窗(声明式)
|
|
833
|
+
|
|
834
|
+
```jsx
|
|
835
|
+
const [deleteVisible, setDeleteVisible] = useState(false);
|
|
836
|
+
const [deleting, setDeleting] = useState(false);
|
|
837
|
+
|
|
838
|
+
const handleDelete = async () => {
|
|
839
|
+
setDeleting(true);
|
|
840
|
+
try {
|
|
841
|
+
await api.deleteResource(id);
|
|
842
|
+
Message.success('删除成功');
|
|
843
|
+
setDeleteVisible(false);
|
|
844
|
+
} catch (error) {
|
|
845
|
+
Message.error('删除失败');
|
|
846
|
+
} finally {
|
|
847
|
+
setDeleting(false);
|
|
848
|
+
}
|
|
849
|
+
};
|
|
850
|
+
|
|
851
|
+
<Modal
|
|
852
|
+
visible={deleteVisible}
|
|
853
|
+
title="确认删除"
|
|
854
|
+
onClose={() => setDeleteVisible(false)}
|
|
855
|
+
onOk={handleDelete}
|
|
856
|
+
okButtonProps={{ loading: deleting, styleType: 'primary' }}
|
|
857
|
+
>
|
|
858
|
+
<Modal.Content>确定要删除该资源吗?此操作不可撤销。</Modal.Content>
|
|
859
|
+
</Modal>
|
|
860
|
+
```
|
|
861
|
+
|
|
862
|
+
#### 表单弹窗
|
|
863
|
+
|
|
864
|
+
```jsx
|
|
865
|
+
<Modal
|
|
866
|
+
visible={formVisible}
|
|
867
|
+
title="创建资源"
|
|
868
|
+
size="lg"
|
|
869
|
+
onClose={() => setFormVisible(false)}
|
|
870
|
+
footer={
|
|
871
|
+
<div>
|
|
872
|
+
<Button styleType="primary" loading={submitting} onClick={handleSubmit}>提交</Button>
|
|
873
|
+
<Button onClick={() => setFormVisible(false)}>取消</Button>
|
|
874
|
+
</div>
|
|
875
|
+
}
|
|
876
|
+
>
|
|
877
|
+
<Modal.Content>
|
|
878
|
+
<Form>
|
|
879
|
+
<Form.Item label="名称" required>
|
|
880
|
+
<Input value={name} onChange={e => setName(e.target.value)} />
|
|
881
|
+
</Form.Item>
|
|
882
|
+
</Form>
|
|
883
|
+
</Modal.Content>
|
|
884
|
+
</Modal>
|
|
885
|
+
```
|
|
825
886
|
<!-- MANUAL_END: best-practices -->
|
|
826
887
|
|
|
827
888
|
<!-- MANUAL_START: faq -->
|
|
828
889
|
## 常见问题
|
|
829
890
|
|
|
830
|
-
|
|
891
|
+
### Q: 命令式调用弹窗后上下文丢失?
|
|
892
|
+
|
|
893
|
+
A: 命令式调用会创建独立的 React 渲染实例,无法访问父组件的 Context(如 Redux Store、国际化等)。请改用声明式写法。
|
|
894
|
+
|
|
895
|
+
### Q: 弹窗内容很多如何处理?
|
|
896
|
+
|
|
897
|
+
A: 使用 `Modal.Content` 包裹内容,它会自动处理滚动。也可以通过 `bodyStyle` 设置最大高度。
|
|
898
|
+
|
|
899
|
+
### Q: 如何在弹窗中使用自定义按钮?
|
|
900
|
+
|
|
901
|
+
A: 通过 `footer` 属性传入自定义底部内容,完全替换默认的确定/取消按钮。
|
|
831
902
|
<!-- MANUAL_END: faq -->
|
|
832
903
|
|
|
833
904
|
<!-- MANUAL_START: critical -->
|