@xfe-repo/web-components 1.6.2 → 1.7.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 +4 -3
- package/skills/GUIDE.md +78 -0
- package/skills/SKILL.md +78 -0
- package/skills/TEMPLATE.md +170 -0
- package/skills/references/ApiService.md +136 -0
- package/skills/references/CInput.md +196 -0
- package/skills/references/CTable.md +274 -0
- package/skills/references/CVideo.md +94 -0
- package/skills/references/Clock.md +53 -0
- package/skills/references/ConfigProvider.md +198 -0
- package/skills/references/Countdown.md +109 -0
- package/skills/references/Counter.md +75 -0
- package/skills/references/Currency.md +112 -0
- package/skills/references/EffectAddressCascade.md +110 -0
- package/skills/references/EffectBrandSelect.md +231 -0
- package/skills/references/EffectBrandTransfer.md +143 -0
- package/skills/references/EffectCategoryCascade.md +228 -0
- package/skills/references/EffectFileUpload.md +349 -0
- package/skills/references/EffectLabelSelect.md +222 -0
- package/skills/references/EffectMerchantSelect.md +183 -0
- package/skills/references/EffectReservoirSelect.md +178 -0
- package/skills/references/EffectScopeSelect.md +220 -0
- package/skills/references/EffectSeriesSelect.md +205 -0
- package/skills/references/EffectSeriesSelectV2.md +154 -0
- package/skills/references/EffectSkuRecognize.md +149 -0
- package/skills/references/EffectSkuSelect.md +251 -0
- package/skills/references/EffectSkuTable.md +184 -0
- package/skills/references/EffectSpuSelect.md +189 -0
- package/skills/references/EffectStaffSelect.md +150 -0
- package/skills/references/EffectWarehouseSelect.md +183 -0
- package/skills/references/EffectWithFilePanel.md +205 -0
- package/skills/references/FileUpload.md +126 -0
- package/skills/references/Iconfont.md +31 -0
- package/skills/references/Loading.md +68 -0
- package/skills/references/MultiWindow.md +145 -0
- package/skills/references/OSSImage.md +230 -0
- package/skills/references/PrivacyField.md +90 -0
- package/skills/references/QRCode.md +148 -0
- package/skills/references/RichTextEditor.md +119 -0
- package/skills/references/SearchForm.md +270 -0
- package/skills/references/SearchList.md +128 -0
- package/skills/references/WithModal.md +328 -0
- package/skills/references/WithPanel.md +307 -0
- package/skills/references/commonFn.md +254 -0
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
# EffectStaffSelect
|
|
2
|
+
|
|
3
|
+
> 员工选择器,支持两种模式:传入部门 ID 时预加载该部门员工列表(前端过滤),不传时按关键字远程搜索员工。
|
|
4
|
+
|
|
5
|
+
## 导入
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
import { EffectStaffSelect } from '@xfe-repo/web-components'
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## 基本用法
|
|
12
|
+
|
|
13
|
+
最常见用法 — 在 Form.Item 中使用,value/onChange 由 Form 自动注入:
|
|
14
|
+
|
|
15
|
+
```tsx
|
|
16
|
+
<Form.Item name="staffId" label="员工">
|
|
17
|
+
<EffectStaffSelect />
|
|
18
|
+
</Form.Item>
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
### 远程搜索模式(不传 deptId)
|
|
22
|
+
|
|
23
|
+
```tsx
|
|
24
|
+
import { EffectStaffSelect } from '@xfe-repo/web-components'
|
|
25
|
+
|
|
26
|
+
function MyForm() {
|
|
27
|
+
const [staffId, setStaffId] = useState<number>()
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<EffectStaffSelect
|
|
31
|
+
value={staffId}
|
|
32
|
+
onChange={(value, name) => {
|
|
33
|
+
setStaffId(value)
|
|
34
|
+
console.log('员工姓名:', name)
|
|
35
|
+
}}
|
|
36
|
+
/>
|
|
37
|
+
)
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### 部门员工模式(传入 deptId)
|
|
42
|
+
|
|
43
|
+
```tsx
|
|
44
|
+
<EffectStaffSelect deptId={10} value={staffId} onChange={(value, name) => setStaffId(value)} />
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## 进阶用法
|
|
48
|
+
|
|
49
|
+
### 部门联动
|
|
50
|
+
|
|
51
|
+
```tsx
|
|
52
|
+
function DeptStaffPicker() {
|
|
53
|
+
const [deptId, setDeptId] = useState<number>()
|
|
54
|
+
const [staffId, setStaffId] = useState<number>()
|
|
55
|
+
|
|
56
|
+
return (
|
|
57
|
+
<div>
|
|
58
|
+
<DeptSelect onChange={(id) => setDeptId(id)} />
|
|
59
|
+
<EffectStaffSelect deptId={deptId} value={staffId} onChange={(value) => setStaffId(value)} />
|
|
60
|
+
</div>
|
|
61
|
+
)
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Props
|
|
66
|
+
|
|
67
|
+
```typescript
|
|
68
|
+
type EffectStaffSelectProps = {
|
|
69
|
+
className?: string
|
|
70
|
+
onChange?: (value?: number, name?: string) => void
|
|
71
|
+
deptId?: number
|
|
72
|
+
value?: number
|
|
73
|
+
defaultValue?: number
|
|
74
|
+
disabled?: boolean
|
|
75
|
+
placeholder?: string
|
|
76
|
+
style?: React.CSSProperties
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
| 属性 | 类型 | 必填 | 默认值 | 说明 |
|
|
81
|
+
| ------------ | ------------------------- | ---- | ------------------ | ----------------------------------------------------------- |
|
|
82
|
+
| value | `number` | 否 | - | 受控值(员工 user_id) |
|
|
83
|
+
| defaultValue | `number` | 否 | - | 默认值 |
|
|
84
|
+
| onChange | `(value?, name?) => void` | 否 | - | 值变化回调,返回员工 ID 和姓名 |
|
|
85
|
+
| deptId | `number` | 否 | - | 部门 ID。传入时:预加载部门员工、前端过滤;不传时:远程搜索 |
|
|
86
|
+
| disabled | `boolean` | 否 | - | 禁用状态 |
|
|
87
|
+
| placeholder | `string` | 否 | 动态生成(见说明) | 占位符文本 |
|
|
88
|
+
| className | `string` | 否 | - | 自定义样式类名 |
|
|
89
|
+
| style | `React.CSSProperties` | 否 | - | 自定义行内样式 |
|
|
90
|
+
|
|
91
|
+
### placeholder 默认值逻辑
|
|
92
|
+
|
|
93
|
+
- 有 `deptId`:`"请选择员工姓名"`
|
|
94
|
+
- 无 `deptId`:`"请先输入员工姓名"`
|
|
95
|
+
|
|
96
|
+
## API 数据源
|
|
97
|
+
|
|
98
|
+
- **内部接口**:`apiService.businessList`
|
|
99
|
+
- **请求参数**:`{ nameFilter: string, deptId?: number, page: 1, pageSize: 20 }`
|
|
100
|
+
- **搜索策略**:
|
|
101
|
+
- 有 `deptId`:挂载时预加载部门员工,搜索时使用前端 `optionFilterProp="label"` 过滤
|
|
102
|
+
- 无 `deptId`:输入时 600ms 防抖后远程搜索
|
|
103
|
+
- **可覆盖**:通过 `<ConfigProvider apiService={{ businessList: customFn }}>` 替换默认实现
|
|
104
|
+
|
|
105
|
+
## 依赖 Props
|
|
106
|
+
|
|
107
|
+
| 依赖属性 | 类型 | 来源 | 说明 |
|
|
108
|
+
| -------- | -------- | ---------- | ------------------------------------ |
|
|
109
|
+
| deptId | `number` | 部门选择器 | 切换搜索模式,变更时重新加载员工列表 |
|
|
110
|
+
|
|
111
|
+
### 联动说明
|
|
112
|
+
|
|
113
|
+
`deptId` 变更时:
|
|
114
|
+
|
|
115
|
+
1. 清空已有选项列表
|
|
116
|
+
2. 如果有新的 `deptId`,自动请求该部门员工列表
|
|
117
|
+
3. 保持 `value` 为 `controlValue`(受控模式)
|
|
118
|
+
|
|
119
|
+
## 常见陷阱
|
|
120
|
+
|
|
121
|
+
- ❌ 不传 `deptId` 时期望挂载后自动加载数据:
|
|
122
|
+
```tsx
|
|
123
|
+
// 无 deptId 模式下,只有用户输入搜索后才会请求
|
|
124
|
+
// notFoundContent 显示 "请先输入员工姓名"
|
|
125
|
+
```
|
|
126
|
+
- ✅ 如需预加载数据,传入 `deptId`
|
|
127
|
+
|
|
128
|
+
- ❌ `deptId` 变更后期望已选值自动更新:
|
|
129
|
+
```tsx
|
|
130
|
+
// deptId 变更时 value 会保持为 controlValue
|
|
131
|
+
// 但选项列表已刷新,之前选中的员工可能不在新列表中
|
|
132
|
+
```
|
|
133
|
+
- ✅ 在 `deptId` 变更时手动清空 value:
|
|
134
|
+
|
|
135
|
+
```tsx
|
|
136
|
+
const handleDeptChange = (nextDeptId: number) => {
|
|
137
|
+
setDeptId(nextDeptId)
|
|
138
|
+
setStaffId(undefined) // 清空已选员工
|
|
139
|
+
}
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
- ❌ 使用 `onSearch` 覆盖搜索行为(组件不暴露此 prop)
|
|
143
|
+
- ✅ 组件内部已处理搜索逻辑,无需额外配置
|
|
144
|
+
|
|
145
|
+
## 相关组件
|
|
146
|
+
|
|
147
|
+
| 场景 | 组件 | 说明 |
|
|
148
|
+
| ---------- | ------------------- | --------------------------------------- |
|
|
149
|
+
| 多维度搜索 | `EffectLabelSelect` | 支持切换搜索维度的通用选择器 |
|
|
150
|
+
| API 配置 | `ConfigProvider` | 提供 `apiService.businessList` 接口实现 |
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
# EffectWarehouseSelect
|
|
2
|
+
|
|
3
|
+
> 仓库选择器,支持按仓库类型筛选(自有仓/门店仓),数据通过 `apiService.storeComboBox` 获取并使用 SWR 缓存。
|
|
4
|
+
|
|
5
|
+
## 导入
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
import { EffectWarehouseSelect } from '@xfe-repo/web-components'
|
|
9
|
+
import type { WarehouseOption } from '@xfe-repo/web-components'
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## 基本用法
|
|
13
|
+
|
|
14
|
+
最常见用法 — 在 Form.Item 中使用,value/onChange 由 Form 自动注入:
|
|
15
|
+
|
|
16
|
+
```tsx
|
|
17
|
+
<Form.Item name="warehouseId" label="仓库">
|
|
18
|
+
<EffectWarehouseSelect />
|
|
19
|
+
</Form.Item>
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
### 独立受控模式
|
|
23
|
+
|
|
24
|
+
```tsx
|
|
25
|
+
import { EffectWarehouseSelect } from '@xfe-repo/web-components'
|
|
26
|
+
|
|
27
|
+
function MyForm() {
|
|
28
|
+
const [warehouseId, setWarehouseId] = useState<string>()
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<EffectWarehouseSelect
|
|
32
|
+
value={warehouseId}
|
|
33
|
+
onChange={(value, name) => {
|
|
34
|
+
setWarehouseId(value)
|
|
35
|
+
console.log('仓库名称:', name)
|
|
36
|
+
}}
|
|
37
|
+
/>
|
|
38
|
+
)
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## 进阶用法
|
|
43
|
+
|
|
44
|
+
### 按仓库类型筛选
|
|
45
|
+
|
|
46
|
+
```tsx
|
|
47
|
+
// 仅显示自有仓
|
|
48
|
+
<EffectWarehouseSelect warehouseType="SELF_OWNED" />
|
|
49
|
+
|
|
50
|
+
// 仅显示门店仓
|
|
51
|
+
<EffectWarehouseSelect warehouseType="STORE" />
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### 显示所有商户仓库
|
|
55
|
+
|
|
56
|
+
默认仅显示当前用户商户的仓库,设置 `currentMerchantOnly={false}` 显示所有:
|
|
57
|
+
|
|
58
|
+
```tsx
|
|
59
|
+
<EffectWarehouseSelect currentMerchantOnly={false} />
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### 仓库 → 库区 → 库位级联
|
|
63
|
+
|
|
64
|
+
```tsx
|
|
65
|
+
import { EffectWarehouseSelect, EffectReservoirSelect, EffectShelveSelect } from '@xfe-repo/web-components'
|
|
66
|
+
|
|
67
|
+
function WarehouseLocationPicker() {
|
|
68
|
+
const [warehouseId, setWarehouseId] = useState<string>()
|
|
69
|
+
const [reservoirId, setReservoirId] = useState<number>()
|
|
70
|
+
const [shelveCode, setShelveCode] = useState<string>()
|
|
71
|
+
|
|
72
|
+
return (
|
|
73
|
+
<div>
|
|
74
|
+
<EffectWarehouseSelect
|
|
75
|
+
value={warehouseId}
|
|
76
|
+
onChange={(value) => {
|
|
77
|
+
setWarehouseId(value)
|
|
78
|
+
setReservoirId(undefined)
|
|
79
|
+
setShelveCode(undefined)
|
|
80
|
+
}}
|
|
81
|
+
/>
|
|
82
|
+
<EffectReservoirSelect
|
|
83
|
+
warehouseId={warehouseId ? Number(warehouseId) : undefined}
|
|
84
|
+
value={reservoirId}
|
|
85
|
+
onChange={(value) => {
|
|
86
|
+
setReservoirId(value)
|
|
87
|
+
setShelveCode(undefined)
|
|
88
|
+
}}
|
|
89
|
+
/>
|
|
90
|
+
<EffectShelveSelect
|
|
91
|
+
warehouseId={warehouseId ? Number(warehouseId) : undefined}
|
|
92
|
+
reservoirId={reservoirId}
|
|
93
|
+
value={shelveCode}
|
|
94
|
+
onChange={(value) => setShelveCode(value)}
|
|
95
|
+
/>
|
|
96
|
+
</div>
|
|
97
|
+
)
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Props
|
|
102
|
+
|
|
103
|
+
```typescript
|
|
104
|
+
interface EffectWarehouseSelectProps {
|
|
105
|
+
className?: string
|
|
106
|
+
disabled?: boolean
|
|
107
|
+
value?: string
|
|
108
|
+
onChange?: (value?: string, name?: string) => void
|
|
109
|
+
warehouseType?: string
|
|
110
|
+
currentMerchantOnly?: boolean
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
| 属性 | 类型 | 必填 | 默认值 | 说明 |
|
|
115
|
+
| ------------------- | ------------------------- | ---- | ------ | ------------------------------------------------------ |
|
|
116
|
+
| value | `string` | 否 | - | 受控值(仓库 ID,注意是 string 类型) |
|
|
117
|
+
| onChange | `(value?, name?) => void` | 否 | - | 值变化回调,返回仓库 ID(string)和名称 |
|
|
118
|
+
| warehouseType | `string` | 否 | - | 仓库类型筛选:`'SELF_OWNED'` 自有仓 / `'STORE'` 门店仓 |
|
|
119
|
+
| currentMerchantOnly | `boolean` | 否 | `true` | 是否仅显示当前用户商户的仓库 |
|
|
120
|
+
| disabled | `boolean` | 否 | - | 禁用状态 |
|
|
121
|
+
| className | `string` | 否 | - | 自定义样式类名 |
|
|
122
|
+
|
|
123
|
+
## 导出类型
|
|
124
|
+
|
|
125
|
+
```typescript
|
|
126
|
+
// 选项数据结构
|
|
127
|
+
export type WarehouseOption = {
|
|
128
|
+
label: string // 仓库名称
|
|
129
|
+
value: number // 仓库 ID
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## API 数据源
|
|
134
|
+
|
|
135
|
+
- **内部 Hook**:`useStoreOptions`(基于 `useSWRImmutable` 缓存)
|
|
136
|
+
- **内部接口**:`apiService.storeComboBox`
|
|
137
|
+
- **请求参数**:`{ type: '', currentMerchantOnly, warehouseType }`
|
|
138
|
+
- **缓存策略**:SWR Immutable 缓存,key 为 `['warehouse/store/combo-box', currentMerchantOnly, warehouseType]`
|
|
139
|
+
- **重试策略**:失败自动重试 3 次,5 秒内去重
|
|
140
|
+
- **可覆盖**:通过 `<ConfigProvider apiService={{ storeComboBox: customFn }}>` 替换默认实现
|
|
141
|
+
|
|
142
|
+
## 常见陷阱
|
|
143
|
+
|
|
144
|
+
- ❌ 将 `value` 类型视为 `number`:
|
|
145
|
+
```tsx
|
|
146
|
+
// value 类型是 string,不是 number!
|
|
147
|
+
const [warehouseId, setWarehouseId] = useState<number>() // ❌
|
|
148
|
+
```
|
|
149
|
+
- ✅ 使用 `string` 类型:
|
|
150
|
+
|
|
151
|
+
```tsx
|
|
152
|
+
const [warehouseId, setWarehouseId] = useState<string>() // ✅
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
- ❌ 传给下游 EffectReservoirSelect 时忘记类型转换:
|
|
156
|
+
```tsx
|
|
157
|
+
// EffectReservoirSelect 的 warehouseId 是 number 类型
|
|
158
|
+
<EffectReservoirSelect warehouseId={warehouseId} /> // ❌ string 类型不兼容
|
|
159
|
+
```
|
|
160
|
+
- ✅ 做类型转换:
|
|
161
|
+
|
|
162
|
+
```tsx
|
|
163
|
+
<EffectReservoirSelect warehouseId={warehouseId ? Number(warehouseId) : undefined} />
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
- ⚠️ `initialValue` 类型陷阱:后端返回的仓库 ID 可能是 `number` 类型,但组件期望 `string`。需确保类型一致:
|
|
167
|
+
|
|
168
|
+
```tsx
|
|
169
|
+
<Form.Item name="warehouseId" initialValue={String(data.warehouseId)}>
|
|
170
|
+
<EffectWarehouseSelect />
|
|
171
|
+
</Form.Item>
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
- ❌ 期望 `onChange` 清空时返回 `''`
|
|
175
|
+
- ✅ 清空时 `onChange` 返回 `(undefined, undefined)`
|
|
176
|
+
|
|
177
|
+
## 相关组件
|
|
178
|
+
|
|
179
|
+
| 场景 | 组件 | 说明 |
|
|
180
|
+
| -------- | ----------------------- | ---------------------------------------- |
|
|
181
|
+
| 级联下游 | `EffectReservoirSelect` | 使用仓库 ID 获取库区列表 |
|
|
182
|
+
| 级联下游 | `EffectShelveSelect` | 使用仓库 ID + 库区 ID 获取库位列表 |
|
|
183
|
+
| API 配置 | `ConfigProvider` | 提供 `apiService.storeComboBox` 接口实现 |
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
# EffectWithFilePanel
|
|
2
|
+
|
|
3
|
+
> 文件上传面板,将 `children` 作为触发元素,点击后弹出抽屉面板进行文件上传。内部集成 `WithPanel` 抽屉和 `EffectFileUpload` 上传组件。
|
|
4
|
+
|
|
5
|
+
## 导入
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
import { EffectWithFilePanel } from '@xfe-repo/web-components'
|
|
9
|
+
import type { SimpleFileData } from '@xfe-repo/web-components'
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## 基本用法
|
|
13
|
+
|
|
14
|
+
```tsx
|
|
15
|
+
import { Button } from 'antd'
|
|
16
|
+
import { EffectWithFilePanel } from '@xfe-repo/web-components'
|
|
17
|
+
|
|
18
|
+
function MyForm() {
|
|
19
|
+
return (
|
|
20
|
+
<EffectWithFilePanel
|
|
21
|
+
onChange={(files) => console.log('文件变更:', files)}
|
|
22
|
+
onSave={(files) => {
|
|
23
|
+
console.log('保存文件:', files)
|
|
24
|
+
// 返回 true/false 或 Promise<boolean> 控制面板是否关闭
|
|
25
|
+
}}
|
|
26
|
+
>
|
|
27
|
+
<Button>上传附件</Button>
|
|
28
|
+
</EffectWithFilePanel>
|
|
29
|
+
)
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## 进阶用法
|
|
34
|
+
|
|
35
|
+
### 查看模式(禁用上传)
|
|
36
|
+
|
|
37
|
+
```tsx
|
|
38
|
+
<EffectWithFilePanel disabled defaultValue={existingFiles}>
|
|
39
|
+
<Button>查看附件</Button>
|
|
40
|
+
</EffectWithFilePanel>
|
|
41
|
+
// disabled 时:标题显示 "查看附件",按钮文字显示 "确定"
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### 限制文件类型和数量
|
|
45
|
+
|
|
46
|
+
```tsx
|
|
47
|
+
<EffectWithFilePanel max={5} accept=".pdf,.doc,.docx" onChange={(files) => setFiles(files)} onSave={handleSave}>
|
|
48
|
+
<Button>上传合同</Button>
|
|
49
|
+
</EffectWithFilePanel>
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### 自定义标题和头部内容
|
|
53
|
+
|
|
54
|
+
```tsx
|
|
55
|
+
<EffectWithFilePanel title="上传报告" header={<Alert message="请上传 PDF 格式的报告文件" type="info" />} onSave={handleSave}>
|
|
56
|
+
<Button>上传报告</Button>
|
|
57
|
+
</EffectWithFilePanel>
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### 远程上传模式
|
|
61
|
+
|
|
62
|
+
```tsx
|
|
63
|
+
<EffectWithFilePanel
|
|
64
|
+
remote
|
|
65
|
+
onSave={async (files) => {
|
|
66
|
+
await submitFiles(files)
|
|
67
|
+
return true
|
|
68
|
+
}}
|
|
69
|
+
>
|
|
70
|
+
<Button>上传文件</Button>
|
|
71
|
+
</EffectWithFilePanel>
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### 文件导入模式
|
|
75
|
+
|
|
76
|
+
```tsx
|
|
77
|
+
<EffectWithFilePanel
|
|
78
|
+
accept=".csv,.xlsx"
|
|
79
|
+
onChange={(file) => {
|
|
80
|
+
// ⚠️ 建议在此处添加二次确认弹窗
|
|
81
|
+
Modal.confirm({
|
|
82
|
+
title: '确认导入?',
|
|
83
|
+
onOk: () => dispatch(effectImport(file)),
|
|
84
|
+
})
|
|
85
|
+
}}
|
|
86
|
+
/>
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Props
|
|
90
|
+
|
|
91
|
+
```typescript
|
|
92
|
+
type Props = {
|
|
93
|
+
disabled?: boolean
|
|
94
|
+
defaultValue?: SimpleFileData[]
|
|
95
|
+
onChange?: (files: SimpleFileData[]) => void
|
|
96
|
+
onSave?: (files?: SimpleFileData[]) => Promise<boolean | void> | boolean | void
|
|
97
|
+
max?: number
|
|
98
|
+
accept?: string
|
|
99
|
+
remote?: boolean
|
|
100
|
+
children: ReactElement
|
|
101
|
+
header?: ReactElement
|
|
102
|
+
title?: string
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
| 属性 | 类型 | 必填 | 默认值 | 说明 |
|
|
107
|
+
| ------------ | --------------------------------------------------------- | ------ | ------------------ | ------------------------------------------------------ |
|
|
108
|
+
| children | `ReactElement` | **是** | - | 触发元素,点击后打开面板 |
|
|
109
|
+
| defaultValue | `SimpleFileData[]` | 否 | - | 已有文件列表 |
|
|
110
|
+
| onChange | `(files) => void` | 否 | - | 文件列表变更回调 |
|
|
111
|
+
| onSave | `(files?) => Promise<boolean \| void> \| boolean \| void` | 否 | - | 点击保存按钮时的回调,返回 false 阻止关闭 |
|
|
112
|
+
| max | `number` | 否 | - | 最大文件数量限制 |
|
|
113
|
+
| accept | `string` | 否 | - | 接受的文件类型(如 `'.pdf,.doc'`),设置后显示格式提示 |
|
|
114
|
+
| remote | `boolean` | 否 | - | 是否启用远程上传模式 |
|
|
115
|
+
| disabled | `boolean` | 否 | - | 禁用上传功能(查看模式) |
|
|
116
|
+
| header | `ReactElement` | 否 | - | 面板头部自定义内容,渲染在上传区域上方 |
|
|
117
|
+
| title | `string` | 否 | 动态生成(见说明) | 面板标题 |
|
|
118
|
+
|
|
119
|
+
### title 和按钮文字默认值逻辑
|
|
120
|
+
|
|
121
|
+
| 状态 | title 默认值 | 按钮文字 |
|
|
122
|
+
| ---------------- | ------------ | ---------------------------- |
|
|
123
|
+
| `disabled=false` | `"上传附件"` | `"保存"` → 上传中 `"上传中"` |
|
|
124
|
+
| `disabled=true` | `"查看附件"` | `"确定"` |
|
|
125
|
+
|
|
126
|
+
## 关联类型
|
|
127
|
+
|
|
128
|
+
```typescript
|
|
129
|
+
// 文件数据结构(来自 EffectFileUpload)
|
|
130
|
+
type SimpleFileData = {
|
|
131
|
+
id?: number
|
|
132
|
+
name?: string
|
|
133
|
+
fileName?: string
|
|
134
|
+
url: string
|
|
135
|
+
}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## 内部结构
|
|
139
|
+
|
|
140
|
+
```
|
|
141
|
+
EffectWithFilePanel
|
|
142
|
+
├── WithPanel (抽屉容器)
|
|
143
|
+
│ ├── header (自定义头部)
|
|
144
|
+
│ ├── accept 格式提示
|
|
145
|
+
│ └── EffectFileUpload (文件上传组件)
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### 保存流程
|
|
149
|
+
|
|
150
|
+
1. 用户点击"保存"按钮
|
|
151
|
+
2. 调用 `uploadRef.current.handleUpload()` 触发所有待上传文件的上传
|
|
152
|
+
3. 上传完成后调用 `onSave(uploadRef.current.value)` 传出最新文件列表
|
|
153
|
+
4. `onSave` 返回值控制面板是否关闭
|
|
154
|
+
|
|
155
|
+
### 关闭面板保护
|
|
156
|
+
|
|
157
|
+
上传进行中时尝试关闭面板,会弹出确认对话框:
|
|
158
|
+
|
|
159
|
+
- 标题:"文件上传中, 确定关闭么?"
|
|
160
|
+
- 内容:"如果关闭,此次上传保存将会失败"
|
|
161
|
+
|
|
162
|
+
## 常见陷阱
|
|
163
|
+
|
|
164
|
+
- ❌ 忘记传 `children`:
|
|
165
|
+
```tsx
|
|
166
|
+
// children 是必需的触发元素
|
|
167
|
+
<EffectWithFilePanel onSave={handleSave} /> // ❌ 报错
|
|
168
|
+
```
|
|
169
|
+
- ✅ 始终传入一个 ReactElement 作为触发元素:
|
|
170
|
+
|
|
171
|
+
```tsx
|
|
172
|
+
<EffectWithFilePanel onSave={handleSave}>
|
|
173
|
+
<Button>上传</Button>
|
|
174
|
+
</EffectWithFilePanel>
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
- ❌ 直接使用 `onChange` 获取最终文件列表:
|
|
178
|
+
```tsx
|
|
179
|
+
// onChange 在文件列表变更时就会触发,包括文件尚未上传完成时
|
|
180
|
+
```
|
|
181
|
+
- ✅ 使用 `onSave` 获取最终上传完成后的文件列表:
|
|
182
|
+
|
|
183
|
+
```tsx
|
|
184
|
+
<EffectWithFilePanel
|
|
185
|
+
onSave={(files) => {
|
|
186
|
+
// files 是上传完成后的最终文件列表
|
|
187
|
+
console.log('最终文件:', files)
|
|
188
|
+
}}
|
|
189
|
+
>
|
|
190
|
+
<Button>上传</Button>
|
|
191
|
+
</EffectWithFilePanel>
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
- ❌ `accept` 仅做 UI 提示,不设防:
|
|
195
|
+
```tsx
|
|
196
|
+
// accept 会传给 EffectFileUpload,但需确认底层是否做了严格校验
|
|
197
|
+
```
|
|
198
|
+
- ✅ 在 `onSave` 中做二次校验确保文件类型正确
|
|
199
|
+
|
|
200
|
+
## 相关组件
|
|
201
|
+
|
|
202
|
+
| 场景 | 组件 | 说明 |
|
|
203
|
+
| -------- | ------------------ | -------------------- |
|
|
204
|
+
| 底层上传 | `EffectFileUpload` | 文件上传核心组件 |
|
|
205
|
+
| 抽屉容器 | `WithPanel` | 可复用的抽屉面板组件 |
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# FileUpload
|
|
2
|
+
|
|
3
|
+
> 基础文件上传组件,基于 antd Upload 封装,支持图片预览、文件数量限制、远程扫码上传,**不包含 API 上传逻辑**。
|
|
4
|
+
|
|
5
|
+
## 导入
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
import { FileUpload } from '@xfe-repo/web-components'
|
|
9
|
+
import type { ModifyFileData } from '@xfe-repo/web-components'
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## 基本用法
|
|
13
|
+
|
|
14
|
+
```tsx
|
|
15
|
+
import { FileUpload } from '@xfe-repo/web-components'
|
|
16
|
+
import { UploadFile } from 'antd/es/upload/interface'
|
|
17
|
+
|
|
18
|
+
function MyUpload() {
|
|
19
|
+
const [files, setFiles] = useState<UploadFile[]>([])
|
|
20
|
+
|
|
21
|
+
return <FileUpload value={files} onChange={(newFiles) => setFiles(newFiles)} max={5} />
|
|
22
|
+
}
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## 进阶用法
|
|
26
|
+
|
|
27
|
+
### 删除确认
|
|
28
|
+
|
|
29
|
+
```tsx
|
|
30
|
+
<FileUpload
|
|
31
|
+
value={files}
|
|
32
|
+
onChange={setFiles}
|
|
33
|
+
deleteConfirm // 删除前弹出确认对话框
|
|
34
|
+
/>
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### 限制文件类型
|
|
38
|
+
|
|
39
|
+
```tsx
|
|
40
|
+
<FileUpload accept="image/png,image/jpeg" value={files} onChange={setFiles} />
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### 单文件上传
|
|
44
|
+
|
|
45
|
+
```tsx
|
|
46
|
+
<FileUpload multiple={false} max={1} value={files} onChange={setFiles} />
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### 远程扫码上传
|
|
50
|
+
|
|
51
|
+
```tsx
|
|
52
|
+
<FileUpload
|
|
53
|
+
remote // 启用远程上传,显示扫码按钮
|
|
54
|
+
value={files}
|
|
55
|
+
onChange={setFiles}
|
|
56
|
+
/>
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### 自定义上传按钮
|
|
60
|
+
|
|
61
|
+
```tsx
|
|
62
|
+
<FileUpload value={files} onChange={setFiles}>
|
|
63
|
+
<Button icon={<UploadOutlined />}>点击上传</Button>
|
|
64
|
+
</FileUpload>
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Props
|
|
68
|
+
|
|
69
|
+
```typescript
|
|
70
|
+
type FileUploadProps = {
|
|
71
|
+
multiple?: boolean
|
|
72
|
+
classNames?: string
|
|
73
|
+
disabled?: boolean
|
|
74
|
+
value?: UploadFile[]
|
|
75
|
+
defaultValue?: UploadFile[]
|
|
76
|
+
type?: UploadListType
|
|
77
|
+
onChange?: (value: UploadFile[], modifyFileData: ModifyFileData) => void
|
|
78
|
+
children?: ReactNode
|
|
79
|
+
accept?: string
|
|
80
|
+
max?: number
|
|
81
|
+
remote?: boolean
|
|
82
|
+
deleteConfirm?: boolean
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
| 属性 | 类型 | 必填 | 默认值 | 说明 |
|
|
87
|
+
| ------------- | --------------------------------------------------------------- | ---- | ---------------- | ------------------------------------------ |
|
|
88
|
+
| value | `UploadFile[]` | 否 | `[]` | 受控文件列表 |
|
|
89
|
+
| defaultValue | `UploadFile[]` | 否 | - | 默认文件列表(非受控) |
|
|
90
|
+
| onChange | `(value: UploadFile[], modifyFileData: ModifyFileData) => void` | 否 | - | 文件变更回调,第二参数包含 add/remove 信息 |
|
|
91
|
+
| max | `number` | 否 | `9` | 最大文件数量 |
|
|
92
|
+
| multiple | `boolean` | 否 | `true` | 是否支持多选 |
|
|
93
|
+
| accept | `string` | 否 | - | 接受的文件类型(MIME 类型) |
|
|
94
|
+
| disabled | `boolean` | 否 | - | 是否禁用 |
|
|
95
|
+
| type | `UploadListType` | 否 | `'picture-card'` | 上传列表展示类型 |
|
|
96
|
+
| remote | `boolean` | 否 | - | 是否启用远程扫码上传 |
|
|
97
|
+
| deleteConfirm | `boolean` | 否 | - | 删除前是否弹出确认框 |
|
|
98
|
+
| classNames | `string` | 否 | - | 自定义 className |
|
|
99
|
+
| children | `ReactNode` | 否 | - | 自定义上传按钮 |
|
|
100
|
+
|
|
101
|
+
## 导出类型
|
|
102
|
+
|
|
103
|
+
### ModifyFileData
|
|
104
|
+
|
|
105
|
+
```typescript
|
|
106
|
+
export type ModifyFileData = {
|
|
107
|
+
add?: UploadFile[]
|
|
108
|
+
remove?: UploadFile[]
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## 常见陷阱
|
|
113
|
+
|
|
114
|
+
- ❌ `FileUpload` 不会自动上传文件到服务器,它只管理本地文件列表
|
|
115
|
+
- ✅ 需要自动上传到 OSS 请使用 `EffectFileUpload`
|
|
116
|
+
|
|
117
|
+
- ❌ 达到 `max` 上限后上传按钮会自动隐藏,而非禁用
|
|
118
|
+
- ✅ 需要删除已有文件后才能继续上传
|
|
119
|
+
|
|
120
|
+
## 相关组件
|
|
121
|
+
|
|
122
|
+
| 场景 | 组件 | 说明 |
|
|
123
|
+
| -------------- | ------------------ | ------------------------------- |
|
|
124
|
+
| 自动上传到 OSS | `EffectFileUpload` | 集成 API 上传能力的文件上传 |
|
|
125
|
+
| 图片展示 | `OSSImage` | 展示 OSS 图片 |
|
|
126
|
+
| 二维码 | `QRCode` | remote 模式内部使用 QRCode 组件 |
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Iconfont
|
|
2
|
+
|
|
3
|
+
> 自定义图标组件,基于 `@ant-design/icons` 的 `createFromIconfontCN` 创建,加载阿里巴巴图标库。
|
|
4
|
+
|
|
5
|
+
## 导入
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
import { Iconfont } from '@xfe-repo/web-components'
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## 基本用法
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
import { Iconfont } from '@xfe-repo/web-components'
|
|
15
|
+
|
|
16
|
+
function MyIcon() {
|
|
17
|
+
return <Iconfont type="icon-example" style={{ fontSize: 24, color: '#1890ff' }} />
|
|
18
|
+
}
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Props
|
|
22
|
+
|
|
23
|
+
`Iconfont` 继承 `@ant-design/icons` 的 `IconFontProps`:
|
|
24
|
+
|
|
25
|
+
| 属性 | 类型 | 必填 | 默认值 | 说明 |
|
|
26
|
+
| --------- | --------------- | ---- | ------- | ------------------------------------------ |
|
|
27
|
+
| type | `string` | 是 | - | 图标名称,对应 iconfont 项目中的 icon name |
|
|
28
|
+
| style | `CSSProperties` | 否 | - | 行内样式(常用于设置 fontSize 和 color) |
|
|
29
|
+
| className | `string` | 否 | - | 自定义 className |
|
|
30
|
+
| rotate | `number` | 否 | - | 旋转角度 |
|
|
31
|
+
| spin | `boolean` | 否 | `false` | 是否有旋转动画 |
|