From 0e01d99b93e5f5d6aae310b9e84c3c25183ebac3 Mon Sep 17 00:00:00 2001 From: liuyijun17 <3476078473@qq.com> Date: Mon, 19 Sep 2022 11:05:37 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9Euploader=E5=B1=9E?= =?UTF-8?q?=E6=80=A7=E8=A1=A5=E9=BD=90=E5=BC=80=E5=8F=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/packages/button/button.tsx | 2 + src/packages/progress/progress.tsx | 2 + .../uploader/__tests__/uploader.spec.tsx | 14 +- src/packages/uploader/demo.scss | 4 +- src/packages/uploader/demo.tsx | 85 +++++++++- src/packages/uploader/doc.en-US.md | 152 ++++++++++++++--- src/packages/uploader/doc.md | 155 +++++++++++++++--- src/packages/uploader/doc.zh-TW.md | 147 ++++++++++++++--- src/packages/uploader/upload.ts | 8 +- src/packages/uploader/uploader.taro.tsx | 11 +- src/packages/uploader/uploader.tsx | 125 ++++++++------ 11 files changed, 556 insertions(+), 149 deletions(-) diff --git a/src/packages/button/button.tsx b/src/packages/button/button.tsx index 4739943..dca6441 100644 --- a/src/packages/button/button.tsx +++ b/src/packages/button/button.tsx @@ -66,6 +66,8 @@ export const Button: FunctionComponent> = (props) => { onClick, className, style, + iconClassPrefix, + iconFontClassName, ...rest } = { ...defaultProps, diff --git a/src/packages/progress/progress.tsx b/src/packages/progress/progress.tsx index 356db2d..8f7264f 100644 --- a/src/packages/progress/progress.tsx +++ b/src/packages/progress/progress.tsx @@ -78,6 +78,8 @@ export const Progress: FunctionComponent< iconSize, rounded, children, + iconClassPrefix, + iconFontClassName, ...rest } = { ...defaultProps, ...props } diff --git a/src/packages/uploader/__tests__/uploader.spec.tsx b/src/packages/uploader/__tests__/uploader.spec.tsx index 84cfa99..e4dcadd 100644 --- a/src/packages/uploader/__tests__/uploader.spec.tsx +++ b/src/packages/uploader/__tests__/uploader.spec.tsx @@ -24,7 +24,7 @@ test('should render base uploader props', () => { accept=".jpg" maximize={1024 * 50} maximum={2} - change={change} + onChange={change} /> ) const input = container.querySelectorAll('.nut-uploader__input')[0] @@ -77,8 +77,8 @@ test('should render base uploader other props', () => { isPreview uploadIcon="dongdong" uploadIconSize="20px" - removeImage={onDelete} - fileItemClick={fileItemClick} + onRemove={onDelete} + onFileItemClick={fileItemClick} /> ) } @@ -166,8 +166,8 @@ test('before-delete prop return false', () => { { + onRemove={onDelete} + onBeforeDelete={() => { return false }} /> @@ -195,8 +195,8 @@ test('before-delete prop return true', () => { { + onRemove={onDelete} + onBeforeDelete={() => { return true }} /> diff --git a/src/packages/uploader/demo.scss b/src/packages/uploader/demo.scss index 3246a56..1619f73 100644 --- a/src/packages/uploader/demo.scss +++ b/src/packages/uploader/demo.scss @@ -1,3 +1,3 @@ -.demo.bg-w { - background: #fff; +.demo-uploader { + padding: 57px 17px 15px 17px !important; } diff --git a/src/packages/uploader/demo.tsx b/src/packages/uploader/demo.tsx index 800baab..bc63202 100644 --- a/src/packages/uploader/demo.tsx +++ b/src/packages/uploader/demo.tsx @@ -1,7 +1,9 @@ -import React, { useRef } from 'react' +import React, { useState, useRef } from 'react' import { useTranslate } from '../../sites/assets/locale' import { Uploader, FileItem, FileType } from './uploader' import Button from '@/packages/button' +import Progress from '@/packages/progress' +import './demo.scss' interface uploadRefState { submit: () => void @@ -17,7 +19,10 @@ interface T { '25e04d44': string d06e873e: string ca3903f3: string + upload_progress_action: string '84aa6bce': string + upload_list_show: string + upload_default_progress: string a4afedb5: string '37c65f47': string bb5caa9c: string @@ -26,6 +31,7 @@ interface T { b7454181: string '5c393e52': string e3217a8d: string + upload_xhr_custom: string '67fffe24': string fcf01d1a: string '7db1a8b2': string @@ -42,7 +48,10 @@ const UploaderDemo = () => { '25e04d44': 'oversize触发文件大小不能超过50kb', d06e873e: 'start触发', ca3903f3: 'delete事件触发', + upload_progress_action: 'progress事件触发', '84aa6bce': '基础用法', + upload_list_show: '基础用法-上传列表展示', + upload_default_progress: '自定义上传使用默认进度条', a4afedb5: '上传状态', '37c65f47': '自定义上传样式', bb5caa9c: '上传文件', @@ -51,6 +60,7 @@ const UploaderDemo = () => { b7454181: '限制上传大小(每个文件最大不超过50kb)', '5c393e52': '图片压缩(在beforeupload钩子中处理)', e3217a8d: '自定义数据 FormData、headers', + upload_xhr_custom: '自定义 xhr 上传方式(before-xhr-upload)', '67fffe24': '选中文件后,通过按钮手动执行上传', fcf01d1a: '执行上传', '7db1a8b2': '禁用状态', @@ -65,7 +75,10 @@ const UploaderDemo = () => { '25e04d44': 'oversize觸發檔大小不能超過50kb', d06e873e: 'start觸發', ca3903f3: 'delete事件觸發', + upload_progress_action: 'progress事件觸發', '84aa6bce': '基础用法', + upload_list_show: '基礎用法-上傳列表展示', + upload_default_progress: '自定義上傳使用默認進度條', a4afedb5: '上傳狀態', '37c65f47': '自定義上傳樣式', bb5caa9c: '上傳檔', @@ -74,6 +87,7 @@ const UploaderDemo = () => { b7454181: '限制上傳大小(每個檔案最大不超過50kb)', '5c393e52': '圖片壓縮(在beforeupload鉤子中處理)', e3217a8d: '自定義數據 FormData、headers', + upload_xhr_custom: '自定義 xhr 上傳方式(before-xhr-upload)', '67fffe24': '選取檔後,通過按鈕手動執行上傳', fcf01d1a: '執行上傳', '7db1a8b2': '禁用狀態', @@ -88,7 +102,10 @@ const UploaderDemo = () => { '25e04d44': 'The oversize trigger file size cannot exceed 50kb', d06e873e: 'start triggered', ca3903f3: 'The delete event is triggered', + upload_progress_action: 'The progress event is triggered', '84aa6bce': 'Basic usage', + upload_list_show: 'Basic usage - upload list display', + upload_default_progress: 'Custom upload uses default progress bar', a4afedb5: 'Upload status', '37c65f47': 'Customize the upload style', bb5caa9c: 'Upload the file', @@ -97,6 +114,7 @@ const UploaderDemo = () => { b7454181: 'Limit upload size (maximum 50kb per file)', '5c393e52': 'Image compression (handled in a foreupload hook)', e3217a8d: 'Custom data FormData, headers', + upload_xhr_custom: 'Custom xhr upload method (before-xhr-upload)', '67fffe24': 'After selecting Chinese, manually perform the upload via the button', fcf01d1a: 'Perform the upload', @@ -104,6 +122,7 @@ const UploaderDemo = () => { }, }) + const [progressPercent, setProgressPercent] = useState(0) const uploadRef = useRef(null) const uploadUrl = 'https://my-json-server.typicode.com/linrufeng/demo/posts' const formData = { @@ -164,6 +183,10 @@ const UploaderDemo = () => { const onStart = () => { console.log(translated.d06e873e) } + const onProgress = ({ event, options, percentage }: any) => { + setProgressPercent(percentage) + console.log(translated.upload_progress_action) + } const onDelete = (file: FileItem, fileList: FileItem[]) => { console.log(translated.ca3903f3, file, fileList) } @@ -180,45 +203,82 @@ const UploaderDemo = () => { const f = await new File([blob], files[0].name, { type: files[0].type }) return [f] } + const beforeXhrUpload = (xhr: XMLHttpRequest, options: any) => { + if (options.method.toLowerCase() == 'put') { + xhr.send(options.sourceFile) + } else { + xhr.send(options.formData) + } + } const submitUpload = () => { ;(uploadRef.current as uploadRefState).submit() } return ( <> -
+

{translated['84aa6bce']}

- +

{translated.a4afedb5}

+ +

{translated.upload_list_show}

+ + + +

{translated['37c65f47']}

+ +

{translated.upload_default_progress}

+ + + +
+ +

{translated['27f1376e']}

-

{translated.a4afedb5}

- +

{translated['0e5eaea3']}

+

{translated.b7454181}

+

{translated['5c393e52']}

- + +

{translated.e3217a8d}

{ headers={formData} withCredentials /> + +

{translated.upload_xhr_custom}

+ +

{translated['67fffe24']}

{ +

{translated['7db1a8b2']}

diff --git a/src/packages/uploader/doc.en-US.md b/src/packages/uploader/doc.en-US.md index cf9b39b..e8ad706 100644 --- a/src/packages/uploader/doc.en-US.md +++ b/src/packages/uploader/doc.en-US.md @@ -32,6 +32,62 @@ export default App; ``` ::: +### upload status + +:::demo +``` tsx +import React, { useState } from "react"; +import { Uploader } from '@nutui/nutui-react'; + +const App = () => { + const uploadUrl = 'https://my-json-server.typicode.com/linrufeng/demo/posts' + const defaultFileList: FileType[] = [ + { + name: 'file1.png', + url: 'https://m.360buyimg.com/babel/jfs/t1/164410/22/25162/93384/616eac6cE6c711350/0cac53c1b82e1b05.gif', + status: 'success', + message: 'Uploaded successfully', + type: 'image', + uid: '123', + }, + { + name: 'file2.png', + url: 'https://m.360buyimg.com/babel/jfs/t1/164410/22/25162/93384/616eac6cE6c711350/0cac53c1b82e1b05.gif', + status: 'error', + message: 'upload failed', + type: 'image', + uid: '124', + }, + { + name: 'file3.png', + url: 'https://m.360buyimg.com/babel/jfs/t1/164410/22/25162/93384/616eac6cE6c711350/0cac53c1b82e1b05.gif', + status: 'uploading', + message: 'uploading...', + type: 'image', + uid: '125', + }, + ] + const onDelete = (file: FileItem, fileList: FileItem[]) => { + console.log(translated.ca3903f3, file, fileList) + } + return ( + <> +

upload status

+ + + ) +} +export default App; +``` +::: + ### Customize the upload style :::demo @@ -45,7 +101,7 @@ const App = () => { <>

Customize the upload style

- @@ -56,19 +112,33 @@ export default App; ``` ::: -### Direct camera up (mobile) +### Custom upload uses default progress bar :::demo ``` tsx import React, { useState } from "react"; -import { Uploader, Button } from '@nutui/nutui-react'; +import { Uploader, Button, Progress } from '@nutui/nutui-react'; const App = () => { const uploadUrl = 'https://my-json-server.typicode.com/linrufeng/demo/posts' + const [progressPercent, setProgressPercent] = useState(0) + const onProgress = ({ event, options, percentage }: any) => { + setProgressPercent(percentage) + } return ( <> -

Direct camera up (mobile)

- +

Custom upload uses default progress bar

+ + + +
+ ) } @@ -76,7 +146,7 @@ export default App; ``` ::: -### Upload status +### Direct camera up (mobile) :::demo ``` tsx @@ -85,13 +155,10 @@ import { Uploader, Button } from '@nutui/nutui-react'; const App = () => { const uploadUrl = 'https://my-json-server.typicode.com/linrufeng/demo/posts' - const onDelete = (file: FileItem, fileList: FileItem[]) => { - console.log('delete event start', file, fileList) - } return ( <> -

Upload status

- +

Direct camera up (mobile)

+ ) } @@ -99,7 +166,6 @@ export default App; ``` ::: - ### Limit the number of uploads to 5 :::demo @@ -172,6 +238,36 @@ export default App; ``` ::: +### Custom xhr upload method (before-xhr-upload) +:::demo +``` tsx +import React, { useState } from "react"; +import { Uploader, Button } from '@nutui/nutui-react'; + +const App = () => { + const uploadUrl = 'https://my-json-server.typicode.com/linrufeng/demo/posts' + const beforeXhrUpload = (xhr: XMLHttpRequest, options: any) => { + if (options.method.toLowerCase() == 'put') { + xhr.send(options.sourceFile); + } else { + xhr.send(options.formData); + } + }; + return ( + <> +

Custom xhr upload method (before-xhr-upload)

+ + + ) +} +export default App; +``` +::: + ### Manual upload :::demo @@ -223,27 +319,32 @@ export default App; | Attribute |Description | Type | Default | |-------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------|------------------| +| autoUpload | Whether to upload the file immediately after selecting it, if false, you need to manually execute the ref submit method to upload | Boolean | true | | name | The name of the `input` tag `name`, the file parameter name sent to the background | String | "file" | | url | The interface address of the upload server | String | - | +| defaultFileList | List of uploaded files by default | FileItem[] | [] | | isPreview | Whether to display the preview image after the upload is successful | Boolean | true | | defaultImg | When uploading a default image URL in a non-image ('image') format | String | '' | | isDeletable | Whether to display the delete button | Boolean | true | | method | The http method of upload request | String | "post" | +| listType | The built-in style of the upload list, supports two basic styles picture, list | String | "picture" | | capture | Capture, can be set to[camera](https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/input#htmlattrdefcapture),,turn on the camera directly | String | false | | maximize | You can set the maximum upload file size (bytes) | Number丨String | Number.MAX_VALUE | | maximum | File upload limit | Number丨String | 1 | -| clearInput | Whether to clear the `input` content, set to `true` to support repeated selection and upload of the same file | Boolean | false | +| clearInput | Whether to clear the `input` content, set to `true` to support repeated selection and upload of the same file | Boolean | true | | accept | File types that can be accepted. See[Des](https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/Input/file#%E9%99%90%E5%88%B6%E5%85%81%E8%AE%B8%E7%9A%84%E6%96%87%E4%BB%B6%E7%B1%BB%E5%9E%8B) | String | * | | headers | Set request headers | Object | {} | | data | Uploading extra params or function which can return uploading extra params formData | Object | {} | | uploadIcon | Upload area[icon name](#/zh-CN/icon)or image link | String | "photograph" | +| uploadIconSize | Upload area [icon size](#/icon) size, such as `20px` `2em` `2rem` | String or Number | - | | xhrState | The success status (status) value of the interface response | Number | 200 | -| withCredentials | The ajax upload with cookie sent | Boolean | fasle | -| multiple | Whether to support multiple file selection | Boolean | fasle | -| disabled | Whether to disable file upload | Boolean | fasle | +| withCredentials | Support for sending cookie credential information | Boolean | false | +| multiple | Whether to support multiple file selection | Boolean | false | +| disabled | Whether to disable file upload | Boolean | false | | timeout | timeout, in milliseconds | Number丨String | 1000 * 30 | -| beforeUpload | Hook before reading the file, return false to stop reading the file, can return Promise | Function | null | -| beforeDelete | Hook before delete the file, return false to stop reading the file, can return Promise | Function(file): boolean 丨Promise | - | +| onBeforeUpload | The pre-upload function needs to return a `Promise` object | Function | null | +| onBeforeXhrUpload | When performing an XHR upload, the custom method | Function(xhr,option) | null | +| onBeforeDelete | Callback when file is removed. If the return value is false, it will not be removed. Supports returning a `Promise` object, which is not removed when the `Promise` object resolves(false) or rejects | Function(file): boolean 丨Promise | - | @@ -262,11 +363,12 @@ export default App; | Event | Description | Arguments | |----------|------------------------|----------------------| -| start | File upload starts | options | -| progress | The progress of the file upload | event,options | -| oversize | Triggered when the file size exceeds the limit | files | -| success | Uploaded successfully | responseText,options | -| failure | Upload failed | responseText,options | -| change | The state when the uploaded file changes | fileList,event | -| removeImage | File delete event | files,fileList | +| onStart | File upload starts | options | +| onProgress | The progress of the file upload | event,options,percentage | +| onOversize | Triggered when the file size exceeds the limit | files | +| onSuccess | Uploaded successfully | responseText,options | +| onFailure | Upload failed | responseText,options | +| onChange | The state when the uploaded file changes | fileList,event | +| onRemove | The state before the file was deleted | files,fileList | +| onFileItemClick | File delete event | fileItem | diff --git a/src/packages/uploader/doc.md b/src/packages/uploader/doc.md index 98605b1..506e2e8 100644 --- a/src/packages/uploader/doc.md +++ b/src/packages/uploader/doc.md @@ -32,6 +32,62 @@ export default App; ``` ::: +### 上传状态 + +:::demo +``` tsx +import React, { useState } from "react"; +import { Uploader } from '@nutui/nutui-react'; + +const App = () => { + const uploadUrl = 'https://my-json-server.typicode.com/linrufeng/demo/posts' + const defaultFileList: FileType[] = [ + { + name: '文件1.png', + url: 'https://m.360buyimg.com/babel/jfs/t1/164410/22/25162/93384/616eac6cE6c711350/0cac53c1b82e1b05.gif', + status: 'success', + message: '上传成功', + type: 'image', + uid: '123', + }, + { + name: '文件2.png', + url: 'https://m.360buyimg.com/babel/jfs/t1/164410/22/25162/93384/616eac6cE6c711350/0cac53c1b82e1b05.gif', + status: 'error', + message: '上传失败', + type: 'image', + uid: '124', + }, + { + name: '文件3.png', + url: 'https://m.360buyimg.com/babel/jfs/t1/164410/22/25162/93384/616eac6cE6c711350/0cac53c1b82e1b05.gif', + status: 'uploading', + message: '上传中...', + type: 'image', + uid: '125', + }, + ] + const onDelete = (file: FileItem, fileList: FileItem[]) => { + console.log(translated.ca3903f3, file, fileList) + } + return ( + <> +

上传状态

+ + + ) +} +export default App; +``` +::: + ### 自定义上传样式 :::demo @@ -45,7 +101,7 @@ const App = () => { <>

自定义上传样式

- @@ -56,19 +112,33 @@ export default App; ``` ::: -### 直接调起摄像头(移动端生效) +### 自定义上传使用默认进度条 :::demo ``` tsx import React, { useState } from "react"; -import { Uploader, Button } from '@nutui/nutui-react'; +import { Uploader, Button, Progress } from '@nutui/nutui-react'; const App = () => { const uploadUrl = 'https://my-json-server.typicode.com/linrufeng/demo/posts' + const [progressPercent, setProgressPercent] = useState(0) + const onProgress = ({ event, options, percentage }: any) => { + setProgressPercent(percentage) + } return ( <> -

直接调起摄像头(移动端生效)

- +

自定义上传使用默认进度条

+ + + +
+ ) } @@ -76,7 +146,7 @@ export default App; ``` ::: -### 上传状态 +### 直接调起摄像头(移动端生效) :::demo ``` tsx @@ -85,13 +155,10 @@ import { Uploader, Button } from '@nutui/nutui-react'; const App = () => { const uploadUrl = 'https://my-json-server.typicode.com/linrufeng/demo/posts' - const onDelete = (file: FileItem, fileList: FileItem[]) => { - console.log('delete 事件触发', file, fileList) - } return ( <> -

上传状态

- +

直接调起摄像头(移动端生效)

+ ) } @@ -99,7 +166,6 @@ export default App; ``` ::: - ### 限制上传数量5个 :::demo @@ -120,7 +186,7 @@ export default App; ``` ::: -### 限制上传大小(每个文件最大不超过 50kb,也可以在beforeupload中自行处理) +### 限制上传大小(每个文件最大不超过 50kb) :::demo ``` tsx @@ -172,6 +238,37 @@ export default App; ``` ::: +### 自定义 xhr 上传方式(before-xhr-upload) + +:::demo +``` tsx +import React, { useState } from "react"; +import { Uploader, Button } from '@nutui/nutui-react'; + +const App = () => { + const uploadUrl = 'https://my-json-server.typicode.com/linrufeng/demo/posts' + const beforeXhrUpload = (xhr: XMLHttpRequest, options: any) => { + if (options.method.toLowerCase() == 'put') { + xhr.send(options.sourceFile); + } else { + xhr.send(options.formData); + } + }; + return ( + <> +

自定义 xhr 上传方式(before-xhr-upload)

+ + + ) +} +export default App; +``` +::: + ### 手动上传 :::demo @@ -223,27 +320,32 @@ export default App; | 字段 | 说明 | 类型 | 默认值 | |-------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------|------------------| +| autoUpload | 是否在选取文件后立即进行上传,false 时需要手动执行 ref submit 方法进行上传 | Boolean | true | | name | `input` 标签 `name` 的名称,发到后台的文件参数名 | String | "file" | | url | 上传服务器的接口地址 | String | - | +| defaultFileList | 默认已经上传的文件列表 | FileItem[] | [] | | isPreview | 是否上传成功后展示预览图 | Boolean | true | | defaultImg | 当上传非图片('image')格式的默认图片地址 | String | '' | | isDeletable | 是否展示删除按钮 | Boolean | true | | method | 上传请求的 http method | String | "post" | +| listType | 上传列表的内建样式,支持两种基本样式 picture、list | String | "picture" | | capture | 图片[选取模式](https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/input#htmlattrdefcapture),直接调起摄像头 | String | false | | maximize | 可以设定最大上传文件的大小(字节) | Number丨String | Number.MAX_VALUE | | maximum | 文件上传数量限制 | Number丨String | 1 | -| clearInput | 是否需要清空`input`内容,设为`true`支持重复选择上传同一个文件 | Boolean | false | +| clearInput | 是否需要清空`input`内容,设为`true`支持重复选择上传同一个文件 | Boolean | true | | accept | 允许上传的文件类型,[详细说明](https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/Input/file#%E9%99%90%E5%88%B6%E5%85%81%E8%AE%B8%E7%9A%84%E6%96%87%E4%BB%B6%E7%B1%BB%E5%9E%8B) | String | * | | headers | 设置上传的请求头部 | Object | {} | | data | 附加上传的信息 formData | Object | {} | | uploadIcon | 上传区域[图标名称](#/zh-CN/icon)或图片链接 | String | "photograph" | +| uploadIconSize | 上传区域[图标尺寸](#/icon)大小,如 `20px` `2em` `2rem` | String or Number | - | | xhrState | 接口响应的成功状态(status)值 | Number | 200 | -| withCredentials | 支持发送 cookie 凭证信息 | Boolean | fasle | -| multiple | 是否支持文件多选 | Boolean | fasle | -| disabled | 是否禁用文件上传 | Boolean | fasle | +| withCredentials | 支持发送 cookie 凭证信息 | Boolean | false | +| multiple | 是否支持文件多选 | Boolean | false | +| disabled | 是否禁用文件上传 | Boolean | false | | timeout | 超时时间,单位为毫秒 | Number丨String | 1000 * 30 | -| beforeUpload | 上传前的函数需要返回一个`Promise`对象 | Function | null | -| beforeDelete | 除文件时的回调,返回值为 false 时不移除。支持返回一个 `Promise` 对象,`Promise` 对象 resolve(false) 或 reject 时不移除 | Function(file): boolean 丨Promise | - | +| onBeforeUpload | 上传前的函数需要返回一个`Promise`对象 | Function | null | +| onBeforeXhrUpload | 执行 XHR 上传时,自定义方式 | Function(xhr,option) | null | +| onBeforeDelete | 除文件时的回调,返回值为 false 时不移除。支持返回一个 `Promise` 对象,`Promise` 对象 resolve(false) 或 reject 时不移除 | Function(file): boolean 丨Promise | - | @@ -262,11 +364,12 @@ export default App; | 名称 | 说明 | 回调参数 | |----------|------------------------|----------------------| -| start | 文件上传开始 | options | -| progress | 文件上传的进度 | event,options | -| oversize | 文件大小超过限制时触发 | files | -| success | 上传成功 | responseText,options | -| failure | 上传失败 | responseText,options | -| change | 上传文件改变时的状态 | fileList,event | -| removeImage | 文件删除之前的状态 | files,fileList | +| onStart | 文件上传开始 | options | +| onProgress | 文件上传的进度 | event,options,percentage | +| onOversize | 文件大小超过限制时触发 | files | +| onSuccess | 上传成功 | responseText,options | +| onFailure | 上传失败 | responseText,options | +| onChange | 上传文件改变时的状态 | fileList,event | +| onRemove | 文件删除之前的状态 | files,fileList | +| onFileItemClick | 文件上传成功后点击触发 | fileItem | diff --git a/src/packages/uploader/doc.zh-TW.md b/src/packages/uploader/doc.zh-TW.md index ade303d..5f0696e 100644 --- a/src/packages/uploader/doc.zh-TW.md +++ b/src/packages/uploader/doc.zh-TW.md @@ -32,6 +32,62 @@ export default App; ``` ::: +### 上传状态 + +:::demo +``` tsx +import React, { useState } from "react"; +import { Uploader } from '@nutui/nutui-react'; + +const App = () => { + const uploadUrl = 'https://my-json-server.typicode.com/linrufeng/demo/posts' + const defaultFileList: FileType[] = [ + { + name: '檔1.png', + url: 'https://m.360buyimg.com/babel/jfs/t1/164410/22/25162/93384/616eac6cE6c711350/0cac53c1b82e1b05.gif', + status: 'success', + message: '上傳成功', + type: 'image', + uid: '123', + }, + { + name: '檔2.png', + url: 'https://m.360buyimg.com/babel/jfs/t1/164410/22/25162/93384/616eac6cE6c711350/0cac53c1b82e1b05.gif', + status: 'error', + message: '上傳失敗', + type: 'image', + uid: '124', + }, + { + name: '檔3.png', + url: 'https://m.360buyimg.com/babel/jfs/t1/164410/22/25162/93384/616eac6cE6c711350/0cac53c1b82e1b05.gif', + status: 'uploading', + message: '上傳中...', + type: 'image', + uid: '125', + }, + ] + const onDelete = (file: FileItem, fileList: FileItem[]) => { + console.log(translated.ca3903f3, file, fileList) + } + return ( + <> +

