@nutui/nutui-react
Version:
京东风格的轻量级移动端 React 组件库,支持一套代码生成 H5 和小程序
404 lines (403 loc) • 16.4 kB
JavaScript
import { _ as __rest } from "./tslib.es6.js";
import React__default, { useRef, useState, useEffect, useImperativeHandle } from "react";
import classNames from "classnames";
import { Failure, Link, Del, Loading, Photograph } from "@nutui/icons-react";
import { useConfig } from "./ConfigProvider.js";
import { b as isPromise } from "./index.js";
import { C as ComponentDefaults } from "./typings.js";
import Button__default from "./Button.js";
import { u as usePropsValue } from "./use-props-value.js";
import Progress__default from "./Progress.js";
import Image__default from "./Image.js";
class UploadOptions {
constructor() {
this.url = "";
this.name = "file";
this.fileType = "image";
this.method = "post";
this.xhrState = 200;
this.timeout = 30 * 1e3;
this.headers = {};
this.withCredentials = false;
}
}
const UPLOADING = "uploading";
const SUCCESS = "success";
const ERROR = "error";
class Upload {
constructor(options) {
this.options = options;
}
upload() {
var _a;
const { options } = this;
const xhr = new XMLHttpRequest();
xhr.timeout = options.timeout;
if (xhr.upload) {
xhr.upload.addEventListener("progress", (e) => {
var _a2;
(_a2 = options.onProgress) === null || _a2 === void 0 ? void 0 : _a2.call(options, e, options);
}, false);
xhr.onreadystatechange = () => {
var _a2, _b;
if (xhr.readyState === 4) {
if (xhr.status === options.xhrState) {
(_a2 = options.onSuccess) === null || _a2 === void 0 ? void 0 : _a2.call(options, xhr.responseText, options);
} else {
(_b = options.onFailure) === null || _b === void 0 ? void 0 : _b.call(options, xhr.responseText, options);
}
}
};
xhr.withCredentials = options.withCredentials;
xhr.open(options.method, options.url, true);
for (const [key, value] of Object.entries(options.headers)) {
xhr.setRequestHeader(key, value);
}
(_a = options.onStart) === null || _a === void 0 ? void 0 : _a.call(options, options);
if (options.beforeXhrUpload) {
options.beforeXhrUpload(xhr, options);
} else {
xhr.send(options.formData);
}
} else {
console.warn("浏览器不支持 XMLHttpRequest");
}
}
}
const funcInterceptor = (interceptor, { args = [], done, canceled }) => {
if (interceptor) {
const returnVal = interceptor.apply(null, args);
if (isPromise(returnVal)) {
returnVal.then((value) => {
if (value) {
done(value);
} else if (canceled) {
canceled();
}
}).catch(() => {
});
} else if (returnVal) {
done();
} else if (canceled) {
canceled();
}
} else {
done();
}
};
const Preview = ({ fileList, previewType, deletable, onDeleteItem, handleItemClick, previewUrl, children }) => {
const renderIcon = (item) => {
if (item.status === ERROR) {
return item.failIcon || React__default.createElement(Failure, { color: "#fff" });
}
return item.loadingIcon || React__default.createElement(Loading, { className: "nut-icon-loading", color: "#fff" });
};
return React__default.createElement(React__default.Fragment, null, fileList.length !== 0 && fileList.map((item, index) => {
var _a;
return React__default.createElement(
"div",
{ className: `nut-uploader-preview ${previewType}`, key: item.uid },
previewType === "picture" && !children && deletable && React__default.createElement(Failure, { color: "rgba(0,0,0,0.6)", className: "close", onClick: () => onDeleteItem(item, index) }),
previewType === "picture" && !children && React__default.createElement(
"div",
{ className: "nut-uploader-preview-img" },
item.status === "ready" ? React__default.createElement(
"div",
{ className: "nut-uploader-preview-progress" },
React__default.createElement("div", { className: "nut-uploader-preview-progress-msg" }, item.message)
) : item.status !== "success" && React__default.createElement(
"div",
{ className: "nut-uploader-preview-progress" },
renderIcon(item),
React__default.createElement("div", { className: "nut-uploader-preview-progress-msg" }, item.message)
),
((_a = item.type) === null || _a === void 0 ? void 0 : _a.includes("image")) ? React__default.createElement(React__default.Fragment, null, item.url && React__default.createElement(Image__default, { className: "nut-uploader-preview-img-c", style: { objectFit: "fill" }, src: item.url, alt: "", onClick: () => handleItemClick(item, index) })) : React__default.createElement(React__default.Fragment, null, previewUrl ? React__default.createElement(Image__default, { className: "nut-uploader-preview-img-c", src: previewUrl, alt: "", onClick: () => handleItemClick(item, index) }) : React__default.createElement(
"div",
{ className: "nut-uploader-preview-img-file" },
React__default.createElement(
"div",
{ onClick: () => handleItemClick(item, index), className: "nut-uploader-preview-img-file-name" },
React__default.createElement(Link, { color: "#808080" }),
React__default.createElement(
"span",
null,
" ",
item.name
)
)
)),
item.status === "success" ? React__default.createElement("div", { className: "tips" }, item.name) : null
),
previewType === "list" && React__default.createElement(
"div",
{ className: "nut-uploader-preview-list" },
React__default.createElement(
"div",
{ className: `nut-uploader-preview-img-file-name ${item.status}`, onClick: () => handleItemClick(item, index) },
React__default.createElement(Link, null),
React__default.createElement(
"span",
null,
" ",
item.name
)
),
deletable && React__default.createElement(Del, { color: "#808080", className: "nut-uploader-preview-img-file-del", onClick: () => onDeleteItem(item, index) }),
item.status === "uploading" && React__default.createElement(Progress__default, { percent: item.percentage, color: "linear-gradient(270deg, rgba(18,126,255,1) 0%,rgba(32,147,255,1) 32.815625%,rgba(13,242,204,1) 100%)", showText: false })
)
);
}));
};
class FileItem {
constructor() {
this.status = "ready";
this.message = "";
this.uid = (/* @__PURE__ */ new Date()).getTime().toString();
this.percentage = 0;
this.formData = {};
}
}
const defaultProps = Object.assign(Object.assign({}, ComponentDefaults), { url: "", maxCount: 1, previewType: "picture", fit: "cover", name: "file", accept: "*", disabled: false, autoUpload: true, multiple: false, maxFileSize: Number.MAX_VALUE, data: {}, headers: {}, method: "post", xhrState: 200, timeout: 1e3 * 30, withCredentials: false, clearInput: true, preview: true, deletable: true, capture: false, uploadIcon: React__default.createElement(Photograph, { width: "20px", height: "20px", color: "#808080" }), beforeDelete: (file, files) => {
return true;
} });
const InternalUploader = (props, ref) => {
const { locale } = useConfig();
const fileListRef = useRef([]);
const _a = Object.assign(Object.assign({}, defaultProps), props), { children, uploadIcon, uploadLabel, name, accept, defaultValue, value, previewType, fit, disabled, multiple, url, previewUrl, headers, timeout, method, xhrState, withCredentials, data, preview, deletable, maxCount, capture, maxFileSize, className, autoUpload, clearInput, onStart, onDelete, onChange, onFileItemClick, onProgress, onSuccess, onUpdate, onFailure, onOversize, beforeUpload, beforeXhrUpload, beforeDelete } = _a, restProps = __rest(_a, ["children", "uploadIcon", "uploadLabel", "name", "accept", "defaultValue", "value", "previewType", "fit", "disabled", "multiple", "url", "previewUrl", "headers", "timeout", "method", "xhrState", "withCredentials", "data", "preview", "deletable", "maxCount", "capture", "maxFileSize", "className", "autoUpload", "clearInput", "onStart", "onDelete", "onChange", "onFileItemClick", "onProgress", "onSuccess", "onUpdate", "onFailure", "onOversize", "beforeUpload", "beforeXhrUpload", "beforeDelete"]);
const [fileList, setFileList] = usePropsValue({
value,
defaultValue,
finalValue: [],
onChange: (v) => {
onChange === null || onChange === void 0 ? void 0 : onChange(v);
}
});
const [uploadQueue, setUploadQueue] = useState([]);
const classes = classNames(className, "nut-uploader");
useEffect(() => {
fileListRef.current = fileList;
}, [fileList]);
useImperativeHandle(ref, () => ({
submit: () => {
Promise.all(uploadQueue).then((res) => {
res.forEach((i) => i.upload());
});
},
clear: () => {
clearUploadQueue();
}
}));
const clearUploadQueue = (index = -1) => {
if (index > -1) {
uploadQueue.splice(index, 1);
setUploadQueue(uploadQueue);
} else {
setUploadQueue([]);
setFileList([]);
}
};
const clearInputValue = (el) => {
el.value = "";
};
const executeUpload = (fileItem, index) => {
var _a2, _b;
const uploadOption = new UploadOptions();
uploadOption.url = url;
for (const [key, value2] of Object.entries(data)) {
(_a2 = fileItem.formData) === null || _a2 === void 0 ? void 0 : _a2.append(key, value2);
}
uploadOption.formData = fileItem.formData;
uploadOption.timeout = timeout * 1;
uploadOption.method = method;
uploadOption.xhrState = xhrState;
uploadOption.headers = headers;
uploadOption.withCredentials = withCredentials;
uploadOption.beforeXhrUpload = beforeXhrUpload;
try {
uploadOption.sourceFile = (_b = fileItem.formData) === null || _b === void 0 ? void 0 : _b.get(name);
} catch (error) {
console.warn(error);
}
uploadOption.onStart = (option) => {
clearUploadQueue(index);
setFileList(fileListRef.current.map((item) => {
if (item.uid === fileItem.uid) {
item.status = "ready";
item.message = locale.uploader.readyUpload;
}
return item;
}));
onStart === null || onStart === void 0 ? void 0 : onStart(option);
};
uploadOption.onProgress = (e, option) => {
setFileList(fileListRef.current.map((item) => {
if (item.uid === fileItem.uid) {
item.status = UPLOADING;
item.message = locale.uploader.uploading;
item.percentage = (e.loaded / e.total * 100).toFixed(0);
onProgress === null || onProgress === void 0 ? void 0 : onProgress({ e, option, percentage: item.percentage });
}
return item;
}));
};
uploadOption.onSuccess = (responseText, option) => {
const list = fileListRef.current.map((item) => {
if (item.uid === fileItem.uid) {
item.status = SUCCESS;
item.message = locale.uploader.success;
item.responseText = responseText;
}
return item;
});
setFileList(list);
onUpdate === null || onUpdate === void 0 ? void 0 : onUpdate(list);
onSuccess === null || onSuccess === void 0 ? void 0 : onSuccess({
responseText,
option,
files: list
});
};
uploadOption.onFailure = (responseText, option) => {
const list = fileListRef.current.map((item) => {
if (item.uid === fileItem.uid) {
item.status = ERROR;
item.message = locale.uploader.error;
item.responseText = responseText;
}
return item;
});
setFileList(list);
onFailure === null || onFailure === void 0 ? void 0 : onFailure({
responseText,
option,
files: list
});
};
const task = new Upload(uploadOption);
if (autoUpload) {
task.upload();
} else {
uploadQueue.push(new Promise((resolve, reject) => {
resolve(task);
}));
setUploadQueue(uploadQueue);
}
};
const readFile = (files) => {
files.forEach((file, index) => {
var _a2;
const formData = new FormData();
formData.append(name, file);
const fileItem = new FileItem();
fileItem.name = file.name;
fileItem.status = "ready";
fileItem.type = file.type;
fileItem.formData = formData;
fileItem.uid = file.lastModified + fileItem.uid;
fileItem.message = autoUpload ? locale.uploader.readyUpload : locale.uploader.waitingUpload;
executeUpload(fileItem, index);
if (preview && ((_a2 = file.type) === null || _a2 === void 0 ? void 0 : _a2.includes("image"))) {
const reader = new FileReader();
reader.onload = (event) => {
fileItem.url = event.target.result;
setFileList([...fileList, fileItem]);
};
reader.readAsDataURL(file);
} else {
setFileList([...fileList, fileItem]);
}
});
};
const filterFiles = (files) => {
const maximum = maxCount * 1;
const oversizes = new Array();
const filterFile = files.filter((file) => {
if (file.size > maxFileSize) {
oversizes.push(file);
return false;
}
return true;
});
oversizes.length && (onOversize === null || onOversize === void 0 ? void 0 : onOversize(files));
if (filterFile.length > maximum) {
filterFile.splice(maximum, filterFile.length - maximum);
}
if (fileList.length !== 0) {
const index = maximum - fileList.length;
filterFile.splice(index, filterFile.length - index);
}
return filterFile;
};
const deleted = (file, index) => {
const deletedFileList = fileList.filter((file2, idx) => idx !== index);
onDelete === null || onDelete === void 0 ? void 0 : onDelete(file, deletedFileList);
setFileList(deletedFileList);
};
const onDeleteItem = (file, index) => {
clearUploadQueue(index);
funcInterceptor(beforeDelete, {
args: [file, fileList],
done: () => deleted(file, index)
});
};
const fileChange = (event) => {
if (disabled)
return;
const $el = event.target;
const { files } = $el;
if (beforeUpload) {
beforeUpload(new Array().slice.call(files)).then((f) => {
const _files = filterFiles(new Array().slice.call(f));
if (!_files.length)
$el.value = "";
readFile(_files);
});
} else {
const _files = filterFiles(new Array().slice.call(files));
readFile(_files);
}
if (clearInput) {
clearInputValue($el);
}
};
const handleItemClick = (file, index) => {
onFileItemClick === null || onFileItemClick === void 0 ? void 0 : onFileItemClick(file, index);
};
return React__default.createElement(
"div",
Object.assign({ className: classes }, restProps),
(children || previewType === "list") && React__default.createElement(
"div",
{ className: "nut-uploader-slot" },
children || React__default.createElement(Button__default, { size: "small", type: "primary" }, locale.uploader.list),
Number(maxCount) > fileList.length && React__default.createElement("input", { className: "nut-uploader-input", type: "file", capture, name, accept, disabled, multiple, onChange: fileChange })
),
React__default.createElement(Preview, {
fileList,
previewType,
deletable,
onDeleteItem,
handleItemClick,
previewUrl,
children
}),
Number(maxCount) > fileList.length && previewType === "picture" && !children && React__default.createElement(
"div",
{ className: classNames("nut-uploader-upload", previewType, {
"nut-uploader-upload-disabled": disabled
}) },
React__default.createElement(
"div",
{ className: "nut-uploader-icon" },
uploadIcon,
React__default.createElement("span", { className: "nut-uploader-icon-tip" }, uploadLabel)
),
React__default.createElement("input", { className: "nut-uploader-input", type: "file", capture, name, accept, disabled, multiple, onChange: fileChange })
)
);
};
const Uploader = React__default.forwardRef(InternalUploader);
Uploader.displayName = "NutUploader";
export {
Uploader as default
};