3.2 Upload源码分析 -- ant-design-vue系列

news2024/11/18 11:43:49

Upload源码分析 – ant-design-vue系列

源码地址:https://github.com/vueComponent/ant-design-vue/blob/main/components/upload/Upload.tsx

1 概述

本篇是对Upload组件的分析,这个组件调用了vc-upload,是对vc-upload的封装。

作用包括:上传数据的管理、上传行为的定义、上传结果的预览等。

Upload上传包括两种形式:点击和拖动,分别如下所示:

| 在这里插入图片描述
| 在这里插入图片描述
|
| ------------------------------------------------------------ | ------------------------------------------------------------ |

Upload预览包括三种形式:textpicturepicture-card,分别如下所示:

| 在这里插入图片描述
| 在这里插入图片描述
| 在这里插入图片描述
|
| ------------------------------------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ |

2 源码分析

2.1 Dragger组件:拖动上传

使用<a-upload-dragger>组件来实现拖动上传,内部还是调用了Upload组件,这是只是设置了type: 'drag'

 const draggerProps = {
   ...restProps,
   ...restAttrs,
   type: 'drag',
   style: { ...(style as any), height: typeof height === 'number' ? `${height}px` : height },
 } as any;
return <Upload {...draggerProps} v-slots={slots}></Upload>;

2.2 Upload组件

2.2.1 渲染函数

主要逻辑如下图:

在这里插入图片描述

  • type === 'drag',需要在VcUpload外面套一个div节点,这个节点是有作用的。

🎯 默认情况下,浏览器会阻止在大多数HTML元素上放置内容。为了改变这个行为,让一个元素成为放入区域或者是可放置的,元素必须同时监听 dragoverdrop 事件。https://developer.mozilla.org/en-US/docs/Web/API/HTML_Drag_and_Drop_API

同时renderUploadList()会把已经上传成功的文件展示在下方。

const { listType, disabled, type } = props;
// ......
if (type === 'drag') {
	// ......
  return (
    <span>
      <div
        class={dragCls}
        onDrop={onFileDrop}
        onDragover={onFileDrop}
        onDragleave={onFileDrop}
      >
        <VcUpload
          {...rcUploadProps}
          ref={upload}
          v-slots={slots}
          >
          <div>{slots.default?.()}</div>
        </VcUpload>
      </div>
      {renderUploadList()}
    </span>
  );
}
  • type === 'select' && listType === 'picture-card',这时候上传按钮也是卡片列表的一部分,且在列表最后,可以通过maxCount来控制数量,如果超过最大值,上传按钮隐藏。
<!-- 上传组件被传入renderUploadList函数中,方便控制样式 -->
const renderUploadButton = (uploadButtonStyle?: CSSProperties) => (
  <div class={uploadButtonCls} style={uploadButtonStyle}>
  	<VcUpload {...rcUploadProps} ref={upload} v-slots={slots} />
  </div>
);

if (listType === 'picture-card') {
  return (
    <span class={classNames(`${prefixCls.value}-picture-card-wrapper`, attrs.class)}>
       {renderUploadList(renderUploadButton, !!(children && children.length))}
    </span>
  );
}
  • 其他情况下,先渲染上传按钮,再渲染列表。
return (
	<span class={attrs.class}>
  	{renderUploadButton(children && children.length ? undefined : { display: 'none' })}
  	{renderUploadList()}
	</span>
);
2.2.2 重要变量 fileList

fileList是一个双向绑定的值,保存了用户上传成功的文件信息。

useMergedStatecascader组件解析中分析过,这个hook可以根据外部是否传入变量,来切换组件的可控和非可控状态。

如果用户设置了fileList的默认值,则需要给每一个file文件对象设置一个uid,这个uid在移除文件时会用到。

const [mergedFileList, setMergedFileList] = useMergedState(props.defaultFileList || [], {
  value: toRef(props, 'fileList'),
  postState: list => {
    const timestamp = Date.now();
    return (list ?? []).map((file, index) => {
      if (!file.uid && !Object.isFrozen(file)) {
        file.uid = `__AUTO__${timestamp}_${index}__`;
      }
      return file;
    });
  },
});
2.2.3 主要流程

在这里插入图片描述

