介绍

在表单功能中,图片,文件上传可以说是非常常见,前端如何快速的传递给后端,通常而言,会直接将 图片/文件地址发送给后端

在这里就需要小伙伴先配置 OSS了,具体的文档请看 配置OSS ,这里就不做多的介绍了(可关闭OSS配置)

为什么要封装呢?

首先这块的功能是比较常见的,比如文件的类型,文件的大小,图片的裁剪,文件的数量,展示的类型等,做了一层封装,以便更好的使用~

这个组件主要使用了 Ant Design 的 Upload 和 react-cropper

为什么不用 antd-img-crop 呢?原因是它不具备自定义裁剪

在线演示:图片文件上传

功能

当你有以下需求时,可以试试这个组件

  • 需要使用 OSS 将文件转化为网络地址
  • 需要设置上传的大小,类型,是否重复上传等功能
  • 图片需要自定义裁剪的功能~

另外,OSS的功能不方便演示~~~

代码演示

功能演示

基础功能

image.png

其他格式

image.png

图片剪辑

image.png

具体代码

文件位置:src/components/OssUpLoad

全局配置文件:src/utils/Setting/OssUpLoadSy

  1. import React, { useEffect } from 'react';
  2. import { Upload, message, Modal, Button } from 'antd';
  3. import { PlusOutlined, UploadOutlined } from '@ant-design/icons';
  4. import { OssUpLoadSy } from '@/utils/Setting'
  5. import { useState, useRef } from 'react';
  6. import Cropper from 'react-cropper';
  7. import 'cropperjs/dist/cropper.css';
  8. import Props from './interface.d';
  9. import './index.less';
  10. let aliOSS = require('ali-oss');
  11. let client = new aliOSS({
  12. region: OssUpLoadSy.OSS.region,
  13. accessKeyId: OssUpLoadSy.OSS.accessKeyId,
  14. accessKeySecret: OssUpLoadSy.OSS.accessKeySecret,
  15. bucket: OssUpLoadSy.OSS.bucket,
  16. });
  17. const OssUpLoad: React.FC<Props> = ({
  18. amount = OssUpLoadSy.amount,
  19. OSS = OssUpLoadSy.open,
  20. rules = {},
  21. listType = OssUpLoadSy.listType,
  22. onRemove,
  23. children,
  24. getFiles,
  25. crop,
  26. _config = {},
  27. button = {},
  28. initFile = [],
  29. cropConfig = {},
  30. ...props
  31. }) => {
  32. const cropperRef = useRef<any>(null)
  33. const [fileList, setFileList] = useState<Array<any>>([]); //总文件数组
  34. const [getFilesList, setGetFilesList] = useState<Array<any>>([]); //总文件数组
  35. const [previewVisible, setPreviewVisible] = useState<boolean>(false); // 是否打开弹出框
  36. const [isFileFlag, setIsFileFlag] = useState<boolean>(false); // 控制照片,如果不满足则不添加
  37. const [previewTitle, setPreviewTitle] = useState<any>(''); // 图片名称
  38. const [previewImage, setPreviewImage] = useState<any>(''); // 图片展示的数据
  39. const [src, setSrc] = useState<any>(false) //裁剪图片
  40. const [file, setFile] = useState<any>(false)
  41. useEffect(() => {
  42. if(initFile.length !== 0){
  43. let fileList: Array<{url: string, name: string, uid?: number | string}> = []
  44. initFile.map((item, index) => {
  45. const param = OSS ? { newFile: item } : {}
  46. if(typeof item === 'string'){
  47. fileList = [...fileList, { url: item, name: `图片`, ...param}]
  48. }else {
  49. fileList = [...fileList, {...item, url: item?.url || '', uid: item?.uid || undefined, name: item?.name || `图片`, ...param}]
  50. }
  51. })
  52. setGetFilesList([...fileList])
  53. setFileList(fileList)
  54. }
  55. }, [])
  56. // 预览
  57. const handlePreview = async (file: any) => {
  58. if (file.type && file.type.indexOf('image') === -1) return;
  59. if (!file.url && !file.preview) {
  60. file.preview = await getBase64(file.originFileObj);
  61. }
  62. setPreviewVisible(true);
  63. setPreviewImage(file.url || file.preview);
  64. setPreviewTitle(file.name || file.url.substring(file.url.lastIndexOf('/') + 1));
  65. };
  66. // 上传前的操作
  67. const beforeUpload = async (file: any) => {
  68. let flag = true; // 控制最终的类型
  69. // 检测是否有相同类型
  70. if (!_config.noCheck && flag) {
  71. const repeat = fileList.filter((item) => item.name === file.name && item.size === file.size);
  72. if (repeat.length !== 0) {
  73. message.error('您已上传过此文件,请勿重复上传');
  74. flag = false;
  75. }
  76. }
  77. // 判断文件类型
  78. if (typeof rules.type === 'string' && flag) {
  79. const type = rules.type.trim() === 'jpg' ? 'jpeg' : rules.type.trim();
  80. if(rules.type === 'xlsx' || rules.type === 'xls') {
  81. // 单独处理 excel
  82. const fileType = file.name.split('.').pop();
  83. if(['xlsx', 'xls'].indexOf(fileType) < 0) {
  84. message.error(rules.typeMsg || '请上传xlsx或xls格式文件');
  85. flag = false
  86. }
  87. } else if (file.type.indexOf(type) === -1) {
  88. message.error(rules.typeMsg || '请上传正确的文件类型');
  89. flag = false;
  90. }
  91. } else if (Array.isArray(rules.type) && flag) {
  92. let allFlag = false;
  93. rules.type.map((item) => {
  94. const type = item.trim() === 'jpg' ? 'jpeg' : item.trim();
  95. if(item === 'xlsx' || item === 'xls'){
  96. const fileType = file.name.split('.').pop();
  97. if(['xlsx', 'xls'].indexOf(fileType) !== -1) {
  98. allFlag = true
  99. }
  100. } else if (file.type.indexOf(type) !== -1) {
  101. allFlag = true;
  102. return;
  103. }
  104. });
  105. if (!allFlag) {
  106. message.error(rules.typeMsg || '请上传正确的文件类型');
  107. flag = false;
  108. }
  109. }
  110. // 根据listType来进行判断
  111. if (listType === 'picture-card' && file.type.indexOf('image') === -1 && flag) {
  112. message.error(_config.pictureCardTip || '请上传正确的图片类型!');
  113. flag = false;
  114. }
  115. // 判断文件大小
  116. if (rules.size && flag) {
  117. const fileSize = file.size / 1024 / 1024 < rules.size;
  118. if (!fileSize) {
  119. message.error(rules.sizeMsg || `上传文件大于${rules.size}M!请重新上传`);
  120. flag = false;
  121. }
  122. }
  123. // 裁剪
  124. if(crop){
  125. const fileReader = new FileReader()
  126. fileReader.onload = (e:any) => {
  127. const dataURL = e.target.result;
  128. setSrc(dataURL)
  129. }
  130. fileReader.readAsDataURL(file)
  131. setFile(file)
  132. return;
  133. }
  134. if (flag) onGetFiles(file);
  135. setIsFileFlag(flag);
  136. return file;
  137. };
  138. // 文件获取
  139. const onGetFiles = async (file: any) => {
  140. if (getFiles) {
  141. let result: any = file;
  142. let OssList: Array<any> = [];
  143. let fileBase64:any = ''
  144. if (OSS) {
  145. const suffix = file.name.slice(file.name.lastIndexOf('.'));
  146. const filename = _config?.ossText ? _config.ossText + suffix : suffix;
  147. const res = await client.put(
  148. `${_config.ossUrl || OssUpLoadSy.OssUrl}/${Date.now() + filename}`,
  149. file,
  150. );
  151. result = res.url;
  152. }else{
  153. fileBase64 = await getBase64(file)
  154. }
  155. result = [...getFilesList, OSS ? { file, newFile: result } : { file, fileBase64 }];
  156. OSS ? result.map((item: any) => (OssList = [...OssList, item.newFile])) : '';
  157. setGetFilesList(result);
  158. getFiles(OSS ? OssList : result, true);
  159. }
  160. };
  161. // 自定义按钮样式
  162. const uploadButton = () => {
  163. if (_config?.uploadNode) {
  164. return typeof _config.uploadNode === 'function' ? _config.uploadNode() : _config.uploadNode;
  165. }
  166. return (
  167. <div>
  168. <PlusOutlined />
  169. <div style={{ marginTop: 8 }}>{_config.text || OssUpLoadSy._config.text}</div>
  170. </div>
  171. );
  172. };
  173. return (
  174. <div className="UpLoadComponents">
  175. <Upload
  176. {...props}
  177. listType={listType}
  178. fileList={fileList}
  179. onPreview={handlePreview}
  180. onChange={({ fileList }) => {
  181. if (isFileFlag) setFileList(fileList);
  182. }}
  183. multiple={!_config.radio && amount !== 1}
  184. onRemove={ (file) => {
  185. const result = fileList.filter((item) => item.uid !== file.uid);
  186. setFileList(result);
  187. if (getFiles) {
  188. let getFileResult:any = getFilesList.filter((item) => item.file ? item.file.uid !== file.uid : item.uid !== file.uid);
  189. let OSSList:any = []
  190. if(OSS && getFileResult.length !== 0) {
  191. getFileResult.map((item:any) => {
  192. OSSList = [...OSSList, item?.newFile]
  193. })
  194. }
  195. setGetFilesList(getFileResult);
  196. // 删除文件
  197. getFiles(OSS ? OSSList : getFileResult, false);
  198. }
  199. if (onRemove) onRemove(file);
  200. }}
  201. beforeUpload={beforeUpload}
  202. maxCount={amount}
  203. >
  204. {listType === 'picture-card' ? (
  205. fileList.length >= amount ? null : (
  206. uploadButton()
  207. )
  208. ) : children ? (
  209. children
  210. ) : (
  211. <Button
  212. {...button}
  213. icon={button.icon || <UploadOutlined />}
  214. type={button.type || 'primary'}
  215. disabled={fileList.length === amount}
  216. onClick={(ev) => {
  217. if (button.onClick) button.onClick(ev);
  218. }}
  219. >
  220. {_config.text || 'Upload'}
  221. </Button>
  222. )}
  223. </Upload>
  224. <Modal
  225. visible={previewVisible}
  226. title={previewTitle}
  227. footer={null}
  228. onCancel={() => setPreviewVisible(false)}
  229. >
  230. <img alt="example" style={{ width: '100%' }} src={previewImage} />
  231. </Modal>
  232. <Modal
  233. destroyOnClose
  234. title={cropConfig?.title || OssUpLoadSy.crop.title}
  235. visible={src ? true : false}
  236. style={{height: 200}}
  237. onCancel={() => {setSrc(false);setFile(false)}}
  238. footer={null}
  239. >
  240. <Cropper
  241. src={src}
  242. ref={cropperRef}
  243. style={{height: 400}}
  244. zoomable={false}
  245. guides={false}
  246. />
  247. <div style={{marginTop: 30, display: 'flex',justifyContent: 'flex-end'}}>
  248. <Button onClick={() => setSrc(false)} {...cropConfig?.cancelProps} style={{marginRight: 20, ...OssUpLoadSy.crop.cancelStyle, ...cropConfig?.cancelStyle}}>{cropConfig?.cancelText || OssUpLoadSy.crop.cancelText}</Button>
  249. <Button
  250. type="primary"
  251. {...cropConfig?.cropProps}
  252. style={{ ...OssUpLoadSy.crop.cropStyle, ...cropConfig?.cropStyle }}
  253. onClick={async () =>{
  254. cropperRef?.current?.cropper?.getCroppedCanvas().toBlob(async (blob:any) => {
  255. const base64 = await getBase64(blob)
  256. const newFile:any = new File([blob], file.name, {type: file.type})
  257. newFile.uid = file.uid
  258. onGetFiles(newFile)
  259. setFileList([...fileList, { url: base64, uid: file.uid, name: file.name}])
  260. setFile(false)
  261. })
  262. setSrc(false)
  263. }}
  264. >{cropConfig?.cropText || OssUpLoadSy.crop.cropText}</Button>
  265. </div>
  266. </Modal>
  267. </div>
  268. );
  269. };
  270. export default OssUpLoad;
  271. const getBase64 = (file: any) => {
  272. return new Promise((resolve, reject) => {
  273. const reader = new FileReader();
  274. reader.readAsDataURL(file);
  275. reader.onload = () => resolve(reader.result);
  276. reader.onerror = (error) => reject(error);
  277. });
  278. };

如何使用

  1. import { OssUpLoad } from '@/components';
  2. <OssUpLoad
  3. ...props
  4. getFiles={(file: Array<any>, flag) => {}} //获取文件
  5. />

结果

开启 OSS的结果
image.png
不开启OSS的结果
image.png

特殊说明

  • 全局配置文件可根据项目的需求去做对应的配置
  • OSSUpLoad 组件本身是单独封装,在表单中的使用需要与 动态表单 结合使用
  • 开启 OSS 功能,返回的则直接是地址的数组,否则有一个文件对象,和Base64的地址
  • 上传的地址都需要使用 **getFiles** 来获取