Skip to content

Commit

Permalink
Upload Style & XHR (#1126)
Browse files Browse the repository at this point in the history
* feat(web): upload xhr

* feat(web): upload

* feat(web): upload dragger-btns

* feat(web): upload buttn style

* feat(web): upload

---------

Co-authored-by: sheepluo <sheepluo@tencent.com>
  • Loading branch information
chaishi and sheepluo authored Jan 29, 2023
1 parent b88026e commit a1ddb54
Show file tree
Hide file tree
Showing 6 changed files with 63 additions and 56 deletions.
1 change: 1 addition & 0 deletions docs/web/api/upload.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ spline: form
- 上传组件中的全部文本,均可通过 `locale` 进行修改,也支持全局配置,<a href='/react/config?tab=api#uploadconfig'>查看文档</a>。
- 如果拖拽上传单个文件,设置 `theme="file"``draggable=true`
- 如果拖拽上传单张图片,设置 `theme="image"``draggable=true`
- 可以使用 `fileListDisplay` 自定义文件信息呈现内容。

{{ draggable }}

Expand Down
41 changes: 22 additions & 19 deletions js/upload/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
SuccessContext,
handleSuccessParams,
UploadTriggerUploadText,
ErrorContext,
} from './types';

export interface BeforeUploadExtra {
Expand Down Expand Up @@ -58,27 +59,24 @@ export function handleBeforeUpload(
});
}

export interface OnErrorParams {
event?: ProgressEvent;
files?: UploadFile[];
response?: any;
export interface OnErrorParams extends ErrorContext {
formatResponse?: HandleUploadParams['formatResponse'];
}

export function handleError(options: OnErrorParams) {
const { event, files, response, formatResponse } = options;
const { event, files, response, XMLHttpRequest, formatResponse } = options;
files.forEach((file) => {
file.status = 'fail';
});
let res = response;
if (typeof formatResponse === 'function') {
res = formatResponse(response, { file: files[0], currentFiles: files });
}
return { response: res, event, files };
return { response: res, event, files, XMLHttpRequest };
}

export function handleSuccess(params: handleSuccessParams) {
const { event, files, response } = params;
const { event, files, response, XMLHttpRequest } = params;
if (files?.length <= 0) {
log.error('Upload', 'Empty File in Success Callback');
}
Expand All @@ -89,7 +87,7 @@ export function handleSuccess(params: handleSuccessParams) {
});
const res = response;
files[0].url = res.url || files[0].url;
return { response: res, event, files };
return { response: res, event, files, XMLHttpRequest };
}