上傳狀態

+ + + ) +} +export default App; +``` +::: + ### 自定義上傳樣式 :::demo @@ -45,7 +101,7 @@ const App = () => { <>

自定義上傳樣式

- @@ -56,19 +112,33 @@ export default App; ``` ::: -### 直接調起攝像頭(移動端生效) +### 自定義上傳使用默認進度條 :::demo ``` tsx import React, { useState } from "react"; -import { Uploader, Button } from '@nutui/nutui-react'; +import { Uploader, Button, Progress } from '@nutui/nutui-react'; const App = () => { const uploadUrl = 'https://my-json-server.typicode.com/linrufeng/demo/posts' + const [progressPercent, setProgressPercent] = useState(0) + const onProgress = ({ event, options, percentage }: any) => { + setProgressPercent(percentage) + } return ( <> -

直接調起攝像頭(移動端生效)

- +

自定義上傳使用默認進度條

+ + + +
+ ) } @@ -76,7 +146,7 @@ export default App; ``` ::: -### 上傳狀態 +### 直接調起攝像頭(移動端生效) :::demo ``` tsx @@ -85,13 +155,10 @@ import { Uploader, Button } from '@nutui/nutui-react'; const App = () => { const uploadUrl = 'https://my-json-server.typicode.com/linrufeng/demo/posts' - const onDelete = (file: FileItem, fileList: FileItem[]) => { - console.log('delete 事件觸發', file, fileList) - } return ( <> -

上傳狀態

- +

直接調起攝像頭(移動端生效)

+ ) } @@ -99,7 +166,6 @@ export default App; ``` ::: - ### 限制上傳數量5個 :::demo @@ -120,7 +186,7 @@ export default App; ``` ::: -### 限制上傳大小(每個文件最大不超過 50kb,也可以在beforeupload中自行處理) +### 限制上傳大小(每個文件最大不超過 50kb) :::demo ``` tsx @@ -172,6 +238,37 @@ export default App; ``` ::: +### 自定義 xhr 上傳方式(before-xhr-upload) + +:::demo +``` tsx +import React, { useState } from "react"; +import { Uploader, Button } from '@nutui/nutui-react'; + +const App = () => { + const uploadUrl = 'https://my-json-server.typicode.com/linrufeng/demo/posts' + const beforeXhrUpload = (xhr: XMLHttpRequest, options: any) => { + if (options.method.toLowerCase() == 'put') { + xhr.send(options.sourceFile); + } else { + xhr.send(options.formData); + } + }; + return ( + <> +

