@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.
Files changed (44) hide show
  1. package/package.json +4 -3
  2. package/skills/GUIDE.md +78 -0
  3. package/skills/SKILL.md +78 -0
  4. package/skills/TEMPLATE.md +170 -0
  5. package/skills/references/ApiService.md +136 -0
  6. package/skills/references/CInput.md +196 -0
  7. package/skills/references/CTable.md +274 -0
  8. package/skills/references/CVideo.md +94 -0
  9. package/skills/references/Clock.md +53 -0
  10. package/skills/references/ConfigProvider.md +198 -0
  11. package/skills/references/Countdown.md +109 -0
  12. package/skills/references/Counter.md +75 -0
  13. package/skills/references/Currency.md +112 -0
  14. package/skills/references/EffectAddressCascade.md +110 -0
  15. package/skills/references/EffectBrandSelect.md +231 -0
  16. package/skills/references/EffectBrandTransfer.md +143 -0
  17. package/skills/references/EffectCategoryCascade.md +228 -0
  18. package/skills/references/EffectFileUpload.md +349 -0
  19. package/skills/references/EffectLabelSelect.md +222 -0
  20. package/skills/references/EffectMerchantSelect.md +183 -0
  21. package/skills/references/EffectReservoirSelect.md +178 -0
  22. package/skills/references/EffectScopeSelect.md +220 -0
  23. package/skills/references/EffectSeriesSelect.md +205 -0
  24. package/skills/references/EffectSeriesSelectV2.md +154 -0
  25. package/skills/references/EffectSkuRecognize.md +149 -0
  26. package/skills/references/EffectSkuSelect.md +251 -0
  27. package/skills/references/EffectSkuTable.md +184 -0
  28. package/skills/references/EffectSpuSelect.md +189 -0
  29. package/skills/references/EffectStaffSelect.md +150 -0
  30. package/skills/references/EffectWarehouseSelect.md +183 -0
  31. package/skills/references/EffectWithFilePanel.md +205 -0
  32. package/skills/references/FileUpload.md +126 -0
  33. package/skills/references/Iconfont.md +31 -0
  34. package/skills/references/Loading.md +68 -0
  35. package/skills/references/MultiWindow.md +145 -0
  36. package/skills/references/OSSImage.md +230 -0
  37. package/skills/references/PrivacyField.md +90 -0
  38. package/skills/references/QRCode.md +148 -0
  39. package/skills/references/RichTextEditor.md +119 -0
  40. package/skills/references/SearchForm.md +270 -0
  41. package/skills/references/SearchList.md +128 -0
  42. package/skills/references/WithModal.md +328 -0
  43. package/skills/references/WithPanel.md +307 -0
  44. package/skills/references/commonFn.md +254 -0
