React+umi+antdesign实现上传文件组件(阿里云)

开始之前需要封装一个上传的方法fileUtils.ts

javascript 复制代码
import { message } from 'antd';
import Base64 from 'crypto-js/enc-base64';
import Utf8 from 'crypto-js/enc-utf8';
import HmacSHA1 from 'crypto-js/hmac-sha1';
import { request } from 'umi';

const isDev = process.env.NODE_ENV === 'development';

export namespace FileUtil {
  const env = {
    timeout: 10000,
    uploadHost: 'https://hopeman.oss-cn-beijing.aliyuncs.com/',
  };

  const genPolicy = () => {
    const date = new Date();
    date.setHours(date.getHours() + env.timeout);
    const srcT = date.toISOString();
    const policyText = {
      expiration: srcT,
      conditions: [
        ['content-length-range', 0, 1 * 1024 * 1024 * 1024], // 设置上传文件的大小限制1G
      ],
    };
    const rawStr = JSON.stringify(policyText);
    const wordArray = Utf8.parse(rawStr);
    const policyBase64 = Base64.stringify(wordArray);
    return policyBase64;
  };

  const genSignature = (policyBase64: string, accessKey: string) => {
    const byte = HmacSHA1(policyBase64, accessKey);
    const signature = Base64.stringify(byte);
    return signature;
  };

  export const upload = async (options: any, dir?: string): Promise<string> => {
    const fileInfo = options.file;
    const { onError = () => {}, onSuccess = () => {}, fullHost = '', ext = false } = options;

    return new Promise(async (resolve) => {
      const res = await request('/common/ram/assumeRole', {
        method: 'POST',
      });

      if (res?.code === 200 && res?.data) {
        const {
          Credentials: { AccessKeyId, AccessKeySecret, SecurityToken },
        } = res.data;

        const fileNameSplit = fileInfo?.name.split('.') || [];
        const aliyunFileKey = `${dir ?? 'excel'}/event_${new Date().getTime()}${
          ext ? '.' + fileNameSplit[fileNameSplit?.length - 1] : ''
        }`; //文件命名

        const policyBase64 = genPolicy();
        const signature = genSignature(policyBase64, AccessKeySecret);

        const data = new FormData();
        data.append('key', aliyunFileKey);
        data.append('policy', policyBase64);
        data.append('OSSAccessKeyId', AccessKeyId);
        data.append('signature', signature);
        data.append('x-oss-security-token', SecurityToken);
        data.append('file', fileInfo);
        data.append('success_action_status', '200');

        try {
          await request(isDev ? '/upload' : UPLOAD_HOST, {
            method: 'POST',
            data,
          });
          const url = `${fullHost}${aliyunFileKey}`;
          onSuccess(url);
          resolve(url);
        } catch (e) {
          onError(options);
          resolve('');
        }
      } else {
        message.error('上传失败');
        onError(options);
        resolve('');
      }
    });
  };

  export const base64CoverFile = (dataUrl: string) => {
    /**
     * base64 转 File
     * @param data
     */
    const arr = dataUrl.split(','),
      mime = arr?.[0]?.match(/:(.*?);/)?.[1],
      bstr = atob(arr[1]);

    let n = bstr.length;
    const u8arr = new Uint8Array(n);
    while (n--) {
      u8arr[n] = bstr.charCodeAt(n);
    }
    const blob = new Blob([u8arr], { type: mime });
    // blob.lastModifiedDate = new Date(); // 文件最后的修改日期
    // blob.name = fileName; // 文件名
    return new File([blob], Date.now().toString(), { type: blob.type, lastModified: Date.now() });
  };
  // 浏览器本页下载文件
  export const downFileByUrl = (url: string) => {
    if (!url || url.length === 0) {
      message.error('url 不存在');
      return;
    }
    const a = document.createElement('a');
    a.setAttribute('href', url);
    document.body.appendChild(a);
    a.click();
    document.body.removeChild(a);
  };

  export const downMultipleFileByUrl = (urls: string[]) => {
    if (!urls || urls.length === 0) {
      message.error('url 不存在');
      return;
    }
    for (let i = 0; i < urls.length; i++) {
      const iframe = document.createElement('iframe');
      iframe.style.display = 'none'; // 防止影响页面
      // @ts-ignore
      iframe.style.height = 0; // 防止影响页面
      iframe.src = urls[i];
      document.body.appendChild(iframe);
      // 5分钟之后删除
      setTimeout(() => {
        iframe.remove();
      }, 5 * 60 * 1000);
    }
  };
}
javascript 复制代码
//AliyunOssUpload 组件
import { FileUtil } from '**/fileUtils';
import { PlusOutlined, UploadOutlined } from '@ant-design/icons';
import type { UploadProps } from 'antd';
import { Button, Image, message, Upload } from 'antd';
import type { RcFile } from 'antd/lib/upload';
import type { UploadFile } from 'antd/lib/upload/interface';
import filesize from 'filesize';
import type { FC, ReactElement } from 'react';
import React, { Fragment, useState } from 'react';
import styles from './styles.less';

export type AliyunOssUploadProps = UploadProps & {
  value?: any;
  fullHost?: string | boolean; // 阿里云的Host,默认只返回目录,当 true 时,默认的是 UPLOAD_HOST
  limitSize?: number; // 文件大小限制单位 bytes(B) 默认是 2 * 1024 * 1024(2M) 0 表示不限制
  ext?: boolean; // 阿里云文件路径中是否包含文类后续名, 默认不包含文件类型后缀
  buttonRef?: any;
  readOnly?: boolean;
  uploadButtonRender?: ReactElement;
  maxCount?: number;
};