自定義 xhr 上傳方式(before-xhr-upload)

+ + + ) +} +export default App; +``` +::: + ### 手動上傳 :::demo @@ -223,12 +320,15 @@ export default App; | 字段 | 說明 | 類型 | 默認值 | |-------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------|------------------| +| autoUpload | 是否在選取文件後立即進行上傳,false 時需要手動執行 ref submit 方法進行上傳 | Boolean | true | | name | `input` 標籤 `name` 的名稱,發到後台的文件參數名 | String | "file" | | url | 上傳服務器的接口地址 | String | - | +| defaultFileList | 默認已經上傳的文件列表 | FileItem[] | [] | | isPreview | 是否上傳成功後展示預覽圖 | Boolean | true | | defaultImg | 當上傳非圖片('image')格式的默認圖片地址 | String | '' | | isDeletable | 是否展示刪除按鈕 | Boolean | true | | method | 上傳請求的 http method | String | "post" | +| listType | 上傳列表的內建樣式,支持兩種基本樣式 picture、list | String | "picture" | | capture | 圖片[選取模式](https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/input#htmlattrdefcapture),直接調起攝像頭 | String | false | | maximize | 可以設定最大上傳文件的大小(字節) | Number丨String | Number.MAX_VALUE | | maximum | 文件上傳數量限制 | Number丨String | 1 | @@ -237,13 +337,15 @@ export default App; | headers | 設置上傳的請求頭部 | Object | {} | | data | 附加上傳的信息 formData | Object | {} | | uploadIcon | 上傳區域[圖標名稱](#/zh-CN/icon)或圖片鏈接 | String | "photograph" | +| uploadIconSize | 上傳區域[圖標尺寸](#/icon)大小,如 `20px` `2em` `2rem` | String or Number | - | | xhrState | 接口響應的成功狀態(status)值 | Number | 200 | | withCredentials | 支持發送 cookie 憑證信息 | Boolean | fasle | | multiple | 是否支持文件多選 | Boolean | fasle | | disabled | 是否禁用文件上傳 | Boolean | fasle | | timeout | 超時時間,單位為毫秒 | Number丨String | 1000 * 30 | -| beforeUpload | 上傳前的函數需要返回一個`Promise`對象 | Function | null | -| beforeDelete | 除文件時的回調,返回值為 false 時不移除。支持返回一個 `Promise` 對象,`Promise` 對象 resolve(false) 或 reject 時不移除 | Function(file): boolean 丨Promise | - | +| onBeforeUpload | 上傳前的函數需要返回一個`Promise`對象 | Function | null | +| onBeforeXhrUpload | 執行 XHR 上傳時,自定義方式 | Function(xhr,option) | null | +| onBeforeDelete | 除文件時的回調,返回值為 false 時不移除。支持返回一個 `Promise` 對象,`Promise` 對象 resolve(false) 或 reject 時不移除 | Function(file): boolean 丨Promise | - | @@ -262,11 +364,12 @@ export default App; | 名稱 | 說明 | 回調參數 | |----------|------------------------|----------------------| -| start | 文件上傳開始 | options | -| progress | 文件上傳的進度 | event,options | -| oversize | 文件大小超過限制時觸發 | files | -| success | 上傳成功 | responseText,options | -| failure | 上傳失敗 | responseText,options | -| change | 上傳文件改變時的狀態 | fileList,event | -| removeImage | 文件刪除之前的狀態 | files,fileList | +| onStart | 文件上傳開始 | options | +| onProgress | 文件上傳的進度 | event,options,percentage | +| onOversize | 文件大小超過限制時觸發 | files | +| onSuccess | 上傳成功 | responseText,options | +| onFailure | 上傳失敗 | responseText,options | +| onChange | 上傳文件改變時的狀態 | fileList,event | +| onRemove | 文件刪除之前的狀態 | files,fileList | +| onFileItemClick | 文件上傳成功後點擊觸發 | fileItem | diff --git a/src/packages/uploader/upload.ts b/src/packages/uploader/upload.ts index 8f24e75..cb1c6ff 100644 --- a/src/packages/uploader/upload.ts +++ b/src/packages/uploader/upload.ts @@ -7,6 +7,8 @@ export class UploadOptions { formData?: FormData + sourceFile: any + method = 'post' xhrState: string | number = 200 @@ -64,7 +66,11 @@ export class Upload { xhr.setRequestHeader(key, value as string) } options.onStart?.(options) - xhr.send(options.formData) + if (options.beforeXhrUpload) { + options.beforeXhrUpload(xhr, options) + } else { + xhr.send(options.formData) + } } else { console.warn('浏览器不支持 XMLHttpRequest') } diff --git a/src/packages/uploader/uploader.taro.tsx b/src/packages/uploader/uploader.taro.tsx index c1239ed..c6e9a3a 100644 --- a/src/packages/uploader/uploader.taro.tsx +++ b/src/packages/uploader/uploader.taro.tsx @@ -50,7 +50,7 @@ export interface UploaderProps extends IComponent { sizeType: (keyof sizeType)[] sourceType: (keyof sourceType)[] maximize: number - defaultFileList: FileType[] + defaultFileList: FileItem[] listType: string uploadIcon: string uploadIconSize: string | number @@ -85,9 +85,9 @@ export interface UploaderProps extends IComponent { responseText: XMLHttpRequest['responseText'] option: UploadOptions }) => void - update?: (fileList: any[]) => void + update?: (fileList: FileItem[]) => void oversize?: (file: Taro.chooseImage.ImageFile[]) => void - change?: (param: { fileList: any[] }) => void + change?: (param: { fileList: FileItem[] }) => void beforeUpload?: (file: any[]) => Promise beforeXhrUpload?: ( file: Taro.chooseImage.ImageFile[] @@ -129,7 +129,7 @@ const defaultProps = { export class FileItem { status: FileItemStatus = 'ready' - message = '准备中..' + message: string = '准备中..' uid: string = new Date().getTime().toString() @@ -189,11 +189,10 @@ const InternalUploader: ForwardRefRenderFunction< beforeDelete, ...restProps } = { ...defaultProps, ...props } - const [fileList, setFileList] = useState([]) + const [fileList, setFileList] = useState([]) const [uploadQueue, setUploadQueue] = useState[]>([]) useEffect(() => { - console.log('defaultFileList', defaultFileList) if (defaultFileList) { setFileList(defaultFileList) } diff --git a/src/packages/uploader/uploader.tsx b/src/packages/uploader/uploader.tsx index 894b31a..33e0670 100644 --- a/src/packages/uploader/uploader.tsx +++ b/src/packages/uploader/uploader.tsx @@ -7,6 +7,7 @@ import React, { } from 'react' import classNames from 'classnames' import Icon from '@/packages/icon' +import Progress from '@/packages/progress' import { Upload, UploadOptions } from './upload' import bem from '@/utils/bem' import { useConfig } from '@/packages/configprovider' @@ -48,29 +49,31 @@ export interface UploaderProps extends IComponent { className: string defaultImg: string style: React.CSSProperties - start?: (option: UploadOptions) => void - removeImage?: (file: FileItem, fileList: FileItem[]) => void - success?: (param: { + onStart?: (option: UploadOptions) => void + onRemove?: (file: FileItem, fileList: FileItem[]) => void + onSuccess?: (param: { responseText: XMLHttpRequest['responseText'] option: UploadOptions }) => void - progress?: (param: { + onProgress?: (param: { e: ProgressEvent option: UploadOptions + percentage: string | number }) => void - failure?: (param: { + onFailure?: (param: { responseText: XMLHttpRequest['responseText'] option: UploadOptions }) => void - update?: (fileList: any[]) => void - oversize?: (file: File[]) => void - change?: (param: { + onUpdate?: (fileList: any[]) => void + onOversize?: (file: File[]) => void + onChange?: (param: { fileList: any[] event: React.ChangeEvent }) => void - beforeUpload?: (file: File[]) => Promise - beforeDelete?: (file: FileItem, files: FileItem[]) => boolean - fileItemClick?: (file: FileItem) => void + onBeforeUpload?: (file: File[]) => Promise + onBeforeXhrUpload?: (xhr: XMLHttpRequest, options: any) => void + onBeforeDelete?: (file: FileItem, files: FileItem[]) => boolean + onFileItemClick?: (file: FileItem) => void } const defaultProps = { @@ -93,11 +96,11 @@ const defaultProps = { xhrState: 200, timeout: 1000 * 30, withCredentials: false, - clearInput: false, + clearInput: true, isPreview: true, isDeletable: true, capture: false, - beforeDelete: (file: FileItem, files: FileItem[]) => { + onBeforeDelete: (file: FileItem, files: FileItem[]) => { return true }, } as UploaderProps @@ -115,6 +118,10 @@ export class FileItem { type?: string + path?: string + + percentage: string | number = 0 + formData: FormData = new FormData() } const InternalUploader: ForwardRefRenderFunction< @@ -148,24 +155,26 @@ const InternalUploader: ForwardRefRenderFunction< className, autoUpload, clearInput, - start, - removeImage, - change, - fileItemClick, - progress, - success, - update, - failure, - oversize, - beforeUpload, - beforeDelete, + iconClassPrefix, + iconFontClassName, + onStart, + onRemove, + onChange, + onFileItemClick, + onProgress, + onSuccess, + onUpdate, + onFailure, + onOversize, + onBeforeUpload, + onBeforeXhrUpload, + onBeforeDelete, ...restProps } = { ...defaultProps, ...props } const [fileList, setFileList] = useState([]) const [uploadQueue, setUploadQueue] = useState[]>([]) useEffect(() => { - console.log('defaultFileList', defaultFileList) if (defaultFileList) { setFileList(defaultFileList) } @@ -207,6 +216,10 @@ const InternalUploader: ForwardRefRenderFunction< uploadOption.xhrState = xhrState uploadOption.headers = headers uploadOption.withCredentials = withCredentials + uploadOption.beforeXhrUpload = onBeforeXhrUpload + try { + uploadOption.sourceFile = fileItem.formData.get(name) + } catch (error) {} uploadOption.onStart = (option: UploadOptions) => { clearUploadQueue(index) setFileList((fileList: FileItem[]) => { @@ -219,7 +232,7 @@ const InternalUploader: ForwardRefRenderFunction< }) return [...fileList] }) - start && start(option) + onStart && onStart(option) } uploadOption.onProgress = ( e: ProgressEvent, @@ -230,19 +243,20 @@ const InternalUploader: ForwardRefRenderFunction< if (item.uid === fileItem.uid) { item.status = 'uploading' item.message = locale.uploader.uploading + item.percentage = ((e.loaded / e.total) * 100).toFixed(0) + onProgress && onProgress({ e, option, percentage: item.percentage }) } return item }) return [...fileList] }) - progress && progress({ e, option }) } uploadOption.onSuccess = ( responseText: XMLHttpRequest['responseText'], option: UploadOptions ) => { setFileList((fileList: FileItem[]) => { - update && update(fileList) + onUpdate && onUpdate(fileList) fileList.map((item) => { if (item.uid === fileItem.uid) { item.status = 'success' @@ -252,8 +266,8 @@ const InternalUploader: ForwardRefRenderFunction< }) return [...fileList] }) - success && - success({ + onSuccess && + onSuccess({ responseText, option, }) @@ -272,8 +286,8 @@ const InternalUploader: ForwardRefRenderFunction< }) return [...fileList] }) - failure && - failure({ + onFailure && + onFailure({ responseText, option, }) @@ -330,7 +344,7 @@ const InternalUploader: ForwardRefRenderFunction< return true }) if (oversizes.length) { - oversize && oversize(files) + onOversize && onOversize(files) } if (filterFile.length > maximum) { @@ -346,9 +360,9 @@ const InternalUploader: ForwardRefRenderFunction< const onDelete = (file: FileItem, index: number) => { clearUploadQueue(index) - if (beforeDelete && beforeDelete(file, fileList)) { + if (onBeforeDelete && onBeforeDelete(file, fileList)) { fileList.splice(index, 1) - removeImage && removeImage(file, fileList) + onRemove && onRemove(file, fileList) setFileList([...fileList]) } else { console.log(locale.uploader.deleteWord) @@ -362,8 +376,8 @@ const InternalUploader: ForwardRefRenderFunction< const $el = event.target const { files } = $el - if (beforeUpload) { - beforeUpload(new Array().slice.call(files)).then( + if (onBeforeUpload) { + onBeforeUpload(new Array().slice.call(files)).then( (f: Array) => { const _files: File[] = filterFiles(new Array().slice.call(f)) readFile(_files) @@ -374,7 +388,7 @@ const InternalUploader: ForwardRefRenderFunction< readFile(_files) } - props.change && props.change({ fileList, event }) + onChange && onChange({ fileList, event }) if (clearInput) { clearInputValue($el) @@ -382,7 +396,7 @@ const InternalUploader: ForwardRefRenderFunction< } const handleItemClick = (file: FileItem) => { - fileItemClick && fileItemClick(file) + onFileItemClick && onFileItemClick(file) } return ( @@ -437,8 +451,8 @@ const InternalUploader: ForwardRefRenderFunction< item.status !== 'success' && (
@@ -512,21 +526,28 @@ const InternalUploader: ForwardRefRenderFunction< onClick={() => handleItemClick(item)} >  {item.name}
onDelete(item, index)} /> - {/* 缺少进度条组件,待更新 */} + {item.status === 'uploading' && ( + + )}
)} @@ -536,8 +557,8 @@ const InternalUploader: ForwardRefRenderFunction< {maximum > fileList.length && listType === 'picture' && !children && (