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} //根据业务需求填写
 />
相关推荐
I_Am_Me_11 分钟前
【JavaEE进阶】 JavaScript
开发语言·javascript·ecmascript
℘团子এ22 分钟前
vue3中如何上传文件到腾讯云的桶(cosbrowser)
前端·javascript·腾讯云
学习前端的小z27 分钟前
【前端】深入理解 JavaScript 逻辑运算符的优先级与短路求值机制
开发语言·前端·javascript
前端百草阁1 小时前
【TS简单上手,快速入门教程】————适合零基础
javascript·typescript
彭世瑜1 小时前
ts: TypeScript跳过检查/忽略类型检查
前端·javascript·typescript
FØund4041 小时前
antd form.setFieldsValue问题总结
前端·react.js·typescript·html
Backstroke fish1 小时前
Token刷新机制
前端·javascript·vue.js·typescript·vue
zwjapple1 小时前
typescript里面正则的使用
开发语言·javascript·正则表达式
小五Five1 小时前
TypeScript项目中Axios的封装
开发语言·前端·javascript
临枫5411 小时前
Nuxt3封装网络请求 useFetch & $fetch
前端·javascript·vue.js·typescript