@xfe-repo/web-components 1.6.1 → 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/dist/index.css +0 -24
- package/dist/index.js +4 -6
- package/dist/index.mjs +4 -6
- package/package.json +6 -5
- 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,251 @@
|
|
|
1
|
+
# EffectSkuSelect
|
|
2
|
+
|
|
3
|
+
> SKU 下拉选择器,级联链末端组件,支持按分类、品牌、系列、SPU 多级过滤,带图片预览和无限滚动加载,数据通过 `apiService.skuListV2` 自动获取。
|
|
4
|
+
|
|
5
|
+
## 导入
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
import { EffectSkuSelect } from '@xfe-repo/web-components'
|
|
9
|
+
import type { ValueType, ItemOption } from '@xfe-repo/web-components'
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## 基本用法
|
|
13
|
+
|
|
14
|
+
最常见用法 — 在 Form.Item 中使用,value/onChange 由 Form 自动注入:
|
|
15
|
+
|
|
16
|
+
```tsx
|
|
17
|
+
<Form.Item name="skuId" label="SKU">
|
|
18
|
+
<EffectSkuSelect categoryId={categoryId} brandId={brandId} />
|
|
19
|
+
</Form.Item>
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
### 独立受控模式
|
|
23
|
+
|
|
24
|
+
```tsx
|
|
25
|
+
import { EffectSkuSelect } from '@xfe-repo/web-components'
|
|
26
|
+
|
|
27
|
+
function MyForm() {
|
|
28
|
+
const [skuId, setSkuId] = useState<number>()
|
|
29
|
+
|
|
30
|
+
return <EffectSkuSelect value={skuId} onChange={(value) => setSkuId(value)} />
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## 进阶用法
|
|
35
|
+
|
|
36
|
+
### 完整级联过滤(分类 → 品牌 → 系列 → SPU → SKU)
|
|
37
|
+
|
|
38
|
+
```tsx
|
|
39
|
+
const [categoryId, setCategoryId] = useState<number>()
|
|
40
|
+
const [brandId, setBrandId] = useState<number>()
|
|
41
|
+
const [seriesId, setSeriesId] = useState<number>()
|
|
42
|
+
const [spuId, setSpuId] = useState<number>()
|
|
43
|
+
const [skuId, setSkuId] = useState<number>()
|
|
44
|
+
|
|
45
|
+
<EffectSkuSelect
|
|
46
|
+
categoryId={categoryId}
|
|
47
|
+
brandId={brandId}
|
|
48
|
+
seriesId={seriesId}
|
|
49
|
+
spuId={spuId}
|
|
50
|
+
value={skuId}
|
|
51
|
+
onChange={(value, skuData) => {
|
|
52
|
+
setSkuId(value)
|
|
53
|
+
// skuData 包含完整的 SKU 信息(ListSkuV2 类型)
|
|
54
|
+
console.log('SKU数据:', skuData)
|
|
55
|
+
}}
|
|
56
|
+
/>
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### 显示 SKU ID 信息
|
|
60
|
+
|
|
61
|
+
开启后选项标签格式为 `skuId | ahsSkuId | skuName`:
|
|
62
|
+
|
|
63
|
+
```tsx
|
|
64
|
+
<EffectSkuSelect showSkuId value={skuId} onChange={(value, skuData) => setSkuId(value)} />
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### 按上架状态和 SKU 类型筛选
|
|
68
|
+
|
|
69
|
+
```tsx
|
|
70
|
+
<EffectSkuSelect
|
|
71
|
+
shelfStatus={1} // 1-上架,2-下架,3-包含所有
|
|
72
|
+
skuType="std" // 'std'-标准SKU,'tmp'-临时SKU
|
|
73
|
+
value={skuId}
|
|
74
|
+
onChange={(value) => setSkuId(value)}
|
|
75
|
+
/>
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### 级联 categoryId 提取
|
|
79
|
+
|
|
80
|
+
EffectCategoryCascade 返回数组,需取末位传给 EffectSkuSelect:
|
|
81
|
+
|
|
82
|
+
```tsx
|
|
83
|
+
const categoryIds = Form.useWatch('categoryIds', form)
|
|
84
|
+
const categoryId = categoryIds?.[categoryIds.length - 1]
|
|
85
|
+
|
|
86
|
+
<Form.Item name="skuId" label="SKU">
|
|
87
|
+
<EffectSkuSelect categoryId={categoryId} brandId={brandId} key={`${categoryId}-${brandId}`} />
|
|
88
|
+
</Form.Item>
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## 使用提示
|
|
92
|
+
|
|
93
|
+
> 💡 常见校验:`<Form.Item name="skuId" label="SKU" rules={[{ required: true, message: '请选择SKU' }]}>`
|
|
94
|
+
|
|
95
|
+
## Props
|
|
96
|
+
|
|
97
|
+
```typescript
|
|
98
|
+
type ConditionsType = { categoryId?: number; brandId?: number; seriesId?: number; spuId?: number }
|
|
99
|
+
|
|
100
|
+
type Props = ConditionsType & {
|
|
101
|
+
className?: string
|
|
102
|
+
onChange?: (value?: number, data?: ListSkuV2) => void
|
|
103
|
+
defaultValue?: number | string
|
|
104
|
+
disabled?: boolean
|
|
105
|
+
value?: number
|
|
106
|
+
/** 上架状态: 1-上架,2-下架,3-包含所有(不传则由后端决定默认行为) */
|
|
107
|
+
shelfStatus?: number
|
|
108
|
+
showSkuId?: boolean
|
|
109
|
+
skuType?: 'std' | 'tmp'
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
| 属性 | 类型 | 必填 | 默认值 | 说明 |
|
|
114
|
+
| ------------ | -------------------------------------------- | ---- | ------- | --------------------------------------------------------------- |
|
|
115
|
+
| value | `number` | 否 | - | 受控值(SKU ID) |
|
|
116
|
+
| defaultValue | `number \| string` | 否 | - | 默认值 |
|
|
117
|
+
| onChange | `(value?: number, data?: ListSkuV2) => void` | 否 | - | 值变化回调,第二个参数为完整 SKU 数据 |
|
|
118
|
+
| categoryId | `number` | 否 | - | 分类 ID,变更时自动重新加载并清空选中值 |
|
|
119
|
+
| brandId | `number` | 否 | - | 品牌 ID,变更时自动重新加载并清空选中值 |
|
|
120
|
+
| seriesId | `number` | 否 | - | 系列 ID,变更时自动重新加载并清空选中值 |
|
|
121
|
+
| spuId | `number` | 否 | - | SPU ID,变更时自动重新加载并清空选中值 |
|
|
122
|
+
| shelfStatus | `number` | 否 | - | 上架状态:1-上架,2-下架,3-包含所有 |
|
|
123
|
+
| showSkuId | `boolean` | 否 | `false` | 是否在选项中显示 SKU ID(格式:`skuId \| ahsSkuId \| skuName`) |
|
|
124
|
+
| skuType | `'std' \| 'tmp'` | 否 | `'std'` | SKU 类型:标准/临时 |
|
|
125
|
+
| disabled | `boolean` | 否 | - | 禁用状态 |
|
|
126
|
+
| className | `string` | 否 | - | 自定义样式类名 |
|
|
127
|
+
|
|
128
|
+
## 导出类型
|
|
129
|
+
|
|
130
|
+
```typescript
|
|
131
|
+
// 选项值类型
|
|
132
|
+
export type ValueType = string | number | undefined
|
|
133
|
+
|
|
134
|
+
// 选项数据结构
|
|
135
|
+
export type ItemOption = { label: string; value: number; image: string }
|
|
136
|
+
|
|
137
|
+
// 图片预览组件的 Props
|
|
138
|
+
export type ImageItemProps<O extends ItemOption = ItemOption> = {
|
|
139
|
+
item: O
|
|
140
|
+
previewed: boolean
|
|
141
|
+
onPreview: (item?: O) => void
|
|
142
|
+
className?: string
|
|
143
|
+
}
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
> **注意**:`ValueType`、`ItemOption`、`SelectDropdown`、`ImageItem`、`getNumberValue` 均从本文件导出,`EffectSeriesSelect` 和 `EffectSpuSelect` 内部会从 `EffectSkuSelect` 导入使用,但不会再次导出。如需使用这些类型,请始终从 `EffectSkuSelect` 导入。
|
|
147
|
+
|
|
148
|
+
## API 数据源
|
|
149
|
+
|
|
150
|
+
- **内部 Hook**:`useSkuOptions`(使用 SWR 缓存)
|
|
151
|
+
- **内部接口**:`apiService.skuListV2`
|
|
152
|
+
- **请求参数**:`{ categoryId, brandId, seriesId, spuId, id, skuName, type, status, page, pageSize }`
|
|
153
|
+
- **可覆盖**:通过 `<ConfigProvider apiService={{ skuListV2: customFn }}>` 替换默认实现
|
|
154
|
+
|
|
155
|
+
## 依赖 Props
|
|
156
|
+
|
|
157
|
+
| 依赖属性 | 类型 | 来源 | 说明 |
|
|
158
|
+
| ---------- | -------- | ---------------------------- | -------------------- |
|
|
159
|
+
| categoryId | `number` | EffectCategoryCascade 的输出 | 按分类过滤 SKU 列表 |
|
|
160
|
+
| brandId | `number` | EffectBrandSelect 的输出 | 按品牌过滤 SKU 列表 |
|
|
161
|
+
| seriesId | `number` | EffectSeriesSelect 的输出 | 按系列过滤 SKU 列表 |
|
|
162
|
+
| spuId | `number` | EffectSpuSelect 的输出 | 按 SPU 过滤 SKU 列表 |
|
|
163
|
+
|
|
164
|
+
### 联动示例
|
|
165
|
+
|
|
166
|
+
```tsx
|
|
167
|
+
import { useState } from 'react'
|
|
168
|
+
import {
|
|
169
|
+
EffectCategoryCascade,
|
|
170
|
+
EffectBrandSelect,
|
|
171
|
+
EffectSeriesSelect,
|
|
172
|
+
EffectSpuSelect,
|
|
173
|
+
EffectSkuSelect,
|
|
174
|
+
isSingleTreeSelected,
|
|
175
|
+
} from '@xfe-repo/web-components'
|
|
176
|
+
|
|
177
|
+
function FullCascadeFilter() {
|
|
178
|
+
const [categoryId, setCategoryId] = useState<number>()
|
|
179
|
+
const [brandId, setBrandId] = useState<number>()
|
|
180
|
+
const [seriesId, setSeriesId] = useState<number>()
|
|
181
|
+
const [spuId, setSpuId] = useState<number>()
|
|
182
|
+
const [skuId, setSkuId] = useState<number>()
|
|
183
|
+
|
|
184
|
+
return (
|
|
185
|
+
<div>
|
|
186
|
+
<EffectCategoryCascade
|
|
187
|
+
onChange={(values, selectedList) => {
|
|
188
|
+
if (selectedList && isSingleTreeSelected(selectedList)) {
|
|
189
|
+
setCategoryId(Number(selectedList[selectedList.length - 1].value))
|
|
190
|
+
} else {
|
|
191
|
+
setCategoryId(undefined)
|
|
192
|
+
}
|
|
193
|
+
}}
|
|
194
|
+
/>
|
|
195
|
+
<EffectBrandSelect categoryId={categoryId} value={brandId} onChange={(value) => setBrandId(value as number)} />
|
|
196
|
+
<EffectSeriesSelect categoryId={categoryId} brandId={brandId} value={seriesId} onChange={(value) => setSeriesId(value)} />
|
|
197
|
+
<EffectSpuSelect categoryId={categoryId} brandId={brandId} seriesId={seriesId} value={spuId} onChange={(value) => setSpuId(value)} />
|
|
198
|
+
<EffectSkuSelect
|
|
199
|
+
categoryId={categoryId}
|
|
200
|
+
brandId={brandId}
|
|
201
|
+
seriesId={seriesId}
|
|
202
|
+
spuId={spuId}
|
|
203
|
+
value={skuId}
|
|
204
|
+
onChange={(value, skuData) => {
|
|
205
|
+
setSkuId(value)
|
|
206
|
+
console.log('完整SKU数据:', skuData)
|
|
207
|
+
}}
|
|
208
|
+
/>
|
|
209
|
+
</div>
|
|
210
|
+
)
|
|
211
|
+
}
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
## 子组件
|
|
215
|
+
|
|
216
|
+
EffectSkuSelect 同时导出了供 EffectSeriesSelect 和 EffectSpuSelect 内部复用的子组件:
|
|
217
|
+
|
|
218
|
+
| 子组件 | 说明 |
|
|
219
|
+
| ---------------- | -------------------------------------- |
|
|
220
|
+
| `SelectDropdown` | 自定义下拉面板,支持图片预览和无限滚动 |
|
|
221
|
+
| `ImageItem` | 选项中的图片展示组件,支持预览 |
|
|
222
|
+
| `getNumberValue` | 将 ValueType 转为 number 的工具函数 |
|
|
223
|
+
|
|
224
|
+
## 常见陷阱
|
|
225
|
+
|
|
226
|
+
- ❌ 不传任何上游条件,期望获取所有 SKU——组件会请求全量数据,可能导致性能问题:
|
|
227
|
+
```tsx
|
|
228
|
+
<EffectSkuSelect onChange={(v) => setSkuId(v)} />
|
|
229
|
+
```
|
|
230
|
+
- ✅ 至少传入一个上游过滤条件缩小范围:
|
|
231
|
+
|
|
232
|
+
```tsx
|
|
233
|
+
<EffectSkuSelect categoryId={categoryId} onChange={(v) => setSkuId(v)} />
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
- ❌ 混淆 `shelfStatus` 值——不传不等于默认上架
|
|
237
|
+
- ✅ 需要只显示上架商品时显式传 `shelfStatus={1}`
|
|
238
|
+
|
|
239
|
+
- ❌ 忽略 `onChange` 第二个参数 `data` 中的完整信息——`ListSkuV2` 包含 skuName、skuImage、ahsSkuId 等
|
|
240
|
+
- ✅ 需要详情时保存完整 `data` 对象
|
|
241
|
+
|
|
242
|
+
## 相关组件
|
|
243
|
+
|
|
244
|
+
| 场景 | 组件 | 说明 |
|
|
245
|
+
| -------- | ----------------------- | ---------------------------------- |
|
|
246
|
+
| 级联上游 | `EffectCategoryCascade` | 提供 categoryId |
|
|
247
|
+
| 级联上游 | `EffectBrandSelect` | 提供 brandId |
|
|
248
|
+
| 级联上游 | `EffectSeriesSelect` | 提供 seriesId |
|
|
249
|
+
| 级联上游 | `EffectSpuSelect` | 提供 spuId |
|
|
250
|
+
| 聚合使用 | `EffectScopeSelect` | 内部已集成本组件,无需单独使用 |
|
|
251
|
+
| API 配置 | `ConfigProvider` | 提供 apiService.skuListV2 接口实现 |
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
# EffectSkuTable
|
|
2
|
+
|
|
3
|
+
> SKU 联动选择表格,以表格形式展示分类 → 品牌 → 系列 → SPU → SKU 的五级级联选择器,每列为一个独立的 Effect 组件。
|
|
4
|
+
|
|
5
|
+
## 导入
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
import { EffectSkuTable } from '@xfe-repo/web-components'
|
|
9
|
+
import type { EffectSkuTableProps } from '@xfe-repo/web-components'
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## 基本用法
|
|
13
|
+
|
|
14
|
+
```tsx
|
|
15
|
+
import { EffectSkuTable } from '@xfe-repo/web-components'
|
|
16
|
+
|
|
17
|
+
function SkuPicker() {
|
|
18
|
+
const [skuId, setSkuId] = useState<number>()
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<EffectSkuTable
|
|
22
|
+
categoryIds={[]}
|
|
23
|
+
onChange={(skuId, skuData) => {
|
|
24
|
+
setSkuId(skuId)
|
|
25
|
+
console.log('SKU 数据:', skuData)
|
|
26
|
+
}}
|
|
27
|
+
/>
|
|
28
|
+
)
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## 进阶用法
|
|
33
|
+
|
|
34
|
+
### 预设品牌和分类
|
|
35
|
+
|
|
36
|
+
```tsx
|
|
37
|
+
<EffectSkuTable categoryIds={[1, 2, 3]} brandId={100} brandName="Nike" onChange={(skuId, skuData) => setSkuId(skuId)} />
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### 通过 SKU ID 回显
|
|
41
|
+
|
|
42
|
+
传入 `value` 时组件会自动调用 `skuFindOne` 接口查询完整数据并填充各级选择器:
|
|
43
|
+
|
|
44
|
+
```tsx
|
|
45
|
+
<EffectSkuTable categoryIds={[]} value={12345} onChange={(skuId, skuData) => console.log(skuData)} />
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### 隐藏分类列
|
|
49
|
+
|
|
50
|
+
当 `categoryIds` 不为数组类型时(传入单个 number),分类列不显示:
|
|
51
|
+
|
|
52
|
+
```tsx
|
|
53
|
+
<EffectSkuTable categoryIds={100} onChange={(skuId, skuData) => setSkuId(skuId)} />
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### 显示 SKU ID 和选择临时 SKU
|
|
57
|
+
|
|
58
|
+
```tsx
|
|
59
|
+
<EffectSkuTable categoryIds={[]} showSkuId skuType="tmp" onChange={(skuId, skuData) => setSkuId(skuId)} />
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Props
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
65
|
+
export interface EffectSkuTableProps {
|
|
66
|
+
/** number[]: 各级分类Id数组,number: 最后一级分类Id */
|
|
67
|
+
categoryIds?: number[] | number
|
|
68
|
+
brandId?: number
|
|
69
|
+
brandName?: string
|
|
70
|
+
seriesId?: number
|
|
71
|
+
seriesName?: string
|
|
72
|
+
spuId?: number
|
|
73
|
+
spuName?: string
|
|
74
|
+
disabled?: boolean
|
|
75
|
+
value?: number
|
|
76
|
+
showSkuId?: boolean
|
|
77
|
+
skuType?: 'std' | 'tmp'
|
|
78
|
+
onChange?: (value?: number, skuData?: ListSkuV2) => void
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
| 属性 | 类型 | 必填 | 默认值 | 说明 |
|
|
83
|
+
| ----------- | ---------------------------- | ---- | ------- | ----------------------------------------------- |
|
|
84
|
+
| categoryIds | `number[] \| number` | 否 | - | 分类 ID。传数组时显示分类列,传数字时隐藏分类列 |
|
|
85
|
+
| brandId | `number` | 否 | - | 预设品牌 ID |
|
|
86
|
+
| brandName | `string` | 否 | - | 预设品牌名称 |
|
|
87
|
+
| seriesId | `number` | 否 | `0` | 预设系列 ID |
|
|
88
|
+
| seriesName | `string` | 否 | - | 预设系列名称 |
|
|
89
|
+
| spuId | `number` | 否 | - | 预设 SPU ID |
|
|
90
|
+
| spuName | `string` | 否 | - | 预设 SPU 名称 |
|
|
91
|
+
| value | `number` | 否 | - | 受控 SKU ID,传入时自动反查填充各级选择器 |
|
|
92
|
+
| onChange | `(value?, skuData?) => void` | 否 | - | 值变化回调(见下方说明) |
|
|
93
|
+
| disabled | `boolean` | 否 | - | 禁用所有选择器 |
|
|
94
|
+
| showSkuId | `boolean` | 否 | `false` | 是否在 SKU 选择器中显示 SKU ID |
|
|
95
|
+
| skuType | `'std' \| 'tmp'` | 否 | `'std'` | SKU 类型,`'std'` 标准 / `'tmp'` 临时 |
|
|
96
|
+
|
|
97
|
+
### onChange 回调说明
|
|
98
|
+
|
|
99
|
+
`onChange` 在不同操作阶段返回不同的数据:
|
|
100
|
+
|
|
101
|
+
- **选中 SKU 时**:`onChange(skuId, { skuId, categoryId, brandId, seriesId, spuId, skuName, brandName, ... })`
|
|
102
|
+
- **变更上游选择器时**(如品牌变更):`onChange(undefined, { categoryId, brandId, seriesId, ... })`(部分字段)
|
|
103
|
+
- **通过 value 反查成功时**:`onChange(value, { 完整 SKU 数据 })`
|
|
104
|
+
|
|
105
|
+
## 导出类型
|
|
106
|
+
|
|
107
|
+
```typescript
|
|
108
|
+
export interface EffectSkuTableProps { ... }
|
|
109
|
+
|
|
110
|
+
// ListSkuV2 来自 @api/product
|
|
111
|
+
// 包含: skuId, skuName, categoryId, categoryName, brandId, brandName,
|
|
112
|
+
// seriesId, seriesName, spuId, spuName, image, skuImage 等
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## API 数据源
|
|
116
|
+
|
|
117
|
+
- **SKU 反查接口**:`apiService.skuFindOne`(传入 `value` 时使用)
|
|
118
|
+
- **请求参数**:`{ id: skuId }`
|
|
119
|
+
- **内部组件各自的接口**:各级选择器使用各自的 API(见相关组件)
|
|
120
|
+
|
|
121
|
+
## 子组件
|
|
122
|
+
|
|
123
|
+
组件内部以 Table Column 形式渲染以下子组件:
|
|
124
|
+
|
|
125
|
+
| 列 | 子组件 | 说明 |
|
|
126
|
+
| ------- | ----------------------- | ------------------------------------ |
|
|
127
|
+
| 分类 | `EffectCategoryCascade` | 仅 `categoryIds` 为数组时显示 |
|
|
128
|
+
| 品牌 | `EffectBrandSelect` | 依赖 categoryId |
|
|
129
|
+
| 系列 | `EffectSeriesSelect` | 依赖 categoryId + brandId |
|
|
130
|
+
| SPU | `EffectSpuSelect` | 依赖 categoryId + brandId + seriesId |
|
|
131
|
+
| SKU | `EffectSkuSelect` | 依赖以上所有 + spuId |
|
|
132
|
+
| SKU图片 | `OSSImage` | 显示选中 SKU 的缩略图 |
|
|
133
|
+
|
|
134
|
+
## 常见陷阱
|
|
135
|
+
|
|
136
|
+
- ❌ 不传 `categoryIds` 而期望分类列显示:
|
|
137
|
+
```tsx
|
|
138
|
+
// 只有传入 number[] 类型时分类列才会渲染
|
|
139
|
+
```
|
|
140
|
+
- ✅ 传入空数组 `[]` 来显示空的分类选择器:
|
|
141
|
+
|
|
142
|
+
```tsx
|
|
143
|
+
<EffectSkuTable categoryIds={[]} />
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
- ❌ 期望 `onChange` 的第二个参数始终包含完整的 SKU 数据:
|
|
147
|
+
```tsx
|
|
148
|
+
// 变更上游选择器(如品牌、系列)时,skuData 只包含已选择的部分字段
|
|
149
|
+
// value 为 undefined 表示 SKU 尚未确定
|
|
150
|
+
```
|
|
151
|
+
- ✅ 根据 `value` 是否有值判断 SKU 是否已选中:
|
|
152
|
+
|
|
153
|
+
```tsx
|
|
154
|
+
<EffectSkuTable
|
|
155
|
+
categoryIds={[]}
|
|
156
|
+
onChange={(value, skuData) => {
|
|
157
|
+
if (value) {
|
|
158
|
+
// SKU 已选中,skuData 包含完整数据
|
|
159
|
+
} else {
|
|
160
|
+
// 上游变更,SKU 被清空,skuData 只含部分信息
|
|
161
|
+
}
|
|
162
|
+
}}
|
|
163
|
+
/>
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
- ❌ 传入 `value` 后立即读取 state:
|
|
167
|
+
```tsx
|
|
168
|
+
// value 反查是异步的,需要等 onChange 回调触发
|
|
169
|
+
```
|
|
170
|
+
- ✅ 在 `onChange` 回调中获取反查结果
|
|
171
|
+
|
|
172
|
+
- ⚠️ 已知问题:组件受控逻辑存在 bug,业务代码通过 `key={reloadKey}` 强制重新挂载来绕过。如遇数据不刷新,可尝试此方法。
|
|
173
|
+
|
|
174
|
+
## 相关组件
|
|
175
|
+
|
|
176
|
+
| 场景 | 组件 | 说明 |
|
|
177
|
+
| ------------- | ----------------------- | ------------------------------------- |
|
|
178
|
+
| 图片识别选择 | `EffectSkuRecognize` | 基于图片智能识别 SKU |
|
|
179
|
+
| 单独 SKU 搜索 | `EffectSkuSelect` | SKU 下拉搜索选择器 |
|
|
180
|
+
| 品牌选择 | `EffectBrandSelect` | 内部使用的品牌选择器 |
|
|
181
|
+
| 分类选择 | `EffectCategoryCascade` | 内部使用的分类级联选择器 |
|
|
182
|
+
| 系列选择 | `EffectSeriesSelect` | 内部使用的系列选择器 |
|
|
183
|
+
| SPU 选择 | `EffectSpuSelect` | 内部使用的 SPU 选择器 |
|
|
184
|
+
| API 配置 | `ConfigProvider` | 提供 `apiService.skuFindOne` 接口实现 |
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
# EffectSpuSelect
|
|
2
|
+
|
|
3
|
+
> SPU 下拉选择器,支持按分类、品牌、系列过滤和上架状态筛选,带图片预览和无限滚动加载,数据通过 `apiService.spuList` 自动获取。
|
|
4
|
+
|
|
5
|
+
## 导入
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
import { EffectSpuSelect } from '@xfe-repo/web-components'
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## 基本用法
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
import { EffectSpuSelect } from '@xfe-repo/web-components'
|
|
15
|
+
|
|
16
|
+
function MyForm() {
|
|
17
|
+
const [spuId, setSpuId] = useState<number>()
|
|
18
|
+
|
|
19
|
+
return <EffectSpuSelect value={spuId} onChange={(value) => setSpuId(value)} />
|
|
20
|
+
}
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## 进阶用法
|
|
24
|
+
|
|
25
|
+
### 完整级联过滤
|
|
26
|
+
|
|
27
|
+
```tsx
|
|
28
|
+
const [categoryId, setCategoryId] = useState<number>()
|
|
29
|
+
const [brandId, setBrandId] = useState<number>()
|
|
30
|
+
const [seriesId, setSeriesId] = useState<number>()
|
|
31
|
+
const [spuId, setSpuId] = useState<number>()
|
|
32
|
+
|
|
33
|
+
<EffectCategoryCascade onChange={handleCategoryChange} />
|
|
34
|
+
<EffectBrandSelect categoryId={categoryId} value={brandId} onChange={(v) => setBrandId(v as number)} />
|
|
35
|
+
<EffectSeriesSelect categoryId={categoryId} brandId={brandId} value={seriesId} onChange={(v) => setSeriesId(v)} />
|
|
36
|
+
<EffectSpuSelect
|
|
37
|
+
categoryId={categoryId}
|
|
38
|
+
brandId={brandId}
|
|
39
|
+
seriesId={seriesId}
|
|
40
|
+
value={spuId}
|
|
41
|
+
onChange={(value, spuData) => {
|
|
42
|
+
setSpuId(value)
|
|
43
|
+
// spuData 包含完整的 SPU 信息(SpuItem 类型)
|
|
44
|
+
console.log('SPU数据:', spuData)
|
|
45
|
+
}}
|
|
46
|
+
/>
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### 按上架状态筛选
|
|
50
|
+
|
|
51
|
+
```tsx
|
|
52
|
+
<EffectSpuSelect
|
|
53
|
+
shelfStatus={1} // 1-上架
|
|
54
|
+
value={spuId}
|
|
55
|
+
onChange={(value) => setSpuId(value)}
|
|
56
|
+
/>
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Props
|
|
60
|
+
|
|
61
|
+
```typescript
|
|
62
|
+
type ConditionsType = { categoryId?: number; brandId?: number; seriesId?: number }
|
|
63
|
+
|
|
64
|
+
type Props = ConditionsType & {
|
|
65
|
+
className?: string
|
|
66
|
+
onChange?: (value?: number, spuData?: SpuItem) => void
|
|
67
|
+
defaultValue?: string
|
|
68
|
+
disabled?: boolean
|
|
69
|
+
value?: number
|
|
70
|
+
shelfStatus?: number
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
| 属性 | 类型 | 必填 | 默认值 | 说明 |
|
|
75
|
+
| ------------ | --------------------------------------------- | ---- | ------ | --------------------------------------- |
|
|
76
|
+
| value | `number` | 否 | - | 受控值(SPU ID) |
|
|
77
|
+
| defaultValue | `string` | 否 | - | 默认值 |
|
|
78
|
+
| onChange | `(value?: number, spuData?: SpuItem) => void` | 否 | - | 值变化回调,第二个参数为完整 SPU 数据 |
|
|
79
|
+
| categoryId | `number` | 否 | - | 分类 ID,变更时自动重新加载并清空选中值 |
|
|
80
|
+
| brandId | `number` | 否 | - | 品牌 ID,变更时自动重新加载并清空选中值 |
|
|
81
|
+
| seriesId | `number` | 否 | - | 系列 ID,变更时自动重新加载并清空选中值 |
|
|
82
|
+
| shelfStatus | `number` | 否 | - | 上架状态过滤 |
|
|
83
|
+
| disabled | `boolean` | 否 | - | 禁用状态 |
|
|
84
|
+
| className | `string` | 否 | - | 自定义样式类名 |
|
|
85
|
+
|
|
86
|
+
## 导出类型
|
|
87
|
+
|
|
88
|
+
组件本身未导出独立类型。`onChange` 回调中的 `SpuItem` 类型来源于 `@api/product`。
|
|
89
|
+
|
|
90
|
+
## API 数据源
|
|
91
|
+
|
|
92
|
+
- **内部 Hook**:`useSpuOptions`(使用 SWR 缓存)
|
|
93
|
+
- **内部接口**:`apiService.spuList`
|
|
94
|
+
- **请求参数**:`{ categoryId, brandId, seriesId, id, spuName, status, page, pageSize }`
|
|
95
|
+
- **可覆盖**:通过 `<ConfigProvider apiService={{ spuList: customFn }}>` 替换默认实现
|
|
96
|
+
|
|
97
|
+
## 依赖 Props
|
|
98
|
+
|
|
99
|
+
| 依赖属性 | 类型 | 来源 | 说明 |
|
|
100
|
+
| ---------- | -------- | ---------------------------- | ------------------- |
|
|
101
|
+
| categoryId | `number` | EffectCategoryCascade 的输出 | 按分类过滤 SPU 列表 |
|
|
102
|
+
| brandId | `number` | EffectBrandSelect 的输出 | 按品牌过滤 SPU 列表 |
|
|
103
|
+
| seriesId | `number` | EffectSeriesSelect 的输出 | 按系列过滤 SPU 列表 |
|
|
104
|
+
|
|
105
|
+
### 联动示例
|
|
106
|
+
|
|
107
|
+
```tsx
|
|
108
|
+
import { useState } from 'react'
|
|
109
|
+
import {
|
|
110
|
+
EffectCategoryCascade,
|
|
111
|
+
EffectBrandSelect,
|
|
112
|
+
EffectSeriesSelect,
|
|
113
|
+
EffectSpuSelect,
|
|
114
|
+
isSingleTreeSelected,
|
|
115
|
+
} from '@xfe-repo/web-components'
|
|
116
|
+
|
|
117
|
+
function CascadeFilter() {
|
|
118
|
+
const [categoryId, setCategoryId] = useState<number>()
|
|
119
|
+
const [brandId, setBrandId] = useState<number>()
|
|
120
|
+
const [seriesId, setSeriesId] = useState<number>()
|
|
121
|
+
const [spuId, setSpuId] = useState<number>()
|
|
122
|
+
|
|
123
|
+
return (
|
|
124
|
+
<div>
|
|
125
|
+
<EffectCategoryCascade
|
|
126
|
+
onChange={(values, selectedList) => {
|
|
127
|
+
if (selectedList && isSingleTreeSelected(selectedList)) {
|
|
128
|
+
setCategoryId(Number(selectedList[selectedList.length - 1].value))
|
|
129
|
+
} else {
|
|
130
|
+
setCategoryId(undefined)
|
|
131
|
+
}
|
|
132
|
+
}}
|
|
133
|
+
/>
|
|
134
|
+
<EffectBrandSelect categoryId={categoryId} value={brandId} onChange={(value) => setBrandId(value as number)} />
|
|
135
|
+
<EffectSeriesSelect categoryId={categoryId} brandId={brandId} value={seriesId} onChange={(value) => setSeriesId(value)} />
|
|
136
|
+
<EffectSpuSelect
|
|
137
|
+
categoryId={categoryId}
|
|
138
|
+
brandId={brandId}
|
|
139
|
+
seriesId={seriesId}
|
|
140
|
+
value={spuId}
|
|
141
|
+
onChange={(value, spuData) => setSpuId(value)}
|
|
142
|
+
/>
|
|
143
|
+
</div>
|
|
144
|
+
)
|
|
145
|
+
}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
## 常见陷阱
|
|
149
|
+
|
|
150
|
+
- ❌ 忽略 `onChange` 第二个参数 `spuData` 的完整数据——它是 `SpuItem` 类型,包含 SPU 图片、名称等完整信息:
|
|
151
|
+
```tsx
|
|
152
|
+
onChange={(value) => setSpuId(value)}
|
|
153
|
+
// 丢失了完整 SPU 数据
|
|
154
|
+
```
|
|
155
|
+
- ✅ 需要 SPU 详细信息时保存第二个参数:
|
|
156
|
+
|
|
157
|
+
```tsx
|
|
158
|
+
onChange={(value, spuData) => {
|
|
159
|
+
setSpuId(value)
|
|
160
|
+
setSpuDetail(spuData) // 保存完整 SPU 数据供后续使用
|
|
161
|
+
}}
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
- ❌ 上游条件变更后期望保留已选 SPU——条件变更时组件会自动清空选中值
|
|
165
|
+
- ✅ 在 `onChange` 中正确处理 `undefined` 情况
|
|
166
|
+
|
|
167
|
+
- ⚠️ onChange 有第二参数 `spuData`(完整 SPU 数据对象),请务必捕获以获取 SPU 详情:
|
|
168
|
+
|
|
169
|
+
```tsx
|
|
170
|
+
<EffectSpuSelect
|
|
171
|
+
onChange={(spuId, spuData) => {
|
|
172
|
+
form.setFieldValue('spuName', spuData?.name)
|
|
173
|
+
}}
|
|
174
|
+
/>
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
- ❌ `shelfStatus` 不传时期望默认只显示上架商品——不传表示不过滤
|
|
178
|
+
- ✅ 需要过滤上架状态时显式传入 `shelfStatus={1}`
|
|
179
|
+
|
|
180
|
+
## 相关组件
|
|
181
|
+
|
|
182
|
+
| 场景 | 组件 | 说明 |
|
|
183
|
+
| -------- | ----------------------- | -------------------------------- |
|
|
184
|
+
| 级联上游 | `EffectCategoryCascade` | 提供 categoryId |
|
|
185
|
+
| 级联上游 | `EffectBrandSelect` | 提供 brandId |
|
|
186
|
+
| 级联上游 | `EffectSeriesSelect` | 提供 seriesId |
|
|
187
|
+
| 级联下游 | `EffectSkuSelect` | 使用 spuId 过滤 SKU 列表 |
|
|
188
|
+
| 聚合使用 | `EffectScopeSelect` | 内部已集成本组件,无需单独使用 |
|
|
189
|
+
| API 配置 | `ConfigProvider` | 提供 apiService.spuList 接口实现 |
|