使用方法
<>
<h2>自定义上传图片组件</h2>
<CustomUpload
defaultValue={defaultData} //默认值(可用于图片回显)
config={configImage} //配置
onUpdateFileNames={handleFileNamesUpdate} // 可以拿到上传图片的name,用来下载上传的图片
onRemove={handleRemove} //可以拿到删除图片name字段组成的数组
/>
<h2>自定义上传文件组件</h2>
<CustomUpload
defaultValue={defaultData}
config={configFile}
onUpdateFileNames={handleFileNamesUpdateFile}
onRemove={handleRemoveFile}
/>
</>
状态数据
// 图片默认值,回显数据
const [defaultData, setDefaultData] = useState<UploadFile[]>([
{
uid: '0',
name: 'image.png',
size: 1234567,
status: 'done',
url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
},
])
// 下载的图片状态
const [downLoadImage, setDownLoadImage] = useState<FileInfo[]>([])
console.log('downLoadImage::: ', downLoadImage)
// 删除的图片状态
const [removeImage, setRemoveImage] = useState<FileInfo[]>([])
配置所需功能属性
1.图片上传
const configImage: configProps = {
fileType: 'image', //配置组件类型
// maxCount: 3, //限制最大上传数量
beforeUploadConfig: {
// maxSize: 10, //校验上传图片的大小不能小于该属性
allowedTypes: ['image/jpeg', 'image/png', 'image/jpg'],//校验上传图片的格式
},
//必传
customRequestConfig: {
requestApi: UploadApi, //配置上传请求接口
openZip: 1, //大于几兆开启压缩
zip: {
quality: 70, //压缩的质量
size: {
maxWidth: 800, //压缩后的最大宽度
maxHeight: 800, //压缩后的最大高度
},
outputFormat: 'JPEG', //压缩后的格式
outPutType: 'file', //压缩后的文件类型
},
},
preview: true, //开启图片预览
}
2.文件上传
const configFile: configProps = {
fileType: 'file', //配置组件类型
// maxCount: 3, //限制最大上传数量
beforeUploadConfig: {
maxSize: 20, //校验上传图片的大小不能小于该属性
allowedTypes: [
'application/pdf',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document', // docx
'application/msword', // doc
'application/zip', // zip,
],
},
//必传
customRequestConfig: {
requestApi: UploadApi,
},
preview: true, //开启图片预览
showFileList: true, //开启上传文件list
}
删除和下载图片
// 删除图片
const handleRemove = (options: any) => {
const { name } = options
console.log('options::: ', options)
setRemoveImage((prev) => [...prev, name])
}
// 下载上传的图片
const handleFileNamesUpdate = async (options: string[]) => {
try {
const result = await regularOrderRequest(options, DownLoadApi) // 调用函数并等待结果
setDownLoadImage([...result])
} catch (error) {
console.error('Error: ', error)
}
}
使用util
/**
* 将file转为base64
* @param file
* @returns
*/
const getBase64 = (file: any): Promise<string> =>
new Promise((resolve, reject) => {
const reader = new FileReader()
reader.readAsDataURL(file)
reader.onload = () => resolve(reader.result as string)
reader.onerror = (error) => reject(error)
})
/**
* 解决多个参数请求接口按顺序返回数据的问题
* @param params 请求所需要传的参数 string[]
* @param requestApi 请求接口
* @returns Promise<[]>
*/
const regularOrderRequest = async (params: string[], requestApi: any) => {
const result = []
let index = 0
for (const item of params) {
try {
const res = await requestApi({ filename: item }) // 按顺序等待请求完成
const blob = new Blob([res])
const base64 = await getBase64(blob) // 将 Blob 转换为 base64
result.push({
name: item,
uid: `${index + 1}`,
status: 'done',
url: base64,
})
index++ // 每次循环后递增 index
} catch (error) {
console.error('Error during request:', error)
}
}
return result
}
export { displayImage, getBase64, regularOrderRequest }
CustomUpload源码展示
组件内主要内置了图片压缩功能,以及可以通过配置config属性实现,可以直接拿来使用,避免页面文件过多的逻辑代码。
import React, { useState, memo } from 'react'
import { Upload, message, Image } from 'antd'
import { UploadFile, UploadProps } from 'antd/es/upload/interface'
import { handleImageResize, filesPermission, textTip, getFileType, changeType, getBase64, downloadFile } from './utils'
import { OnUpdateFileNames, configProps } from './type'
import { DownloadOutlined } from '@ant-design/icons'
import './index.less'
interface CustomUploadProps extends UploadProps {
customOnChange?: (fileList: UploadFile[]) => void
beforeUpload?: (file: File) => boolean | Promise<File>
defaultValue: UploadFile[]
onUpdateFileNames: OnUpdateFileNames
config: configProps
downFilesApi?: (fileList: UploadFile[]) => void
}
const CustomUpload: React.FC<CustomUploadProps> = ({
onUpdateFileNames,
defaultValue = [],
customOnChange,
beforeUpload,
config,
downFilesApi,
...rest
}) => {
const {
fileType,
maxCount = 3,
beforeUploadConfig = { maxSize: 3, allowedTypes: [] },
customRequestConfig,
preview,
showFileList,
} = config
const { maxSize = 3, allowedTypes } = beforeUploadConfig
const { requestApi, openZip = 0, zip } = customRequestConfig
const [fileList, setFileList] = useState<UploadFile[]>(defaultValue)
const [saveImageName, setSaveImageName] = useState<string[]>([])
const type_message = textTip(fileType)
// 内置上传方法
const settingBeforeUpload = (file: File) => {
if (file.size > 1024 * 1024 * maxSize) {
message.error(`不能超过文件最大容量${maxSize}MB!`)
return false
}
if (!filesPermission(allowedTypes, file.type)) {
message.error(`不支持的${type_message}类型!`)
return false
}
return true
}
// 内置自定义请求方法
const handleCustomRequest = async (options: any) => {
const { onSuccess, onError, file, onProgress } = options
if (!file || !(file instanceof File)) {
// console.error('无效的文件:', file) // 确认文件对象是否有效
onError(`无效的${type_message}!`)
return
}
try {
const formData = new FormData()
const fileSizeInMB = file.size / (1024 * 1024)
if (fileSizeInMB > openZip && zip) {
const resizedImage = (await handleImageResize(file, zip)) as Blob // 确保类型为 Blob
formData.append('file', new File([resizedImage], file.name, { type: file.type }))
} else {
formData.append('file', file)
}
const res = await requestApi(formData)
if (res.message === 'Success') {
const newFiles = res.file
setSaveImageName((prev: string[]) => [...prev, ...newFiles])
onUpdateFileNames([...saveImageName, ...newFiles])
onSuccess(`${type_message}上传成功`)
} else {
onError(`${type_message}上传失败`)
}
} catch (error) {
onError(`处理${type_message}时出错`)
}
}
const handleChange: UploadProps['onChange'] = ({ fileList: newFileList }) => {
setFileList(newFileList)
if (customOnChange) {
customOnChange(newFileList)
}
}
const handleBeforeUpload = (file: File) => {
return beforeUpload ? beforeUpload(file) : settingBeforeUpload(file)
}
const [previewOpen, setPreviewOpen] = useState(false)
const [previewImage, setPreviewImage] = useState('')
// 弹窗查看图片downloadFile
const handlePreview = async (file: UploadFile) => {
const allowedExtensions = ['png', 'jpg', 'jpeg', 'gif']
const fileExtension = file.name.split('.').pop() // 获取文件扩展名并转换为小写
if (fileExtension && !allowedExtensions.includes(fileExtension)) {
downloadFile(file.originFileObj, file.name)
} else {
if (!file.url && !file.preview) {
file.preview = await getBase64(file.originFileObj)
}
setPreviewImage(file.url || (file.preview as string))
setPreviewOpen(true)
}
}
const showUploadList = {
extra: ({ size = 10 }) => {
return <span style={{ color: '#cccccc' }}>({(size / 1024 / 1024).toFixed(2)}MB)</span>
},
showDownloadIcon: true,
downloadIcon: React.createElement(DownloadOutlined),
showRemoveIcon: true,
}
return (
<>
<Upload
{...rest}
listType={fileType === 'image' ? 'picture-card' : 'text'}
fileList={showFileList ? fileList : []}
onChange={handleChange}
beforeUpload={(file) => {
if (!getFileType(fileType, file, allowedTypes)) {
message.error(`不支持的${type_message}格式!`)
return Upload.LIST_IGNORE
}
return handleBeforeUpload(file)
}}
customRequest={customRequestConfig ? handleCustomRequest : undefined}
onPreview={preview ? handlePreview : undefined}
showUploadList={fileType === 'file' ? showUploadList : undefined}>
{changeType(fileType, fileList, maxCount)}
</Upload>
{previewImage && (
<Image
wrapperStyle={{ display: 'none' }}
preview={{
visible: previewOpen,
onVisibleChange: (visible) => {
setPreviewOpen(visible)
if (!visible) {
setPreviewImage('') // 关闭时清空预览图片
}
},
}}
src={previewImage}
/>
)}
</>
)
}
// 设置默认值
CustomUpload.defaultProps = {
config: {
fileType: 'image',
beforeUploadConfig: {
maxSize: 3,
allowedTypes: [],
},
customRequestConfig: {
requestApi: (data: any) => {},
openZip: 1,
zip: {
quality: 60,
size: {
maxWidth: 800,
maxHeight: 800,
},
outputFormat: 'jpeg',
outPutType: 'File',
},
},
preview: false,
showFileList: false,
},
}
export default memo(CustomUpload)
//暂时解决预览图片不居中的问题
.ant-image-preview-img-wrapper{
display: flex;
justify-content: center;
align-items: center;
img{
max-width: 900px;
max-height:900px;
}
}
export type FileInfo = {
name: string
url: string
uid: string
status: string
}
// 使用 interface 定义函数类型
export interface OnUpdateFileNames {
(val: string[]): void
}
export type sizeInfo = {
maxWidth: number
maxHeight: number
}
export interface zipProps {
quality: number
size: sizeInfo
outputFormat: string
outPutType: string
}
export interface configProps {
fileType: 'image' | 'file' | 'both'
maxCount?: number
beforeUploadConfig: {
maxSize?: number
allowedTypes: string[]
}
customRequestConfig: {
requestApi: any
openZip?: number
zip?: zipProps
}
preview?: boolean
showFileList?: boolean
}
import React from 'react'
import Resizer from 'react-image-file-resizer'
import { Button } from 'antd'
import { zipProps } from './type'
import { UploadOutlined } from '@ant-design/icons'
/**
* 压缩图片处理函数
* @param file
* @param zip
* @returns
*/
const handleImageResize = (file: File, zip: zipProps) => {
const { quality = 60, size, outputFormat = 'jpeg', outPutType = 'file' } = zip
return new Promise((resolve, reject) => {
Resizer.imageFileResizer(
file,
size.maxWidth, // 最大宽度
size.maxHeight, // 最大高度
outputFormat, // 输出格式
quality, // 压缩质量
0, // 图片旋转角度
(resizedImage) => {
resolve(resizedImage) // 返回压缩后的图片
},
outPutType, // 输出类型,返回文件
)
})
}
/**
* 校验上传的文件是否是支持的格式
* @param fileType
* @param file
* @param allowedTypes
* @returns
*/
const getFileType = (fileType: string, file: File, allowedTypes: string[]) => {
if (fileType === 'image') {
return allowedTypes.includes(file.type)
}
if (fileType === 'file') {
return allowedTypes.includes(file.type)
}
return true
}
/**
* 根据不同的文件类型显示不同的样式
* @param fileType
* @param fileList
* @param maxCount
* @returns
*/
const changeType = (fileType: any, fileList: any, maxCount: number) => {
switch (fileType) {
case 'image':
return fileList.length < maxCount && '+ Upload'
case 'file':
return (
<Button icon={React.createElement(UploadOutlined)}>
{fileType === 'image' ? 'Upload Image' : fileType === 'file' ? 'Upload File' : 'Upload'}
</Button>
)
case 'both':
return (
<Button icon={React.createElement(UploadOutlined)}>
{fileType === 'image' ? 'Upload Image' : fileType === 'file' ? 'Upload File' : 'Upload'}
</Button>
)
}
}
function filesPermission(type: any, fileType: any) {
if (!type.includes(fileType)) {
return false
}
return true
}
const textTip = (type: string) => {
return type === 'image' ? '图片' : '文件'
}
/**
* 将file转为base64
* @param file
* @returns
*/
const getBase64 = (file: any): Promise<string> =>
new Promise((resolve, reject) => {
const reader = new FileReader()
reader.readAsDataURL(file)
reader.onload = () => resolve(reader.result as string)
reader.onerror = (error) => reject(error)
})
/**
* 浏览器下载文件方法
* @param file File
* @param name string
*/
const downloadFile = (file: any, name: string) => {
let downloadElement = document.createElement('a')
// 创建下载的链接
let href = window.URL.createObjectURL(file)
downloadElement.href = href
// 下载后文件名
downloadElement.download = name
document.body.appendChild(downloadElement)
// 点击下载
downloadElement.click()
// 下载完成移除元素
document.body.removeChild(downloadElement)
// 释放掉blob对象
window.URL.revokeObjectURL(href)
}
export { handleImageResize, filesPermission, textTip, getFileType, changeType, getBase64, downloadFile }