@wzyjs/antd 0.2.41 → 0.2.43

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wzyjs/antd",
3
- "version": "0.2.41",
3
+ "version": "0.2.43",
4
4
  "description": "description",
5
5
  "author": "wzy",
6
6
  "main": "src/index.ts",
@@ -18,7 +18,7 @@
18
18
  "@wzyjs/utils": "^0.2.37",
19
19
  "antd": "^5.24.3"
20
20
  },
21
- "gitHead": "22795bdb9c670d3e37e9a6e83b544f916bab3c16",
21
+ "gitHead": "3cbddeb0836445d439d13d095fbf07ce3cc3bc9e",
22
22
  "publishConfig": {
23
23
  "access": "public"
24
24
  }
@@ -1,21 +1,24 @@
1
1
  'use client'
2
2
 
3
+ import { CSSProperties } from 'react'
3
4
  import { Radio } from 'antd'
4
5
 
5
6
  interface FilterButtonProps<V> {
6
7
  options: { label: string; value: V }[]
7
8
  value: V
8
9
  onChange: (value: V) => void
10
+ style: CSSProperties
9
11
  }
10
12
 
11
13
  export const RadioButton = <V = string | number | boolean | null>(props: FilterButtonProps<V>) => {
12
- const { value, options, onChange } = props
14
+ const { value, options, style, onChange } = props
13
15
 
14
16
  return (
15
17
  <Radio.Group
16
18
  optionType='button'
17
19
  buttonStyle='solid'
18
20
  value={value}
21
+ style={style}
19
22
  onChange={e => onChange(e.target.value as V)}
20
23
  >
21
24
  {options.filter(item => item.label).map(item => (
@@ -0,0 +1,312 @@
1
+ 'use client'
2
+
3
+ import React, { useEffect, useRef, useState } from 'react'
4
+ import { Alert, Button, Modal, Popover, Space, Typography } from 'antd'
5
+ import { DeleteOutlined, PlusOutlined } from '@ant-design/icons'
6
+
7
+ interface UploadImageProps {
8
+ value?: string[];
9
+ onChange?: (value: string[]) => void;
10
+ maxImages?: number;
11
+ enableDragDrop?: boolean;
12
+ enableFileSelect?: boolean;
13
+ enablePaste?: boolean;
14
+ enablePasteTip?: boolean;
15
+ }
16
+
17
+ export const UploadImage = (props: UploadImageProps) => {
18
+ const {
19
+ value = [],
20
+ onChange,
21
+ maxImages = 10,
22
+ enableDragDrop = false,
23
+ enableFileSelect = false,
24
+ enablePaste = true,
25
+ enablePasteTip = false,
26
+ } = props
27
+
28
+ const [isDragging, setIsDragging] = useState(false)
29
+ const fileInputRef = useRef<HTMLInputElement>(null)
30
+
31
+ useEffect(() => {
32
+ if (!enablePaste) return
33
+
34
+ const handlePaste = (event: ClipboardEvent) => {
35
+ if (event.clipboardData && event.clipboardData.items) {
36
+ const items = event.clipboardData.items
37
+ for (let i = 0; i < items.length; i++) {
38
+ if (items[i].type.indexOf('image') !== -1) {
39
+ const file = items[i].getAsFile()
40
+ if (file) {
41
+ convertToBase64(file)
42
+ }
43
+ }
44
+ }
45
+ }
46
+ }
47
+
48
+ document.addEventListener('paste', handlePaste)
49
+ return () => {
50
+ document.removeEventListener('paste', handlePaste)
51
+ }
52
+ }, [value, enablePaste])
53
+
54
+ const handleDragOver = (e: React.DragEvent<HTMLDivElement>) => {
55
+ if (!enableDragDrop) return
56
+ e.preventDefault()
57
+ e.stopPropagation()
58
+ setIsDragging(true)
59
+ }
60
+
61
+ const handleDragLeave = (e: React.DragEvent<HTMLDivElement>) => {
62
+ if (!enableDragDrop) return
63
+ e.preventDefault()
64
+ e.stopPropagation()
65
+ setIsDragging(false)
66
+ }
67
+
68
+ const handleDrop = (e: React.DragEvent<HTMLDivElement>) => {
69
+ if (!enableDragDrop) return
70
+ e.preventDefault()
71
+ e.stopPropagation()
72
+ setIsDragging(false)
73
+
74
+ if (e.dataTransfer.files && e.dataTransfer.files.length > 0) {
75
+ const fileList = Array.from(e.dataTransfer.files)
76
+ fileList.forEach((file) => {
77
+ if (file.type.match('image.*')) {
78
+ convertToBase64(file)
79
+ }
80
+ })
81
+ }
82
+ }
83
+
84
+ const handleClick = () => {
85
+ if (!enableFileSelect) return
86
+ fileInputRef.current?.click()
87
+ }
88
+
89
+ const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
90
+ if (!enableFileSelect) return
91
+ if (e.target.files && e.target.files.length > 0) {
92
+ const fileList = Array.from(e.target.files)
93
+ fileList.forEach((file) => {
94
+ if (file.type.match('image.*')) {
95
+ convertToBase64(file)
96
+ }
97
+ })
98
+ }
99
+ }
100
+
101
+ const handlePasteButton = () => {
102
+ if (!enablePaste) return
103
+ navigator.clipboard.read().then((clipboardItems) => {
104
+ for (const clipboardItem of clipboardItems) {
105
+ for (const type of clipboardItem.types) {
106
+ if (type.startsWith('image/')) {
107
+ clipboardItem.getType(type).then((blob) => {
108
+ convertToBase64(blob)
109
+ })
110
+ }
111
+ }
112
+ }
113
+ }).catch(error => {
114
+ console.error('读取剪贴板失败:', error)
115
+ alert('读取剪贴板失败。请尝试使用 Ctrl+V 粘贴或手动上传图片。')
116
+ })
117
+ }
118
+
119
+ const convertToBase64 = (file: File | Blob) => {
120
+ if (value.length >= maxImages) {
121
+ alert(`最多允许上传 ${maxImages} 张图片。`)
122
+ return
123
+ }
124
+
125
+ compressImage(file).then(compressedFile => {
126
+ const reader = new FileReader()
127
+ reader.onloadend = () => {
128
+ const base64String = reader.result as string
129
+ if (!value.includes(base64String)) {
130
+ onChange([...value, base64String])
131
+ }
132
+ }
133
+ reader.readAsDataURL(compressedFile)
134
+ })
135
+ }
136
+
137
+ const compressImage = (file: File | Blob): Promise<Blob> => {
138
+ return new Promise((resolve) => {
139
+ const originalSize = file.size / 1024
140
+
141
+ const img = new Image()
142
+ img.src = URL.createObjectURL(file)
143
+
144
+ img.onload = () => {
145
+ URL.revokeObjectURL(img.src)
146
+
147
+ const canvas = document.createElement('canvas')
148
+ let width = img.width
149
+ let height = img.height
150
+
151
+ // 计算压缩比例,保持宽高比
152
+ const maxWidth = 1200
153
+ const maxHeight = 1200
154
+
155
+ if (width > maxWidth || height > maxHeight) {
156
+ const ratio = Math.min(maxWidth / width, maxHeight / height)
157
+ width = width * ratio
158
+ height = height * ratio
159
+ }
160
+
161
+ canvas.width = width
162
+ canvas.height = height
163
+
164
+ const ctx = canvas.getContext('2d')
165
+ ctx.drawImage(img, 0, 0, width, height)
166
+
167
+ // 转换为blob,压缩质量为0.8
168
+ canvas.toBlob(
169
+ (blob) => {
170
+ // 获取压缩后的图片大小
171
+ const compressedSize = blob.size / 1024
172
+ console.log(`原始图片: ${originalSize.toFixed(2)} KB => ${compressedSize.toFixed(2)} KB`)
173
+ console.log(`压缩到了: ${((compressedSize / originalSize) * 100).toFixed(2)}%`)
174
+ resolve(blob)
175
+ },
176
+ 'image/jpeg',
177
+ 0.8,
178
+ )
179
+ }
180
+ })
181
+ }
182
+
183
+ const handleRemoveImage = (index: number) => {
184
+ Modal.confirm({
185
+ title: '确认删除',
186
+ content: '您确定要删除这张图片吗?',
187
+ okText: '确定',
188
+ cancelText: '取消',
189
+ onOk: () => {
190
+ const newImages = [...value]
191
+ newImages.splice(index, 1)
192
+ onChange(newImages)
193
+ },
194
+ })
195
+ }
196
+
197
+ return (
198
+ <div className='w-full space-y-4'>
199
+ <div className='flex flex-wrap gap-3'>
200
+ {value.map((image, index) => (
201
+ <div
202
+ key={index}
203
+ className='relative'
204
+ style={{ width: '100px', height: '100px' }}
205
+ >
206
+ <Popover
207
+ trigger='hover'
208
+ placement='top'
209
+ content={(
210
+ <img
211
+ src={image}
212
+ alt={`预览图片 ${index + 1}`}
213
+ style={{
214
+ maxWidth: '300px',
215
+ maxHeight: '300px',
216
+ }}
217
+ />
218
+ )}
219
+ >
220
+ <div className='rounded border overflow-hidden h-full w-full relative group'>
221
+ <img
222
+ src={image}
223
+ alt={`上传图片 ${index + 1}`}
224
+ className='w-full h-full object-cover'
225
+ />
226
+ <Button
227
+ type='text'
228
+ icon={<DeleteOutlined />}
229
+ onClick={(e) => {
230
+ e.stopPropagation()
231
+ handleRemoveImage(index)
232
+ }}
233
+ style={{ position: 'absolute', top: 0, right: 0 }}
234
+ className='absolute top-0 right-0 bg-black/40 text-white border-none hover:bg-black/60'
235
+ size='small'
236
+ />
237
+ </div>
238
+ </Popover>
239
+ </div>
240
+ ))}
241
+
242
+ {value.length < maxImages && (
243
+ <Space style={{ width: '100%' }}>
244
+ {enableFileSelect && (
245
+ <div
246
+ className={`border-2 border-dashed rounded flex flex-col items-center justify-center cursor-pointer transition-colors ${
247
+ isDragging && enableDragDrop ? 'border-blue-500 bg-blue-50' : 'border-gray-300 hover:border-blue-500'
248
+ }`}
249
+ onDragOver={enableDragDrop ? handleDragOver : undefined}
250
+ onDragLeave={enableDragDrop ? handleDragLeave : undefined}
251
+ onDrop={enableDragDrop ? handleDrop : undefined}
252
+ onClick={handleClick}
253
+ style={{ width: '50px', height: '50px' }}
254
+ >
255
+ <PlusOutlined style={{ fontSize: '24px', color: '#8c8c8c' }} />
256
+ <Typography.Text type='secondary' className='mt-1'>上传</Typography.Text>
257
+ </div>
258
+ )}
259
+
260
+ {enablePaste && (
261
+ <div
262
+ style={{ width: '50px', height: '50px' }}
263
+ className={`border-2 border-dashed rounded flex flex-col items-center justify-center cursor-pointer transition-colors ${
264
+ isDragging ? 'border-blue-500 bg-blue-50' : 'border-gray-300 hover:border-blue-500'
265
+ }`}
266
+ onClick={handlePasteButton}
267
+ >
268
+ <PlusOutlined style={{ fontSize: '24px', color: '#8c8c8c' }} />
269
+ <Typography.Text type='secondary' className='mt-1'>粘贴</Typography.Text>
270
+ </div>
271
+ )}
272
+ </Space>
273
+ )}
274
+ </div>
275
+
276
+ {enableFileSelect && (
277
+ <input
278
+ type='file'
279
+ ref={fileInputRef}
280
+ onChange={handleFileChange}
281
+ accept='image/*'
282
+ multiple
283
+ style={{ display: 'none' }}
284
+ />
285
+ )}
286
+
287
+ {(enablePasteTip && enablePaste) && (
288
+ <Alert
289
+ type='info'
290
+ showIcon
291
+ message={
292
+ <Typography.Text type='secondary'>
293
+ 提示:您也可以在任意位置按下 <kbd style={{
294
+ padding: '0.1rem 0.4rem',
295
+ background: '#f5f5f5',
296
+ border: '1px solid #d9d9d9',
297
+ borderRadius: '3px',
298
+ fontSize: '12px',
299
+ }}>Ctrl</kbd> + <kbd style={{
300
+ padding: '0.1rem 0.4rem',
301
+ background: '#f5f5f5',
302
+ border: '1px solid #d9d9d9',
303
+ borderRadius: '3px',
304
+ fontSize: '12px',
305
+ }}>V</kbd> 从剪贴板粘贴图片
306
+ </Typography.Text>
307
+ }
308
+ />
309
+ )}
310
+ </div>
311
+ )
312
+ }
package/src/form/index.ts CHANGED
@@ -1,3 +1,5 @@
1
1
  export * from './Upload'
2
+ export * from './UploadImage'
3
+
2
4
  export * from './CheckboxButton'
3
5
  export * from './RadioButton'
package/src/index.ts CHANGED
@@ -16,6 +16,7 @@ export {
16
16
 
17
17
  ProCard,
18
18
  ProList,
19
+ ProTable,
19
20
 
20
21
  ProFormText,
21
22
  ProFormCaptcha,