vc-upload流程在上一篇已经分析过了,本篇只分析Upload组件的方法。

2.2.3.1 mergedBeforeUpload 方法
const mergedBeforeUpload = async (file: FileType, fileListArgs: FileType[]) => {
  const { beforeUpload, transformFile } = props;

  let parsedFile: FileType | Blob | string = file;
  if (beforeUpload) {
    const result = await beforeUpload(file, fileListArgs);

    /**
    * 针对某一个file,如果返回false,则用户会自定义上传过程。后续流程不会继续。
    * 对应官方示例的“手动上传”
    */
    if (result === false) {
      return false;
    }

    
    delete (file as any)[LIST_IGNORE];
    /**
    * 如果结果是LIST_IGNORE,则给file文件增加这个属性,下一步后过滤掉这些ignore的file文件
    */
    if ((result as any) === LIST_IGNORE) {
      Object.defineProperty(file, LIST_IGNORE, {
        value: true,
        configurable: true,
      });
      return false;
    }

    if (typeof result === 'object' && result) {
      parsedFile = result as File;
    }
  }

  if (transformFile) {
    parsedFile = await transformFile(parsedFile as any);
  }

  return parsedFile as File;
};
2.2.3.2 onBatchStart 方法
const onBatchStart: RcUploadProps['onBatchStart'] = batchFileInfoList => {
  /**
  * 过滤掉LIST_IGNORE的文件,这些文件不会进入fileList
  */
  const filteredFileInfoList = batchFileInfoList.filter(
    info => !(info.file as any)[LIST_IGNORE],
  );

  // Nothing to do since no file need upload
  if (!filteredFileInfoList.length) {
    return;
  }
  
	// 生成文件对象
  const objectFileList = filteredFileInfoList.map(info => file2Obj(info.file as FileType));

  /**
  * 合并新老文件
  */
  let newFileList = [...mergedFileList.value];
  objectFileList.forEach(fileObj => {
    // Replace file if exist
    newFileList = updateFileList(fileObj, newFileList);
  });

  /**
  * 对新文件进行处理。
  * 在vc-upload的processFile方法中,如果beforeUpload返回了false,那么返回的对象如下。
  *		{	origin: file,
  *     parsedFile: null,
  *     action: null,
  *     data: null, }
  * 这时候用户会自己处理上传过程,我们只要补充必要属性即可,不需要管上传过程。
  *
  * 否则,设置上传状态为uploading。
  */
  objectFileList.forEach((fileObj, index) => {
    // Repeat trigger `onChange` event for compatible
    let triggerFileObj: UploadFile = fileObj;

    if (!filteredFileInfoList[index].parsedFile) {
      // `beforeUpload` return false
      const { originFileObj } = fileObj;
      let clone;

      try {
        clone = new File([originFileObj], originFileObj.name, {
          type: originFileObj.type,
        }) as any as UploadFile;
      } catch (e) {
        clone = new Blob([originFileObj], {
          type: originFileObj.type,
        }) as any as UploadFile;
        clone.name = originFileObj.name;
        clone.lastModifiedDate = new Date();
        clone.lastModified = new Date().getTime();
      }

      clone.uid = fileObj.uid;
      triggerFileObj = clone;
    } else {
      // Inject `uploading` status
      fileObj.status = 'uploading';
    }

    onInternalChange(triggerFileObj, newFileList);
  });
};
2.2.3.3 onSuccess 方法

onErroronProcess同理,不做分析。

const onSuccess = (response: any, file: FileType, xhr: any) => {
  try {
    if (typeof response === 'string') {
      response = JSON.parse(response);
    }
  } catch (e) {
    /* do nothing */
  }

  /**
  * 如果文件在上传过程中被移除了,那么就返回
  * getFileItem 的过程是在mergedFileList中查找和file的uid(或者name)相同的对象
  */
  if (!getFileItem(file, mergedFileList.value)) {
    return;
  }

  /**
  * 修改状态
  */
  const targetItem = file2Obj(file);
  targetItem.status = 'done';
  targetItem.percent = 100;
  targetItem.response = response;
  targetItem.xhr = xhr;

  const nextFileList = updateFileList(targetItem, mergedFileList.value);

  onInternalChange(targetItem, nextFileList);
};
2.2.3.4 onInternalChange 方法