const UploadImageButton = React.forwardRef((props, ref: any) => {
  return (
    <div ref={ref} className={'aliyunUpBtn'}>
      <PlusOutlined className={'aliyunUpIcon'} />
      <div className={'aliyunUpTitle'} style={{ marginTop: 8 }}>
        上传图片
      </div>
    </div>
  );
});

const UploadNormalButton = React.forwardRef((props, ref: any) => (
  <Button ref={ref} icon={<UploadOutlined />}>
    点击上传
  </Button>
));

const AliyunOssUpload: FC<AliyunOssUploadProps> = (props) => {
  const [previewVisible, setPreviewVisible] = useState(false);
  const [previewSrc, setPreviewSrc] = useState('');
  let { fullHost = '' } = props;
  const {
    ext = false,
    disabled,
    buttonRef,
    readOnly = false,
    maxCount = 1,
    uploadButtonRender,
  } = props;

  fullHost = typeof fullHost === 'boolean' ? (fullHost ? UPLOAD_HOST : '') : fullHost;

  const uploadProps: UploadProps = {
    listType: 'picture-card',
    headers: {
      authorization: 'authorization-text',
    },
    maxCount: maxCount,
    customRequest: (options) => FileUtil.upload({ ...options, fullHost, ext }),
    onPreview: (file: UploadFile) => {
      const src = file?.response || file?.thumbUrl;
      if (src) {
        setPreviewSrc(src);
        setPreviewVisible(true);
      }
    },
    defaultFileList: props?.value?.fileList?.map((item: any) => ({
      uid: item?.uid,
      name: item?.url,
      url: item?.url,
      response: item?.url,
      thumbUrl: item?.url,
    })),
    beforeUpload: async (file: RcFile) => {
      const initAccept = props?.accept || 'image/png,image/jpg,image/jpeg';
      const accept = initAccept.split(',');

      const limitSize = props?.limitSize ?? 1024 * 1024 * 2;
      const isValidType = accept?.includes(file.type);
      const isValidSize = limitSize ? file.size < limitSize : true;
      if (!isValidType) {
        await message.error(`仅支持 ${initAccept} 格式`);
      }
      if (!isValidSize) {
        await message.error(
          `图片大小不能超过 ${filesize(limitSize, { base: 2, standard: 'jedec' })}`,
        );
      }
      return (isValidType && isValidSize) || Upload.LIST_IGNORE;
    },
    showUploadList: { showRemoveIcon: !readOnly },
    ...props,
  };

  const uploadButton = () => {
    if (readOnly || disabled) {
      return;
    }
    if (uploadProps?.maxCount) {
      if (props?.value?.fileList?.length >= uploadProps.maxCount) {
        return null;
      } else {
        if (uploadButtonRender) return uploadButtonRender;
        return props.listType === 'picture-card' || 'picture' ? (
          <UploadImageButton ref={buttonRef} />
        ) : (
          <UploadNormalButton ref={buttonRef} />
        );
      }
    } else {
      if (uploadButtonRender) return uploadButtonRender;
      return props.listType === 'picture-card' || 'picture' ? (
        <UploadImageButton ref={buttonRef} />
      ) : (
        <UploadNormalButton ref={buttonRef} />
      );
    }
  };

  if (!props.value?.fileList?.length && disabled) return <>-</>;

  return (
    <Fragment>
      <Upload className={styles.uploadBox} {...uploadProps}>
        {uploadButton()}
      </Upload>
      <div style={{ fontSize: 0, position: 'absolute' }}>
        <Image
          width={200}
          style={{ display: 'none' }}
          src={previewSrc}
          preview={{
            visible: previewVisible,
            src: previewSrc,
            onVisibleChange: (val) => {
              setPreviewVisible(val);
            },
          }}
        />
      </div>
    </Fragment>
  );
};

export default AliyunOssUpload;

在页面中使用 时

javascript 复制代码
<AliyunOssUpload
	accept={'image/png,image/jpg,image/jpeg'}
	fullHost
	maxCount={5} //根据业务需求填写
	listType={'picture-card'}
	limitSize={1024 * 1024 * 5} //根据业务需求填写
 />
相关推荐
懒大王爱吃狼41 分钟前
Python教程:python枚举类定义和使用
开发语言·前端·javascript·python·python基础·python编程·python书籍
小牛itbull2 小时前
ReactPress:重塑内容管理的未来
react.js·github·reactpress
待磨的钝刨2 小时前
【格式化查看JSON文件】coco的json文件内容都在一行如何按照json格式查看
开发语言·javascript·json
前端青山7 小时前
Node.js-增强 API 安全性和性能优化
开发语言·前端·javascript·性能优化·前端框架·node.js
从兄8 小时前
vue 使用docx-preview 预览替换文档内的特定变量
javascript·vue.js·ecmascript
清灵xmf9 小时前
在 Vue 中实现与优化轮询技术
前端·javascript·vue·轮询
昔我往昔9 小时前
阿里云文本内容安全处理
安全·阿里云·云计算
薛一半10 小时前
PC端查看历史消息,鼠标向上滚动加载数据时页面停留在上次查看的位置
前端·javascript·vue.js
过期的H2O210 小时前
【H2O2|全栈】JS进阶知识(四)Ajax
开发语言·javascript·ajax
MarcoPage10 小时前
第十九课 Vue组件中的方法
前端·javascript·vue.js