【react】上传头像或图片列表组件的实现

在http文件中写预请求,通常用于非hooks的页面请求

// 预请求api 常用于非useHook的页面请求
const preRequest = async (
  request: AxiosRequestConfigs,
  config: preRequestReturnProp
): Promise<AnyIfEmpty> => {
  return new Promise((reslove, reject) => {
    preload(request, () => api(requestInit(request, config)))
      .then((res: AnyIfEmpty) => {
        config.onSuccess && config.onSuccess(res)
        reslove(res)
      })
      .catch((err: AnyIfEmpty) => {
        config?.onError && config.onError(err)
        reject(err)
      })
  })
}
export { preRequest }

在api文件中写传入接口

const upLoad = (url: string) => {
  return {
    url: url + Math.random(),
    method: 'post',
    type: 'form',
  }
}

在data.d.ts文件定义父组件需要传的值

interface UpLoadProps {
  setReturn?: object // 返回值的时候增加的除了url其他的东西
  urlTitle?: string // 父组件传过来的图片链接名称
  uploadAddress?: string // 上传的地址
  imageList?: ImageUploadItem[] // 默认图片集合 type node 时候必传
  imageSize?: number // 图片大小 宽高
  getUploadImg: (arr: ImageUploadItem[]) => void // 返回的数组集合
  additionParams?: object // 上传所需额外参数
  [key: string]: unknown
  customButton?: ReactDOM // 自定义上传按钮
  upLoadType?: 'avatar' | 'list' // 头像上传 / 列表上传 默认列表
  defaultImg?: string // 默认图 只有头像上传的时候有默认图
  uploadTips?: string //上传中的提示语
}
export { UpLoadProps }

在index.tsx中实现

import { upLoad } from '@/api'
import { preRequest } from '@/http'
import { useSafeState, useUpdateEffect } from 'ahooks'
import { ImageUploader, Image, Toast } from 'antd-mobile'
import { mapValues } from 'lodash-es'
import React, { useEffect, useRef } from 'react'
import { createRoot } from 'react-dom/client'
import { UpLoadProps } from './data'
import { ImageUploaderList } from './styled'
import { ReactComponent as UpLoadDetele } from '@svg/upLoadDetele.svg'
import { ReactComponent as UploadAdd } from '@svg/uploadAdd.svg'