在上传过程中/成功/失败,都会修改fileList数组。可以获取到上传状态和结果。

const onInternalChange = (
  file: UploadFile,
  changedFileList: UploadFile[],
  event?: { percent: number },
) => {
  let cloneList = [...changedFileList];

  /**
  * 如果maxCount是1,取最后一项;如果不是,取前面maxCount项
  */
  if (props.maxCount === 1) {
    cloneList = cloneList.slice(-1);
  } else if (props.maxCount) {
    cloneList = cloneList.slice(0, props.maxCount);
  }

  /**
  * 修改fileList数组
  */
  setMergedFileList(cloneList);

  const changeInfo: UploadChangeParam<UploadFile> = {
    file: file as UploadFile,
    fileList: cloneList,
  };

  if (event) {
    changeInfo.event = event;
  }
  props['onUpdate:fileList']?.(changeInfo.fileList);
  props.onChange?.(changeInfo);
  
  /**
  * 和Form表单组件的联动
  */
  formItemContext.onFieldChange();
};
2.2.3.5 handleRemove 方法

这是用户点删除的时候触发的方法。这里使用到了uid,来寻找应该移除的元素。

const handleRemove = (file: UploadFile) => {
  let currentFile: UploadFile;
  const mergedRemove = props.onRemove || props.remove;
  Promise.resolve(typeof mergedRemove === 'function' ? mergedRemove(file) : mergedRemove).then(
    ret => {
      // mergedRemove函数返回了false,则不移除
      if (ret === false) {
        return;
      }

      const removedFileList = removeFileItem(file, mergedFileList.value);

      if (removedFileList) {
        currentFile = { ...file, status: 'removed' };
        mergedFileList.value?.forEach(item => {
          const matchKey = currentFile.uid !== undefined ? 'uid' : 'name';
          if (item[matchKey] === currentFile[matchKey] && !Object.isFrozen(item)) {
            item.status = 'removed';
          }
        });
        
        /**
        * 上传过程中的文件,需要取消上传
        */
        upload.value?.abort(currentFile);

        onInternalChange(currentFile, removedFileList);
      }
    },
  );
};

3 其他函数

3.1 图片预览

这个函数的目的是预览给定的图片文件,并将其转换为一个Base64编码的数据URL(dataURL),然后返回这个数据URL。

  1. 创建一个canvas,设置canvas的样式,使其固定在页面左上角,宽度和高度均为MEASURE_SIZE,并设置z-index以确保它位于顶层,同时设置display: none;使其不可见。
  2. 开启2d绘图,首先获取图片的真实宽度和高度,然后计算大小和偏移量,确保图片能够按照其原始比例缩放,并居中显示。
  3. 使用canvas.toDataURL()生成图片的Base64编码的数据URL,设置到图片的src。清除canvas

所以 默认的预览方式,并不需要获取后端返回的图片url

export function previewImage(file: File | Blob): Promise<string> {
  return new Promise(resolve => {
    if (!file.type || !isImageFileType(file.type)) {
      resolve('');
      return;
    }

    const canvas = document.createElement('canvas');
    canvas.width = MEASURE_SIZE;
    canvas.height = MEASURE_SIZE;
    canvas.style.cssText = `position: fixed; left: 0; top: 0; width: ${MEASURE_SIZE}px; height: ${MEASURE_SIZE}px; z-index: 9999; display: none;`;
    document.body.appendChild(canvas);
    const ctx = canvas.getContext('2d');
    const img = new Image();
    img.onload = () => {
      const { width, height } = img;

      let drawWidth = MEASURE_SIZE;
      let drawHeight = MEASURE_SIZE;
      let offsetX = 0;
      let offsetY = 0;

      if (width > height) {
        drawHeight = height * (MEASURE_SIZE / width);
        offsetY = -(drawHeight - drawWidth) / 2;
      } else {
        drawWidth = width * (MEASURE_SIZE / height);
        offsetX = -(drawWidth - drawHeight) / 2;
      }

      ctx!.drawImage(img, offsetX, offsetY, drawWidth, drawHeight);
      const dataURL = canvas.toDataURL();
      document.body.removeChild(canvas);

      resolve(dataURL);
    };
    img.src = window.URL.createObjectURL(file);
  });
}