export type UploadRequestReturn = {
Expand Down Expand Up @@ -173,7 +171,7 @@ export function uploadOneRequest(params: HandleUploadParams): Promise<UploadRequ
files: params.toUploadFiles,
useMockProgress: params.useMockProgress,
mockProgressDuration: params.mockProgressDuration,
onError: (p: OnErrorParams) => {
onError: (p: ErrorContext) => {
const r = handleError({ ...p, formatResponse: params.formatResponse });
params.onResponseError?.(r);
resolve({ status: 'fail', data: r });
Expand Down Expand Up @@ -272,11 +270,12 @@ Promise<UploadRequestReturn> {
}

export function formatToUploadFile(
tmpFiles: File[],
files: File[],
format: FileChangeParams['format'],
autoUpload: boolean,
status,
percent = 0,
) {
return tmpFiles.map((fileRaw: File) => {
return files.map((fileRaw: File) => {
let file: UploadFile = fileRaw;
if (typeof format === 'function') {
file = format(fileRaw);
Expand All @@ -287,8 +286,8 @@ export function formatToUploadFile(
name: fileRaw.name,
size: fileRaw.size,
type: fileRaw.type,
percent: 0,
status: autoUpload ? 'progress' : 'waiting',
percent,
status,
...file,
};
return uploadFile;
Expand All @@ -311,11 +310,10 @@ export function validateFile(
hasSameNameFile = true;
}
if (!tmpFiles.length) {
const tFiles = formatToUploadFile(files, params.format, params.autoUpload);
const tFiles = formatToUploadFile(files, params.format, params.autoUpload ? 'progress' : 'waiting');
resolve({ hasSameNameFile, file: tFiles?.[0], files: tFiles, validateResult: { type: 'FILTER_FILE_SAME_NAME' } });
return;
}

// 上传文件数量限制
let lengthOverLimit = false;
if (max && tmpFiles.length && !params.isBatchUpload) {
Expand All @@ -326,7 +324,7 @@ export function validateFile(
}

// 格式化文件对象
const formattedFiles = formatToUploadFile(tmpFiles, params.format, params.autoUpload);
const formattedFiles = formatToUploadFile(tmpFiles, params.format, params.autoUpload ? 'progress' : 'waiting');

// 全量文件,一波校验,整体上传 或 终止上传
let allFileValidatePromise;
Expand All @@ -351,6 +349,7 @@ export function validateFile(
}));
Promise.all([allFileValidatePromise].concat(promiseList)).then((results) => {
const [allFilesResult, ...others] = results;
// 如果 beforeAllFilesUpload 校验未通过
if (allFilesResult === false) {
resolve({
lengthOverLimit,
Expand All @@ -372,9 +371,13 @@ export function validateFile(

export function getFilesAndErrors(fileValidateList: FileChangeReturn[], getError: (p: {[key: string]: any }) => string) {
const sizeLimitErrors: FileChangeReturn[] = [];
const beforeUploadErrorFiles: UploadFile[] = [];
const toFiles: UploadFile[] = [];
fileValidateList.forEach((oneFile) => {
if (oneFile.validateResult?.type === 'CUSTOM_BEFORE_UPLOAD') return;
if (oneFile.validateResult?.type === 'CUSTOM_BEFORE_UPLOAD') {
beforeUploadErrorFiles.push(oneFile.file);
return;
}
if (oneFile.validateResult?.type === 'FILE_OVER_SIZE_LIMIT') {
if (!oneFile.file.response) {
oneFile.file.response = {};
Expand All @@ -387,7 +390,7 @@ export function getFilesAndErrors(fileValidateList: FileChangeReturn[], getError
toFiles.push(oneFile.file);
});

return { sizeLimitErrors, toFiles };
return { sizeLimitErrors, beforeUploadErrorFiles, toFiles };
}

/**
Expand Down
18 changes: 13 additions & 5 deletions js/upload/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ export interface UploadFile {
export interface RequestMethodResponse {
status: 'success' | 'fail';
error?: string;
/**
* response.XMLHttpRequest is going to be deprecated
*/
response: { url?: string; [key: string]: any }
}

Expand All @@ -74,10 +77,19 @@ export interface InnerProgressContext {
XMLHttpRequest?: XMLHttpRequest;
}

export interface ErrorContext {
event?: ProgressEvent;
file?: UploadFile;
files?: UploadFile[];
response?: any;
XMLHttpRequest?: XMLHttpRequest;
}

export interface SuccessContext {
event?: ProgressEvent;
file?: UploadFile;
files?: UploadFile[];
XMLHttpRequest?: XMLHttpRequest;
response?: RequestMethodResponse['response'];
}

Expand Down Expand Up @@ -108,11 +120,7 @@ export interface XhrOptions {
name: string;
/** 可与 data 共存 */
formatRequest?: (requestData: { [key: string]: any }) => { [key: string]: any };
onError: ({
event, file, files, response
}: {
event?: ProgressEvent; file?: UploadFile; files?: UploadFile[]; response?: any
}) => void;
onError: (context: ErrorContext) => void;
onSuccess: (context: SuccessContext) => void;
onProgress: (context: InnerProgressContext) => void;
}
Expand Down
28 changes: 1 addition & 27 deletions js/upload/utils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { SizeUnit, TdUploadFile } from './types';
import { SizeUnit } from './types';
import log from '../log/log';

/**
Expand Down Expand Up @@ -146,32 +146,6 @@ export function isOverSizeLimit1(

export const urlCreator = () => window.webkitURL || window.URL;

/**
*
* @param files 待处理文件
* @param format 格式化规则
*/
export function formatFiles(
files: File[] = [],
format?: (file: File) => TdUploadFile
) {
return files.map((fileRaw) => {
const file = typeof format === 'function' ? format(fileRaw) : fileRaw;
const uploadFile: TdUploadFile = {
raw: fileRaw,
lastModified: fileRaw.lastModified,
name: fileRaw.name,
size: fileRaw.size,
type: fileRaw.type,
percent: 0,
status: 'waiting',
...file,
};
uploadFile.url = urlCreator()?.createObjectURL(fileRaw);
return uploadFile;
});
}

export function getFileUrlByFileRaw(fileRaw: File): Promise<string> {
return new Promise((resolve) => {
if (!fileRaw) {
Expand Down
24 changes: 20 additions & 4 deletions js/upload/xhr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,14 +58,17 @@ export default function xhr({

let requestData: { [key: string]: any } = {};
if (data) {
const extraData = typeof data === 'function' ? data(file) : data;
const extraData = typeof data === 'function' ? data(innerFiles) : data;
Object.assign(requestData, extraData);
}
innerFiles.forEach((file, index) => {
const fileField = innerFiles.length > 1 ? `${name}[${index}]` : name;
requestData[fileField] = file.raw;
requestData[name] = file.raw;
});
if (innerFiles.length === 1) {
requestData[name] = innerFiles[0].raw;
}
requestData.length = innerFiles.length;

if (formatRequest) {
requestData = formatRequest(requestData);
Expand All @@ -84,11 +87,15 @@ export default function xhr({
});

xhr.onerror = (event: ProgressEvent) => {
onError({ event, file, files: innerFiles });
onError({ event, file, files: innerFiles, XMLHttpRequest: xhr, });
clearInterval(timer1);
clearTimeout(timer2);
};

xhr.ontimeout = (event) => {
onError({ event, file, files: innerFiles, XMLHttpRequest: xhr, });
};

if (xhr.upload) {
xhr.upload.onprogress = (event: ProgressEvent) => {
let realPercent = 0;
Expand Down Expand Up @@ -117,7 +124,11 @@ export default function xhr({
const isFail = xhr.status < 200 || xhr.status >= 300;
if (isFail) {
return onError({
event, file, files: innerFiles, response
event,
file,
files: innerFiles,
response,
XMLHttpRequest: xhr,
});
}
const text = xhr.responseText || xhr.response;
Expand All @@ -142,11 +153,16 @@ export default function xhr({
event,
file: file || innerFiles[0],
files: [...innerFiles],
XMLHttpRequest: xhr,
response,
});
};

xhr.send(formData);
// @ts-ignore
xhr.upload.requestParams = requestData;
// @ts-ignore
xhr.upload.requestHeaders = headers;

return xhr;
}
7 changes: 6 additions & 1 deletion style/web/components/upload/_index.less
Original file line number Diff line number Diff line change
Expand Up @@ -502,7 +502,8 @@
}
}

.@{prefix}-upload__dragger-progress-cancel {
.@{prefix}-upload__dragger-progress-cancel,
.@{prefix}-upload__dragger-progress-reupload {
margin-right: @upload-dragger-progress-cancel-margin;

&:hover {
Expand Down Expand Up @@ -664,3 +665,7 @@
.@{prefix}-upload--theme-file-input {
width: 100%;
}

.@{prefix}-upload__dragger-btns > .@{prefix}-button {
padding: 0;
}

0 comments on commit a1ddb54

Please sign in to comment.