React18+Antd封装上传到阿里云的上传组件

这个组件目前还不是很完善 比如自定义占位
组件是老早就写了 但是一直有异常问题没有处理 今天处理了一下oss授权参数过期的情况 但是还是感觉不完美 求意识到的大佬点播

贴下源码

// AliyunOSSUpload.tsx

import { Modal, Upload, UploadFile, UploadProps } from "antd";
import Icon from "@/components/Icon";
import { message } from "@/hooks/useTips";

import { getBase64 } from "@/utils/utils";
import { ReactNode, useEffect, useState } from "react";
import { type OssParams, getFilename, getOSSParams, getYMDStr } from "@/utils/upload";

type Props = {
  fileList: UploadFile[];
  accept?: string;
  maxLength?: number;
  children?: ReactNode;
  onChange: (list: UploadFile[]) => void;
};

const AliOssUpload = (props: Props) => {
  const { fileList, accept = "image/*", maxLength = 10, onChange } = props;

  const [ossData, setOssData] = useState<OssParams>();
  const [previewOpen, setPreviewOpen] = useState(false);
  const [previewImage, setPreviewImage] = useState("");
  const [previewTitle, setPreviewTitle] = useState("");

  const handleChange: UploadProps["onChange"] = ({ file, fileList }) => {
    let files = [...fileList];
    const url = `${ossData?.host}/${ossData?.dir}${getYMDStr()}/${file.fileName}`;
    if (file.status === "done") {
      file.url = url;
    }
    if (file.status === "error") {
      files = files.filter(item => item.status !== "error");
      message.error("上传失败,请稍后重试");
    }
    onChange(files);
  };

  const getExtraData: UploadProps["data"] = file => {
    const subDir = `${getYMDStr()}/`;
    const dir = ossData?.dir;
    // 传递给后端的额外参数
    return {
      key: `${dir}${subDir}${file.fileName}`,
      OSSAccessKeyId: ossData?.accessId,
      policy: ossData?.policy,
      success_action_status: "200",
      Signature: ossData?.signature
    };
  };

  const handleBeforeUpload: UploadProps["beforeUpload"] = async (file: File) => {
    await fetchOSSParams();

    const isLt5M = file.size / 1024 / 1024 < 5;

    return new Promise(resolve => {
      if (!isLt5M) {
        message.error("图片大小不能超过5M");
        return false;
      } else {
        // eslint-disable-next-line @typescript-eslint/ban-ts-comment
        // @ts-expect-error
        file.fileName = getFilename(file.name);
        return resolve(true);
      }
    });
  };

  const handlePreview = async (file: UploadFile) => {
    if (!file.url && !file.preview) {
      file.preview = await getBase64(file.originFileObj as File);
    }
    setPreviewImage(file.url || (file.preview as string));
    setPreviewOpen(true);
    setPreviewTitle(file.name || file.url!.substring(file.url!.lastIndexOf("/") + 1));
  };

  const handleCancel = () => setPreviewOpen(false);

  const uploadProps = {
    name: "file",
    fileList: fileList,
    action: ossData?.host,
    accept: accept,
    onChange: handleChange,
    data: getExtraData,
    beforeUpload: handleBeforeUpload,
    onPreview: handlePreview
  };

  const fetchOSSParams = async () => {
    const ossParams = await getOSSParams();
    setOssData(ossParams);
  };

  useEffect(() => {
    fetchOSSParams();
  }, []);

  return (
    <>
      <Upload {...uploadProps} listType="picture-card" multiple={true} fileList={fileList}>
        {fileList.length >= maxLength ? null : (
          <div>
            <Icon name="PlusOutlined" antd />
            <div className="mt-2">上传</div>
          </div>
        )}
      </Upload>
      <Modal open={previewOpen} title={previewTitle} footer={null} onCancel={handleCancel}>
        <img alt="example" className="w-full" src={previewImage} />
      </Modal>
    </>
  );
};

export default AliOssUpload;
// upload.ts

import commonApi from "@/api/interface/common";
import { localStore } from "./storage";
import { OSS_PARAMS_KEY } from "@/const";

export type OssParams = {
  accessId: number;
  host: string;
  policy: string;
  signature: string;
  dir: string;
  expire: number;
};

export const getOSSParams = async () => {
  const KEY = OSS_PARAMS_KEY;
  let ossParams = localStore.get(KEY);
  const expired = ossParams && ossParams?.expire < Date.now();
  if (!ossParams || expired) {
    const res = await commonApi.getOSSParams();
    ossParams = {
      accessId: res.accessKeyId,
      host: res.accessURL,
      policy: res.securityToken,
      signature: res.signature,
      dir: res.dir,
      expire: res.expiresAt
    };
    localStore.set(KEY, ossParams);
  }
  return ossParams;
};

const getFileSuffix = (fileName: string) => {
  const pos = fileName.lastIndexOf(".");
  let suffix = "";
  if (pos !== -1) {
    suffix = fileName.substring(pos);
  }
  return suffix;
};

export const genRandomStr = (length: number) => {
  const chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
  const charLength = chars.length;
  let result = "";
  for (let i = 0; i < length; i++) {
    result += chars.charAt(Math.floor(Math.random() * charLength));
  }
  return result;
};

export const getYMDStr = () => {
  const now = new Date();
  const YYYY = now.getFullYear();
  const MM = now.getMonth() + 1;
  const DD = now.getDate();
  return `${YYYY}${MM > 9 ? MM : `0${MM}`}${DD > 9 ? DD : `0${DD}`}`;
};

export const getFilename = (filePath: string) => {
  return `${genRandomStr(8)}-${genRandomStr(8)}-${genRandomStr(8)}${getFileSuffix(filePath)}`;
};

export function downloadURLFile(url: string) {
  const element = document.createElement("a");
  element.download = url;
  element.style.display = "none";
  element.href = url;
  document.body.appendChild(element);
  element.click();
  document.body.removeChild(element);
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值