@xfe-repo/web-components 1.6.2 → 1.7.1

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 +6 -5
  2. package/skills/xfe-web-components/GUIDE.md +78 -0
  3. package/skills/xfe-web-components/SKILL.md +78 -0
  4. package/skills/xfe-web-components/TEMPLATE.md +170 -0
  5. package/skills/xfe-web-components/references/ApiService.md +136 -0
  6. package/skills/xfe-web-components/references/CInput.md +196 -0
  7. package/skills/xfe-web-components/references/CTable.md +274 -0
  8. package/skills/xfe-web-components/references/CVideo.md +94 -0
  9. package/skills/xfe-web-components/references/Clock.md +53 -0
  10. package/skills/xfe-web-components/references/ConfigProvider.md +198 -0
  11. package/skills/xfe-web-components/references/Countdown.md +109 -0
  12. package/skills/xfe-web-components/references/Counter.md +75 -0
  13. package/skills/xfe-web-components/references/Currency.md +112 -0
  14. package/skills/xfe-web-components/references/EffectAddressCascade.md +110 -0
  15. package/skills/xfe-web-components/references/EffectBrandSelect.md +231 -0
  16. package/skills/xfe-web-components/references/EffectBrandTransfer.md +143 -0
  17. package/skills/xfe-web-components/references/EffectCategoryCascade.md +227 -0
  18. package/skills/xfe-web-components/references/EffectFileUpload.md +348 -0
  19. package/skills/xfe-web-components/references/EffectLabelSelect.md +222 -0
  20. package/skills/xfe-web-components/references/EffectMerchantSelect.md +183 -0
  21. package/skills/xfe-web-components/references/EffectReservoirSelect.md +178 -0
  22. package/skills/xfe-web-components/references/EffectScopeSelect.md +220 -0
  23. package/skills/xfe-web-components/references/EffectSeriesSelect.md +204 -0
  24. package/skills/xfe-web-components/references/EffectSeriesSelectV2.md +154 -0
  25. package/skills/xfe-web-components/references/EffectSkuRecognize.md +149 -0
  26. package/skills/xfe-web-components/references/EffectSkuSelect.md +251 -0
  27. package/skills/xfe-web-components/references/EffectSkuTable.md +184 -0
  28. package/skills/xfe-web-components/references/EffectSpuSelect.md +189 -0
  29. package/skills/xfe-web-components/references/EffectStaffSelect.md +150 -0
  30. package/skills/xfe-web-components/references/EffectWarehouseSelect.md +183 -0
  31. package/skills/xfe-web-components/references/EffectWithFilePanel.md +205 -0
  32. package/skills/xfe-web-components/references/FileUpload.md +126 -0
  33. package/skills/xfe-web-components/references/Iconfont.md +31 -0
  34. package/skills/xfe-web-components/references/Loading.md +68 -0
  35. package/skills/xfe-web-components/references/MultiWindow.md +145 -0
  36. package/skills/xfe-web-components/references/OSSImage.md +230 -0
  37. package/skills/xfe-web-components/references/PrivacyField.md +90 -0
  38. package/skills/xfe-web-components/references/QRCode.md +148 -0
  39. package/skills/xfe-web-components/references/RichTextEditor.md +119 -0
  40. package/skills/xfe-web-components/references/SearchForm.md +270 -0
  41. package/skills/xfe-web-components/references/SearchList.md +128 -0
  42. package/skills/xfe-web-components/references/WithModal.md +328 -0
  43. package/skills/xfe-web-components/references/WithPanel.md +307 -0
  44. package/skills/xfe-web-components/references/commonFn.md +254 -0
