react+ts上传图片的新需求,通过复制微信的图片直接粘贴上传

106 阅读3分钟

一直用的antd design的# Upload上传,突然接到一个客户需求说,看到别人可以从微信上直接复制,然后粘贴到我们系统直接上传了,感觉很方便,不需要先在微信上下载到电脑,然后在上传。 我操作了下在电脑端微信上只要点开图片就可以拖拽到上传组件上传,这点upload组件已经实现了 但是复制粘贴没有,直接开干! 思路: 1.首先在微信上右键复制后,可以获取到电脑剪切板的内容 2.upload组件需要监听粘贴事件onPaste 3.因为我这里是在一个表单里,所以还要获取焦点 4.监听粘贴事件后,然后通过判断是否是图片类型,如果是就通过FormData参数调用后台的接口上传 5.上传成功后同时赋值给upload组件设置值 思路有了,写代码

import { FC, useState, useEffect, useCallback } from "react";
import {
  UploadOutlined,
  LoadingOutlined,
  PlusOutlined,
} from "@ant-design/icons";
import ImgCrop from "antd-img-crop";
import {
  Button,
  Upload,
  message,
  UploadProps,
  UploadFile,
  RcFile,
  Modal,
} from "antd";
import { listRequest, removeRequest } from "service/common/upload";
import classNames from "classnames";
import axios from "axios";
import "./style.scss";

// 表单传入参数类型定义
interface IProps {
  bizId?: string;
  bizType?: string;
  uploadTip?: string;
  ifImgCrop?: boolean;
  btnSize?: "large" | "middle" | "small";
  btnTitle?: string;
  isBtn?: boolean;
  getFileList?: (fileList) => void; // 回调
  hideBtn?: boolean;
  dependBizId?: boolean; // 是否依赖主键ID;做文件上传,文件删除。
}
type IUploadProps = UploadProps & IProps;

const getBase64 = (file: RcFile): Promise<string> =>
  new Promise((resolve, reject) => {
    const reader = new FileReader();
    reader.readAsDataURL(file);
    reader.onload = () => resolve(reader.result as string);
    reader.onerror = (error) => reject(error);
  });
/**
 * 上传文件
 * @param props
 * @returns
 */
