Saber2pr's Blog

图片上传

前端文件上传我建议是用 cors 直传,不要去代理转发,因为文件流转发 node 比较麻烦。

antd Upload 组件根据你提供的 api 将文件发送出去,然后提供返回结果的回调函数,你把返回结果传入 onChange 中交给表单提交。

import './style.less'

import { Avatar, Input, message, Upload } from 'antd'
import { UploadChangeParam } from 'antd/lib/upload'
import { UploadFile, UploadProps } from 'antd/lib/upload/interface'
import classnames from 'classnames'
import React, { useEffect, useState } from 'react'

import { LoadingOutlined, PlusOutlined } from '@ant-design/icons'

import { Link } from '../link'

export interface ImgUpload {
  className?: string
  action?: string
  value?: string
  onChange?: (value: string) => void
  multiple?: boolean
  size?: [number, number]
}

function beforeUpload(file: File) {
  const isJpgOrPng = file.type === 'image/jpeg' || file.type === 'image/png'
  if (!isJpgOrPng) {
    message.error('只能上传 JPG/PNG 格式图片!')
  }
  const isLt2M = file.size / 1024 / 1024 < 2
  if (!isLt2M) {
    message.error('图片大小必须小于 2MB!')
  }
  return isJpgOrPng && isLt2M
}

export const ImgUpload = ({
  className,
  action,
  value,
  onChange,
  multiple = false,
  size,
}: ImgUpload) => {
  const [loading, setLoading] = useState(false)
  const [imgUrl, setImgUrl] = useState<string>(value)

  useEffect(() => {
    setImgUrl(value)
  }, [value])

  const handleChange = (info: UploadChangeParam<UploadFile<any>>) => {
    console.log('up-info', info)
    if (info.file.status === 'uploading') {
      setLoading(true)
      return
    }
    if (info.file.status === 'done') {
      setLoading(false)
      const imageUrl = info?.file?.response?.url
      setImgUrl(imageUrl)
      onChange && onChange(imageUrl)
    }
  }

  const uploadProps: UploadProps = {
    action,
    multiple,
    beforeUpload,
    onChange: handleChange,
  }

  return (
    <Upload
      listType="picture-card"
      showUploadList={false}
      className={classnames('ImgUpload', className)}
      {...uploadProps}
    >
      {imgUrl ? (
        <Avatar className="uploadedImg" shape="square" src={imgUrl} />
      ) : (
        <div>
          {loading ? <LoadingOutlined /> : <PlusOutlined />}
          <div style={{ marginTop: 8 }}>上传图片</div>
          {size && (
            <div style={{ marginTop: 4 }}>
              ({size[0]}x{size[1]})
            </div>
          )}
        </div>
      )}
    </Upload>
  )
}

export const ImgUploadOrAdd = ({ value, onChange, ...props }: ImgUpload) => {
  const [useUrl, setUseUrl] = useState(false)

  let content = <></>
  if (useUrl) {
    content = (
      <Input
        placeholder="请输入图片url地址"
        value={value}
        onChange={e => onChange(e?.target?.value)}
      />
    )
  } else {
    content = <ImgUpload {...props} value={value} onChange={onChange} />
  }

  return (
    <div className="ImgUploadOrAdd">
      {content}
      <Link className="change" onClick={() => setUseUrl(!useUrl)}>
        {useUrl ? '没有url?上传图片' : '设置已有图片url'}
      </Link>
    </div>
  )
}
.ImgUpload {
  .uploadedImg {
    display: block;
    width: 100%;
    height: 100%;
    img {
      object-fit: contain;
    }
  }
}

.ImgUploadOrAdd {
  .ant-upload-picture-card-wrapper {
    width: auto;
    .ant-upload-select {
      margin: 0;
    }
  }
  .change {
    text-decoration: underline;
    vertical-align: bottom;
    margin-left: 16px;
  }
}