@@ -0,0 +1,68 @@
1
+ # Loading
2
+
3
+ > 全屏加载状态组件,基于 antd Spin 封装,`loading` 为 false 时不渲染任何内容。
4
+
5
+ ## 导入
6
+
7
+ ```tsx
8
+ import { Loading } from '@xfe-repo/web-components'
9
+ ```
10
+
11
+ ## 基本用法
12
+
13
+ ```tsx
14
+ import { Loading } from '@xfe-repo/web-components'
15
+
16
+ function MyPage() {
17
+ const [loading, setLoading] = useState(true)
18
+
19
+ return (
20
+ <div>
21
+ <Loading loading={loading} />
22
+ <Content />
23
+ </div>
24
+ )
25
+ }
26
+ ```
27
+
28
+ ### 条件式初始化加载(最常用模式)
29
+
30
+ 在数据未加载完成时展示 Loading,加载完成后渲染正常内容:
31
+
32
+ ```tsx
33
+ function OrderDetail({ orderId }) {
34
+ const data = useSelector(selectOrderDetail)
35
+
36
+ // 数据未加载完成时,展示 Loading
37
+ if (!data) return <Loading loading={true} />
38
+
39
+ return (
40
+ <div>
41
+ <h2>{data.orderNo}</h2>
42
+ {/* 正常内容 */}
43
+ </div>
44
+ )
45
+ }
46
+ ```
47
+
48
+ ### 路由懒加载 fallback
49
+
50
+ ```tsx
51
+ const OrderPage = loadable(() => import('./pages/Order'), {
52
+ fallback: <Loading loading={true} />,
53
+ })
54
+ ```
55
+
56
+ ## Props
57
+
58
+ ```typescript
59
+ type Props = {
60
+ loading: boolean
61
+ }
62
+ ```
63
+
64
+ | 属性 | 类型 | 必填 | 默认值 | 说明 |
65
+ | ------- | --------- | ---- | ------ | -------------------------------------- |
66
+ | loading | `boolean` | 是 | - | 是否显示加载状态,为 false 时返回 null |
67
+
68
+ > 💡 内部 Spin 设置了 `delay={200}`,200ms 内完成的加载不会显示 loading 动画,避免闪烁。此值不可通过 props 自定义。
@@ -0,0 +1,145 @@
1
+ # MultiWindow
2
+
3
+ > 多窗口 Tab 容器,基于 KeepAlive 实现页面缓存,支持标签页拖拽排序、搜索、刷新和关闭操作。
4
+
5
+ ## 导入
6
+
7
+ ```tsx
8
+ import { MultiWindow, MultiWindowContents, useMultiWindowContentsRef } from '@xfe-repo/web-components'
9
+ import type { MultiWindowProps, PagePathType, MultiWindowContentsProps, MultiWindowContentsRefType } from '@xfe-repo/web-components'
10
+ ```
11
+
12
+ ## 基本用法
13
+
14
+ ```tsx
15
+ import { MultiWindow, MultiWindowContents, useMultiWindowContentsRef } from '@xfe-repo/web-components'
16
+
17
+ function Layout() {
18
+ const contentRef = useMultiWindowContentsRef()
19
+ const routerPath = window.location.pathname + window.location.search
20
+
21
+ return (
22
+ <div>
23
+ <MultiWindow routerPath={routerPath} contentRef={contentRef} appName="我的应用" />
24
+ <MultiWindowContents routerPath={routerPath} contentRef={contentRef}>
25
+ <RouterOutlet />
26
+ </MultiWindowContents>
27
+ </div>
28
+ )
29
+ }
30
+ ```
31
+
32
+ ## 进阶用法
33
+
34
+ ### 自定义主题和黑名单
35
+
36
+ 通过 `collectionBlackList` 禁止某些路径启用搜索集合(默认对 detail 页面自动启用),通过 `theme` 传入 antd 主题配置。
37
+
38
+ ```tsx
39
+ <MultiWindow
40
+ routerPath={routerPath}
41
+ contentRef={contentRef}
42
+ appName="ERP"
43
+ theme={{ token: { colorPrimary: '#1890ff' } }}
44
+ collectionBlackList={['/order/detail', '/refund/detail']}
45
+ />
46
+ ```
47
+
48
+ ## Props
49
+
50
+ ### MultiWindowProps
51
+
52
+ ```typescript
53
+ export type MultiWindowProps = {
54
+ routerPath: string
55
+ collectionBlackList?: string[]
56
+ contentRef?: MultiWindowContentsRefType
57
+ appName?: string
58
+ theme?: ThemeConfig
59
+ className?: string
60
+ }
61
+ ```
62
+
63
+ | 属性 | 类型 | 必填 | 默认值 | 说明 |
64
+ | ------------------- | ---------------------------- | ---- | ------ | -------------------------------------------- |
65
+ | routerPath | `string` | 是 | - | 当前路由路径(含 search) |
66
+ | collectionBlackList | `string[]` | 否 | `[]` | 不启用搜索集合的路径黑名单 |
67
+ | contentRef | `MultiWindowContentsRefType` | 否 | - | 内容页的 ref,用于控制缓存销毁 |
68
+ | appName | `string` | 否 | - | 应用名称,用于从 document.title 中提取标签名 |
69
+ | theme | `ThemeConfig` | 否 | - | antd 主题配置 |
70
+ | className | `string` | 否 | - | 自定义 className |
71
+
72
+ ### MultiWindowContentsProps
73
+
74
+ ```typescript
75
+ export type MultiWindowContentsProps = {
76
+ routerPath: string
77
+ children: ReactNode
78
+ contentRef?: MultiWindowContentsRefType
79
+ }
80
+ ```
81
+
82
+ | 属性 | 类型 | 必填 | 默认值 | 说明 |
83
+ | ---------- | ---------------------------- | ---- | ------ | ------------------------------------- |
84
+ | routerPath | `string` | 是 | - | 当前路由路径,用于提取 activeCacheKey |
85
+ | children | `ReactNode` | 是 | - | 路由页面内容 |
86
+ | contentRef | `MultiWindowContentsRefType` | 否 | - | KeepAlive ref,与 MultiWindow 共享 |
87
+
88
+ ## 导出类型
89
+
90
+ ### PagePathType
91
+
92
+ ```typescript
93
+ export type PagePathType = {
94
+ label: string // 页面标签名
95
+ key: string // 页面路径(不含 search),用作唯一标识
96
+ fullPath: string // 页面完整路径(含 search)
97
+ closable?: boolean // 是否可关闭
98
+ disabled?: boolean // 是否禁用
99
+ searchCollection?: string[] // search 集合,用于存储同一页面不同搜索条件
100
+ }
101
+ ```
102
+
103
+ ### MultiWindowContentsRefType
104
+
105
+ ```typescript
106
+ export type MultiWindowContentsRefType = RefObject<KeepAliveRef | undefined>
107
+ ```
108
+
109
+ ## 子组件
110
+
111
+ | 子组件 | 说明 |
112
+ | --------------------- | ---------------------------------------------------------------------------------------------- |
113
+ | `MultiWindowContents` | 内容缓存容器,内部使用 `keepalive-for-react` 实现页面缓存,最多缓存 10 个页面,最长存活 1 小时 |
114
+
115
+ ## Ref 方法
116
+
117
+ 通过 `useMultiWindowContentsRef()` 获取的 ref 提供以下方法(来自 `KeepAliveRef`):
118
+
119
+ | 方法 | 说明 |
120
+ | ------------------- | -------------------------- |
121
+ | `destroy(key)` | 销毁指定页面的缓存 |
122
+ | `destroyOther(key)` | 销毁除指定页面外的所有缓存 |
123
+
124
+ ## 常见陷阱
125
+
126
+ - ❌ 忘记将 `contentRef` 同时传给 `MultiWindow` 和 `MultiWindowContents`:
127
+ ```tsx
128
+ <MultiWindow routerPath={path} contentRef={contentRef} />
129
+ <MultiWindowContents routerPath={path}> {/* 缺少 contentRef,关闭标签时无法清除缓存 */}
130
+ ```
131
+ - ✅ 两个组件共享同一个 `contentRef`:
132
+
133
+ ```tsx
134
+ const contentRef = useMultiWindowContentsRef()
135
+
136
+ <MultiWindow routerPath={path} contentRef={contentRef} />
137
+ <MultiWindowContents routerPath={path} contentRef={contentRef}>
138
+ ```
139
+
140
+ ## 注意事项
141
+
142
+ - 标签页最多保留 **10 个**,超出后最早的标签会被自动移除
143
+ - 标签状态通过 **sessionStorage** 持久化,刷新页面后自动恢复
144
+
145
+ > 💡 MultiWindow 可独立使用(不依赖其他组件),用于管理多窗口/多标签页场景。
@@ -0,0 +1,230 @@
1
+ # OSSImage
2
+
3
+ > OSS 图片组件,基于 antd Image 封装,自动对阿里云 OSS 图片进行尺寸裁剪,支持预览原图和分组预览。
4
+
5
+ ## 导入
6
+
7
+ ```tsx
8
+ import { OSSImage } from '@xfe-repo/web-components'
9
+ import type { OSSImageProps } from '@xfe-repo/web-components'
10
+ ```
11
+
12
+ ## 基本用法
13
+
14
+ ```tsx
15
+ import { OSSImage } from '@xfe-repo/web-components'
16
+
17
+ function ProductImage() {
18
+ return <OSSImage src="https://imgs.eshetang.com/product/example.jpg" width={100} height={100} />
19
+ }
20
+ ```
21
+
22
+ ### Table 列渲染
23
+
24
+ 最高频场景 — 在表格列中渲染商品/订单图片:
25
+
26
+ ```tsx
27
+ const columns = [
28
+ {
29
+ title: '商品图片',
30
+ dataIndex: 'imageUrl',
31
+ render: (url: string) => (url ? <OSSImage width={60} height={60} src={url} /> : '-'),
32
+ align: 'center' as const,
33
+ },
34
+ ]
35
+ ```
36
+
37
+ 多图预览(配合 PreviewGroup):
38
+
39
+ ```tsx
40
+ const renderImages = (urls: string[]) => (
41
+ <OSSImage.PreviewGroup>
42
+ {urls.filter(Boolean).map((url) => (
43
+ <OSSImage key={url} width={60} height={60} src={url} preview={{ src: url }} />
44
+ ))}
45
+ </OSSImage.PreviewGroup>
46
+ )
47
+ ```
48
+
49
+ > 💡 始终对 `src` 做空值防守(三元或 `filter(Boolean)`),避免空 URL 导致的 broken image。
50
+
51
+ ### Descriptions 详情页展示
52
+
53
+ 在详情页中展示商品/订单图片:
54
+
55
+ ```tsx
56
+ <Descriptions>
57
+ <Descriptions.Item label="商品图片">
58
+ <OSSImage width={120} height={120} src={data.imageUrl} preview />
59
+ </Descriptions.Item>
60
+ </Descriptions>
61
+ ```
62
+
63
+ ## 进阶用法
64
+
65
+ ### 自定义裁剪宽度
66
+
67
+ ```tsx
68
+ <OSSImage
69
+ src="https://imgs.eshetang.com/product/example.jpg"
70
+ resizeWidth={300} // 缩略图裁剪到 300px 宽
71
+ />
72
+ ```
73
+
74
+ ### 预览原图
75
+
76
+ 默认预览图也会被裁剪到 1200px,设置 `showOriginPreview` 可预览原始尺寸。
77
+
78
+ ```tsx
79
+ <OSSImage src="https://imgs.eshetang.com/product/example.jpg" showOriginPreview />
80
+ ```
81
+
82
+ ### 清除默认 fallback
83
+
84
+ 默认在图片加载失败时显示占位图,设置 `clearDefault` 可禁用。
85
+
86
+ ```tsx
87
+ <OSSImage src={imageSrc} clearDefault />
88
+ ```
89
+
90
+ ### 图片分组预览(PreviewGroup)
91
+
92
+ ```tsx
93
+ <OSSImage.PreviewGroup>
94
+ <OSSImage src="https://imgs.eshetang.com/1.jpg" />
95
+ <OSSImage src="https://imgs.eshetang.com/2.jpg" />
96
+ <OSSImage src="https://imgs.eshetang.com/3.jpg" />
97
+ </OSSImage.PreviewGroup>
98
+ ```
99
+
100
+ ### 弹窗分组预览(ModalGroup)
101
+
102
+ 点击第一张图片即可预览所有图片,常用于列表中的缩略图。
103
+
104
+ ```tsx
105
+ <OSSImage.ModalGroup images={['https://imgs.eshetang.com/1.jpg', 'https://imgs.eshetang.com/2.jpg']} width={80} resizeWidth={300} />
106
+ ```
107
+
108
+ ### 交互式预览(countRender)
109
+
110
+ 在图片预览工具栏中嵌入业务操作按钮:
111
+
112
+ ```tsx
113
+ const previewProps = {
114
+ countRender: (current: number, total: number) => {
115
+ const currentImage = images[current - 1]
116
+ return (
117
+ <Space>
118
+ {current} / {total}
119
+ <Button type="primary" onClick={() => handleAction(currentImage)}>
120
+ 选择此图
121
+ </Button>
122
+ </Space>
123
+ ) as unknown as string // antd 类型定义限制,实际支持 ReactNode
124
+ },
125
+ }
126
+
127
+ <OSSImage.PreviewGroup preview={previewProps}>
128
+ {images.map((img) => (
129
+ <OSSImage key={img.id} width={100} height={100} src={img.url} />
130
+ ))}
131
+ </OSSImage.PreviewGroup>
132
+ ```
133
+
134
+ > ⚠️ `countRender` 的 antd 类型签名要求返回 `string`,但实际支持 `ReactNode`。需要使用 `as unknown as string` 类型断言。
135
+
136
+ ### 常用继承属性
137
+
138
+ OSSImage 支持所有 antd Image 属性,常用的有:
139
+
140
+ ```tsx
141
+ <OSSImage
142
+ src={url}
143
+ width={100}
144
+ height={100}
145
+ alt="商品图片" // 无障碍文案
146
+ style={{ borderRadius: 8 }} // 圆角
147
+ className="product-img" // 自定义样式类
148
+ preview={{ src: originalUrl }} // 预览时使用原图
149
+ fallback={defaultImg} // 加载失败时的兜底图
150
+ />
151
+ ```
152
+
153
+ ### 后端逗号分隔字符串格式
154
+
155
+ 后端常返回逗号分隔的图片 URL 字符串,需转换后渲染:
156
+
157
+ ```tsx
158
+ const renderImages = (urlStr: string) => {
159
+ const urls = urlStr?.split(',').filter(Boolean) ?? []
160
+ return (
161
+ <OSSImage.PreviewGroup>
162
+ {urls.map((url) => (
163
+ <OSSImage key={url} width={60} height={60} src={url} />
164
+ ))}
165
+ </OSSImage.PreviewGroup>
166
+ )
167
+ }
168
+ ```
169
+
170
+ ## Props
171
+
172
+ ### OSSImageProps
173
+
174
+ ```typescript
175
+ export interface OSSImageProps extends ImageProps {
176
+ resizeWidth?: number
177
+ clearDefault?: boolean
178
+ showOriginPreview?: boolean
179
+ }
180
+ ```
181
+
182
+ | 属性 | 类型 | 必填 | 默认值 | 说明 |
183
+ | ----------------- | ------------ | ---- | ------- | ----------------------------------------- |
184
+ | resizeWidth | `number` | 否 | `750` | 缩略图裁剪宽度(通过阿里云 OSS 图片处理) |
185
+ | clearDefault | `boolean` | 否 | `false` | 是否清除默认 fallback 占位图 |
186
+ | showOriginPreview | `boolean` | 否 | `false` | 预览时是否使用原始图片(裁剪到 1200px) |
187
+ | ...imageProps | `ImageProps` | 否 | - | 所有 antd Image 属性 |
188
+
189
+ ### OSSImageGroupProps(ModalGroup)
190
+
191
+ ```typescript
192
+ export interface OSSImageGroupProps extends Pick<ImageProps, 'fallback' | 'height' | 'width' | 'onError' | 'rootClassName'> {
193
+ resizeWidth?: number
194
+ images: string[]
195
+ }
196
+ ```
197
+
198
+ | 属性 | 类型 | 必填 | 默认值 | 说明 |
199
+ | ----------- | ---------- | ---- | ---------- | ------------------ |
200
+ | images | `string[]` | 是 | - | 图片 URL 数组 |
201
+ | resizeWidth | `number` | 否 | `750` | 缩略图裁剪宽度 |
202
+ | width | `number` | 否 | `100` | 缩略图宽度 |
203
+ | fallback | `string` | 否 | 默认占位图 | 加载失败时的占位图 |
204
+
205
+ ## 子组件
206
+
207
+ | 子组件 | 说明 |
208
+ | ----------------------- | --------------------------------------------------- |
209
+ | `OSSImage.PreviewGroup` | 图片分组预览,直接透传 antd 的 `Image.PreviewGroup` |
210
+ | `OSSImage.ModalGroup` | 弹窗分组预览,点击首张图片弹出预览所有图片 |
211
+
212
+ ## 常见陷阱
213
+
214
+ - ❌ 直接使用 antd `Image` 加载 OSS 大图,页面加载慢:
215
+ ```tsx
216
+ <Image src="https://imgs.eshetang.com/product/big-image.jpg" width={100} />
217
+ ```
218
+ - ✅ 使用 `OSSImage` 自动裁剪,缩略图加载 750px,预览加载 1200px:
219
+ ```tsx
220
+ <OSSImage src="https://imgs.eshetang.com/product/big-image.jpg" width={100} />
221
+ ```
222
+
223
+ > 💡 可以只设 `height` 不设 `width`(或反过来),图片将按比例自适应另一维度。
224
+
225
+ ## 相关组件
226
+
227
+ | 场景 | 组件 | 说明 |
228
+ | ------------------ | ------------------ | ----------------------------------- |
229
+ | 文件上传 | `FileUpload` | 上传组件,预览部分内部使用 OSSImage |
230
+ | 文件上传(带接口) | `EffectFileUpload` | 带 API 上传能力的文件上传 |
@@ -0,0 +1,90 @@
1
+ # PrivacyField
2
+
3
+ > 隐私脱敏字段组件,展示脱敏值并支持本地解密查看原始值,内置神策埋点。
4
+
5
+ ## 导入
6
+
7
+ ```tsx
8
+ import { PrivacyField } from '@xfe-repo/web-components'
9
+ ```
10
+
11
+ ## 基本用法
12
+
13
+ ```tsx
14
+ import { PrivacyField } from '@xfe-repo/web-components'
15
+
16
+ function UserInfo() {
17
+ return <PrivacyField value="138****1234" encryptedValue="base64_encrypted_string" />
18
+ }
19
+ ```
20
+
21
+ ## 进阶用法
22
+
23
+ ### Table 列渲染
24
+
25
+ ```tsx
26
+ const columns = [
27
+ {
28
+ title: '手机号',
29
+ dataIndex: 'phone',
30
+ render: (phone: string) => <PrivacyField value={phone} />,
31
+ },
32
+ ]
33
+ ```
34
+
35
+ ### 带埋点的解密
36
+
37
+ 每条数据在组件生命周期内仅触发一次埋点。
38
+
39
+ ```tsx
40
+ <PrivacyField
41
+ value="138****1234"
42
+ encryptedValue={encrypted}
43
+ trackEventName="view_phone_number"
44
+ trackEventParams={{ userId: 123, source: 'order_list' }}
45
+ />
46
+ ```
47
+
48
+ ### 仅展示脱敏值(无解密按钮)
49
+
50
+ ```tsx
51
+ <PrivacyField value="138****1234" />
52
+ ```
53
+
54
+ ### 隐藏解密按钮
55
+
56
+ ```tsx
57
+ <PrivacyField
58
+ value="138****1234"
59
+ encryptedValue={encrypted}
60
+ showDecryptButton={false} // 有加密值但不显示解密按钮
61
+ />
62
+ ```
63
+
64
+ ## Props
65
+
66
+ ```typescript
67
+ interface PrivacyFieldProps {
68
+ value: string
69
+ encryptedValue?: string
70
+ showDecryptButton?: boolean
71
+ trackEventName?: string
72
+ trackEventParams?: Record<string, any>
73
+ }
74
+ ```
75
+
76
+ | 属性 | 类型 | 必填 | 默认值 | 说明 |
77
+ | ----------------- | --------------------- | ---- | ------ | ---------------------------------- |
78
+ | value | `string` | 是 | - | 脱敏后的展示值(如 `138****1234`) |
79
+ | encryptedValue | `string` | 否 | - | 后端返回的加密值,用于前端解密 |
80
+ | showDecryptButton | `boolean` | 否 | `true` | 是否展示解密按钮 |
81
+ | trackEventName | `string` | 否 | - | 神策事件名称 |
82
+ | trackEventParams | `Record<string, any>` | 否 | `{}` | 神策事件参数 |
83
+
84
+ ## 常见陷阱
85
+
86
+ - ❌ 无 `encryptedValue` 或 `showDecryptButton={false}` 时,组件仅渲染一个 `<span>`,不显示眼睛图标
87
+ - ✅ 解密在前端完成(XOR + Base64),无需额外请求后端
88
+
89
+ - ❌ `encryptedValue` 变化时,解密状态会自动重置为隐藏
90
+ - ✅ 这是预期行为,防止切换数据时误显示上一条的解密值
@@ -0,0 +1,148 @@
1
+ # QRCode
2
+
3
+ > 二维码展示组件,配合 `useSseQrcode` Hook 实现 SSE 轮询扫码状态,支持过期刷新和多次扫码。
4
+
5
+ ## 导入
6
+
7
+ ```tsx
8
+ import { QRCode, useSseQrcode, QRCodeStatus } from '@xfe-repo/web-components'
9
+ import type { QRCodePropsType, UseRemoteUploadConfigType, SseEventDataType } from '@xfe-repo/web-components'
10
+ ```
11
+
12
+ ## 基本用法
13
+
14
+ ### 纯展示二维码
15
+
16
+ ```tsx
17
+ import { QRCode } from '@xfe-repo/web-components'
18
+
19
+ function SimpleQRCode() {
20
+ return <QRCode url="https://example.com/qrcode.png" />
21
+ }
22
+ ```
23
+
24
+ ### 配合 SSE 轮询
25
+
26
+ ```tsx
27
+ import { QRCode, useSseQrcode } from '@xfe-repo/web-components'
28
+
29
+ function ScanUpload() {
30
+ const [result] = useSseQrcode({
31
+ enable: true,
32
+ createQRCode: async (requestConfig) => {
33
+ const { data } = await api.createQRCode({}, requestConfig)
34
+ return data // { uuid: string, qrcode: string }
35
+ },
36
+ sseUrl: '/common/upload/remote/sse',
37
+ onSuccess: (payload) => {
38
+ console.log('扫码成功:', payload)
39
+ },
40
+ })
41
+
42
+ return (
43
+ <QRCode
44
+ url={result.qrCodeUrl || ''}
45
+ status={result.qrCodeStatus}
46
+ onRefresh={result.refreshQRCode}
47
+ refreshLoading={result.refreshLoading}
48
+ />
49
+ )
50
+ }
51
+ ```
52
+
53
+ ## 进阶用法
54
+
55
+ ### 支持多次扫码
56
+
57
+ 设置 `multiple` 后,完成状态也会显示刷新按钮,允许重新扫码。
58
+
59
+ ```tsx
60
+ <QRCode
61
+ url={result.qrCodeUrl || ''}
62
+ status={result.qrCodeStatus}
63
+ onRefresh={result.refreshQRCode}
64
+ refreshLoading={result.refreshLoading}
65
+ multiple
66
+ />
67
+ ```
68
+
69
+ ## Props
70
+
71
+ ### QRCodePropsType
72
+
73
+ ```typescript
74
+ export type QRCodePropsType = {
75
+ url: string
76
+ status?: QRCodeStatus
77
+ onRefresh?: () => void
78
+ refreshLoading?: boolean
79
+ multiple?: boolean
80
+ }
81
+ ```
82
+
83
+ | 属性 | 类型 | 必填 | 默认值 | 说明 |
84
+ | -------------- | -------------- | ---- | ------- | ---------------------------------------- |
85
+ | url | `string` | 是 | - | 二维码图片地址 |
86
+ | status | `QRCodeStatus` | 否 | - | 二维码状态 |
87
+ | onRefresh | `() => void` | 否 | - | 刷新按钮回调 |
88
+ | refreshLoading | `boolean` | 否 | `false` | 刷新中 loading 状态 |
89
+ | multiple | `boolean` | 否 | - | 是否支持多次扫码(完成后仍显示刷新按钮) |
90
+
91
+ ## 导出类型
92
+
93
+ ### QRCodeStatus
94
+
95
+ ```typescript
96
+ export enum QRCodeStatus {
97
+ ACTIVE = 'active', // 等待扫码
98
+ SCANNED = 'scanned', // 扫码成功
99
+ EXPIRED = 'expired', // 二维码已失效
100
+ COMPLETE = 'complete', // 已完成
101
+ }
102
+ ```
103
+
104
+ ### useSseQrcode
105
+
106
+ ```typescript
107
+ export const useSseQrcode: <SuccessPayloadType>(
108
+ config: UseRemoteUploadConfigType<SuccessPayloadType>,
109
+ ) => [{ qrCodeUrl?: string; qrCodeStatus?: QRCodeStatus; refreshQRCode: () => void; refreshLoading: boolean; sseLoading: boolean }]
110
+ ```
111
+
112
+ ### UseRemoteUploadConfigType
113
+
114
+ ```typescript
115
+ export type UseRemoteUploadConfigType<SuccessPayloadType = any> = {
116
+ createQRCode: (requestConfig: BFFRequestConfig) => Promise<{ uuid: string; qrcode: string }>
117
+ sseUrl: string
118
+ enable?: boolean
119
+ onSuccess?: (data: SuccessPayloadType) => void
120
+ }
121
+ ```
122
+
123
+ | 属性 | 类型 | 必填 | 默认值 | 说明 |
124
+ | ------------ | --------------------------------------- | ---- | ------ | ---------------------------------- |
125
+ | createQRCode | `(config) => Promise<{ uuid, qrcode }>` | 是 | - | 创建二维码的异步函数 |
126
+ | sseUrl | `string` | 是 | - | SSE 地址(会拼接 baseURL 和 uuid) |
127
+ | enable | `boolean` | 否 | - | 是否启用,为 true 时自动创建二维码 |
128
+ | onSuccess | `(data) => void` | 否 | - | 扫码完成回调 |
129
+
130
+ ### SseEventDataType
131
+
132
+ ```typescript
133
+ export type SseEventDataType<SuccessPayloadType> = {
134
+ data: { status: QRCodeStatus; payload: SuccessPayloadType }
135
+ }
136
+ ```
137
+
138
+ ## 常见陷阱
139
+
140
+ - ❌ SSE 连接出错时状态自动变为 `EXPIRED`,需要用户点击刷新按钮重新创建
141
+ - ✅ 组件卸载时会自动关闭 EventSource 连接
142
+
143
+ ## 相关组件
144
+
145
+ | 场景 | 组件 | 说明 |
146
+ | -------- | ------------------ | ------------------------------------------- |
147
+ | 远程上传 | `FileUpload` | `remote` 模式内部使用 QRCode + useSseQrcode |
148
+ | 文件上传 | `EffectFileUpload` | 带 API 上传的增强版 |