UNPKG

@nutui/nutui-react

Version:

京东风格的轻量级移动端 React 组件库,支持一套代码生成 H5 和小程序

404 lines (403 loc) 16.4 kB
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 };