3.2 isImageUrl

判断一个url是不是图片的url,只看主要逻辑部分。

/**
* 如果是baseUrl,那么以 data:image 开头
* 如果是图片文件,那么扩展名必须是 webp|svg|png|gif|jpg|jpeg|jfif|bmp|dpg|ico
*/
if (
  /^data:image\//.test(url) ||
  /(webp|svg|png|gif|jpg|jpeg|jfif|bmp|dpg|ico)$/i.test(extension)
) {
  return true;
}

4 总结

本篇分析了 DrageerUpload 组件的实现,上传的整个过程都是串行的,比较容易理解。在上传的各个阶段,都可以通过钩子来获取上传文件的信息和状态。

renderUploadList是一个单独的组件,用来渲染上传的结果。这个组件提供了很多插槽,比如itemRender,可以自定义整个列表。因为不涉及上传,请自行学习。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2133050.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【【通信协议之ICMP协议】】

【【通信协议之ICMP协议】】 下面先展示出ICMP协议的数据格式 用户数据打包在 ICMP 协议中&#xff0c;ICMP 协议又是基于 IP 协议之上的&#xff0c;IP 协议又是走 MAC 层发送的&#xff0c;即从包含关系来说&#xff1a;MAC 帧中的数据段为 IP 数据报&#xff0c;IP 报文中…

LCSS—最长回文子序列

思路分析 关于”回文串“的问题&#xff0c;是面试中常见的&#xff0c;本文提升难度&#xff0c;讲一讲”最长回文子序列“问题&#xff0c;题目很好理解&#xff1a; 输入一个字符串 s&#xff0c;请找出 s 中的最长回文子序列长度。 比如输入 s"aecda"&#xff0c…

【数据结构】字符串与JSON字符串、JSON字符串及相应数据结构(如对象与数组)之间的相互转换

前言&#xff1a; 下面打印日志用的是FastJSON依赖库中的 Log4j2。依赖&#xff1a; <!-- Alibaba Fastjson --> <dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.80</version> …

prometheus 集成 grafana 保姆级别安装部署

前言 本文 grafana 展示效果只需要 prometheus node_exporter grafana 其他的选择安装 环境和版本号 系统: CentOS 7.9 prometheus: 2.54.1 pushgateway: 1.9.0 node_exporter: 1.8.2 alertmanager: 0.27.0 grafana:11.2.0 官网:https://prometheus.io/ 下载地址:h…

算法基础-二分查找

