seven-design-ui 0.0.8 → 0.0.10
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/README.md +1 -1
- package/docs/components/upload.mdx +212 -0
- package/docs/components/virtual-list.mdx +414 -0
- package/docs/doc_build/404.html +4 -4
- package/docs/doc_build/CHANGELOG.html +14 -4
- package/docs/doc_build/components/button.html +28 -4
- package/docs/doc_build/components/cascader.html +34 -4
- package/docs/doc_build/components/form.html +47 -4
- package/docs/doc_build/components/input.html +28 -4
- package/docs/doc_build/components/message.html +34 -4
- package/docs/doc_build/components/pagination.html +28 -4
- package/docs/doc_build/components/switch.html +27 -4
- package/docs/doc_build/components/table.html +48 -4
- package/docs/doc_build/components/upload.html +41 -0
- package/docs/doc_build/components/virtual-list.html +37 -0
- package/docs/doc_build/guide/introduction.html +45 -4
- package/docs/doc_build/guide/quick-start.html +87 -4
- package/docs/doc_build/guide/theme.html +128 -4
- package/docs/doc_build/index.html +4 -4
- package/docs/doc_build/static/css/{styles.90fb98bd.css → styles.81f01bfc.css} +1 -1
- package/docs/doc_build/static/js/414.e7b95f5f.js +6 -0
- package/docs/doc_build/static/js/async/166.d781bc1b.js +13 -0
- package/docs/doc_build/static/js/async/358.11ff19ec.js +1 -0
- package/docs/doc_build/static/js/async/656.99a32841.js +1 -0
- package/docs/doc_build/static/js/async/787.fc84dc3b.js +1 -0
- package/docs/doc_build/static/js/index.a5987e76.js +1 -0
- package/docs/doc_build/static/{search_index.ba6ba2c0.json → search_index.b65df64a.json} +1 -1
- package/docs/rspress.config.ts +15 -2
- package/package.json +1 -1
- package/packages/components/package.json +1 -1
- package/packages/components/src/index.ts +2 -0
- package/packages/components/src/message/Message.tsx +6 -1
- package/packages/components/src/message/message.css +1 -1
- package/packages/components/src/table/index.ts +1 -1
- package/packages/components/src/upload/Upload.tsx +411 -0
- package/packages/components/src/upload/index.ts +2 -0
- package/packages/components/src/upload/upload.css +183 -0
- package/packages/components/src/virtual-list/VirtualList.tsx +179 -0
- package/packages/components/src/virtual-list/index.ts +2 -0
- package/packages/components/src/virtual-list/virtual-list.css +85 -0
- package/play/src/App.tsx +133 -1
- package/docs/doc_build/static/js/414.9945b82b.js +0 -6
- package/docs/doc_build/static/js/async/166.d636f95e.js +0 -13
- package/docs/doc_build/static/js/index.7edcc197.js +0 -1
- /package/docs/doc_build/static/js/{414.9945b82b.js.LICENSE.txt → 414.e7b95f5f.js.LICENSE.txt} +0 -0
- /package/docs/doc_build/static/js/async/{166.d636f95e.js.LICENSE.txt → 166.d781bc1b.js.LICENSE.txt} +0 -0
package/README.md
CHANGED
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import { Upload, Button } from '@seven-design-ui/components'
|
|
2
|
+
import React from 'react'
|
|
3
|
+
|
|
4
|
+
# Upload 上传
|
|
5
|
+
|
|
6
|
+
通过点击或者拖拽上传文件。
|
|
7
|
+
|
|
8
|
+
## 基础用法
|
|
9
|
+
|
|
10
|
+
通过 slot 你可以传入自定义的上传按钮类型和文字提示。 可通过设置 limit 和 on-exceed 来限制上传文件的个数和定义超出限制时的行为。 可通过设置 before-remove 来阻止文件移除操作。
|
|
11
|
+
|
|
12
|
+
```tsx preview
|
|
13
|
+
export default function Demo() {
|
|
14
|
+
return (
|
|
15
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: '16px' }}>
|
|
16
|
+
<Upload
|
|
17
|
+
action="https://httpbin.org/post"
|
|
18
|
+
onSuccess={(response, file) => {
|
|
19
|
+
console.log('上传成功:', response, file);
|
|
20
|
+
}}
|
|
21
|
+
onError={(error, file) => {
|
|
22
|
+
console.log('上传失败:', error, file);
|
|
23
|
+
}}
|
|
24
|
+
>
|
|
25
|
+
<Button type="primary">点击上传</Button>
|
|
26
|
+
</Upload>
|
|
27
|
+
</div>
|
|
28
|
+
)
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## 覆盖前一个文件
|
|
33
|
+
|
|
34
|
+
设置 limit 和 on-exceed 可以在选中时自动替换上一个文件。
|
|
35
|
+
|
|
36
|
+
```tsx preview
|
|
37
|
+
export default function Demo() {
|
|
38
|
+
return (
|
|
39
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: '16px' }}>
|
|
40
|
+
<Upload
|
|
41
|
+
action="https://httpbin.org/post"
|
|
42
|
+
limit={1}
|
|
43
|
+
onExceed={(files, uploadFiles) => {
|
|
44
|
+
console.log('超出限制的文件:', files);
|
|
45
|
+
}}
|
|
46
|
+
onSuccess={(response, file) => {
|
|
47
|
+
console.log('上传成功:', response, file);
|
|
48
|
+
}}
|
|
49
|
+
onError={(error, file) => {
|
|
50
|
+
console.log('上传失败:', error, file);
|
|
51
|
+
}}
|
|
52
|
+
>
|
|
53
|
+
<Button type="primary">最多上传1个文件</Button>
|
|
54
|
+
</Upload>
|
|
55
|
+
</div>
|
|
56
|
+
)
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## 用户头像
|
|
61
|
+
|
|
62
|
+
在 before-upload 钩子中限制用户上传文件的格式和大小。
|
|
63
|
+
|
|
64
|
+
```tsx preview
|
|
65
|
+
export default function Demo() {
|
|
66
|
+
return (
|
|
67
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: '16px' }}>
|
|
68
|
+
<Upload
|
|
69
|
+
action="https://httpbin.org/post"
|
|
70
|
+
accept="image/*"
|
|
71
|
+
maxSize={1024 * 1024} // 1MB
|
|
72
|
+
beforeUpload={(file) => {
|
|
73
|
+
const isImage = file.type.startsWith('image/');
|
|
74
|
+
if (!isImage) {
|
|
75
|
+
alert('只能上传图片文件');
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
const isLt1M = file.size < 1024 * 1024;
|
|
79
|
+
if (!isLt1M) {
|
|
80
|
+
alert('图片大小不能超过1MB');
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
return true;
|
|
84
|
+
}}
|
|
85
|
+
onSuccess={(response, file) => {
|
|
86
|
+
console.log('上传成功:', response, file);
|
|
87
|
+
}}
|
|
88
|
+
onError={(error, file) => {
|
|
89
|
+
console.log('上传失败:', error, file);
|
|
90
|
+
}}
|
|
91
|
+
>
|
|
92
|
+
<Button type="primary">上传头像</Button>
|
|
93
|
+
</Upload>
|
|
94
|
+
</div>
|
|
95
|
+
)
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## 拖拽上传
|
|
100
|
+
|
|
101
|
+
你可以将文件拖拽到特定区域以进行上传。
|
|
102
|
+
|
|
103
|
+
```tsx preview
|
|
104
|
+
export default function Demo() {
|
|
105
|
+
return (
|
|
106
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: '16px' }}>
|
|
107
|
+
<Upload
|
|
108
|
+
action="https://httpbin.org/post"
|
|
109
|
+
drag
|
|
110
|
+
multiple
|
|
111
|
+
onSuccess={(response, file) => {
|
|
112
|
+
console.log('上传成功:', response, file);
|
|
113
|
+
}}
|
|
114
|
+
onError={(error, file) => {
|
|
115
|
+
console.log('上传失败:', error, file);
|
|
116
|
+
}}
|
|
117
|
+
/>
|
|
118
|
+
</div>
|
|
119
|
+
)
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## 其他补充功能
|
|
124
|
+
|
|
125
|
+
### 文件预览
|
|
126
|
+
|
|
127
|
+
点击文件名可以预览文件(目前只支持日志输出,可根据需要扩展)。
|
|
128
|
+
|
|
129
|
+
### 文件移除
|
|
130
|
+
|
|
131
|
+
可以通过点击删除按钮移除文件,也可以通过 before-remove 钩子阻止移除操作。
|
|
132
|
+
|
|
133
|
+
```tsx preview
|
|
134
|
+
export default function Demo() {
|
|
135
|
+
return (
|
|
136
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: '16px' }}>
|
|
137
|
+
<Upload
|
|
138
|
+
action="https://httpbin.org/post"
|
|
139
|
+
beforeRemove={(file, fileList) => {
|
|
140
|
+
return confirm(`确定要删除文件 ${file.name} 吗?`);
|
|
141
|
+
}}
|
|
142
|
+
onSuccess={(response, file) => {
|
|
143
|
+
console.log('上传成功:', response, file);
|
|
144
|
+
}}
|
|
145
|
+
onError={(error, file) => {
|
|
146
|
+
console.log('上传失败:', error, file);
|
|
147
|
+
}}
|
|
148
|
+
onRemove={(file, fileList) => {
|
|
149
|
+
console.log('文件已移除:', file, fileList);
|
|
150
|
+
}}
|
|
151
|
+
>
|
|
152
|
+
<Button type="primary">上传文件(可移除)</Button>
|
|
153
|
+
</Upload>
|
|
154
|
+
</div>
|
|
155
|
+
)
|
|
156
|
+
}
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
## API
|
|
160
|
+
|
|
161
|
+
### Props
|
|
162
|
+
|
|
163
|
+
| 属性 | 说明 | 类型 | 默认值 |
|
|
164
|
+
| --- | --- | --- | --- |
|
|
165
|
+
| action | 上传的地址 | `string` | - |
|
|
166
|
+
| headers | 上传时附带的请求头部 | `Record<string, string>` | - |
|
|
167
|
+
| name | 上传的文件字段名 | `string` | `file` |
|
|
168
|
+
| data | 上传时附带的额外参数 | `Record<string, any>` | - |
|
|
169
|
+
| accept | 接受上传的文件类型 | `string` | - |
|
|
170
|
+
| multiple | 是否支持多选文件 | `boolean` | `false` |
|
|
171
|
+
| disabled | 是否禁用 | `boolean` | `false` |
|
|
172
|
+
| showFileList | 是否显示文件列表 | `boolean` | `true` |
|
|
173
|
+
| fileList | 文件列表 | `UploadFile[]` | - |
|
|
174
|
+
| drag | 是否支持拖拽上传 | `boolean` | `false` |
|
|
175
|
+
| limit | 最大允许上传个数 | `number` | - |
|
|
176
|
+
| maxSize | 文件大小限制,单位字节 | `number` | - |
|
|
177
|
+
| className | 自定义类名 | `string` | - |
|
|
178
|
+
| children | 子元素,用于自定义上传区域 | `ReactNode` | - |
|
|
179
|
+
| beforeUpload | 上传文件之前的钩子 | `(file: File, fileList: UploadFile[]) => boolean \| Promise<boolean> \| File` | - |
|
|
180
|
+
| beforeRemove | 文件移除之前的钩子 | `(file: UploadFile, fileList: UploadFile[]) => boolean \| Promise<boolean>` | - |
|
|
181
|
+
| onExceed | 超出限制时的回调 | `(files: File[], uploadFiles: UploadFile[]) => void` | - |
|
|
182
|
+
| onChange | 文件状态改变时的回调 | `(file: UploadFile, fileList: UploadFile[]) => void` | - |
|
|
183
|
+
| onSuccess | 文件上传成功的回调 | `(response: any, file: UploadFile, fileList: UploadFile[]) => void` | - |
|
|
184
|
+
| onError | 文件上传失败的回调 | `(error: any, file: UploadFile, fileList: UploadFile[]) => void` | - |
|
|
185
|
+
| onRemove | 文件移除的回调 | `(file: UploadFile, fileList: UploadFile[]) => void` | - |
|
|
186
|
+
| onPreview | 点击文件列表中已上传的文件时的回调 | `(file: UploadFile) => void` | - |
|
|
187
|
+
|
|
188
|
+
### Events
|
|
189
|
+
|
|
190
|
+
| 事件名 | 说明 | 类型 |
|
|
191
|
+
| --- | --- | --- |
|
|
192
|
+
| onChange | 文件状态改变时触发 | `(file: UploadFile, fileList: UploadFile[]) => void` |
|
|
193
|
+
| onSuccess | 文件上传成功时触发 | `(response: any, file: UploadFile, fileList: UploadFile[]) => void` |
|
|
194
|
+
| onError | 文件上传失败时触发 | `(error: any, file: UploadFile, fileList: UploadFile[]) => void` |
|
|
195
|
+
| onRemove | 文件移除时触发 | `(file: UploadFile, fileList: UploadFile[]) => void` |
|
|
196
|
+
| onPreview | 点击文件预览时触发 | `(file: UploadFile) => void` |
|
|
197
|
+
| onExceed | 超出文件限制时触发 | `(files: File[], uploadFiles: UploadFile[]) => void` |
|
|
198
|
+
|
|
199
|
+
### UploadFile
|
|
200
|
+
|
|
201
|
+
| 属性 | 说明 | 类型 | 默认值 |
|
|
202
|
+
| --- | --- | --- | --- |
|
|
203
|
+
| uid | 文件唯一标识 | `string` | - |
|
|
204
|
+
| name | 文件名 | `string` | - |
|
|
205
|
+
| size | 文件大小 | `number` | - |
|
|
206
|
+
| type | 文件类型 | `string` | - |
|
|
207
|
+
| status | 上传状态 | `'ready' \| 'uploading' \| 'success' \| 'error'` | `ready` |
|
|
208
|
+
| percent | 上传进度百分比 | `number` | - |
|
|
209
|
+
| raw | 原始File对象 | `File` | - |
|
|
210
|
+
| url | 上传成功后的URL | `string` | - |
|
|
211
|
+
| error | 错误信息 | `string` | - |
|
|
212
|
+
| lastModified | 最后修改时间 | `number` | - |
|
|
@@ -0,0 +1,414 @@
|
|
|
1
|
+
import { VirtualList } from '@seven-design-ui/components'
|
|
2
|
+
import React, { useState, useEffect, useCallback } from 'react'
|
|
3
|
+
|
|
4
|
+
# VirtualList 虚拟列表
|
|
5
|
+
|
|
6
|
+
虚拟列表组件,用于大数据量的列表渲染,通过只渲染可见区域的内容来提高性能。
|
|
7
|
+
|
|
8
|
+
## 基础用法
|
|
9
|
+
|
|
10
|
+
使用 `data` 指定数据源,`renderItem` 指定渲染函数。
|
|
11
|
+
|
|
12
|
+
```tsx preview
|
|
13
|
+
export default function Demo() {
|
|
14
|
+
const [data, setData] = useState([])
|
|
15
|
+
const [loading, setLoading] = useState(true)
|
|
16
|
+
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
const loadData = async () => {
|
|
19
|
+
try {
|
|
20
|
+
const response = await fetch('https://randomuser.me/api/?results=20&inc=name,gender,email,nat,picture&noinfo')
|
|
21
|
+
const result = await response.json()
|
|
22
|
+
const formattedData = result.results.map((user, index) => ({
|
|
23
|
+
id: index + 1,
|
|
24
|
+
name: `${user.name.first} ${user.name.last}`,
|
|
25
|
+
email: user.email,
|
|
26
|
+
gender: user.gender,
|
|
27
|
+
country: user.nat,
|
|
28
|
+
avatar: user.picture.thumbnail
|
|
29
|
+
}))
|
|
30
|
+
setData(formattedData)
|
|
31
|
+
} catch (error) {
|
|
32
|
+
console.error('Failed to load data:', error)
|
|
33
|
+
} finally {
|
|
34
|
+
setLoading(false)
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
loadData()
|
|
38
|
+
}, [])
|
|
39
|
+
|
|
40
|
+
if (loading) {
|
|
41
|
+
return <div style={{ padding: '20px', textAlign: 'center' }}>加载中...</div>
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
<VirtualList
|
|
46
|
+
data={data}
|
|
47
|
+
height={300}
|
|
48
|
+
itemHeight={70}
|
|
49
|
+
itemKey="id"
|
|
50
|
+
gap={8}
|
|
51
|
+
renderItem={(item, index) => (
|
|
52
|
+
<div style={{
|
|
53
|
+
padding: '12px',
|
|
54
|
+
border: '1px solid #e4e7ed',
|
|
55
|
+
borderRadius: '8px',
|
|
56
|
+
backgroundColor: '#fff',
|
|
57
|
+
display: 'flex',
|
|
58
|
+
alignItems: 'center',
|
|
59
|
+
gap: '12px'
|
|
60
|
+
}}>
|
|
61
|
+
<img
|
|
62
|
+
src={item.avatar}
|
|
63
|
+
alt={item.name}
|
|
64
|
+
style={{
|
|
65
|
+
width: '40px',
|
|
66
|
+
height: '40px',
|
|
67
|
+
borderRadius: '50%',
|
|
68
|
+
objectFit: 'cover'
|
|
69
|
+
}}
|
|
70
|
+
/>
|
|
71
|
+
<div style={{ flex: 1 }}>
|
|
72
|
+
<div style={{ fontWeight: 'bold', marginBottom: '4px' }}>{item.name}</div>
|
|
73
|
+
<div style={{ color: '#666', fontSize: '14px' }}>
|
|
74
|
+
{item.email} • {item.country}
|
|
75
|
+
</div>
|
|
76
|
+
</div>
|
|
77
|
+
</div>
|
|
78
|
+
)}
|
|
79
|
+
/>
|
|
80
|
+
)
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## 等高模式
|
|
85
|
+
|
|
86
|
+
等高模式下,所有项目具有相同的高度,通过 `itemHeight` 指定项目高度。
|
|
87
|
+
|
|
88
|
+
```tsx preview
|
|
89
|
+
export default function Demo() {
|
|
90
|
+
const [data, setData] = useState([])
|
|
91
|
+
const [loading, setLoading] = useState(true)
|
|
92
|
+
|
|
93
|
+
useEffect(() => {
|
|
94
|
+
const loadData = async () => {
|
|
95
|
+
try {
|
|
96
|
+
const response = await fetch('https://randomuser.me/api/?results=20&inc=name,gender,email,nat,picture&noinfo')
|
|
97
|
+
const result = await response.json()
|
|
98
|
+
const formattedData = result.results.map((user, index) => ({
|
|
99
|
+
id: index + 1,
|
|
100
|
+
name: `${user.name.first} ${user.name.last}`,
|
|
101
|
+
email: user.email,
|
|
102
|
+
gender: user.gender,
|
|
103
|
+
country: user.nat,
|
|
104
|
+
avatar: user.picture.medium,
|
|
105
|
+
bio: `来自${user.nat}的${user.gender === 'male' ? '开发者' : '设计师'},热爱技术创新。`
|
|
106
|
+
}))
|
|
107
|
+
setData(formattedData)
|
|
108
|
+
} catch (error) {
|
|
109
|
+
console.error('Failed to load data:', error)
|
|
110
|
+
} finally {
|
|
111
|
+
setLoading(false)
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
loadData()
|
|
115
|
+
}, [])
|
|
116
|
+
|
|
117
|
+
if (loading) {
|
|
118
|
+
return <div style={{ padding: '20px', textAlign: 'center' }}>加载中...</div>
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return (
|
|
122
|
+
<VirtualList
|
|
123
|
+
data={data}
|
|
124
|
+
height={400}
|
|
125
|
+
itemHeight={100}
|
|
126
|
+
itemKey="id"
|
|
127
|
+
gap={12}
|
|
128
|
+
renderItem={(item, index) => (
|
|
129
|
+
<div style={{
|
|
130
|
+
padding: '16px',
|
|
131
|
+
border: '1px solid #e4e7ed',
|
|
132
|
+
borderRadius: '12px',
|
|
133
|
+
backgroundColor: '#fff',
|
|
134
|
+
height: '100%',
|
|
135
|
+
display: 'flex',
|
|
136
|
+
alignItems: 'center',
|
|
137
|
+
gap: '16px'
|
|
138
|
+
}}>
|
|
139
|
+
<img
|
|
140
|
+
src={item.avatar}
|
|
141
|
+
alt={item.name}
|
|
142
|
+
style={{
|
|
143
|
+
width: '60px',
|
|
144
|
+
height: '60px',
|
|
145
|
+
borderRadius: '50%',
|
|
146
|
+
objectFit: 'cover',
|
|
147
|
+
border: '2px solid #e4e7ed'
|
|
148
|
+
}}
|
|
149
|
+
/>
|
|
150
|
+
<div style={{ flex: 1 }}>
|
|
151
|
+
<h4 style={{ margin: '0 0 8px 0', color: '#333' }}>{item.name}</h4>
|
|
152
|
+
<p style={{ margin: '0 0 8px 0', color: '#666', fontSize: '14px', lineHeight: '1.4' }}>
|
|
153
|
+
{item.bio}
|
|
154
|
+
</p>
|
|
155
|
+
<div style={{ fontSize: '12px', color: '#999' }}>
|
|
156
|
+
📧 {item.email} • 🌍 {item.country}
|
|
157
|
+
</div>
|
|
158
|
+
</div>
|
|
159
|
+
</div>
|
|
160
|
+
)}
|
|
161
|
+
/>
|
|
162
|
+
)
|
|
163
|
+
}
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
## 滚动加载更多
|
|
167
|
+
|
|
168
|
+
通过 `requestMore` 属性实现滚动到底部时自动加载更多数据。
|
|
169
|
+
|
|
170
|
+
```tsx preview
|
|
171
|
+
export default function Demo() {
|
|
172
|
+
const [data, setData] = useState([])
|
|
173
|
+
const [loading, setLoading] = useState(false)
|
|
174
|
+
const fakeDataUrl = 'https://randomuser.me/api/?results=20&inc=name,gender,email,nat,picture&noinfo'
|
|
175
|
+
|
|
176
|
+
const loadMoreData = useCallback(async () => {
|
|
177
|
+
setLoading(true)
|
|
178
|
+
try {
|
|
179
|
+
const response = await fetch(fakeDataUrl)
|
|
180
|
+
const result = await response.json()
|
|
181
|
+
const newData = result.results.map((user, index) => ({
|
|
182
|
+
id: data.length + index + 1,
|
|
183
|
+
name: `${user.name.first} ${user.name.last}`,
|
|
184
|
+
email: user.email,
|
|
185
|
+
gender: user.gender,
|
|
186
|
+
country: user.nat,
|
|
187
|
+
avatar: user.picture.thumbnail
|
|
188
|
+
}))
|
|
189
|
+
setData(prev => [...prev, ...newData])
|
|
190
|
+
} catch (error) {
|
|
191
|
+
console.error('Failed to load data:', error)
|
|
192
|
+
} finally {
|
|
193
|
+
setLoading(false)
|
|
194
|
+
}
|
|
195
|
+
}, [data.length])
|
|
196
|
+
|
|
197
|
+
useEffect(() => {
|
|
198
|
+
loadMoreData()
|
|
199
|
+
}, [])
|
|
200
|
+
|
|
201
|
+
return (
|
|
202
|
+
<VirtualList
|
|
203
|
+
data={data}
|
|
204
|
+
height={400}
|
|
205
|
+
itemHeight={70}
|
|
206
|
+
itemKey="id"
|
|
207
|
+
gap={8}
|
|
208
|
+
requestMore={loadMoreData}
|
|
209
|
+
renderItem={(item, index) => (
|
|
210
|
+
<div style={{
|
|
211
|
+
padding: '12px',
|
|
212
|
+
border: '1px solid #e4e7ed',
|
|
213
|
+
borderRadius: '8px',
|
|
214
|
+
backgroundColor: '#fff',
|
|
215
|
+
display: 'flex',
|
|
216
|
+
alignItems: 'center',
|
|
217
|
+
gap: '12px'
|
|
218
|
+
}}>
|
|
219
|
+
<img
|
|
220
|
+
src={item.avatar}
|
|
221
|
+
alt={item.name}
|
|
222
|
+
style={{
|
|
223
|
+
width: '40px',
|
|
224
|
+
height: '40px',
|
|
225
|
+
borderRadius: '50%',
|
|
226
|
+
objectFit: 'cover'
|
|
227
|
+
}}
|
|
228
|
+
/>
|
|
229
|
+
<div style={{ flex: 1 }}>
|
|
230
|
+
<div style={{ fontWeight: 'bold', marginBottom: '4px' }}>{item.name}</div>
|
|
231
|
+
<div style={{ color: '#666', fontSize: '14px' }}>
|
|
232
|
+
{item.email} • {item.country}
|
|
233
|
+
</div>
|
|
234
|
+
</div>
|
|
235
|
+
</div>
|
|
236
|
+
)}
|
|
237
|
+
/>
|
|
238
|
+
)
|
|
239
|
+
}
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
## 自定义样式
|
|
243
|
+
|
|
244
|
+
通过 `className` 和内联样式自定义虚拟列表的外观。
|
|
245
|
+
|
|
246
|
+
```tsx preview
|
|
247
|
+
export default function Demo() {
|
|
248
|
+
const [data, setData] = useState([])
|
|
249
|
+
const [loading, setLoading] = useState(true)
|
|
250
|
+
|
|
251
|
+
useEffect(() => {
|
|
252
|
+
const loadData = async () => {
|
|
253
|
+
try {
|
|
254
|
+
const response = await fetch('https://randomuser.me/api/?results=20&inc=name,gender,email,nat,picture&noinfo')
|
|
255
|
+
const result = await response.json()
|
|
256
|
+
const formattedData = result.results.map((user, index) => ({
|
|
257
|
+
id: index + 1,
|
|
258
|
+
name: `${user.name.first} ${user.name.last}`,
|
|
259
|
+
email: user.email,
|
|
260
|
+
gender: user.gender,
|
|
261
|
+
country: user.nat,
|
|
262
|
+
avatar: user.picture.thumbnail,
|
|
263
|
+
status: ['活跃', '在线', '离线'][index % 3],
|
|
264
|
+
level: Math.floor(Math.random() * 100) + 1
|
|
265
|
+
}))
|
|
266
|
+
setData(formattedData)
|
|
267
|
+
} catch (error) {
|
|
268
|
+
console.error('Failed to load data:', error)
|
|
269
|
+
} finally {
|
|
270
|
+
setLoading(false)
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
loadData()
|
|
274
|
+
}, [])
|
|
275
|
+
|
|
276
|
+
const getStatusColor = (status) => {
|
|
277
|
+
switch (status) {
|
|
278
|
+
case '活跃': return '#67c23a'
|
|
279
|
+
case '在线': return '#e6a23c'
|
|
280
|
+
case '离线': return '#909399'
|
|
281
|
+
default: return '#909399'
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
const getStatusIcon = (status) => {
|
|
286
|
+
switch (status) {
|
|
287
|
+
case '活跃': return '🟢'
|
|
288
|
+
case '在线': return '🟡'
|
|
289
|
+
case '离线': return '⚪'
|
|
290
|
+
default: return '⚪'
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
if (loading) {
|
|
295
|
+
return <div style={{ padding: '20px', textAlign: 'center' }}>加载中...</div>
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
return (
|
|
299
|
+
<VirtualList
|
|
300
|
+
data={data}
|
|
301
|
+
height={400}
|
|
302
|
+
itemHeight={120}
|
|
303
|
+
itemKey="id"
|
|
304
|
+
gap={8}
|
|
305
|
+
className="custom-virtual-list"
|
|
306
|
+
renderItem={(item, index) => (
|
|
307
|
+
<div style={{
|
|
308
|
+
padding: '16px',
|
|
309
|
+
border: '2px solid #e4e7ed',
|
|
310
|
+
borderRadius: '12px',
|
|
311
|
+
backgroundColor: '#fafbfc',
|
|
312
|
+
display: 'flex',
|
|
313
|
+
alignItems: 'center',
|
|
314
|
+
gap: '16px',
|
|
315
|
+
boxShadow: '0 2px 8px rgba(0,0,0,0.05)',
|
|
316
|
+
transition: 'all 0.3s ease',
|
|
317
|
+
':hover': {
|
|
318
|
+
boxShadow: '0 4px 16px rgba(0,0,0,0.1)',
|
|
319
|
+
borderColor: getStatusColor(item.status)
|
|
320
|
+
}
|
|
321
|
+
}}>
|
|
322
|
+
<div style={{ position: 'relative' }}>
|
|
323
|
+
<img
|
|
324
|
+
src={item.avatar}
|
|
325
|
+
alt={item.name}
|
|
326
|
+
style={{
|
|
327
|
+
width: '50px',
|
|
328
|
+
height: '50px',
|
|
329
|
+
borderRadius: '50%',
|
|
330
|
+
objectFit: 'cover',
|
|
331
|
+
border: `3px solid ${getStatusColor(item.status)}`
|
|
332
|
+
}}
|
|
333
|
+
/>
|
|
334
|
+
<div style={{
|
|
335
|
+
position: 'absolute',
|
|
336
|
+
bottom: '-5px',
|
|
337
|
+
right: '-5px',
|
|
338
|
+
backgroundColor: '#fff',
|
|
339
|
+
borderRadius: '50%',
|
|
340
|
+
width: '20px',
|
|
341
|
+
height: '20px',
|
|
342
|
+
display: 'flex',
|
|
343
|
+
alignItems: 'center',
|
|
344
|
+
justifyContent: 'center',
|
|
345
|
+
fontSize: '12px'
|
|
346
|
+
}}>
|
|
347
|
+
{getStatusIcon(item.status)}
|
|
348
|
+
</div>
|
|
349
|
+
</div>
|
|
350
|
+
<div style={{ flex: 1 }}>
|
|
351
|
+
<div style={{ fontWeight: 'bold', marginBottom: '4px', color: '#333' }}>{item.name}</div>
|
|
352
|
+
<div style={{ fontSize: '14px', color: '#666', marginBottom: '8px' }}>
|
|
353
|
+
📧 {item.email} • 🌍 {item.country}
|
|
354
|
+
</div>
|
|
355
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
|
|
356
|
+
<span style={{
|
|
357
|
+
fontSize: '12px',
|
|
358
|
+
color: getStatusColor(item.status),
|
|
359
|
+
fontWeight: 'bold'
|
|
360
|
+
}}>
|
|
361
|
+
{item.status}
|
|
362
|
+
</span>
|
|
363
|
+
<div style={{
|
|
364
|
+
flex: 1,
|
|
365
|
+
height: '6px',
|
|
366
|
+
backgroundColor: '#e4e7ed',
|
|
367
|
+
borderRadius: '3px',
|
|
368
|
+
overflow: 'hidden'
|
|
369
|
+
}}>
|
|
370
|
+
<div style={{
|
|
371
|
+
width: `${item.level}%`,
|
|
372
|
+
height: '100%',
|
|
373
|
+
backgroundColor: getStatusColor(item.status),
|
|
374
|
+
transition: 'width 0.3s ease'
|
|
375
|
+
}} />
|
|
376
|
+
</div>
|
|
377
|
+
<span style={{
|
|
378
|
+
fontSize: '12px',
|
|
379
|
+
color: '#666',
|
|
380
|
+
fontWeight: 'bold'
|
|
381
|
+
}}>
|
|
382
|
+
Lv.{item.level}
|
|
383
|
+
</span>
|
|
384
|
+
</div>
|
|
385
|
+
</div>
|
|
386
|
+
</div>
|
|
387
|
+
)}
|
|
388
|
+
/>
|
|
389
|
+
)
|
|
390
|
+
}
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
## API 参考
|
|
394
|
+
|
|
395
|
+
### VirtualListProps
|
|
396
|
+
|
|
397
|
+
| 属性 | 类型 | 默认值 | 说明 |
|
|
398
|
+
|------|------|--------|------|
|
|
399
|
+
| data | `T[]` | - | 数据源数组 |
|
|
400
|
+
| height | `number \| string` | - | 列表高度 |
|
|
401
|
+
| itemHeight | `number` | 50 | 子项高度 |
|
|
402
|
+
| renderItem | `(item: T, index: number) => ReactNode` | - | 子项渲染函数 |
|
|
403
|
+
| requestMore | `() => Promise<void>` | - | 异步加载数据的方法 |
|
|
404
|
+
| itemKey | `keyof T` | - | 作为子项key的值在item中对应的属性字段 |
|
|
405
|
+
| gap | `number` | 0 | 子项间隔 |
|
|
406
|
+
| onScroll | `(event: UIEvent<HTMLDivElement>) => void` | - | 滚动事件 |
|
|
407
|
+
| className | `string` | - | 自定义类名 |
|
|
408
|
+
| children | `ReactNode` | - | 子元素 |
|
|
409
|
+
|
|
410
|
+
## 注意事项
|
|
411
|
+
|
|
412
|
+
1. 请在使用时传入 `itemKey`,这对应你传入的 `data` 中单项的一个属性,保证它是唯一的。
|
|
413
|
+
2. 或者选择在 `renderItem` 返回的元素中手动传入 `key` 值,不推荐使用 `index` 作为 `key`。
|
|
414
|
+
3. 滚动加载功能需要配合 `requestMore` 属性使用,当用户滚动到列表底部时会自动触发加载更多数据。
|
package/docs/doc_build/404.html
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
<!doctype html>
|
|
2
|
-
<html >
|
|
2
|
+
<html lang="en">
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
|
6
6
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
7
|
<meta name="generator" content="Rspress v1.47.0">
|
|
8
|
-
<title data-rh="true"
|
|
9
|
-
<script>{;const saved = localStorage.getItem('rspress-theme-appearance');const preferDark = window.matchMedia('(prefers-color-scheme: dark)').matches;const isDark = !saved || saved === 'auto' ? preferDark : saved === 'dark';document.documentElement.classList.toggle('dark', isDark);document.documentElement.style.colorScheme = isDark ? 'dark' : 'light';}</script><link rel="icon" href="/sevenDesign/logo.svg" type="image/svg+xml"><script defer src="/sevenDesign/static/js/styles.f2af9a40.js"></script><script defer src="/sevenDesign/static/js/lib-react.ce4199ca.js"></script><script defer src="/sevenDesign/static/js/lib-router.4000fe55.js"></script><script defer src="/sevenDesign/static/js/414.
|
|
8
|
+
<title data-rh="true">404 - SevenDesign</title><meta data-rh="true" name="description" content="企业级 React UI 组件库"/>
|
|
9
|
+
<script>{;const saved = localStorage.getItem('rspress-theme-appearance');const preferDark = window.matchMedia('(prefers-color-scheme: dark)').matches;const isDark = !saved || saved === 'auto' ? preferDark : saved === 'dark';document.documentElement.classList.toggle('dark', isDark);document.documentElement.style.colorScheme = isDark ? 'dark' : 'light';}</script><link rel="icon" href="/sevenDesign/logo.svg" type="image/svg+xml"><script defer src="/sevenDesign/static/js/styles.f2af9a40.js"></script><script defer src="/sevenDesign/static/js/lib-react.ce4199ca.js"></script><script defer src="/sevenDesign/static/js/lib-router.4000fe55.js"></script><script defer src="/sevenDesign/static/js/414.e7b95f5f.js"></script><script defer src="/sevenDesign/static/js/index.a5987e76.js"></script><link href="/sevenDesign/static/css/styles.81f01bfc.css" rel="stylesheet"></head>
|
|
10
10
|
|
|
11
11
|
<body >
|
|
12
|
-
<div id="root"></div>
|
|
12
|
+
<div id="root"><link rel="preload" as="image" href="/sevenDesign/logo.svg"/><div class="navContainer_d18b1 rspress-nav px-6 sticky_ddfa7"><div class="container_e4235 flex justify-between items-center h-full"><div class="navBarTitle_c5f07"><a href="/sevenDesign/index.html" class="link_a7cea flex items-center w-full h-full text-base font-semibold transition-opacity duration-300 hover:opacity-60"><div class="mr-1 min-w-8"><img src="/sevenDesign/logo.svg" alt="logo" id="logo" class="rspress-logo dark:hidden"/><img src="/sevenDesign/logo.svg" alt="logo" id="logo" class="rspress-logo hidden dark:block"/></div></a></div><div class="flex flex-1 justify-end items-center"><div class="rightNav_a2fea"><div class="flex sm:flex-1 items-center sm:pl-4 sm:pr-2"><div class="rspress-nav-search-button navSearchButton_df1fb"><button><svg width="18" height="18" viewBox="0 0 32 32"><path fill="var(--rp-c-gray)" d="m29 27.586-7.552-7.552a11.018 11.018 0 1 0-1.414 1.414L27.586 29ZM4 13a9 9 0 1 1 9 9 9.01 9.01 0 0 1-9-9"></path></svg><p class="searchWord_af2c1">Search</p><div style="opacity:0"><span></span><span>K</span></div></button></div><div class="mobileNavSearchButton_d85a9"><svg width="24" height="24" viewBox="0 0 32 32"><path fill="var(--rp-c-gray)" d="m29 27.586-7.552-7.552a11.018 11.018 0 1 0-1.414 1.414L27.586 29ZM4 13a9 9 0 1 1 9 9 9.01 9.01 0 0 1-9-9"></path></svg></div></div><div class="rspress-nav-menu menu h-14"><a href="/sevenDesign/guide/introduction.html" class="link_a7cea "><div class="rspress-nav-menu-item singleItem_c1154 text-sm font-medium mx-1.5 px-3 py-2 flex items-center break-keep">指南</div></a><a href="/sevenDesign/components/button.html" class="link_a7cea "><div class="rspress-nav-menu-item singleItem_c1154 text-sm font-medium mx-1.5 px-3 py-2 flex items-center break-keep">组件</div></a></div><div class="flex-center flex-row"><div class="mx-2"><div class="md:mr-2 rspress-nav-appearance"><div class="p-1 border border-solid border-gray-300 text-gray-400 cursor-pointer rounded-md hover:border-gray-600 hover:text-gray-600 dark:hover:border-gray-200 dark:hover:text-gray-200 transition-all duration-300 w-7 h-7"><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" viewBox="0 0 24 24" class="dark:hidden" width="18" height="18" fill="currentColor"><path d="M12 18c-3.3 0-6-2.7-6-6s2.7-6 6-6 6 2.7 6 6-2.7 6-6 6m0-10c-2.2 0-4 1.8-4 4s1.8 4 4 4 4-1.8 4-4-1.8-4-4-4M12 4c-.6 0-1-.4-1-1V1c0-.6.4-1 1-1s1 .4 1 1v2c0 .6-.4 1-1 1M12 24c-.6 0-1-.4-1-1v-2c0-.6.4-1 1-1s1 .4 1 1v2c0 .6-.4 1-1 1M5.6 6.6c-.3 0-.5-.1-.7-.3L3.5 4.9c-.4-.4-.4-1 0-1.4s1-.4 1.4 0l1.4 1.4c.4.4.4 1 0 1.4-.1.2-.4.3-.7.3M19.8 20.8c-.3 0-.5-.1-.7-.3l-1.4-1.4c-.4-.4-.4-1 0-1.4s1-.4 1.4 0l1.4 1.4c.4.4.4 1 0 1.4-.2.2-.5.3-.7.3M3 13H1c-.6 0-1-.4-1-1s.4-1 1-1h2c.6 0 1 .4 1 1s-.4 1-1 1M23 13h-2c-.6 0-1-.4-1-1s.4-1 1-1h2c.6 0 1 .4 1 1s-.4 1-1 1M4.2 20.8c-.3 0-.5-.1-.7-.3-.4-.4-.4-1 0-1.4l1.4-1.4c.4-.4 1-.4 1.4 0s.4 1 0 1.4l-1.4 1.4c-.2.2-.4.3-.7.3M18.4 6.6c-.3 0-.5-.1-.7-.3-.4-.4-.4-1 0-1.4l1.4-1.4c.4-.4 1-.4 1.4 0s.4 1 0 1.4l-1.4 1.4c-.2.2-.5.3-.7.3"></path></svg><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" viewBox="0 0 24 24" class="hidden dark:block" width="18" height="18" fill="currentColor"><path d="M12.1 22h-.9c-5.5-.5-9.5-5.4-9-10.9.4-4.8 4.2-8.6 9-9 .4 0 .8.2 1 .5s.2.8-.1 1.1c-2 2.7-1.4 6.4 1.3 8.4 2.1 1.6 5 1.6 7.1 0 .3-.2.7-.3 1.1-.1.3.2.5.6.5 1-.2 2.7-1.5 5.1-3.6 6.8-1.9 1.4-4.1 2.2-6.4 2.2M9.3 4.4c-2.9 1-5 3.6-5.2 6.8-.4 4.4 2.8 8.3 7.2 8.7 2.1.2 4.2-.4 5.8-1.8 1.1-.9 1.9-2.1 2.4-3.4-2.5.9-5.3.5-7.5-1.1-2.8-2.2-3.9-5.9-2.7-9.2"></path></svg></div></div></div><div class="social-links menu-item_e90a6 flex-center relative"><div class="flex-center h-full gap-x-4 transition-colors duration-300 md:mr-2"><a href="https://github.com/7777even/sevenDesign.git" target="_blank" rel="noopener noreferrer" class="social-links"><div class="social-links-icon_a4ad0"><div><svg xmlns="http://www.w3.org/2000/svg" width="100%" viewBox="0 0 24 24"><path fill="currentColor" d="M12 .297c-6.63 0-12 5.373-12 12c0 5.303 3.438 9.8 8.205 11.385c.6.113.82-.258.82-.577c0-.285-.01-1.04-.015-2.04c-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729c1.205.084 1.838 1.236 1.838 1.236c1.07 1.835 2.809 1.305 3.495.998c.108-.776.417-1.305.76-1.605c-2.665-.3-5.466-1.332-5.466-5.93c0-1.31.465-2.38 1.235-3.22c-.135-.303-.54-1.523.105-3.176c0 0 1.005-.322 3.3 1.23c.96-.267 1.98-.399 3-.405c1.02.006 2.04.138 3 .405c2.28-1.552 3.285-1.23 3.285-1.23c.645 1.653.24 2.873.12 3.176c.765.84 1.23 1.91 1.23 3.22c0 4.61-2.805 5.625-5.475 5.92c.42.36.81 1.096.81 2.22c0 1.606-.015 2.896-.015 3.286c0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"/></svg></div></div></a></div></div></div></div><div class="mobileNavMenu_e7045"><div class="navScreen_ec30c rspress-nav-screen" id="navScreen"><div class="container_c935d"><div class="navMenu_b887b"><div class="navMenuItem_e7978 w-full"><a href="/sevenDesign/guide/introduction.html" class="link_a7cea "><div class="rspress-nav-menu-item singleItem_c1154 text-sm font-medium mx-1.5 px-3 py-2 flex items-center break-keep">指南</div></a></div><div class="navMenuItem_e7978 w-full"><a href="/sevenDesign/components/button.html" class="link_a7cea "><div class="rspress-nav-menu-item singleItem_c1154 text-sm font-medium mx-1.5 px-3 py-2 flex items-center break-keep">组件</div></a></div></div><div class="flex-center flex-col gap-2"><div class="mt-2 navAppearance_bf893 flex justify-center"></div><div class="social-links menu-item_e90a6 flex-center relative"><div class="flex-center h-full gap-x-4 transition-colors duration-300 md:mr-2"><a href="https://github.com/7777even/sevenDesign.git" target="_blank" rel="noopener noreferrer" class="social-links"><div class="social-links-icon_a4ad0"><div><svg xmlns="http://www.w3.org/2000/svg" width="100%" viewBox="0 0 24 24"><path fill="currentColor" d="M12 .297c-6.63 0-12 5.373-12 12c0 5.303 3.438 9.8 8.205 11.385c.6.113.82-.258.82-.577c0-.285-.01-1.04-.015-2.04c-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729c1.205.084 1.838 1.236 1.838 1.236c1.07 1.835 2.809 1.305 3.495.998c.108-.776.417-1.305.76-1.605c-2.665-.3-5.466-1.332-5.466-5.93c0-1.31.465-2.38 1.235-3.22c-.135-.303-.54-1.523.105-3.176c0 0 1.005-.322 3.3 1.23c.96-.267 1.98-.399 3-.405c1.02.006 2.04.138 3 .405c2.28-1.552 3.285-1.23 3.285-1.23c.645 1.653.24 2.873.12 3.176c.765.84 1.23 1.91 1.23 3.22c0 4.61-2.805 5.625-5.475 5.92c.42.36.81 1.096.81 2.22c0 1.606-.015 2.896-.015 3.286c0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"/></svg></div></div></a></div></div></div></div></div><button aria-label="mobile hamburger" class=" rspress-mobile-hamburger navHamburger_ac64c text-gray-500"><svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32" fill="currentColor"><circle cx="8" cy="16" r="2" fill="currentColor"></circle><circle cx="16" cy="16" r="2" fill="currentColor"></circle><circle cx="24" cy="16" r="2" fill="currentColor"></circle></svg></button></div></div></div></div><section><div class="m-auto mt-50 p-16 sm:p-8 sm:pt-24 sm:pb-40 text-center flex-center flex-col"><p class="text-6xl font-semibold">404</p><h1 class="leading-5 pt-3 text-xl font-bold">PAGE NOT FOUND</h1><div style="height:1px" class="mt-6 mx-auto mb-4.5 w-16 bg-gray-light-1"></div><div class="pt-5"><a class="py-2 px-4 rounded-2xl inline-block border-solid border-brand text-brand font-medium hover:border-brand-dark hover:text-brand-dark transition-colors duration-300" href="/sevenDesign/" aria-label="go to home">Take me home</a></div></div></section></div>
|
|
13
13
|
<div id="search-container"></div>
|
|
14
14
|
</body>
|
|
15
15
|
</html>
|