腾讯COS上传图片封装

cos-js-sdk-v5 封装 适用于微信浏览器H5环境
Github地址:https://github.com/ihopefulChina/Tencent-COS-uploads-picture

config.ts

import { genID, get_suffix } from '~/tools/tools';
import { autobind } from 'core-decorators';
import COS from 'cos-js-sdk-v5';

const Bucket = 'Bucket值';
const Region = 'Region值';

@autobind
export default class COSUpload {
  private getOssToken: () => Promise<any>

  constructor(config: {
    getOssToken: () => Promise<any>
  }) {
    this.getOssToken = config.getOssToken;
  }

  /**
   * 批量上传
   * @param {Object} obj Bucket、Region、Body 详情查看cos文档
   * @returns err || 批量上传filesData
   */
  uploadFiles(fileList: any) {
    // eslint-disable-next-line no-async-promise-executor
    return new Promise(async (resolve, reject) => {
      const { cos, content } = await this.getOss();
      const files = await fileList.map((fileObject: any) => {
        const file = fileObject.file ? fileObject.file : fileObject;
        const fileName = `${content.requestId}${genID(3)}.${file.path ? get_suffix(file.path) : 'png'}`;
        return {
          Bucket: Bucket,
          Region: Region,
          Key: fileName,
          StorageClass: 'STANDARD',
          Body: file,
        };
      });
      await cos.uploadFiles({
        files,
        SliceSize: 1024 * 1024 * 5,    /* 设置大于5MB采用分块上传 */
      }, function (err: any, data: any) {
        const val = (err || data);
        const list = !err ? val.files.map((item: any) => `https://${item.data.Location}`
        ) : [];
        const newArr = list.filter((it: string) => it);
        return err ? reject(err) : resolve(newArr);
      });
    });
  }


  private async getOss() {
    const data = await this.getOssToken();
    const content = data as any;
    const credentials = data.credentials;
    // console.log(window,wx);
    const cos = new COS({
      getAuthorization: function (options: any, callback: any) {
        callback({
          TmpSecretId: credentials.tmpSecretId,
          TmpSecretKey: credentials.tmpSecretKey,
          SecurityToken: credentials.sessionToken,
          // 建议返回服务器时间作为签名的开始时间,避免用户浏览器本地时间偏差过大导致签名错误
          StartTime: data.startTime, // 时间戳,单位秒,如:1580000000
          ExpiredTime: data.expiredTime, // 时间戳,单位秒,如:1580000900
        });
      }
    });

    return {
      cos,
      content
    };
  }
}

index.ts

import COSUpload from './config';
import { get } from "~/request";

<!-- get请求 -->
const { uploadFiles } = new COSUpload({
  getOssToken: () => get(`xxx.com/tencent/cos-token`)
});

export { uploadFiles };

引入文件内容

/**
 * @description: 生成唯一id
 * @param {number} length 数字长度
 * @return {string} 
 */
export function genID(length = 3) {
    return Number(Math.random().toString().substr(3, length) + Date.now()).toString(36);
}

//获取文件名称后缀
export const get_suffix = (fileName: string) => {
    const suffix = fileName.substring(fileName.lastIndexOf('.') + 1);
    return suffix;
};

上传多张图片组件

/* 组件 -- 上传 */
import React, { useState } from "react";
import WxImageViewer from "react-wx-images-viewer";
import { uploadFiles } from "~/components/tencent";
import { Toast } from "antd-mobile";
import Compressor from "compressorjs";

import styles from "./index.module.less";

interface IProps {
  imgList?: string[];
  onChange: (list: string[]) => void;
  onDelete: (idx: number) => void;
  progressChange?: (percent: number) => void;
  count?: number;
}

const Index = ({ imgList = [], onChange, onDelete, count = 2 }: IProps) => {
  const [showViewer, setShowViewer] = useState(false);
  const [imgIndex, setImgIndex] = useState(0);

  const onInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    const fileList = e.target.files;

    if (imgList.length < count) {
      //代表还可以上传
      if (fileList && fileList.length > 0) {
        const newFileList = [] as any;
        for (const key in fileList) {
          if (Object.prototype.hasOwnProperty.call(fileList, key)) {
            const element = fileList[key] as any;
            element && newFileList.push(element);
          }
        }
        let paths = [];
        if (imgList.length + newFileList.length < count) {
          paths = newFileList;
        } else {
          //数量超过了
          const surplus = count - imgList.length;
          paths = newFileList.slice(0, surplus);
          // paths = newFileList.filter((item, index) => index < surplus);
        }

        //压缩图片
        const newPaths = paths.map((image: any) => {
          let newImg = image;
          new Compressor(image, {
            quality: 0.5, // 0.6 can also be used, but its not recommended to go below.
            success: (res) => (newImg = res),
          });
          return newImg;
        });
        Toast.loading("上传中...");
        uploadFiles(newPaths).then((res: any) => {
          onChange && onChange([...imgList, ...res]);
          Toast.hide();
        });
      }
    }
  };

  return (
    <div className={styles.upload}>
      <div className={styles.uploadList}>
        {imgList.map((item, index) => (
          <div
            key={`${item}`}
            className={styles.item}
            onClick={(event) => {
              event.stopPropagation();
              setShowViewer(true);
              setImgIndex(index);
              document
                .getElementsByTagName("body")[0]
                .setAttribute("style", "overflow:hidden"); //给body添加overflow:hidden阻止遮罩层滚动主页面滚动
            }}
          >
            <img
              className={styles.img}
              data-preview-proto={item}
              src={item}
              alt="上传图片"
            />
            <img
              className={styles.del}
              src={require("./images/del.png")}
              alt="删除按钮"
              onClick={(event) => {
                event.stopPropagation();
                onDelete(index);
              }}
            />
          </div>
        ))}

        {imgList.length < count && (
          <div className={styles.uploadBtn}>
            <input
              type="file"
              name="image"
              accept="image/*"
              multiple={true}
              className={styles.uploadInput}
              onChange={onInputChange}
            />
            <div className={styles.uploadPlus}>
              <img src={require("./images/plus.png")} alt="加号" />
              <span>
                ({imgList.length}/{count})
              </span>
            </div>
          </div>
        )}
      </div>
      {showViewer ? (
        <WxImageViewer
          onClose={() => {
            setShowViewer(false);
            document.getElementsByTagName("body")[0].removeAttribute("style");
          }}
          urls={imgList}
          index={imgIndex}
        />
      ) : (
        ""
      )}
    </div>
  );
};
export default Index;

引用组件

import Upload from "~/components/upload";
<Upload
                  imgList={form.discoverServiceImgs}
                  count={6}
                  onChange={(imgs) =>
                    setForm({ ...form, discoverServiceImgs: imgs })
                  }
                  onDelete={(idx) =>
                    setForm({
                      ...form,
                      discoverServiceImgs: form.discoverServiceImgs.filter(
                        (_value: any, _index: any) => _index !== idx
                      ),
                    })
                  }
                />