提交 0e01d99b 编写于 作者: L liuyijun17

feat: 新增uploader属性补齐开发

上级 56b913f8
......@@ -66,6 +66,8 @@ export const Button: FunctionComponent<Partial<ButtonProps>> = (props) => {
onClick,
className,
style,
iconClassPrefix,
iconFontClassName,
...rest
} = {
...defaultProps,
......
......@@ -78,6 +78,8 @@ export const Progress: FunctionComponent<
iconSize,
rounded,
children,
iconClassPrefix,
iconFontClassName,
...rest
} = { ...defaultProps, ...props }
......
......@@ -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', () => {
<Uploader
isDeletable
defaultFileList={defaultFileList}
removeImage={onDelete}
beforeDelete={() => {
onRemove={onDelete}
onBeforeDelete={() => {
return false
}}
/>
......@@ -195,8 +195,8 @@ test('before-delete prop return true', () => {
<Uploader
isDeletable
defaultFileList={defaultFileList}
removeImage={onDelete}
beforeDelete={() => {
onRemove={onDelete}
onBeforeDelete={() => {
return true
}}
/>
......
.demo.bg-w {
background: #fff;
.demo-uploader {
padding: 57px 17px 15px 17px !important;
}
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<uploadRefState>(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 (
<>
<div className="demo bg-w">
<div className="demo bg-w demo-uploader">
<h2>{translated['84aa6bce']}</h2>
<Uploader url={uploadUrl} start={onStart} />
<Uploader url={uploadUrl} onStart={onStart} />
<h2>{translated.a4afedb5}</h2>
<Uploader
url={uploadUrl}
defaultFileList={defaultFileList}
removeImage={onDelete}
onRemove={onDelete}
maximum="3"
multiple
uploadIcon="dongdong"
/>
<h2>{translated.upload_list_show}</h2>
<Uploader
url={uploadUrl}
defaultFileList={defaultFileList}
maximum="10"
multiple
listType="list"
>
<Button type="success" size="small">
{translated.bb5caa9c}
</Button>
</Uploader>
<h2>{translated['37c65f47']}</h2>
<Uploader url={uploadUrl}>
<Button type="success" size="small">
{translated.bb5caa9c}
</Button>
</Uploader>
<h2>{translated.upload_default_progress}</h2>
<Uploader url={uploadUrl} onProgress={onProgress}>
<Button type="success" size="small">
{translated.bb5caa9c}
</Button>
</Uploader>
<br />
<Progress
percentage={progressPercent}
strokeColor="linear-gradient(270deg, rgba(18,126,255,1) 0%,rgba(32,147,255,1) 32.815625%,rgba(13,242,204,1) 100%)"
status
/>
<h2>{translated['27f1376e']}</h2>
<Uploader url={uploadUrl} capture />
<h2>{translated.a4afedb5}</h2>
<Uploader url={uploadUrl} multiple removeImage={onDelete} />
<h2>{translated['0e5eaea3']}</h2>
<Uploader url={uploadUrl} multiple maximum="5" />
<h2>{translated.b7454181}</h2>
<Uploader
url={uploadUrl}
multiple
maximize={1024 * 50}
oversize={onOversize}
onOversize={onOversize}
/>
<h2>{translated['5c393e52']}</h2>
<Uploader url={uploadUrl} multiple beforeUpload={beforeUpload} />
<Uploader url={uploadUrl} multiple onBeforeUpload={beforeUpload} />
<h2>{translated.e3217a8d}</h2>
<Uploader
url={uploadUrl}
......@@ -226,6 +286,14 @@ const UploaderDemo = () => {
headers={formData}
withCredentials
/>
<h2>{translated.upload_xhr_custom}</h2>
<Uploader
url={uploadUrl}
method="put"
onBeforeXhrUpload={beforeXhrUpload}
/>
<h2>{translated['67fffe24']}</h2>
<Uploader
url={uploadUrl}
......@@ -237,6 +305,7 @@ const UploaderDemo = () => {
<Button type="success" size="small" onClick={submitUpload}>
{translated.fcf01d1a}
</Button>
<h2>{translated['7db1a8b2']}</h2>
<Uploader disabled />
</div>
......
......@@ -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<string>[] = [
{
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 (
<>
<h2>upload status</h2>
<Uploader
url={uploadUrl}
defaultFileList={defaultFileList}
onRemove={onDelete}
maximum="3"
multiple
uploadIcon="dongdong"
/>
</>
)
}
export default App;
```
:::
### Customize the upload style
:::demo
......@@ -45,7 +101,7 @@ const App = () => {
<>
<h2>Customize the upload style</h2>
<Uploader url={uploadUrl}>
<Button type="primary" icon="uploader">
<Button type="success" size="small">
Upload the file
</Button>
</Uploader>
......@@ -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 (
<>
<h2>Direct camera up (mobile)</h2>
<Uploader capture url={uploadUrl} />
<h2>Custom upload uses default progress bar</h2>
<Uploader url={uploadUrl} onProgress={onProgress}>
<Button type="success" size="small">
upload files
</Button>
</Uploader>
<br />
<Progress
percentage={progressPercent}
strokeColor="linear-gradient(270deg, rgba(18,126,255,1) 0%,rgba(32,147,255,1) 32.815625%,rgba(13,242,204,1) 100%)"
status
/>
</>
)
}
......@@ -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 (
<>
<h2>Upload status</h2>
<Uploader url={uploadUrl} multiple removeImage={onDelete} />
<h2>Direct camera up (mobile)</h2>
<Uploader capture url={uploadUrl} />
</>
)
}
......@@ -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 (
<>
<h2>Custom xhr upload method (before-xhr-upload)</h2>
<Uploader
url={uploadUrl}
method="put"
onBeforeXhrUpload={beforeXhrUpload}
/>
</>
)
}
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 |
......@@ -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<string>[] = [
{
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 (
<>
<h2>上传状态</h2>
<Uploader
url={uploadUrl}
defaultFileList={defaultFileList}
onRemove={onDelete}
maximum="3"
multiple
uploadIcon="dongdong"
/>
</>
)
}
export default App;
```
:::
### 自定义上传样式
:::demo
......@@ -45,7 +101,7 @@ const App = () => {
<>
<h2>自定义上传样式</h2>
<Uploader url={uploadUrl}>
<Button type="primary" icon="uploader">
<Button type="success" size="small">
上传文件
</Button>
</Uploader>
......@@ -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 (
<>
<h2>直接调起摄像头(移动端生效)</h2>
<Uploader capture url={uploadUrl} />
<h2>自定义上传使用默认进度条</h2>
<Uploader url={uploadUrl} onProgress={onProgress}>
<Button type="success" size="small">
上传文件
</Button>
</Uploader>
<br />
<Progress
percentage={progressPercent}
strokeColor="linear-gradient(270deg, rgba(18,126,255,1) 0%,rgba(32,147,255,1) 32.815625%,rgba(13,242,204,1) 100%)"
status
/>
</>
)
}
......@@ -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 (
<>
<h2>上传状态</h2>
<Uploader url={uploadUrl} multiple removeImage={onDelete} />
<h2>直接调起摄像头(移动端生效)</h2>
<Uploader capture url={uploadUrl} />
</>
)
}
......@@ -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 (
<>
<h2>自定义 xhr 上传方式(before-xhr-upload)</h2>
<Uploader
url={uploadUrl}
method="put"
onBeforeXhrUpload={beforeXhrUpload}
/>
</>
)
}
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 |
......@@ -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<string>[] = [
{
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 (
<>
<h2>上傳狀態</h2>
<Uploader
url={uploadUrl}
defaultFileList={defaultFileList}
onRemove={onDelete}
maximum="3"
multiple
uploadIcon="dongdong"
/>
</>
)
}
export default App;
```
:::
### 自定義上傳樣式
:::demo
......@@ -45,7 +101,7 @@ const App = () => {
<>
<h2>自定義上傳樣式</h2>
<Uploader url={uploadUrl}>
<Button type="primary" icon="uploader">
<Button type="success" size="small">
上傳文件
</Button>
</Uploader>
......@@ -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 (
<>
<h2>直接調起攝像頭(移動端生效)</h2>
<Uploader capture url={uploadUrl} />
<h2>自定義上傳使用默認進度條</h2>
<Uploader url={uploadUrl} onProgress={onProgress}>
<Button type="success" size="small">
上傳文件
</Button>
</Uploader>
<br />
<Progress
percentage={progressPercent}
strokeColor="linear-gradient(270deg, rgba(18,126,255,1) 0%,rgba(32,147,255,1) 32.815625%,rgba(13,242,204,1) 100%)"
status
/>
</>
)
}
......@@ -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 (
<>
<h2>上傳狀態</h2>
<Uploader url={uploadUrl} multiple removeImage={onDelete} />
<h2>直接調起攝像頭(移動端生效)</h2>
<Uploader capture url={uploadUrl} />
</>
)
}
......@@ -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 (
<>
<h2>自定義 xhr 上傳方式(before-xhr-upload)</h2>
<Uploader
url={uploadUrl}
method="put"
onBeforeXhrUpload={beforeXhrUpload}
/>
</>
)
}
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 |
......@@ -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')
}
......
......@@ -50,7 +50,7 @@ export interface UploaderProps extends IComponent {
sizeType: (keyof sizeType)[]
sourceType: (keyof sourceType)[]
maximize: number
defaultFileList: FileType<string>[]
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<any[]>
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<any>([])
const [fileList, setFileList] = useState<FileItem[]>([])
const [uploadQueue, setUploadQueue] = useState<Promise<Upload>[]>([])
useEffect(() => {
console.log('defaultFileList', defaultFileList)
if (defaultFileList) {
setFileList(defaultFileList)
}
......
......@@ -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<XMLHttpRequestEventTarget>
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<HTMLInputElement>
}) => void
beforeUpload?: (file: File[]) => Promise<File[]>
beforeDelete?: (file: FileItem, files: FileItem[]) => boolean
fileItemClick?: (file: FileItem) => void
onBeforeUpload?: (file: File[]) => Promise<File[]>
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<any>([])
const [uploadQueue, setUploadQueue] = useState<Promise<Upload>[]>([])
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<XMLHttpRequestEventTarget>,
......@@ -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<File>().slice.call(files)).then(
if (onBeforeUpload) {
onBeforeUpload(new Array<File>().slice.call(files)).then(
(f: Array<File>) => {
const _files: File[] = filterFiles(new Array<File>().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' && (
<div className="nut-uploader__preview__progress">
<Icon
classPrefix={props.iconClassPrefix}
fontClassName={props.iconFontClassName}
classPrefix={iconClassPrefix}
fontClassName={iconFontClassName}
color="#fff"
name={`${
item.status === 'error' ? 'failure' : 'loading'
......@@ -453,8 +467,8 @@ const InternalUploader: ForwardRefRenderFunction<
{isDeletable && (
<Icon
classPrefix={props.iconClassPrefix}
fontClassName={props.iconFontClassName}
classPrefix={iconClassPrefix}
fontClassName={iconFontClassName}
color="rgba(0,0,0,0.6)"
className="close"
name="failure"
......@@ -489,8 +503,8 @@ const InternalUploader: ForwardRefRenderFunction<
className="nut-uploader__preview-img__file__name"
>
<Icon
classPrefix={props.iconClassPrefix}
fontClassName={props.iconFontClassName}
classPrefix={iconClassPrefix}
fontClassName={iconFontClassName}
color="#808080"
name="link"
/>
......@@ -512,21 +526,28 @@ const InternalUploader: ForwardRefRenderFunction<
onClick={() => handleItemClick(item)}
>
<Icon
classPrefix={props.iconClassPrefix}
fontClassName={props.iconFontClassName}
classPrefix={iconClassPrefix}
fontClassName={iconFontClassName}
name="link"
/>
&nbsp;{item.name}
</div>
<Icon
classPrefix={props.iconClassPrefix}
fontClassName={props.iconFontClassName}
classPrefix={iconClassPrefix}
fontClassName={iconFontClassName}
color="#808080"
className="nut-uploader__preview-img__file__del"
name="del"
onClick={() => onDelete(item, index)}
/>
{/* 缺少进度条组件,待更新 */}
{item.status === 'uploading' && (
<Progress
size="small"
percentage={item.percentage}
strokeColor="linear-gradient(270deg, rgba(18,126,255,1) 0%,rgba(32,147,255,1) 32.815625%,rgba(13,242,204,1) 100%)"
showText={false}
/>
)}
</div>
)}
</div>
......@@ -536,8 +557,8 @@ const InternalUploader: ForwardRefRenderFunction<
{maximum > fileList.length && listType === 'picture' && !children && (
<div className={`nut-uploader__upload ${listType}`}>
<Icon
classPrefix={props.iconClassPrefix}
fontClassName={props.iconFontClassName}
classPrefix={iconClassPrefix}
fontClassName={iconFontClassName}
size={uploadIconSize}
color="#808080"
name={uploadIcon}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册