const UploadFileCopy: FC<IUploadProps> = (props) => {
  const {
    bizType,
    bizId,
    getFileList,
    maxCount,
    listType,
    disabled,
    className,
    uploadTip,
    ifImgCrop,
    btnTitle,
    btnSize,
    isBtn,
    hideBtn,
    dependBizId = true,
    ...restProps
  } = props;
  const [loading, setLoading] = useState(false);
  const [previewOpen, setPreviewOpen] = useState(false);
  const [previewImage, setPreviewImage] = useState("");
  const [previewTitle, setPreviewTitle] = useState("");
  const [fileList, setFileList] = useState<UploadFile[]>([]);
  const classes = classNames(
    className,
    !isBtn
      ? listType === "picture-card"
        ? "upload-image-div"
        : "upload-files-div"
      : ""
  );

  // 将fileList传递给上层组件
  useEffect(() => {
    // 完成上传后,才通知外面改变数据
    let checkPass = true;
    for (let i = 0; i < fileList.length; i++) {
      const item = fileList[i];
      if (item.status !== "done") {
        checkPass = false;
        break;
      }
    }
    if (checkPass) {
      getFileList && getFileList(fileList);
    }
  }, [fileList, getFileList]);

  /**
   * 上传文件参数以及方法
   */
  const filesProps: any = {
    name: "files",
    maxCount: maxCount,
    multiple: maxCount == 1 ? false : true,
    listType: listType,
    showUploadList: {
      showDownloadIcon: true, // 显示下载按钮
    },
    data: {
      bizType,
      bizId: dependBizId ? bizId : null,
    },
    disabled: disabled,
    action: "/mapi/zserp/file/uploadFile",
    fileList: fileList,
    className: classes,
  };
  /**
   *  删除方法
   */
  const onRemove = (file: any) => {
    if (dependBizId) {
      return new Promise((resolve, reject) => {
        removeRequest({ id: file.uid }).then((data) => {
          resolve(true);
        });
      });
    } else {
      return true;
    }
  };
  /**
   * change方法
   * @param param0
   */
  const onChange = ({ file, fileList }) => {
    // 状态处理
    if (file.status === "uploading") setLoading(true);
    if (file.status === "done") setLoading(false);
    // 方法处理
    let tempFileList = [...fileList];
    let newFileList: any[] = [];
    tempFileList.forEach((file: any) => {
      if (file.response) {
        if (file.response?.code != 0) {
          message.error(file.response?.msg || "文件上传失败");
        } else {
          let list = file.response?.data?.list;
          if (list && list.length > 0) {
            newFileList.push(setStandardFileObj(list[0]));
          }
        }
      } else {
        newFileList.push(file);
      }
    });
    console.log(newFileList, "55555555555555");
    setFileList(newFileList);
  };
  const beforeUpload = (file) => {
    if (maxCount && fileList.length >= maxCount) {
      return Upload.LIST_IGNORE;
    }
  };
  const setStandardFileObj = (item: any) => {
    let obj = {
      uid: item.id,
      name: item.name,
      status: "done",
      path: item.path,
      url: "/sfile/" + item.path,
    };
    // 本地测试

    if (window.location.host.indexOf("localhost") != -1) {
      obj.url = "http://xxxxx:5000" + obj.url;
    }
    return obj;
  };
  /**
   * 图片上传
   */
  const uploadButton = (
    <div>
      {loading ? <LoadingOutlined /> : <PlusOutlined />}
      <div style={{ marginTop: 8 }}>{uploadTip || "上传图片"}</div>
    </div>
  );
  /**
   * 打开图片预览
   * @param file
   */
  const handlePreview = async (file: UploadFile) => {
    if (!file.url && !file.preview) {
      file.preview = await getBase64(file.originFileObj as RcFile);
    }
    setPreviewImage(file.url || (file.preview as string));
    setPreviewOpen(true);
    setPreviewTitle(
      file.name || file.url!.substring(file.url!.lastIndexOf("/") + 1)
    );
  };
  const setDefaultFileList = useCallback((list: any) => {
    let newList: any[] = [];
    list.forEach((item: any) => {
      newList.push(setStandardFileObj(item));
    });
    setFileList(newList);
  }, []);
  // 有业务数据改变,文件列表置空
  useEffect(() => {
    setFileList([]);
    if (!bizId || !bizType) return;
    listRequest({ bizId, bizType }).then((res) => {
      let list = res?.data?.list || [];
      setDefaultFileList(list);
    });
  }, [bizId, bizType, setDefaultFileList]);
  // const handleBeforeUpload = (file) => {
  //   // 处理上传前的逻辑,例如检查文件类型、大小等
  //   // 如果需要上传,则返回 true;否则返回 false。
  //   return true;
  // };
  /**
   * 粘贴图片上传功能
   * @param event
   */
  const handlePaste = (event) => {
    let paramsData: any = [];
    const items = (event.clipboardData || event.originalEvent.clipboardData)
      .items;

    for (let i = 0; i < items.length; i++) {
      if (items[i].type.indexOf("image") !== -1) {
        // 只处理图片类型
        const files = items[i].getAsFile();
        const formData = new FormData();
        let _bizId: any = dependBizId ? bizId : "";
        paramsData.push(files);
        formData.append("files", files);
        formData.append("bizType", bizType || "");
        formData.append("bizId", _bizId);

        axios
          .post("/mapi/xxx/xxx/xxx", formData)
          .then((response) => {
            let copyData = response?.data?.data?.list;
            if (copyData && copyData.length) {
              setPasteList(copyData);
            }
          })
          .catch((error) => {});
      } else {
        message.warning("只支持图片进行粘贴!");
        return;
      }
    }
  };

  //处理粘贴的图片
  const setPasteList = (list: any) => {
    let tempFileList = [...fileList];
    let newList: any[] = [];
    list.forEach((item: any) => {
      newList.push(setStandardFileObj(item));
    });
    tempFileList = tempFileList.concat(newList);
    setFileList(tempFileList);
  };

  return (
    <>
      {ifImgCrop ? (
        <ImgCrop rotationSlider>
          <Upload
            {...filesProps}
            {...restProps}
            onChange={onChange}
            onRemove={onRemove}
            onPreview={handlePreview}
          >
            {hideBtn || (maxCount && fileList.length >= maxCount)
              ? null
              : uploadButton}
          </Upload>
        </ImgCrop>
      ) : (
        <div tabIndex={-1} onPaste={handlePaste}>
          <Upload
            {...filesProps}
            {...restProps}
            beforeUpload={beforeUpload}
            onChange={onChange}
            onRemove={onRemove}
            onPreview={handlePreview}
          >
            {hideBtn || (maxCount && fileList.length >= maxCount)
              ? null
              : listType === "picture-card" || listType === "picture-circle"
              ? // 图片
                uploadButton
              : // 文件
                !disabled && (
                  <>
                    <Button
                      size={btnSize || "small"}
                      type="primary"
                      disabled={
                        (maxCount && fileList.length >= maxCount) || false
                      }
                      icon={<UploadOutlined />}
                    >
                      {btnTitle || "上传"}
                    </Button>
                    {maxCount && (
                      <span className="upload_tips">
                        {`最多上传${maxCount}个文件`}
                      </span>
                    )}
                  </>
                )}
          </Upload>
        </div>
      )}
      {(listType === "picture-card" || listType === "picture-circle") && (
        <div className="image_paste_div">
          <p>可进行复制图片粘贴上传</p>
        </div>
      )}

      <Modal
        open={previewOpen}
        title={previewTitle}
        footer={null}
        onCancel={() => setPreviewOpen(false)}
      >
        <img alt="example" style={{ width: "100%" }} src={previewImage} />
      </Modal>
    </>
  );
};
export default UploadFileCopy;

这是整个上传图片和编辑图片的组件,写在一起了,其中开发途中遇到了一个坑 1.formData传参时,其他业务的参数也只能通过formData.append("bizId", _bizId);这种方式传,axios接收formData形式的参数固定只能传formData对象 2.div必须要设置好焦点( tabIndex={-1})这样设置,不然监听不到 其他就没什么了,还是比较简单的,有什么不懂的可以找我QQ 2981739544