const ImageUpload: React.FC<UpLoadProps> = (props) => {
    const { 
          setReturn,
		  urlTitle='url', 
		  uploadAddress='/api/plugin/sider/customer/uploadFileByPhotos/customerHeadImage',
		  imageList,
		  imageSize=106,
		  getUploadImg,
		  additionParams,
		  customButton,
		  upLoadType= 'list',
		  defaultImg='',
		  uploadTips='上传中',
		  deletable
     } = props
       
     // 将父组件传过来的url链接的字段名称改为url
     const fileUrlName = (list: { [key: string]: string; url: string }[]) => {
	    list.map((item: { url: string; [key: string]: string }) => {
	      item.url = item[urlTitle] || item.url
	    })
	    return list
     }

     // 头像图片,默认图片为父组件传过来的默认图片
     const [avatarImg, setAvatarImg] = useSafeState<string>(defaultImg)
     // 默认刚进来的父组件传过来的图片
	 useUpdateEffect(() => {
	    if (defaultImg) {
	      setAvatarImg(defaultImg)
	    }
	  }, [defaultImg])
	  
    // 获取上传列表图片中的dom元素对象
	const deteleIcon = useRef<HTMLDivElement>(null)
	// 替换或删除图片
	const replaceIcon = () => {
		  const iconList: AnyIfEmpty = deteleIcon.current?.getElementsByClassName(
		    'adm-image-uploader-cell-delete'
		  )
		  mapValues(iconList, (item) => {
		    if (!item.getElementsByClassName('deleteIcon')[0]) {
		      createRoot(item).render(<UpLoadDetele className="deleteIcon" />)
		    }
		  })
	}
	
	// 当变化之后 重新赋值icon
	useEffect(() => {
	    replaceIcon()
	}, [imageList, deletable])

    // 获取上传头像的dom元素对象
    const upLoadImg = useRef<HTMLDivElement>(null)
    
    // 上传的回调
    const handlerUpload = async (file: File) => {
		    type !== 'list' &&
		      Toast.show({
		        icon: 'loading',
		        content:  uploadTips, // 上传提示
		        duration: 5000, // 等待5秒
		      })
		    // 如果是上传列表
		    const Lists: File = file
		    const index = Lists.name.lastIndexOf('.')
		    const fileName =
		      'img' + '.' + Lists.name.substring(index + 1, Lists.name.length)
		    const newFile = new window.File([Lists], fileName)
		    const res = await preRequest(upLoad(action), {
			      defaultParams: {
			        file: newFile,
			        ...data,
			      },
			      onError: () => {
			        setTimeout(() => {
			          replactIcon()
			        })
			      },
		    })
		    Toast.clear()
		
		    // 如果是头像上传直接返回string url
		    if (type !== 'list') {
		      getUploadImg([{ url: res.fileUrl }])
		      setAvatarImg(res.fileUrl)
		    } 
		    return Promise.resolve({
		      url: res.fileUrl || '',
		      ...(setReturn || {}),
		    })
    }     
    // 获取当前dom对象并调用此对象的点击事件
    const handlerCustom = () => {
	    const addpicDom = document.getElementsByClassName('adm-image-uploader-input')[0] as HTMLElement
	    addpicDom.click()
    }

   return (
    <>
      {type === 'list' ? (
        <div ref={deteleIcon}>
          <ImageList
            size={imageSize}
            {...props}
            value={fileUrlName(imageList || [])}
            onChange={(val) => {
              val.map((item: AnyIfEmpty) => {
                item[urlName] = item.url
              })
              getUploadImg(val)
            }}
            upload={handlerUpload}
          >
            <div className="add">
              <UploadAdd className="addIcon" />
            </div>
          </ImageList>
        </div>
      ) : (
        <div ref={upLoadImg} onClick={() => handlerCustom()}>
          <Image className="avatar" src={avatarImg} fit="fill" />
          <ImageUploader
            style={{ display: 'none' }}
            preview={false}
            {...props}
            upload={handlerUpload}
          >
            {customButton}
          </ImageUploader>
        </div>
      )}
    </>
  )
}

样式显示

import { ImageUploader } from 'antd-mobile'
import styled from 'styled-components'

const ImageList = styled(ImageUploader)<{
  size: number
}>`
  && {
    --cell-size: ${(p) => p.size + 'px'};
    .adm-space {
      --gap-vertical: 0;
      --gap-horizontal: 4px;
    }
    .adm-space-item {
      margin-bottom: 4px;
      &:nth-child(3n) {
        margin-right: 0;
      }
    }
    .adm-image-uploader-cell {
      border-radius: 4px;
    }
    .add {
      width: ${(p) => p.size + 'px'};
      height: ${(p) => p.size + 'px'};
      cursor: pointer;
      background: #f2f2f2;
      border-radius: 4px;
      display: flex;
      align-items: center;
      justify-content: center;
      .addIcon {
        width: 28px;
        height: 28px;
      }
    }
    .adm-image-uploader-cell-delete {
      position: absolute;
      top: 2px;
      right: 2px;
      width: 20px;
      height: 20px;
      background-color: #fff;
      border-radius: 50%;
      font-size: 0;
      cursor: pointer;
      .deleteIcon {
        width: 20px;
        height: 20px;
      }
    }
  }
`
export { ImageList }

1、调用此组件传图片列表

const [imgList, setImgList] = useState<AnyIfEmpty>([]) // 图片列表传接口
const [buttonState, { setTrue, setFalse }] = useBoolean(true) //保存按钮状态 根据场景需要

<ImageUpload
	additionParams={{
	    groupId: paramsId.groupId,
	    userId: paramsId.userId,
	}}
	maxCount={9} // 最多上传几张图片,超出数量会自动隐藏上传按钮
	urlTitle="mediaUrl"
	setReturn={{
	   mediaType: 1,
	}}
	imageList={
	   imgList.length > 0 ? imgList : ''
	}
	getUploadImg={(val) => {
	   setImgList(val)
	   setFalse()
	}}
/>

2、调用此组件传头像

<ImageUpload
     upLoadType={'avatar'}
     uploadTips="头像上传中"
     defaultImg={data.Image}
     getUploadImg={(val) =>
       run({ ImageUrl: val[0].url })
     }
     additionParams={{ groupId: paramsId.groupId, userId: paramsId.userId }}
/>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值