@@ -0,0 +1,231 @@
1
+ # EffectBrandSelect
2
+
3
+ > 品牌下拉选择器,支持分类过滤、远程搜索、多选,数据通过 `apiService.brandComboBox` 自动获取。
4
+
5
+ ## 导入
6
+
7
+ ```tsx
8
+ import { EffectBrandSelect } from '@xfe-repo/web-components'
9
+ import type { BrandValueType, BrandOptions } from '@xfe-repo/web-components'
10
+ ```
11
+
12
+ ## 基本用法
13
+
14
+ 最常见用法 — 在 Form.Item 中使用,value/onChange 由 Form 自动注入:
15
+
16
+ ```tsx
17
+ <Form.Item name="brandId" label="品牌">
18
+ <EffectBrandSelect categoryId={categoryId} />
19
+ </Form.Item>
20
+ ```
21
+
22
+ ### 独立受控模式
23
+
24
+ ```tsx
25
+ import { EffectBrandSelect } from '@xfe-repo/web-components'
26
+
27
+ function MyForm() {
28
+ const [brandId, setBrandId] = useState<number>()
29
+
30
+ return <EffectBrandSelect value={brandId} onChange={(value) => setBrandId(value as number)} />
31
+ }
32
+ ```
33
+
34
+ ## 进阶用法
35
+
36
+ ### 按分类过滤品牌
37
+
38
+ `categoryId` 变更时,组件会自动重新请求品牌列表并清空已选值。
39
+
40
+ ```tsx
41
+ const [categoryId, setCategoryId] = useState<number>()
42
+ const [brandId, setBrandId] = useState<number>()
43
+
44
+ <EffectCategoryCascade
45
+ onChange={(values, selectedList) => {
46
+ if (selectedList && isSingleTreeSelected(selectedList)) {
47
+ const lastItem = selectedList[selectedList.length - 1]
48
+ setCategoryId(Number(lastItem.value))
49
+ }
50
+ }}
51
+ />
52
+ <EffectBrandSelect
53
+ categoryId={categoryId}
54
+ value={brandId}
55
+ onChange={(value) => setBrandId(value as number)}
56
+ />
57
+ ```
58
+
59
+ ### 多选模式
60
+
61
+ ```tsx
62
+ const [brandIds, setBrandIds] = useState<number[]>()
63
+
64
+ <EffectBrandSelect
65
+ mode="multiple"
66
+ value={brandIds}
67
+ onChange={(value, labels) => {
68
+ setBrandIds(value as number[])
69
+ // labels 多选时以 '>>>' 连接,如 "Nike>>>Adidas"
70
+ console.log('选中品牌名称:', labels)
71
+ }}
72
+ />
73
+ ```
74
+
75
+ ### 显示"其它"品牌
76
+
77
+ 默认隐藏"其它"品牌选项,需要时显式开启:
78
+
79
+ ```tsx
80
+ <EffectBrandSelect showOtherBrand />
81
+ ```
82
+
83
+ ### onChange 同步品牌名称
84
+
85
+ onChange 第二参数携带品牌名称,需手动同步到隐藏字段:
86
+
87
+ ```tsx
88
+ <Form.Item name="brandId" label="品牌">
89
+ <EffectBrandSelect
90
+ categoryId={categoryId}
91
+ key={categoryId}
92
+ onChange={(brandId, brandName) => {
93
+ form.setFieldValue('brandName', brandName)
94
+ }}
95
+ />
96
+ </Form.Item>
97
+ <Form.Item name="brandName" hidden><Input /></Form.Item>
98
+ ```
99
+
100
+ ## Props
101
+
102
+ ```typescript
103
+ type Props = {
104
+ className?: string
105
+ onChange?: (value?: BrandValueType, label?: string) => void
106
+ value?: BrandValueType
107
+ defaultValue?: BrandValueType
108
+ disabled?: boolean
109
+ categoryId?: number
110
+ mode?: 'multiple' | 'tags'
111
+ sysBizTypeId?: InQueryBrandComboBox['sysBizTypeId']
112
+ /** 是否显示其他品牌,默认不露出 */
113
+ showOtherBrand?: boolean
114
+ style?: React.CSSProperties
115
+ }
116
+ ```
117
+
118
+ | 属性 | 类型 | 必填 | 默认值 | 说明 |
119
+ | -------------- | -------------------------- | ---- | ------- | ----------------------------------------------- |
120
+ | value | `BrandValueType` | 否 | - | 受控值 |
121
+ | defaultValue | `BrandValueType` | 否 | - | 默认值 |
122
+ | onChange | `(value?, label?) => void` | 否 | - | 值变化回调,多选时 label 以 `>>>` 连接 |
123
+ | categoryId | `number` | 否 | - | 分类 ID,变更时自动重新加载品牌列表并清空选中值 |
124
+ | mode | `'multiple' \| 'tags'` | 否 | - | 多选/标签模式 |
125
+ | sysBizTypeId | `string` | 否 | - | 业务品牌类型 ID |
126
+ | showOtherBrand | `boolean` | 否 | `false` | 是否显示"其它"品牌选项(value=1310) |
127
+ | disabled | `boolean` | 否 | - | 禁用状态 |
128
+ | className | `string` | 否 | - | 自定义样式类名 |
129
+ | style | `React.CSSProperties` | 否 | - | 自定义行内样式 |
130
+
131
+ ## 导出类型
132
+
133
+ ```typescript
134
+ // 品牌值类型:单选为 number,多选为 number[]
135
+ export type BrandValueType = number | number[] | string | undefined
136
+
137
+ // 品牌选项列表
138
+ export type BrandOptions = { label: string; value: number }[]
139
+ ```
140
+
141
+ ## API 数据源
142
+
143
+ - **内部 Hook**:`useBrandOptions`(使用 SWR 缓存)
144
+ - **内部接口**:`apiService.brandComboBox`
145
+ - **请求参数**:`{ categoryId, sysBizTypeId, brandName, brandId, page: 1, pageSize: 500 }`
146
+ - **可覆盖**:通过 `<ConfigProvider apiService={{ brandComboBox: customFn }}>` 替换默认实现
147
+
148
+ ## 依赖 Props
149
+
150
+ | 依赖属性 | 类型 | 来源 | 说明 |
151
+ | ------------ | -------- | ---------------------------- | -------------------------------------- |
152
+ | categoryId | `number` | EffectCategoryCascade 的输出 | 按分类过滤品牌列表,变更时清空已选品牌 |
153
+ | sysBizTypeId | `string` | 业务参数 | 业务品牌类型过滤 |
154
+
155
+ ### 联动示例
156
+
157
+ ```tsx
158
+ import { useState } from 'react'
159
+ import { EffectCategoryCascade, EffectBrandSelect, isSingleTreeSelected } from '@xfe-repo/web-components'
160
+ import type { TreeValueType, TreeSelectList, BrandValueType } from '@xfe-repo/web-components'
161
+
162
+ function CategoryBrandFilter() {
163
+ const [categoryId, setCategoryId] = useState<number>()
164
+ const [brandId, setBrandId] = useState<number>()
165
+
166
+ const handleCategoryChange = (values?: TreeValueType, selectedList?: TreeSelectList) => {
167
+ if (selectedList && isSingleTreeSelected(selectedList)) {
168
+ const lastItem = selectedList[selectedList.length - 1]
169
+ setCategoryId(Number(lastItem.value))
170
+ } else {
171
+ setCategoryId(undefined)
172
+ }
173
+ }
174
+
175
+ return (
176
+ <div>
177
+ <EffectCategoryCascade onChange={handleCategoryChange} />
178
+ <EffectBrandSelect categoryId={categoryId} value={brandId} onChange={(value) => setBrandId(value as number)} />
179
+ </div>
180
+ )
181
+ }
182
+ ```
183
+
184
+ ## 使用提示
185
+
186
+ > 💡 不传 `categoryId` 时,组件加载全部品牌(不按分类过滤)。适用于跨分类的品牌搜索场景。
187
+
188
+ ## 常见陷阱
189
+
190
+ ### 分类切换后强制刷新
191
+
192
+ 当 `categoryId` 变化时,需要通过 `key` 强制重新挂载组件以刷新品牌列表:
193
+
194
+ ```tsx
195
+ <Form.Item name="brandId" label="品牌">
196
+ <EffectBrandSelect categoryId={categoryId} key={categoryId} />
197
+ </Form.Item>
198
+ ```
199
+
200
+ > ⚠️ 不设置 `key` 时,categoryId 变化后品牌列表可能残留旧分类的数据。
201
+
202
+ - ❌ `categoryId` 变更后仍然期望保留之前选中的品牌值:
203
+ ```tsx
204
+ // categoryId 变更时组件内部会自动清空 value 并触发 onChange()
205
+ ```
206
+ - ✅ 在 `onChange` 回调中处理清空逻辑:
207
+
208
+ ```tsx
209
+ <EffectBrandSelect
210
+ categoryId={categoryId}
211
+ onChange={(value) => {
212
+ // value 可能为 undefined(categoryId 变更导致的自动清空)
213
+ setBrandId(value as number | undefined)
214
+ }}
215
+ />
216
+ ```
217
+
218
+ - ❌ 用字符串作为 value 传入(虽然类型允许 string,但 options 的 value 是 number)
219
+ - ✅ 使用 number 类型的 value
220
+
221
+ - ❌ 在多选模式下解析 label 时忽略分隔符
222
+ - ✅ 多选模式的 label 以 `>>>` 连接多个品牌名,使用 `label.split('>>>')` 解析
223
+
224
+ ## 相关组件
225
+
226
+ | 场景 | 组件 | 说明 |
227
+ | -------- | ----------------------- | -------------------------------------- |
228
+ | 级联上游 | `EffectCategoryCascade` | 提供 categoryId 过滤品牌列表 |
229
+ | 级联下游 | `EffectSeriesSelect` | 使用品牌 ID 过滤系列列表 |
230
+ | 聚合使用 | `EffectScopeSelect` | 内部已集成本组件,无需单独使用 |
231
+ | API 配置 | `ConfigProvider` | 提供 apiService.brandComboBox 接口实现 |
@@ -0,0 +1,143 @@
1
+ # EffectBrandTransfer
2
+
3
+ > 品牌穿梭框,基于 Ant Design Transfer 实现品牌多选,支持分类过滤和搜索,数据复用 `useBrandOptions` Hook 获取。
4
+
5
+ ## 导入
6
+
7
+ ```tsx
8
+ import { EffectBrandTransfer } from '@xfe-repo/web-components'
9
+ import type { EffectBrandTransferProps, TransferValueType, TransferPropValueType } from '@xfe-repo/web-components'
10
+ ```
11
+
12
+ ## 基本用法
13
+
14
+ ```tsx
15
+ import { EffectBrandTransfer } from '@xfe-repo/web-components'
16
+
17
+ function MyForm() {
18
+ const [brandIds, setBrandIds] = useState<number[]>()
19
+
20
+ return <EffectBrandTransfer value={brandIds} onChange={(value) => setBrandIds(value as number[])} />
21
+ }
22
+ ```
23
+
24
+ ## 进阶用法
25
+
26
+ ### 按分类过滤品牌
27
+
28
+ `categoryId` 变更时,组件会自动重新请求品牌列表并清空已选值。
29
+
30
+ ```tsx
31
+ const [categoryId, setCategoryId] = useState<number>()
32
+ const [brandIds, setBrandIds] = useState<number[]>()
33
+
34
+ <EffectCategoryCascade
35
+ onChange={(values, selectedList) => {
36
+ if (selectedList && isSingleTreeSelected(selectedList)) {
37
+ const lastItem = selectedList[selectedList.length - 1]
38
+ setCategoryId(Number(lastItem.value))
39
+ }
40
+ }}
41
+ />
42
+ <EffectBrandTransfer
43
+ categoryId={categoryId}
44
+ value={brandIds}
45
+ onChange={(value, name) => {
46
+ setBrandIds(value as number[])
47
+ // name 多选时以 '>>>' 连接,如 "Nike>>>Adidas"
48
+ console.log('选中品牌名称:', name)
49
+ }}
50
+ />
51
+ ```
52
+
53
+ ### 显示"其它"品牌
54
+
55
+ ```tsx
56
+ <EffectBrandTransfer showOtherBrand />
57
+ ```
58
+
59
+ ## Props
60
+
61
+ ```typescript
62
+ export type EffectBrandTransferProps = {
63
+ className?: string
64
+ onChange?: (value?: BrandValueType, name?: string) => void
65
+ value?: TransferPropValueType
66
+ defaultValue?: TransferPropValueType
67
+ disabled?: boolean
68
+ categoryId?: number
69
+ sysBizTypeId?: InQueryBrandComboBox['sysBizTypeId']
70
+ /** 是否显示其他品牌,默认不露出 */
71
+ showOtherBrand?: boolean
72
+ }
73
+ ```
74
+
75
+ | 属性 | 类型 | 必填 | 默认值 | 说明 |
76
+ | -------------- | ------------------------- | ---- | ------- | ---------------------------------------- |
77
+ | value | `number[] \| undefined` | 否 | - | 受控值,已选中的品牌 ID 数组 |
78
+ | defaultValue | `number[] \| undefined` | 否 | - | 默认值 |
79
+ | onChange | `(value?, name?) => void` | 否 | - | 值变化回调,name 以 `>>>` 连接多个品牌名 |
80
+ | categoryId | `number` | 否 | - | 分类 ID,变更时自动重新加载并清空选中值 |
81
+ | sysBizTypeId | `string` | 否 | - | 业务品牌类型 ID |
82
+ | showOtherBrand | `boolean` | 否 | `false` | 是否显示"其它"品牌选项(key=1310) |
83
+ | disabled | `boolean` | 否 | - | 禁用状态 |
84
+ | className | `string` | 否 | - | 自定义样式类名 |
85
+
86
+ ## 导出类型
87
+
88
+ ```typescript
89
+ // 穿梭框内部使用的值类型(string 数组)
90
+ export type TransferValueType = string[]
91
+
92
+ // Props 中 value/defaultValue 的类型
93
+ export type TransferPropValueType = number[] | undefined
94
+
95
+ // Props 类型
96
+ export type EffectBrandTransferProps = { ... }
97
+ ```
98
+
99
+ ## API 数据源
100
+
101
+ - **内部 Hook**:`useBrandOptions`(复用 EffectBrandSelect 的 Hook,使用 SWR 缓存)
102
+ - **内部接口**:`apiService.brandComboBox`
103
+ - **请求参数**:`{ categoryId, sysBizTypeId, brandName, brandId, pageSize: 500 }`
104
+ - **可覆盖**:通过 `<ConfigProvider apiService={{ brandComboBox: customFn }}>` 替换默认实现
105
+
106
+ ## 依赖 Props
107
+
108
+ | 依赖属性 | 类型 | 来源 | 说明 |
109
+ | ------------ | -------- | ---------------------------- | -------------------------------------- |
110
+ | categoryId | `number` | EffectCategoryCascade 的输出 | 按分类过滤品牌列表,变更时清空已选品牌 |
111
+ | sysBizTypeId | `string` | 业务参数 | 业务品牌类型过滤 |
112
+
113
+ ## 常见陷阱
114
+
115
+ - ❌ `categoryId` 变更后仍然期望保留已穿梭的品牌:
116
+ ```tsx
117
+ // categoryId 或 sysBizTypeId 变更时,组件内部会自动清空 targetKeys 并触发 onChange()
118
+ ```
119
+ - ✅ 在 `onChange` 回调中处理清空逻辑:
120
+
121
+ ```tsx
122
+ <EffectBrandTransfer
123
+ categoryId={categoryId}
124
+ onChange={(value) => {
125
+ // value 可能为 undefined(依赖属性变更导致的自动清空)
126
+ setBrandIds(value as number[] | undefined)
127
+ }}
128
+ />
129
+ ```
130
+
131
+ - ❌ 将 `value` 传入字符串数组
132
+ - ✅ 使用 `number[]` 类型的 value,组件内部会自动转换为 string 用于 Transfer
133
+
134
+ - ❌ 在 `onChange` 中直接使用 name 而不拆分
135
+ - ✅ 多选时 name 以 `>>>` 连接,使用 `name.split('>>>')` 解析
136
+
137
+ ## 相关组件
138
+
139
+ | 场景 | 组件 | 说明 |
140
+ | ------------ | ----------------------- | --------------------------------------------------- |
141
+ | 级联上游 | `EffectCategoryCascade` | 提供 categoryId 过滤品牌列表 |
142
+ | 下拉选择替代 | `EffectBrandSelect` | 品牌下拉选择器(单选/多选),共享 `useBrandOptions` |
143
+ | API 配置 | `ConfigProvider` | 提供 `apiService.brandComboBox` 接口实现 |
@@ -0,0 +1,228 @@
1
+ # EffectCategoryCascade
2
+
3
+ > 分类级联选择器,基于 antd Cascader 封装,是级联链(分类→品牌→系列→SPU→SKU)的起点,数据通过 `apiService.categoryListToTree` 自动获取。
4
+
5
+ ## 导入
6
+
7
+ ```tsx
8
+ import { EffectCategoryCascade, isSingleTreeSelected } from '@xfe-repo/web-components'
9
+ import type {
10
+ TreeSelectItem,
11
+ TreeSelectList,
12
+ TreeSingleValueType,
13
+ TreeValueType,
14
+ CustomTreeItem,
15
+ CustomTreeOptions,
16
+ } from '@xfe-repo/web-components'
17
+ ```
18
+
19
+ ## 基本用法
20
+
21
+ 最常见用法 — 在 Form.Item 中使用,value/onChange 由 Form 自动注入:
22
+
23
+ ```tsx
24
+ <Form.Item name="categoryIds" label="分类">
25
+ <EffectCategoryCascade changeOnSelect />
26
+ </Form.Item>
27
+ ```
28
+
29
+ ### 独立受控模式
30
+
31
+ ```tsx
32
+ import { EffectCategoryCascade } from '@xfe-repo/web-components'
33
+ import type { TreeValueType, TreeSelectList } from '@xfe-repo/web-components'
34
+
35
+ function MyForm() {
36
+ const handleChange = (value?: TreeValueType, selectedList?: TreeSelectList) => {
37
+ console.log('选中的分类路径值:', value) // 如 [1, 11, 111]
38
+ console.log('选中的分类详情:', selectedList) // 如 [{ value: 1, label: '手表', level: 0 }, ...]
39
+ }
40
+
41
+ return <EffectCategoryCascade onChange={handleChange} />
42
+ }
43
+ ```
44
+
45
+ ## 进阶用法
46
+
47
+ ### 受控模式
48
+
49
+ ```tsx
50
+ const [categoryValue, setCategoryValue] = useState<TreeValueType>()
51
+
52
+ <EffectCategoryCascade
53
+ value={categoryValue}
54
+ onChange={(value, selectedList) => {
55
+ setCategoryValue(value)
56
+ }}
57
+ />
58
+ ```
59
+
60
+ ### 多选模式
61
+
62
+ ```tsx
63
+ <EffectCategoryCascade
64
+ multiple
65
+ onChange={(values, selectedList) => {
66
+ // values: [[1, 11, 111], [2, 22, 222]]
67
+ // selectedList: [[{ value: 1, ... }, ...], [{ value: 2, ... }, ...]]
68
+ console.log('多选值:', values)
69
+ }}
70
+ />
71
+ ```
72
+
73
+ ### 选择即触发(不需要选到叶子节点)
74
+
75
+ ```tsx
76
+ <EffectCategoryCascade
77
+ changeOnSelect
78
+ onChange={(value, selectedList) => {
79
+ // 选择任意层级都会触发
80
+ console.log('当前选择:', value)
81
+ }}
82
+ />
83
+ ```
84
+
85
+ ### 获取分类原始数据
86
+
87
+ ```tsx
88
+ import { TreeCategory } from '@api/product'
89
+
90
+ ;<EffectCategoryCascade
91
+ onGetData={(dataList?: TreeCategory[]) => {
92
+ // 分类树数据加载完成后回调
93
+ console.log('分类树数据:', dataList)
94
+ }}
95
+ />
96
+ ```
97
+
98
+ ### 编辑态禁用
99
+
100
+ 编辑页面中分类不可修改时:
101
+
102
+ ```tsx
103
+ <Form.Item name="categoryIds" label="分类">
104
+ <EffectCategoryCascade changeOnSelect disabled={isEdit} />
105
+ </Form.Item>
106
+ ```
107
+
108
+ ## Props
109
+
110
+ ```typescript
111
+ type Props = Omit<CascaderProps<any>, 'onChange' | 'defaultValue' | 'value'> & {
112
+ className?: string
113
+ onChange?: (value?: TreeValueType, selected?: TreeSelectList) => void
114
+ defaultValue?: TreeValueType
115
+ value?: TreeValueType
116
+ sysBizTypeId?: string
117
+ onGetData?: (dataList?: TreeCategory[]) => void
118
+ }
119
+ ```
120
+
121
+ | 属性 | 类型 | 必填 | 默认值 | 说明 |
122
+ | -------------- | ----------------------------- | ---- | ------ | --------------------------------------------------------------------------------- |
123
+ | value | `TreeValueType` | 否 | - | 受控值,单选为 `Array<string \| number>`,多选为 `Array<Array<string \| number>>` |
124
+ | defaultValue | `TreeValueType` | 否 | - | 默认值 |
125
+ | onChange | `(value?, selected?) => void` | 否 | - | 值变化回调,第二个参数包含每级节点的详细信息 |
126
+ | sysBizTypeId | `string` | 否 | - | 业务分类类型 ID,变更时重新加载分类树并清空选中值 |
127
+ | onGetData | `(dataList?) => void` | 否 | - | 分类树数据加载完成回调 |
128
+ | multiple | `boolean` | 否 | - | 继承自 antd Cascader,开启多选模式 |
129
+ | changeOnSelect | `boolean` | 否 | - | 继承自 antd Cascader,选择任意层级即触发 onChange |
130
+ | className | `string` | 否 | - | 自定义样式类名 |
131
+
132
+ > **注意**:继承了 antd `CascaderProps` 的其他属性(如 `placeholder`、`disabled`、`showSearch` 等),但 `onChange`、`defaultValue`、`value` 被自定义实现覆盖。
133
+
134
+ ## 导出类型
135
+
136
+ ```typescript
137
+ // 单个选中项
138
+ export type TreeSelectItem = {
139
+ value: string | number
140
+ label: string
141
+ level: string | number
142
+ isLastLevel?: boolean
143
+ }
144
+
145
+ // 选中项列表:单选为 TreeSelectItem[],多选为 TreeSelectItem[][]
146
+ export type TreeSelectList = TreeSelectItem[] | TreeSelectItem[][]
147
+
148
+ // 单选值类型:表示一条分类路径
149
+ export type TreeSingleValueType = Array<string | number>
150
+
151
+ // 值类型:单选为 TreeSingleValueType,多选为 TreeSingleValueType[]
152
+ export type TreeValueType = TreeSingleValueType | TreeSingleValueType[]
153
+
154
+ // 分类树节点(扩展自 TreeCategory)
155
+ export type CustomTreeItem = TreeCategory & { index?: number }
156
+ export type CustomTreeOptions = CustomTreeItem[] | CustomTreeItem[][]
157
+ ```
158
+
159
+ ### 辅助函数
160
+
161
+ ```typescript
162
+ // 判断 selectedList 是否为单选模式的结果
163
+ export const isSingleTreeSelected = (selectList?: TreeSelectList): selectList is TreeSelectItem[]
164
+
165
+ // 将选项转换为 selectedList
166
+ export const transformOptionToSelectedList = (selectedOptions?: CustomTreeOptions, multiple?: boolean) => TreeSelectList
167
+
168
+ // 将 selectedList 转换为 values
169
+ export const transformSelectListToValues = (selectList?: TreeSelectList, multiple?: boolean) => TreeValueType
170
+
171
+ // 将 values 转换为 selectedList(注意:此时 label 为 value 的字符串形式)
172
+ export const transformValuesToSelectList = (values?: TreeValueType, multiple?: boolean) => TreeSelectList
173
+ ```
174
+
175
+ ## API 数据源
176
+
177
+ - **内部 Hook**:`useCategoryTree`(使用 SWR 缓存)
178
+ - **内部接口**:`apiService.categoryListToTree`
179
+ - **请求参数**:`{ sysBizTypeId }`
180
+ - **可覆盖**:通过 `<ConfigProvider apiService={{ categoryListToTree: customFn }}>` 替换默认实现
181
+
182
+ ## 使用提示
183
+
184
+ > 💡 约 70% 业务场景传 `changeOnSelect`,允许用户选择任意级别分类。建议默认使用。
185
+
186
+ > 💡 常见宽度:`style={{ width: 160 }}` 或 `style={{ width: 200 }}`。
187
+
188
+ ## 常见陷阱
189
+
190
+ - ❌ 混淆单选和多选时 `value`、`onChange` 的类型:
191
+ ```tsx
192
+ // 单选:value = [1, 11, 111],selectedList = [{ value: 1, ... }, ...]
193
+ // 多选:value = [[1, 11], [2, 22]],selectedList = [[...], [...]]
194
+ ```
195
+ - ✅ 使用 `isSingleTreeSelected` 安全地判断 selectedList 模式:
196
+
197
+ ```tsx
198
+ onChange={(values, selectedList) => {
199
+ if (selectedList && isSingleTreeSelected(selectedList)) {
200
+ // 单选模式
201
+ const lastCategoryId = selectedList[selectedList.length - 1]?.value
202
+ }
203
+ }}
204
+ ```
205
+
206
+ - ❌ 从 `onChange` 的 `value` 中直接取最后一级分类 ID 来传给 EffectBrandSelect:
207
+ ```tsx
208
+ // value 的元素类型是 string | number,可能不是纯 number
209
+ ```
210
+ - ✅ 从 `selectedList` 中取值并转换:
211
+
212
+ ```tsx
213
+ const lastItem = selectedList[selectedList.length - 1]
214
+ const categoryId = Number(lastItem.value)
215
+ ```
216
+
217
+ - ❌ `sysBizTypeId` 变更后期望保留已选分类
218
+ - ✅ `sysBizTypeId` 变更时组件会自动清空选中值并触发 `onChange()`
219
+
220
+ - ⚠️ 编辑回填时使用 `setTimeout(() => form.setFieldsValue(...))` 可能产生时序问题,建议使用 `useEffect` + `form.setFieldsValue` 在数据加载完成后统一设置。
221
+
222
+ ## 相关组件
223
+
224
+ | 场景 | 组件 | 说明 |
225
+ | -------- | ------------------- | ------------------------------------------- |
226
+ | 级联下游 | `EffectBrandSelect` | 使用分类末级 ID 作为 categoryId 过滤品牌 |
227
+ | 聚合使用 | `EffectScopeSelect` | 内部已集成本组件,无需单独使用 |
228
+ | API 配置 | `ConfigProvider` | 提供 apiService.categoryListToTree 接口实现 |