react 基于 antd-img-crop 封装的图片裁切上传组件

参考 antd 的官网图片裁剪案例就基本能实现效果了,但是还需要进一步封装成自己想要的样子。
https://ant.design/components/upload-cn/

antd-img-crop 中文文档:
https://github.com/nanxiaobei/antd-img-crop/blob/main/README.zh-CN.md

props结构

需要封装的组件,可以携带一个图片地址字符串的数组,并且改变这个数组就可以更改;当然携带 antd 的文件数组也是需要支持的。根据需求我自己定义的 props 如下。

type PropsType = {
  /** 图片地址数组 */
  urls?: string[];
  /** 图片文件对象数组  */
  fileList?: UploadFile[];
  /** 裁切区域宽高比,width / height */
  aspect?: number;
  /** 是否弹出删除确认框 默认:true */
  isRemovePop?: boolean;
  /** 上传图片的最大数量 默认: 5 */
  maxLength?: number;
  /** 是否展示文件列表 */
  showUploadList?: boolean;
  children?: ReactNode;
  beforeUpload?: (file: File) => Promise<File>;
  onChange: (urls: string[], fileList?: UploadFile[]) => void;
};
使用方式
  • url数组方式
const [ulrs, setUrls] = useState<string[]>([]);
return (
<UploadCropperImg
  urls={urls}
  onChange={setUrls}
  aspect={760 / 1140}  // 这个就是长宽比
/>
)
  • file文件数组形式
