• AntDesign upload 多图可拖拽排序 Demo


    需要安装依赖

    yarn add react-dnd-html5-backend@14.1.0
    yarn add react-dnd@14.0.5
    yarn add immutability-helper@3.1.1
    

    直接上代码

    DragSortingUpload.tsx

    import React, { useState, useCallback } from 'react';
    import { Upload, Tooltip } from 'antd';
    import { DndProvider, useDrag, useDrop } from 'react-dnd';
    import { HTML5Backend } from 'react-dnd-html5-backend';
    import update from 'immutability-helper';
    import { PlusOutlined } from '@ant-design/icons';
    import { getBase64 } from '@/utils';
    import uploadRequest from '@/utils/uploadRequest';
    import Modal from 'antd/lib/modal/Modal';
    import { applyToken } from '../../services/ant-design-pro/qiniu';
    import './ManyUpload.less';
    
    const type = 'DragableUploadList';
    
    const DragableUploadListItem = ({ originNode, moveRow, file, fileList }: any) => {
      const ref = React.useRef(null);
      const index = fileList.indexOf(file);
      const [{ isOver, dropClassName }, drop] = useDrop({
        accept: type,
        collect: (monitor: any) => {
          const { index: dragIndex } = monitor.getItem() || {};
          if (dragIndex === index) {
            return {};
          }
          return {
            isOver: monitor.isOver(),
            dropClassName: dragIndex < index ? ' drop-over-downward' : ' drop-over-upward',
          };
        },
        drop: (item: any) => {
          moveRow(item.index, index);
        },
      });
      const [, drag] = useDrag({
        type,
        item: { index },
        collect: (monitor) => ({
          isDragging: monitor.isDragging(),
        }),
      });
      drop(drag(ref));
      const errorNode = <Tooltip title="Upload Error">{originNode.props.children}</Tooltip>;
      return (
        <div
          ref={ref}
          className={`ant-upload-draggable-list-item ${isOver ? dropClassName : ''}`}
          style={{ cursor: 'move', height: '100%' }}
        >
          {file.status === 'error' ? errorNode : originNode}
        </div>
      );
    };
    
    const uploadButton = (
      <div>
        <PlusOutlined />
        <div style={{ marginTop: 8 }}>Upload</div>
      </div>
    );
    
    const DragSortingUpload = () => {
      const [previewVisible, setPreviewVisible] = React.useState(false);
      const [previewImage, setPreviewImage] = React.useState('');
      const [previewTitle, setPreviewTitle] = React.useState('');
    
      const [fileList, setFileList] = useState<any>([
        {
          uid: '-1',
          name: 'image1.png',
          status: 'done',
          url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
        },
      ]);
    
      // 自定义上传 由于antDesign使用的是 RC.upload 组件 https://github.com/react-component/upload
      const customRequest = async (options: any) => {
        const { data } = await applyToken();
        options.action = data.url;
        options.data['token'] = data.token
        // 复制了RC.upload request的上传方法 https://github.com/react-component/upload/blob/master/src/request.ts
        uploadRequest(options);
      };
    
      const moveRow = useCallback(
        (dragIndex, hoverIndex) => {
          const dragRow = fileList[dragIndex];
          setFileList(
            update(fileList, {
              $splice: [
                [dragIndex, 1],
                [hoverIndex, 0, dragRow],
              ],
            }),
          );
        },
        [fileList],
      );
    
      const handleCancel = () => setPreviewVisible(false);
    
      const handlePreview = async (file: any) => {
        if (!file.url && !file.preview) {
          file.preview = await getBase64(file.originFileObj);
        }
        setPreviewVisible(true);
        setPreviewImage(file.url || file.preview);
        setPreviewTitle(file.name || file.url.substring(file.url.lastIndexOf('/') + 1));
      };
    
      return (
        <div className="many-upload">
          <DndProvider backend={HTML5Backend}>
            <Upload
              multiple
              customRequest={customRequest}
              fileList={fileList}
              onPreview={handlePreview}
              listType="picture-card"
              onChange={(e) => {
                // console.log("change?", e);
                setFileList(e.fileList);
              }}
              onRemove={(e: any) => {
                const arr = fileList.filter((v: any) => e.uid !== v.uid);
                setFileList(arr);
              }}
              itemRender={(originNode, file, currFileList) => (
                <DragableUploadListItem
                  originNode={originNode}
                  file={file}
                  fileList={currFileList}
                  moveRow={moveRow}
                />
              )}
            >
              {fileList.length >= 8 ? null : uploadButton}
            </Upload>
          </DndProvider>
          <Modal visible={previewVisible} title={previewTitle} footer={null} onCancel={handleCancel}>
            <img alt="preview" style={{  '100%' }} src={previewImage} />
          </Modal>
        </div>
      );
    };
    
    export default DragSortingUpload;
    

    此文件是复制 RC.upload 的 request.ts
    @/utils/uploadRequest.ts

    
    import type { UploadRequestOption, UploadRequestError, UploadProgressEvent } from './interface';
    
    
    function getError(option: UploadRequestOption, xhr: XMLHttpRequest) {
      const msg = `cannot ${option.method} ${option.action} ${xhr.status}'`;
      const err = new Error(msg) as UploadRequestError;
      err.status = xhr.status;
      err.method = option.method;
      err.url = option.action;
      return err;
    }
    
    function getBody(xhr: XMLHttpRequest) {
      const text = xhr.responseText || xhr.response;
      if (!text) {
        return text;
      }
    
      try {
        return JSON.parse(text);
      } catch (e) {
        return text;
      }
    }
    
    export default function upload(option: UploadRequestOption) {
      // eslint-disable-next-line no-undef
      const xhr = new XMLHttpRequest();
    
      if (option.onProgress && xhr.upload) {
        xhr.upload.onprogress = function progress(e: UploadProgressEvent) {
          if (e.total > 0) {
            e.percent = (e.loaded / e.total) * 100;
          }
          option.onProgress(e);
        };
      }
    
      // eslint-disable-next-line no-undef
      const formData = new FormData();
    
      if (option.data) {
        Object.keys(option.data).forEach(key => {
          const value = option.data[key];
          // support key-value array data
          if (Array.isArray(value)) {
            value.forEach(item => {
              // { list: [ 11, 22 ] }
              // formData.append('list[]', 11);
              formData.append(`${key}[]`, item);
            });
            return;
          }
    
          formData.append(key, value as string | Blob);
        });
      }
    
      // eslint-disable-next-line no-undef
      if (option.file instanceof Blob) {
        formData.append(option.filename, option.file, (option.file as any).name);
      } else {
        formData.append(option.filename, option.file);
      }
    
      xhr.onerror = function error(e) {
        option.onError && option.onError(e);
      };
    
      xhr.onload = function onload() {
        // allow success when 2xx status
        // see https://github.com/react-component/upload/issues/34
        if (xhr.status < 200 || xhr.status >= 300) {
          return option.onError(getError(option, xhr), getBody(xhr));
        }
    
        return option.onSuccess(getBody(xhr), xhr);
      };
    
      xhr.open(option.method, option.action, true);
    
      // Has to be after `.open()`. See https://github.com/enyo/dropzone/issues/179
      if (option.withCredentials && 'withCredentials' in xhr) {
        xhr.withCredentials = true;
      }
    
      const headers = option.headers || {};
    
      // when set headers['X-Requested-With'] = null , can close default XHR header
      // see https://github.com/react-component/upload/issues/33
      if (headers['X-Requested-With'] !== null) {
        xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
      }
    
      Object.keys(headers).forEach(h => {
        if (headers[h] !== null) {
          xhr.setRequestHeader(h, headers[h]);
        }
      });
    
      xhr.send(formData);
    
      return {
        abort() {
          xhr.abort();
        },
      };
    }
    

    引用 RC.upload 的 interface.ts
    interface.ts

    import type * as React from 'react';
    
    export type BeforeUploadFileType = File | Blob | boolean | string;
    
    export type Action = string | ((file: RcFile) => string | PromiseLike<string>);
    
    export interface UploadProps
      extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'onError' | 'onProgress'> {
      name?: string;
      style?: React.CSSProperties;
      className?: string;
      disabled?: boolean;
      component?: React.JSXElementConstructor<any>;
      action?: Action;
      method?: UploadRequestMethod;
      directory?: boolean;
      data?: Record<string, unknown> | ((file: RcFile | string | Blob) => Record<string, unknown>);
      headers?: UploadRequestHeader;
      accept?: string;
      multiple?: boolean;
      onBatchStart?: (
        fileList: { file: RcFile; parsedFile: Exclude<BeforeUploadFileType, boolean> }[],
      ) => void;
      onStart?: (file: RcFile) => void;
      onError?: (error: Error, ret: Record<string, unknown>, file: RcFile) => void;
      onSuccess?: (response: Record<string, unknown>, file: RcFile, xhr: XMLHttpRequest) => void;
      onProgress?: (event: UploadProgressEvent, file: RcFile) => void;
      beforeUpload?: (
        file: RcFile,
        FileList: RcFile[],
      ) => BeforeUploadFileType | Promise<void | BeforeUploadFileType>;
      customRequest?: (option: UploadRequestOption) => void;
      withCredentials?: boolean;
      openFileDialogOnClick?: boolean;
      prefixCls?: string;
      id?: string;
      onMouseEnter?: (e: React.MouseEvent<HTMLDivElement>) => void;
      onMouseLeave?: (e: React.MouseEvent<HTMLDivElement>) => void;
      onClick?: (e: React.MouseEvent<HTMLDivElement> | React.KeyboardEvent<HTMLDivElement>) => void;
    }
    
    export interface UploadProgressEvent extends Partial<ProgressEvent> {
      percent?: number;
      loaded: number
      total: number
    }
    
    export type UploadRequestMethod = 'POST' | 'PUT' | 'PATCH' | 'post' | 'put' | 'patch';
    
    export type UploadRequestHeader = Record<string, string>;
    
    export interface UploadRequestError extends Error {
      status?: number;
      method?: UploadRequestMethod;
      url?: string;
    }
    
    export interface UploadRequestOption<T = any> {
      onProgress: (event: UploadProgressEvent) => void;
      onError: (event: UploadRequestError | ProgressEvent, body?: T) => void;
      onSuccess: (body: T, xhr?: XMLHttpRequest) => void;
      data: Record<string, unknown>;
      filename: string;
      file: Exclude<BeforeUploadFileType, File | boolean> | RcFile;
      withCredentials: boolean;
      action: string;
      headers?: UploadRequestHeader;
      method: UploadRequestMethod;
    }
    
    export interface RcFile extends File {
      uid: string;
    }
    

    参考文档

    AntDesign Upload
    react-component/upload

  • 相关阅读:
    C#序列化效率对比
    将聚合记录集逆时针和顺时针旋转90度(行列互换)
    Sql的行列转换
    log4net配置
    input框添加阴影效果
    WCF自定义地址路由映射(不用svc文件)
    Jquery对当前日期的操作(格式化当前日期)
    用JQuery获取输入框中的光标位置
    sublime text3安装后html:5+Tab不能快速生成html头部信息的解决办法
    js获取url传递参数,js获取url?号后面的参数
  • 原文地址:https://www.cnblogs.com/zjhblogs/p/15947267.html
Copyright © 2020-2023  润新知