@wzyjs/uis 0.3.8
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 +49 -0
- package/src/antd/form/CheckboxButton/index.module.scss +24 -0
- package/src/antd/form/CheckboxButton/index.tsx +31 -0
- package/src/antd/form/RadioButton/index.tsx +30 -0
- package/src/antd/form/Upload/index.tsx +65 -0
- package/src/antd/form/UploadImage/index.tsx +338 -0
- package/src/antd/form/index.ts +5 -0
- package/src/antd/index.ts +43 -0
- package/src/antd/pro/Alert/index.tsx +24 -0
- package/src/antd/pro/Button/components/Confirm.tsx +24 -0
- package/src/antd/pro/Button/components/Copy.tsx +46 -0
- package/src/antd/pro/Button/components/Drawer.tsx +37 -0
- package/src/antd/pro/Button/components/Group.tsx +26 -0
- package/src/antd/pro/Button/index.tsx +11 -0
- package/src/antd/pro/Card/index.tsx +92 -0
- package/src/antd/pro/Collapse/components/Item.tsx +30 -0
- package/src/antd/pro/Collapse/index.tsx +27 -0
- package/src/antd/pro/Image/index.tsx +17 -0
- package/src/antd/pro/Input/components/Range.tsx +46 -0
- package/src/antd/pro/Input/index.tsx +61 -0
- package/src/antd/pro/Popconfirm/index.tsx +16 -0
- package/src/antd/pro/Radio/components/Cancel.tsx +30 -0
- package/src/antd/pro/Radio/index.tsx +7 -0
- package/src/antd/pro/Space/index.tsx +15 -0
- package/src/antd/pro/Typography/components/String.tsx +72 -0
- package/src/antd/pro/Typography/index.tsx +9 -0
- package/src/antd/pro/index.ts +10 -0
- package/src/components/BottomBar/index.tsx +28 -0
- package/src/components/CodeView/index.tsx +85 -0
- package/src/components/Collapse/index.tsx +26 -0
- package/src/components/Com2Canvas/index.tsx +60 -0
- package/src/components/CompileHtml/index.tsx +26 -0
- package/src/components/DateSwitcher/index.module.scss +10 -0
- package/src/components/DateSwitcher/index.tsx +75 -0
- package/src/components/DownloadLink/index.tsx +36 -0
- package/src/components/DragSort/index.tsx +77 -0
- package/src/components/DynamicSelect/index.tsx +77 -0
- package/src/components/DynamicSelect/utils.ts +47 -0
- package/src/components/EnumTag/index.tsx +24 -0
- package/src/components/FetchSelect/index.tsx +57 -0
- package/src/components/Fold/index.tsx +52 -0
- package/src/components/FormPro/index.tsx +28 -0
- package/src/components/GroupLayout/index.tsx +45 -0
- package/src/components/HtmlPro/index.tsx +18 -0
- package/src/components/IframePro/index.tsx +52 -0
- package/src/components/JsonRenderer/index.tsx +115 -0
- package/src/components/JsonView/index.tsx +21 -0
- package/src/components/Markdown/index.tsx +152 -0
- package/src/components/Markdown/style.ts +106 -0
- package/src/components/MultiImageDisplay/index.tsx +63 -0
- package/src/components/SectorButton/index.tsx +247 -0
- package/src/components/TextInput/index.tsx +61 -0
- package/src/components/Video/index.tsx +37 -0
- package/src/components/index.ts +22 -0
- package/src/web.ts +2 -0
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@wzyjs/uis",
|
|
3
|
+
"version": "0.3.8",
|
|
4
|
+
"description": "description",
|
|
5
|
+
"author": "wzy",
|
|
6
|
+
"sideEffects": false,
|
|
7
|
+
"files": [
|
|
8
|
+
"src"
|
|
9
|
+
],
|
|
10
|
+
"exports": {
|
|
11
|
+
"./web": {
|
|
12
|
+
"import": "./src/web.ts"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"@ant-design/icons": "^6.0.0",
|
|
17
|
+
"@ant-design/nextjs-registry": "^1.0.2",
|
|
18
|
+
"@ant-design/pro-components": "^2.8.6",
|
|
19
|
+
"ali-oss": "^6.21.0",
|
|
20
|
+
"echarts": "^5.4.3",
|
|
21
|
+
"echarts-for-react": "^3.0.2",
|
|
22
|
+
"github-markdown-css": "^5.8.1",
|
|
23
|
+
"handlebars": "^4.7.8",
|
|
24
|
+
"html2canvas": "^1.4.1",
|
|
25
|
+
"js-beautify": "^1.14.7",
|
|
26
|
+
"prettier": "^3.5.3",
|
|
27
|
+
"prismjs": "^1.29.0",
|
|
28
|
+
"react": "^19.0.0",
|
|
29
|
+
"react-beautiful-dnd": "^13.1.1",
|
|
30
|
+
"react-json-view": "^1.21.3",
|
|
31
|
+
"react-markdown": "^10.1.0",
|
|
32
|
+
"react-syntax-highlighter": "^15.5.0",
|
|
33
|
+
"rehype-autolink-headings": "^7.1.0",
|
|
34
|
+
"rehype-slug": "^6.0.0",
|
|
35
|
+
"rehype-starry-night": "^2.2.0",
|
|
36
|
+
"remark-gfm": "^4.0.1",
|
|
37
|
+
"remark-toc": "^9.0.0"
|
|
38
|
+
},
|
|
39
|
+
"peerDependencies": {
|
|
40
|
+
"@wzyjs/hooks": "^0.2.37",
|
|
41
|
+
"@wzyjs/types": "^0.2.37",
|
|
42
|
+
"@wzyjs/utils": "^0.2.37",
|
|
43
|
+
"antd": "^6.0.0"
|
|
44
|
+
},
|
|
45
|
+
"gitHead": "f52c49060c511ee5dd658c68449467394efac69a",
|
|
46
|
+
"publishConfig": {
|
|
47
|
+
"access": "public"
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
.checkcard {
|
|
2
|
+
:global(.ant-pro-checkcard-content) {
|
|
3
|
+
padding: 0 !important;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
:global(.ant-pro-checkcard) {
|
|
7
|
+
margin-bottom: 0 !important;
|
|
8
|
+
text-align: center !important;
|
|
9
|
+
display: flex;
|
|
10
|
+
justify-content: center;
|
|
11
|
+
border-radius: 0 !important;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
:global(.ant-pro-checkcard-title) {
|
|
15
|
+
height: 100%;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
:global(.ant-pro-checkcard div) {
|
|
19
|
+
width: 100%;
|
|
20
|
+
font-weight: 400 !important;
|
|
21
|
+
height: 30px !important;
|
|
22
|
+
line-height: 28px !important;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { Col, Row } from 'antd'
|
|
4
|
+
import { CheckCard } from '@ant-design/pro-components'
|
|
5
|
+
import { CheckCardValueType, CheckGroupValueType } from '@ant-design/pro-card/es/components/CheckCard/Group'
|
|
6
|
+
|
|
7
|
+
import styles from './index.module.scss'
|
|
8
|
+
|
|
9
|
+
interface CheckboxButtonProps {
|
|
10
|
+
options: { label: string; value: CheckCardValueType }[]
|
|
11
|
+
value?: CheckGroupValueType
|
|
12
|
+
onChange?: (value: CheckGroupValueType) => void
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const CheckboxButton = (props: CheckboxButtonProps) => {
|
|
16
|
+
const { options, value, onChange } = props
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
<div className={styles.checkcard}>
|
|
20
|
+
<CheckCard.Group style={{ width: '100%' }} size='small' multiple value={value} onChange={onChange}>
|
|
21
|
+
<Row>
|
|
22
|
+
{options.map((item, index) => (
|
|
23
|
+
<Col key={index}>
|
|
24
|
+
<CheckCard title={item.label} value={item.value} style={{ width: 60, height: 30 }} />
|
|
25
|
+
</Col>
|
|
26
|
+
))}
|
|
27
|
+
</Row>
|
|
28
|
+
</CheckCard.Group>
|
|
29
|
+
</div>
|
|
30
|
+
)
|
|
31
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { CSSProperties } from 'react'
|
|
4
|
+
import { Radio } from 'antd'
|
|
5
|
+
|
|
6
|
+
interface FilterButtonProps<V> {
|
|
7
|
+
options: { label: string; value: V }[]
|
|
8
|
+
value?: V
|
|
9
|
+
buttonStyle?: 'outline' | 'solid'
|
|
10
|
+
onChange?: (value: V) => void
|
|
11
|
+
style?: CSSProperties
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const RadioButton = <V = string | number | boolean | null>(props: FilterButtonProps<V>) => {
|
|
15
|
+
const { value, options, buttonStyle = 'outline', style, onChange } = props
|
|
16
|
+
|
|
17
|
+
return (
|
|
18
|
+
<Radio.Group
|
|
19
|
+
optionType='button'
|
|
20
|
+
buttonStyle={buttonStyle}
|
|
21
|
+
value={value}
|
|
22
|
+
style={style}
|
|
23
|
+
onChange={e => onChange?.(e.target.value as V)}
|
|
24
|
+
>
|
|
25
|
+
{options.filter(item => item.label).map(item => (
|
|
26
|
+
<Radio key={String(item.value)} value={item.value}>{item.label}</Radio>
|
|
27
|
+
))}
|
|
28
|
+
</Radio.Group>
|
|
29
|
+
)
|
|
30
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { Upload } from 'antd'
|
|
4
|
+
import { InboxOutlined } from '@ant-design/icons'
|
|
5
|
+
import { generateUniqueFileName } from '@wzyjs/utils/web'
|
|
6
|
+
|
|
7
|
+
interface item {
|
|
8
|
+
name: string
|
|
9
|
+
url: string
|
|
10
|
+
status: string
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
interface UploadFormItemProps {
|
|
14
|
+
oss: any
|
|
15
|
+
multiple?: boolean,
|
|
16
|
+
mode?: 'image' | 'video'
|
|
17
|
+
value?: item[]
|
|
18
|
+
onChange?: (value: item[]) => void
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export const UploadFormItem = (props: UploadFormItemProps) => {
|
|
22
|
+
const { oss, mode = 'image', multiple = true, onChange } = props
|
|
23
|
+
|
|
24
|
+
const accept = mode === 'image' ? '.jpg,.png,.jpeg' : '.mp4,.mov'
|
|
25
|
+
|
|
26
|
+
const onUpload = async (info: any) => {
|
|
27
|
+
onChange?.(info.fileList.map((item: any) => ({
|
|
28
|
+
name: item?.response.name,
|
|
29
|
+
url: item?.response.url,
|
|
30
|
+
})))
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const customRequest = async ({ file, onSuccess }: any) => {
|
|
34
|
+
const newName = generateUniqueFileName(file.name)
|
|
35
|
+
const result = await oss.put(newName, file, {
|
|
36
|
+
headers: {
|
|
37
|
+
'Cache-Control': 'public, max-age=31536000, immutable', // 设置缓存时间为365天
|
|
38
|
+
},
|
|
39
|
+
})
|
|
40
|
+
onSuccess(result)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<Upload.Dragger
|
|
45
|
+
multiple={multiple}
|
|
46
|
+
accept={accept}
|
|
47
|
+
listType='picture-card'
|
|
48
|
+
onChange={onUpload}
|
|
49
|
+
customRequest={customRequest}
|
|
50
|
+
>
|
|
51
|
+
<p className='ant-upload-drag-icon'>
|
|
52
|
+
<InboxOutlined />
|
|
53
|
+
</p>
|
|
54
|
+
<p className='ant-upload-text'>
|
|
55
|
+
<span>点击或拖拽文件到这里以上传</span>
|
|
56
|
+
</p>
|
|
57
|
+
{mode === 'image' && (
|
|
58
|
+
<p className='ant-upload-hint'>支持上传多张 JPG 或者 PNG 格式的照片</p>
|
|
59
|
+
)}
|
|
60
|
+
{mode === 'video' && (
|
|
61
|
+
<p className='ant-upload-hint'>支持上传多个 MP4 或者 MOV 格式的视频</p>
|
|
62
|
+
)}
|
|
63
|
+
</Upload.Dragger>
|
|
64
|
+
)
|
|
65
|
+
}
|
|
@@ -0,0 +1,338 @@
|
|
|
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
|
+
enablePreview?: boolean;
|
|
12
|
+
enableDragDrop?: boolean;
|
|
13
|
+
enableFileSelect?: boolean;
|
|
14
|
+
enablePaste?: boolean;
|
|
15
|
+
enablePasteTip?: boolean;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const UploadImage = (props: UploadImageProps) => {
|
|
19
|
+
const {
|
|
20
|
+
value = [],
|
|
21
|
+
onChange,
|
|
22
|
+
maxImages = 10,
|
|
23
|
+
enablePreview = false,
|
|
24
|
+
enableDragDrop = false,
|
|
25
|
+
enableFileSelect = false,
|
|
26
|
+
enablePaste = true,
|
|
27
|
+
enablePasteTip = false,
|
|
28
|
+
} = props
|
|
29
|
+
|
|
30
|
+
const [isDragging, setIsDragging] = useState(false)
|
|
31
|
+
const fileInputRef = useRef<HTMLInputElement>(null)
|
|
32
|
+
|
|
33
|
+
useEffect(() => {
|
|
34
|
+
if (!enablePaste) return
|
|
35
|
+
|
|
36
|
+
const handlePaste = (event: ClipboardEvent) => {
|
|
37
|
+
if (event.clipboardData && event.clipboardData.items) {
|
|
38
|
+
const items = event.clipboardData.items
|
|
39
|
+
for (let i = 0; i < items.length; i++) {
|
|
40
|
+
const item = items[i]
|
|
41
|
+
if (item && item.type.indexOf('image') !== -1) {
|
|
42
|
+
const file = item.getAsFile()
|
|
43
|
+
if (file) {
|
|
44
|
+
convertToBase64(file)
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
document.addEventListener('paste', handlePaste)
|
|
52
|
+
return () => {
|
|
53
|
+
document.removeEventListener('paste', handlePaste)
|
|
54
|
+
}
|
|
55
|
+
}, [value, enablePaste])
|
|
56
|
+
|
|
57
|
+
const handleDragOver = (e: React.DragEvent<HTMLDivElement>) => {
|
|
58
|
+
if (!enableDragDrop) return
|
|
59
|
+
e.preventDefault()
|
|
60
|
+
e.stopPropagation()
|
|
61
|
+
setIsDragging(true)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const handleDragLeave = (e: React.DragEvent<HTMLDivElement>) => {
|
|
65
|
+
if (!enableDragDrop) return
|
|
66
|
+
e.preventDefault()
|
|
67
|
+
e.stopPropagation()
|
|
68
|
+
setIsDragging(false)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const handleDrop = (e: React.DragEvent<HTMLDivElement>) => {
|
|
72
|
+
if (!enableDragDrop) return
|
|
73
|
+
e.preventDefault()
|
|
74
|
+
e.stopPropagation()
|
|
75
|
+
setIsDragging(false)
|
|
76
|
+
|
|
77
|
+
if (e.dataTransfer.files && e.dataTransfer.files.length > 0) {
|
|
78
|
+
const fileList = Array.from(e.dataTransfer.files)
|
|
79
|
+
fileList.forEach((file) => {
|
|
80
|
+
if (file.type.match('image.*')) {
|
|
81
|
+
convertToBase64(file)
|
|
82
|
+
}
|
|
83
|
+
})
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const handleClick = () => {
|
|
88
|
+
if (!enableFileSelect) return
|
|
89
|
+
fileInputRef.current?.click()
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
93
|
+
if (!enableFileSelect) return
|
|
94
|
+
if (e.target.files && e.target.files.length > 0) {
|
|
95
|
+
const fileList = Array.from(e.target.files)
|
|
96
|
+
fileList.forEach((file) => {
|
|
97
|
+
if (file.type.match('image.*')) {
|
|
98
|
+
convertToBase64(file)
|
|
99
|
+
}
|
|
100
|
+
})
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const handlePasteButton = () => {
|
|
105
|
+
if (!enablePaste) return
|
|
106
|
+
navigator.clipboard.read().then((clipboardItems) => {
|
|
107
|
+
for (const clipboardItem of clipboardItems) {
|
|
108
|
+
for (const type of clipboardItem.types) {
|
|
109
|
+
if (type.startsWith('image/')) {
|
|
110
|
+
clipboardItem.getType(type).then((blob) => {
|
|
111
|
+
convertToBase64(blob)
|
|
112
|
+
})
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}).catch(error => {
|
|
117
|
+
console.error('读取剪贴板失败:', error)
|
|
118
|
+
alert('读取剪贴板失败。请尝试使用 Ctrl+V 粘贴或手动上传图片。')
|
|
119
|
+
})
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const convertToBase64 = (file: File | Blob) => {
|
|
123
|
+
if (value.length >= maxImages) {
|
|
124
|
+
alert(`最多允许上传 ${maxImages} 张图片。`)
|
|
125
|
+
return
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
compressImage(file).then(compressedFile => {
|
|
129
|
+
const reader = new FileReader()
|
|
130
|
+
reader.onloadend = () => {
|
|
131
|
+
const base64String = reader.result as string
|
|
132
|
+
if (!value.includes(base64String)) {
|
|
133
|
+
onChange?.([...value, base64String])
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
reader.readAsDataURL(compressedFile)
|
|
137
|
+
})
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const compressImage = (file: File | Blob): Promise<Blob> => {
|
|
141
|
+
return new Promise((resolve) => {
|
|
142
|
+
const originalSize = file.size / 1024
|
|
143
|
+
|
|
144
|
+
const img = new Image()
|
|
145
|
+
img.src = URL.createObjectURL(file)
|
|
146
|
+
|
|
147
|
+
img.onload = () => {
|
|
148
|
+
URL.revokeObjectURL(img.src)
|
|
149
|
+
|
|
150
|
+
const canvas = document.createElement('canvas')
|
|
151
|
+
let width = img.width
|
|
152
|
+
let height = img.height
|
|
153
|
+
|
|
154
|
+
// 计算压缩比例,保持宽高比
|
|
155
|
+
const maxWidth = 1200
|
|
156
|
+
const maxHeight = 1200
|
|
157
|
+
|
|
158
|
+
if (width > maxWidth || height > maxHeight) {
|
|
159
|
+
const ratio = Math.min(maxWidth / width, maxHeight / height)
|
|
160
|
+
width = width * ratio
|
|
161
|
+
height = height * ratio
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
canvas.width = width
|
|
165
|
+
canvas.height = height
|
|
166
|
+
|
|
167
|
+
const ctx = canvas.getContext('2d')
|
|
168
|
+
if (!ctx) {
|
|
169
|
+
resolve(file as Blob)
|
|
170
|
+
return
|
|
171
|
+
}
|
|
172
|
+
ctx.drawImage(img, 0, 0, width, height)
|
|
173
|
+
|
|
174
|
+
// 转换为blob,压缩质量为0.8
|
|
175
|
+
canvas.toBlob(
|
|
176
|
+
(blob) => {
|
|
177
|
+
if (!blob) {
|
|
178
|
+
resolve(file as Blob)
|
|
179
|
+
return
|
|
180
|
+
}
|
|
181
|
+
// 获取压缩后的图片大小
|
|
182
|
+
const compressedSize = blob.size / 1024
|
|
183
|
+
console.log(`原始图片: ${originalSize.toFixed(2)} KB => ${compressedSize.toFixed(2)} KB`)
|
|
184
|
+
console.log(`压缩到了: ${((compressedSize / originalSize) * 100).toFixed(2)}%`)
|
|
185
|
+
resolve(blob)
|
|
186
|
+
},
|
|
187
|
+
'image/jpeg',
|
|
188
|
+
0.8,
|
|
189
|
+
)
|
|
190
|
+
}
|
|
191
|
+
})
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const handleRemoveImage = (index: number) => {
|
|
195
|
+
Modal.confirm({
|
|
196
|
+
title: '确认删除',
|
|
197
|
+
content: '您确定要删除这张图片吗?',
|
|
198
|
+
okText: '确定',
|
|
199
|
+
cancelText: '取消',
|
|
200
|
+
onOk: () => {
|
|
201
|
+
const newImages = [...value]
|
|
202
|
+
newImages.splice(index, 1)
|
|
203
|
+
onChange?.(newImages)
|
|
204
|
+
},
|
|
205
|
+
})
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return (
|
|
209
|
+
<div className='w-full space-y-4'>
|
|
210
|
+
<div className='flex flex-wrap gap-3'>
|
|
211
|
+
{value.map((image, index) => {
|
|
212
|
+
const imageNode = (
|
|
213
|
+
<div
|
|
214
|
+
className='rounded border overflow-hidden relative group bg-white'
|
|
215
|
+
style={{ maxWidth: 200, maxHeight: 200 }}
|
|
216
|
+
>
|
|
217
|
+
<img
|
|
218
|
+
src={image}
|
|
219
|
+
alt={`图片 ${index + 1}`}
|
|
220
|
+
className='object-contain'
|
|
221
|
+
style={{ maxWidth: '100%', maxHeight: '100%', display: 'block' }}
|
|
222
|
+
/>
|
|
223
|
+
<Button
|
|
224
|
+
type='text'
|
|
225
|
+
icon={<DeleteOutlined />}
|
|
226
|
+
onClick={(e) => {
|
|
227
|
+
e.stopPropagation()
|
|
228
|
+
handleRemoveImage(index)
|
|
229
|
+
}}
|
|
230
|
+
style={{ position: 'absolute', top: 0, right: 0 }}
|
|
231
|
+
className='absolute top-0 right-0 bg-black/40 text-white border-none hover:bg-black/60'
|
|
232
|
+
size='small'
|
|
233
|
+
/>
|
|
234
|
+
</div>
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
if (!enablePreview) {
|
|
238
|
+
return (
|
|
239
|
+
<div key={index} className='relative'>
|
|
240
|
+
{imageNode}
|
|
241
|
+
</div>
|
|
242
|
+
)
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
return (
|
|
246
|
+
<div key={index} className='relative'>
|
|
247
|
+
<Popover
|
|
248
|
+
trigger='hover'
|
|
249
|
+
placement='top'
|
|
250
|
+
content={(
|
|
251
|
+
<img
|
|
252
|
+
src={image}
|
|
253
|
+
alt={`预览图片 ${index + 1}`}
|
|
254
|
+
style={{
|
|
255
|
+
maxWidth: '200px',
|
|
256
|
+
maxHeight: '200px',
|
|
257
|
+
objectFit: 'contain',
|
|
258
|
+
}}
|
|
259
|
+
/>
|
|
260
|
+
)}
|
|
261
|
+
>
|
|
262
|
+
{imageNode}
|
|
263
|
+
</Popover>
|
|
264
|
+
</div>
|
|
265
|
+
)
|
|
266
|
+
})}
|
|
267
|
+
|
|
268
|
+
{value.length < maxImages && (
|
|
269
|
+
<Space style={{ width: '100%' }}>
|
|
270
|
+
{enableFileSelect && (
|
|
271
|
+
<div
|
|
272
|
+
className={`border-2 border-dashed rounded flex flex-col items-center justify-center cursor-pointer transition-colors ${
|
|
273
|
+
isDragging && enableDragDrop ? 'border-blue-500 bg-blue-50' : 'border-gray-300 hover:border-blue-500'
|
|
274
|
+
}`}
|
|
275
|
+
onDragOver={enableDragDrop ? handleDragOver : undefined}
|
|
276
|
+
onDragLeave={enableDragDrop ? handleDragLeave : undefined}
|
|
277
|
+
onDrop={enableDragDrop ? handleDrop : undefined}
|
|
278
|
+
onClick={handleClick}
|
|
279
|
+
style={{ width: '50px', height: '50px' }}
|
|
280
|
+
>
|
|
281
|
+
<PlusOutlined style={{ fontSize: '24px', color: '#8c8c8c' }} />
|
|
282
|
+
<Typography.Text type='secondary' className='mt-1'>上传</Typography.Text>
|
|
283
|
+
</div>
|
|
284
|
+
)}
|
|
285
|
+
|
|
286
|
+
{enablePaste && (
|
|
287
|
+
<div
|
|
288
|
+
style={{ width: '50px', height: '50px' }}
|
|
289
|
+
className={`border-2 border-dashed rounded flex flex-col items-center justify-center cursor-pointer transition-colors ${
|
|
290
|
+
isDragging ? 'border-blue-500 bg-blue-50' : 'border-gray-300 hover:border-blue-500'
|
|
291
|
+
}`}
|
|
292
|
+
onClick={handlePasteButton}
|
|
293
|
+
>
|
|
294
|
+
<PlusOutlined style={{ fontSize: '24px', color: '#8c8c8c' }} />
|
|
295
|
+
<Typography.Text type='secondary' className='mt-1'>粘贴</Typography.Text>
|
|
296
|
+
</div>
|
|
297
|
+
)}
|
|
298
|
+
</Space>
|
|
299
|
+
)}
|
|
300
|
+
</div>
|
|
301
|
+
|
|
302
|
+
{enableFileSelect && (
|
|
303
|
+
<input
|
|
304
|
+
type='file'
|
|
305
|
+
ref={fileInputRef}
|
|
306
|
+
onChange={handleFileChange}
|
|
307
|
+
accept='image/*'
|
|
308
|
+
multiple
|
|
309
|
+
style={{ display: 'none' }}
|
|
310
|
+
/>
|
|
311
|
+
)}
|
|
312
|
+
|
|
313
|
+
{(enablePasteTip && enablePaste) && (
|
|
314
|
+
<Alert
|
|
315
|
+
type='info'
|
|
316
|
+
showIcon
|
|
317
|
+
message={
|
|
318
|
+
<Typography.Text type='secondary'>
|
|
319
|
+
提示:您也可以在任意位置按下 <kbd style={{
|
|
320
|
+
padding: '0.1rem 0.4rem',
|
|
321
|
+
background: '#f5f5f5',
|
|
322
|
+
border: '1px solid #d9d9d9',
|
|
323
|
+
borderRadius: '3px',
|
|
324
|
+
fontSize: '12px',
|
|
325
|
+
}}>Ctrl</kbd> + <kbd style={{
|
|
326
|
+
padding: '0.1rem 0.4rem',
|
|
327
|
+
background: '#f5f5f5',
|
|
328
|
+
border: '1px solid #d9d9d9',
|
|
329
|
+
borderRadius: '3px',
|
|
330
|
+
fontSize: '12px',
|
|
331
|
+
}}>V</kbd> 从剪贴板粘贴图片
|
|
332
|
+
</Typography.Text>
|
|
333
|
+
}
|
|
334
|
+
/>
|
|
335
|
+
)}
|
|
336
|
+
</div>
|
|
337
|
+
)
|
|
338
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
export * from 'antd'
|
|
2
|
+
|
|
3
|
+
export * from '@ant-design/icons'
|
|
4
|
+
|
|
5
|
+
export { default as zh_CN } from 'antd/locale/zh_CN'
|
|
6
|
+
|
|
7
|
+
export { AntdRegistry } from '@ant-design/nextjs-registry'
|
|
8
|
+
|
|
9
|
+
export {
|
|
10
|
+
PageLoading,
|
|
11
|
+
PageHeader,
|
|
12
|
+
|
|
13
|
+
ProCard,
|
|
14
|
+
ProList,
|
|
15
|
+
ProTable,
|
|
16
|
+
ProDescriptions,
|
|
17
|
+
|
|
18
|
+
ProFormText,
|
|
19
|
+
ProFormCaptcha,
|
|
20
|
+
ProFormCheckbox,
|
|
21
|
+
ProFormSelect,
|
|
22
|
+
ProFormUploadButton,
|
|
23
|
+
LoginForm,
|
|
24
|
+
BetaSchemaForm,
|
|
25
|
+
StepsForm,
|
|
26
|
+
|
|
27
|
+
CheckCard,
|
|
28
|
+
EditableProTable,
|
|
29
|
+
DragSortTable,
|
|
30
|
+
|
|
31
|
+
ProProvider,
|
|
32
|
+
} from '@ant-design/pro-components'
|
|
33
|
+
|
|
34
|
+
export type {
|
|
35
|
+
ProTableProps,
|
|
36
|
+
ProFormProps,
|
|
37
|
+
ProColumns,
|
|
38
|
+
ProLayoutProps,
|
|
39
|
+
ProFormInstance
|
|
40
|
+
} from '@ant-design/pro-components'
|
|
41
|
+
|
|
42
|
+
export * from './pro'
|
|
43
|
+
export * from './form'
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import React, { ReactNode } from 'react'
|
|
4
|
+
import { Alert, AlertProps, Space } from 'antd'
|
|
5
|
+
|
|
6
|
+
export interface AlertProProps extends AlertProps {
|
|
7
|
+
children?: ReactNode,
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
// 1. 支持 children 作为 description 显示
|
|
11
|
+
export const AlertPro = (props: AlertProProps) => {
|
|
12
|
+
const { children, description = children } = props
|
|
13
|
+
|
|
14
|
+
return (
|
|
15
|
+
<Alert
|
|
16
|
+
{...props}
|
|
17
|
+
description={(
|
|
18
|
+
<Space style={{ width: '100%' }} orientation='vertical'>
|
|
19
|
+
{description}
|
|
20
|
+
</Space>
|
|
21
|
+
)}
|
|
22
|
+
/>
|
|
23
|
+
)
|
|
24
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import React from 'react'
|
|
4
|
+
import { _ } from '@wzyjs/utils/web'
|
|
5
|
+
import { Button, Popconfirm, PopconfirmProps, ButtonProps } from 'antd'
|
|
6
|
+
|
|
7
|
+
export interface ConfirmProps extends PopconfirmProps {
|
|
8
|
+
btnProps?: ButtonProps
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// 1. 给按钮增加了确认功能
|
|
12
|
+
export const Confirm = (props: ConfirmProps) => {
|
|
13
|
+
const { children, btnProps } = props
|
|
14
|
+
|
|
15
|
+
return (
|
|
16
|
+
<Popconfirm
|
|
17
|
+
okText='是'
|
|
18
|
+
cancelText='否'
|
|
19
|
+
{..._.omit(props, ['children', 'btnProps'])}
|
|
20
|
+
>
|
|
21
|
+
<Button type='link' {...btnProps}>{children}</Button>
|
|
22
|
+
</Popconfirm>
|
|
23
|
+
)
|
|
24
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import React, { useState, ReactNode } from 'react'
|
|
4
|
+
|
|
5
|
+
import { Button, ButtonProps, message } from 'antd'
|
|
6
|
+
import { CheckOutlined } from '@ant-design/icons'
|
|
7
|
+
|
|
8
|
+
import { copy, readClipboard } from '@wzyjs/utils/web'
|
|
9
|
+
import { useControllableValue } from '@wzyjs/hooks/web'
|
|
10
|
+
|
|
11
|
+
export interface CopyProps extends ButtonProps {
|
|
12
|
+
value?: string,
|
|
13
|
+
canPaste?: boolean,
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const Copy = (props: CopyProps) => {
|
|
17
|
+
const [icon, setIcon] = useState<ReactNode>(null)
|
|
18
|
+
|
|
19
|
+
const [value, setValue] = useControllableValue<string>(props, {
|
|
20
|
+
defaultValue: props.value,
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
const onContextMenu = async () => {
|
|
24
|
+
setValue(await readClipboard())
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<Button
|
|
29
|
+
{...props}
|
|
30
|
+
icon={icon}
|
|
31
|
+
onContextMenu={props.canPaste ? onContextMenu : undefined}
|
|
32
|
+
children={props.children || value || '记录'}
|
|
33
|
+
onClick={(ev: any) => {
|
|
34
|
+
props.onClick?.(ev)
|
|
35
|
+
|
|
36
|
+
copy(value)
|
|
37
|
+
message.success('复制成功')
|
|
38
|
+
|
|
39
|
+
setIcon(<CheckOutlined />)
|
|
40
|
+
setTimeout(() => {
|
|
41
|
+
setIcon(null)
|
|
42
|
+
}, 1000)
|
|
43
|
+
}}
|
|
44
|
+
/>
|
|
45
|
+
)
|
|
46
|
+
}
|