左闭右闭 [ left&#xff0c;right ] [1,1]可以 while( left < right ) if( a[mid] > target ) right mid - 1 else if( a[mid] < target ) left mid 1 左闭右开 [ left&#xff0c;right ) …

工业平板电脑轻薄与耐用并存

在现代工业环境中&#xff0c;工业平板电脑的应用越来越广泛。它们不仅需要具备轻薄的设计以便于携带和操作&#xff0c;还必须具备耐用性以应对恶劣的工作条件。 一、工业平板电脑的定义与特点 工业平板电脑是一种专为工业环境设计的计算设备&#xff0c;通常具备防尘、防水、…

MySQL分页查询(DQL)

因DataGrip我的激活到期&#xff0c;也没太多精力去破解&#xff0c;最后换了Navicat&#xff0c;实际上操作是一样的&#xff0c;不变。 先看我的表数据&#xff0c;以我的数据作为例子 基本语法 select 字段列表 from 表名 起始索引&#xff0c;查询记录数。 1.查询第1页员…

[数据集][目标检测]车油口挡板开关闭合检测数据集VOC+YOLO格式138张2类别

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;138 标注数量(xml文件个数)&#xff1a;138 标注数量(txt文件个数)&#xff1a;138 标注类别…

期权组合策略有什么风险?期权组合策略是什么?

今天期权懂带你了解期权组合策略有什么风险&#xff1f;期权组合策略是什么&#xff1f;期权组合策略是通过结合不同期权合约&#xff08;如看涨期权和看跌期权&#xff09;&#xff0c;以及标的资产&#xff08;如股票&#xff09;来实现特定投资目标的策略。 期权组合策略市…

2024.9.13 重拾数据库,不用就忘T-T

在之前学习Web的时候&#xff0c;电脑安装过mysql和navicate&#xff0c;所以安装步骤跳过 直接使用navicate创建一个新的连接&#xff0c;然后在这个连接里面新建数据库 新建数据库弹出要求如下图 一般的数据库学习教程都是字符集选择utf-8&#xff08;有中文&#xff09;&a…

PyTorch安装指南:轻松上手深度学习框架(CUDA)

PyTorch 是一个非常流行的开源深度学习框架&#xff0c;它支持动态图&#xff0c;这使得开发者能够更容易地构建和调试复杂的模型。PyTorch 可以运行在 CPU 上&#xff0c;也可以利用 NVIDIA 的 CUDA 平台加速计算&#xff0c;从而在 GPU 上执行。下面是如何在你的系统上安装 P…

JS面试真题 part5

JS面试真题 part5 21、说说对事件循环的理解22、JavaScript本地存储方式有哪些&#xff1f;区别及应用场景&#xff1f;23、大文件上传如何断点续传&#xff1f;24、ajax原理是什么&#xff1f;如何实现&#xff1f;25、什么是防抖和节流&#xff1f;有什么区别&#xff1f;如何…

如何在Windows10系统安装docker?

1.wsl安装 Windows Subsystem for Linux(简称WSL)是一个在Windows 10\11上能够运行原生Linux二进制可执行文件(ELF格式)的兼容层。它是由微软与Canonical公司合作开发,开发人员可以在 Windows 计算机上同时访问 Windows 和 Linux 的强大功能。 通过适用于 Linux 的 Window…

UE5 阴影通道

Shadow Pass Switch节点中 Default代表模型遮罩的效果 Shadow代表阴影的生成遮罩效果

Web开发:使用C#创建、安装、调试和卸载服务

目录 一、创建服务 1.创建项目&#xff08;.NET Framework&#xff09; 2.重命名 3.编写逻辑代码 二、安装服务 1.方案一&#xff1a;利用VS2022安装文件的配置 选择添加安装程序 安装文件的介绍及配置 ​编辑​ 重新编译 工具安装 2.方案二&#xff1a;编写bat脚本安…

SCRM电商管理后台Axure高保真原型 源文件

在电商行业蓬勃发展的今天&#xff0c;企业急需一个全面的客户关系管理&#xff08;CRM&#xff09;系统来优化他们的电商运营。我们的Scrm电商管理后台应运而生&#xff0c;它不仅是一个集中化的管理平台&#xff0c;更是企业提升客户互动和销售业绩的得力助手。 预览地址 ht…

yolo8训练自己的模型

1.数据源准备 1.1 准备图片资源 1.2 对图片资源标注&#xff0c;生成 对应的 .txt 文件&#xff0c;里面的数字表示 物体被标注的 x或y 等坐标点信息 1.2.1 标注工具下载以及使用教程参考 Windows 10下安装labelImg标注工具&#xff01;_labelimg windows exe 1.5版本-…

YOLOv5 Detect.py 改变检测框box线条的粗细,隐藏检测框的检测信息,只显示检测框box

Ctrl F 搜索 line_thickness 修改值 值越小 线条越细 hide-labels 隐藏检测框的类别信息 hide-conf 隐藏检测框的置信度信息

【OpenAPI】Spring3 集成 OpenAPI 生成接口文档

Spring3 集成 OpenAPI 生成接口文档 1. 依赖 Spring 版本&#xff1a;3.0.5 Java 版本&#xff1a;jdk21 OpenAPI 依赖&#xff1a; <!-- https://mvnrepository.com/artifact/org.springdoc/springdoc-openapi-starter-webmvc-ui --> <dependency><groupI…

jdk环境变量配置+eclipse配置jdk

文章目录 安装jdkjdk环境变量配置eclipse里边配置jdkeclipse覆盖率插件——EclEmma的安装和使用 安装jdk 在安装前可以先建两个文件夹&#xff0c;注意不要文件夹用英文&#xff0c;不要用中文&#xff0c;如图&#xff1a; 然后我们开始安装 然后就看我们有没有安装成功…