const [fileList, setFileList] = useState<UploadFile[]>([]);
useEffect(() => {
  getData()
}, [])
// 从接口获取数据
const getData = async () => {
  const data = await getDataAPI()
  const {imgList} = data
  const newArr = imgList?.map((url) => {
    return {url: url, uid: url, name: url,}
  })
  setFileList(newArr)
}
function beforeUpload(file: File) {
  return new Promise<File>((resolve, reject) => {
    const isLt2M = file.size / 1024 / 1024 < 2;
    if (!isLt2M) {
      message.error('图片最大不超过 2MB!');
      return reject(false);
    }
    return resolve(file);
});
return (
<UploadCropperImg
  aspect={1920/1080}
  fileList={fileList}
  beforeUpload={beforeUpload}
  onChange={(urls, fileList) => {
    setFileList(fileList ?? []);
  }}
>
  <div>上传可以裁剪的图片</div>
</UploadCropperImg>
)
完整代码
import { useState, useEffect, ReactNode } from 'react';
import { Upload, message, Modal } from 'antd';
import ImgCrop from 'antd-img-crop';
// 注意:这两个样式需要导入,不然样式可能会有问题
// 弹出层的样式
import 'antd/es/modal/style';
// 这是滑动条的样式
import 'antd/es/slider/style';
import './index.less';
import type {
  UploadChangeParam,
  RcFile,
  UploadFile,
  UploadProps,
} from 'antd/lib/upload/interface';
import { BASE_URL, CDN_URL } from '@/configs/base';
// 这里是后端报错返回的类型
import { ResponseError } from '@/service/type';
// 这里是后端接口返回的数据类型
import { UploadResponse } from '@/service/api-oss';
// import { getBase64 } from '@/utils/handleFile';
import { PlusOutlined } from '@ant-design/icons';

const { confirm } = Modal;

type PropsType = {
  /** 图片地址数组 */
  urls?: string[];
  /** 图片文件对象数组  */
  fileList?: UploadFile[];
  /** 裁切区域宽高比,width / height */
  aspect?: number;
  /** 是否弹出删除确认框 默认:true */
  isRemovePop?: boolean;
  /** 上传图片的最大数量 默认: 5 */
  maxLength?: number;
  /** 是否展示文件列表 */
  showUploadList?: boolean;
  children?: ReactNode;
  beforeUpload?: (file: File) => Promise<File>;
  onChange: (urls: string[], fileList?: UploadFile[]) => void;
};

/** https://github.com/nanxiaobei/antd-img-crop/blob/main/README.zh-CN.md */

/** 可裁剪的上传图片组件 */
export default ({
  urls = [],
  fileList: imgList,
  aspect,
  isRemovePop = true,
  maxLength = 5,
  showUploadList = true,
  children,
  beforeUpload,
  onChange,
}: PropsType) => {
  const [fileList, setFileList] = useState<UploadFile[]>([]);
  const [previewVisible, setPreviewVisible] = useState(false);
  const [previewImage, setPreviewImage] = useState('');

  /** 初始化url */
  useEffect(() => {
    if (!fileList.length && urls?.length) {
      const arr: UploadFile[] =
        urls?.map((url) => {
          const reg = new RegExp(CDN_URL);
          const name = url.replace(reg, '');
          return { uid: name, name, url };
        }) ?? [];
      setFileList(arr);
    }
  }, [urls]);
  useEffect(() => {
    if (imgList) {
      setFileList(imgList);
    }
  }, [imgList]);

  const _onChange: UploadProps['onChange'] = (
    res: UploadChangeParam<UploadFile<UploadResponse[]>>,
  ) => {
    // console.log('_onChange: ', res);
    setFileList(res.fileList);
    if (res.file.status === 'uploading') {
      return;
    } else if (res.file.status === 'done') {
      // 上传完成
      if (!res?.file?.response?.[0]?.url) return;
      const resUrl = res?.file?.response[0]?.url;
      onChange?.([...urls, resUrl], res.fileList);
    } else if (res.file.status === 'error') {
      message.error({
        content: (res?.file?.response as unknown as ResponseError)?.msg,
      });
    }
  };
  
  /** 预览图片 */
  const onPreview = async (file: UploadFile) => {
    if (!file.url && !file.preview) {
      file.preview = await getBase64(file.originFileObj as RcFile);
    }
    setPreviewImage(file.url || (file.preview as string));
    setPreviewVisible(true);
  };

  /** 删除图片 */
  const onRemove = (res: UploadFile) => {
    if (isRemovePop) {
      return new Promise<boolean>((resolve) =>
        confirm({
          title: '您确定删除图片吗?',
          okText: '确定',
          cancelText: '取消',
          onOk: () => {
            const index = fileList.findIndex((item) => item.uid === res.uid);
            if (index !== -1) {
              urls.splice(index, 1);
              onChange([...urls]);
            }
            resolve(true);
          },
          onCancel: () => resolve(false),
        }),
      );
    } else {
      const index = fileList.findIndex((item) => item.uid === res.uid);
      if (index !== -1) {
        urls.splice(index, 1);
        onChange([...urls]);
      }
      return true;
    }
  };

  const imgCropProps = {
    // 裁切区域宽高比,width / height
    aspect: aspect,
    // 启用图片旋转
    rotate: true,
    // 显示裁切区域网格(九宫格)
    grid: true,
  };

  return (
    <div className="com-uploadCropperImg">
      <ImgCrop {...imgCropProps}>
        <Upload
          name="files"
          action={BASE_URL + 'api/master/oss/fromFiles?fileType=img'}
          headers={{
            'x-master-session-token':
              localStorage.getItem('x-master-session-token') ||
              sessionStorage.getItem('x-master-session-token') ||
              '',
          }} // 以上三个属性根据自己的api需求更改就好了
          showUploadList={showUploadList}
          listType="picture-card"
          fileList={fileList}
          beforeUpload={beforeUpload}
          onChange={_onChange}
          onPreview={onPreview}
          onRemove={onRemove}
        >
          {fileList.length < maxLength && (
            <>
              {children ?? (
                <div>
                  <PlusOutlined />
                  <div style={{ marginTop: 8 }}>Upload</div>
                </div>
              )}
            </>
          )}
        </Upload>
      </ImgCrop>
      <Modal
        visible={previewVisible}
        title="预览图片"
        footer={null}
        onCancel={() => setPreviewVisible(false)}
      >
        <div className={`uploadCropperImg-modal`}>
          <img src={previewImage} className={`uploadCropperImg-modalImg`} />
        </div>
      </Modal>
    </div>
  );
};

/** 获取antd的base64 */
export 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);
  });

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值