UNPKG

@mijadesign/mjui-react-taro

Version:

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

700 lines (699 loc) 24.6 kB
import { a as __awaiter, _ as __rest } from "./tslib.es6-iWu3F_1J.js"; import React, { useState, useImperativeHandle } from "react"; import classNames from "classnames"; /* empty css */ import Taro, { uploadFile, getEnv, chooseMedia, chooseImage } from "@tarojs/taro"; import { B as Button } from "./button.taro-BpN44TAv.js"; import { Progress, ImagePreview, useConfig } from "@nutui/nutui-react-taro"; import { c as c$3 } from "./WarningFill-4RpFcV8c.js"; import { l as l$1 } from "./DynamicLoading-BbSSKJQx.js"; import { Link } from "@nutui/icons-react-taro"; import { Image } from "@tarojs/components"; import { i as isPromise } from "./index-akbK29uE.js"; import { C as ComponentDefaults } from "./typings-DV9RBfhj.js"; import { u as usePropsValue } from "./use-props-value-DuhZMy4U.js"; const c$2 = { size: "", classPrefix: "icon", fontClassName: "iconfont", color: "", tag: "i", onClick: (e) => { }, className: "" }; function h$2(e) { return e === "" ? "" : Number.isNaN(Number(e)) ? String(e) : `${e}px`; } function n(e) { const l2 = "camera-fill", { size: i, classPrefix: r, color: f, tag: m, children: p, className: N, fontClassName: u, style: d, logValue: o, onClick: s, ...k } = { ...c$2, ...e }, C = (g) => { s && s(g); }, a2 = {}, t = h$2(i); return t && (a2.fontSize = t), React.createElement( m, { className: classNames(`${u} ${r}-${l2} ${N || ""}`, { "auto-track": !!o }), style: { color: f, ...a2, ...d }, ...k, onClick: C, "data-log-name": o }, p ); } n.defaultProps = c$2; n.displayName = "FiresoonMobileIcon-camera-fill"; const c$1 = { size: "", classPrefix: "icon", fontClassName: "iconfont", color: "", tag: "i", onClick: (e) => { }, className: "" }; function h$1(e) { return e === "" ? "" : Number.isNaN(Number(e)) ? String(e) : `${e}px`; } function l(e) { const a2 = "delete-fill", { size: i, classPrefix: r, color: f, tag: m, children: d, className: p, fontClassName: N, style: u, logValue: o, onClick: t, ...k } = { ...c$1, ...e }, C = (g) => { t && t(g); }, s = {}, n2 = h$1(i); return n2 && (s.fontSize = n2), React.createElement( m, { className: classNames(`${N} ${r}-${a2} ${p || ""}`, { "auto-track": !!o }), style: { color: f, ...s, ...u }, ...k, onClick: C, "data-log-name": o }, d ); } l.defaultProps = c$1; l.displayName = "FiresoonMobileIcon-delete-fill"; const c = { size: "", classPrefix: "icon", fontClassName: "iconfont", color: "", tag: "i", onClick: (e) => { }, className: "" }; function h(e) { return e === "" ? "" : Number.isNaN(Number(e)) ? String(e) : `${e}px`; } function a(e) { const l2 = "delete", { size: i, classPrefix: r, color: f, tag: m, children: d, className: p, fontClassName: N, style: u, logValue: o, onClick: t, ...k } = { ...c, ...e }, C = (g) => { t && t(g); }, s = {}, n2 = h(i); return n2 && (s.fontSize = n2), React.createElement( m, { className: classNames(`${N} ${r}-${l2} ${p || ""}`, { "auto-track": !!o }), style: { color: f, ...s, ...u }, ...k, onClick: C, "data-log-name": o }, d ); } a.defaultProps = c; a.displayName = "FiresoonMobileIcon-delete"; 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"); } } } class UploaderTaro extends Upload { constructor(options) { super(options); this.uploadTaro = (uploadFile2, env) => __awaiter(this, void 0, void 0, function* () { var _a, _b; const options2 = this.options; if (env === "WEB") { this.upload(); } else if (options2.beforeXhrUpload) { options2.beforeXhrUpload(uploadFile2, options2); } else { const res = yield (_a = options2 === null || options2 === void 0 ? void 0 : options2.beforeUpload) === null || _a === void 0 ? void 0 : _a.call(options2, options2); if (res === false) { return false; } const uploadTask = uploadFile2({ url: options2.url, filePath: options2.taroFilePath, fileType: options2.fileType, header: Object.assign({ "Content-Type": "multipart/form-data" }, options2.headers), formData: options2.formData, name: options2.name, success(response) { var _a2, _b2; if (options2.xhrState === response.statusCode) { (_a2 = options2.onSuccess) === null || _a2 === void 0 ? void 0 : _a2.call(options2, response, options2); } else { (_b2 = options2.onFailure) === null || _b2 === void 0 ? void 0 : _b2.call(options2, response, options2); } }, fail(e) { var _a2; (_a2 = options2.onFailure) === null || _a2 === void 0 ? void 0 : _a2.call(options2, e, options2); } }); (_b = options2.onStart) === null || _b === void 0 ? void 0 : _b.call(options2, options2); uploadTask.progress((res2) => { var _a2; (_a2 = options2.onProgress) === null || _a2 === void 0 ? void 0 : _a2.call(options2, res2, options2); }); } }); } } const prefiexClass = (index, span, maxCount, previewType) => { if (previewType === "picture") { if (maxCount > 1) { return (index + 1) % span === 0 ? "last-row" : ""; } return "single"; } return ""; }; const formatterImagePreview = (list) => { return (list || []).map((v) => { return { src: v.url }; }); }; const Preview = ({ fileList, previewType, deletable, onDeleteItem, handleItemClick, previewUrl, children, span, maxCount, previewMode, imagePreview, tips, multiple }) => { const [previewVisible, setPreviewVisible] = useState(false); const [previewIndex, setPreviewIndex] = useState(0); const renderIcon = (item) => { if (item.status === ERROR) { return item.failIcon || React.createElement(c$3, { size: 20, color: "#fff", style: { lineHeight: "20px" } }); } return item.loadingIcon || React.createElement( "div", { className: "extra-loading-style" }, React.createElement(l$1, { size: 18, color: "#fff" }) ); }; const handlePreview = (index) => { setPreviewIndex(index); setPreviewVisible(true); }; return React.createElement( React.Fragment, null, fileList.length !== 0 && fileList.map((item, index) => { var _a; return React.createElement( "div", { className: `nut-uploader__preview ${previewType} ${item.status} ${prefiexClass(index, span, maxCount, previewType)} ${multiple && span === 3 ? "multiple" : ""}`, key: item.uid }, previewType === "picture" && deletable && !["uploading", "ready"].includes(item.status) && React.createElement(l, { className: "close auto-track", onClick: () => onDeleteItem(item, index) }), previewType === "picture" && !children && React.createElement( "div", { className: `nut-uploader__preview-img col-${span} ${multiple && span === 3 ? "multiple" : ""}` }, item.status === "ready" ? React.createElement( "div", { className: "nut-uploader__preview__progress" }, renderIcon(item), React.createElement("div", { className: "nut-uploader__preview__progress__msg" }, item.message) ) : item.status !== "success" && React.createElement( "div", { className: "nut-uploader__preview__progress" }, renderIcon(item), React.createElement("div", { className: "nut-uploader__preview__progress__msg" }, item.message) ), ((_a = item.type) === null || _a === void 0 ? void 0 : _a.includes("image")) ? React.createElement(React.Fragment, null, item.url && React.createElement(Image, { className: "nut-uploader__preview-img__c", mode: previewMode, src: item.url, onClick: () => { if (imagePreview) { handlePreview(index + 1); } handleItemClick === null || handleItemClick === void 0 ? void 0 : handleItemClick(item, index); } })) : React.createElement(React.Fragment, null, previewUrl ? React.createElement(Image, { className: "nut-uploader__preview-img__c", mode: previewMode, src: previewUrl, onClick: () => handleItemClick(item, index) }) : React.createElement( "div", { className: "nut-uploader__preview-img__file" }, React.createElement( "div", { onClick: () => handleItemClick(item, index), className: "nut-uploader__preview-img__file__name" }, React.createElement(Link, { color: "#808080" }), React.createElement( "span", null, " ", item.name ) ) )), item.status === "success" && tips ? React.createElement("div", { className: "tips" }, item.name) : null ), previewType === "list" && React.createElement( "div", { className: "nut-uploader__preview-list" }, React.createElement("div", { className: `nut-uploader__preview-img__file__name ${item.status}`, onClick: () => handleItemClick(item, index) }, item.name), deletable && React.createElement(a, { onClick: () => onDeleteItem(item, index) }), item.status === "uploading" && React.createElement(Progress, { percent: item.percentage, showText: false }) ) ); }), React.createElement(ImagePreview, { images: formatterImagePreview(fileList), visible: previewVisible && previewType === "picture", value: previewIndex, // indicator onChange: (value) => setPreviewIndex(value), onClose: () => setPreviewVisible(false) }) ); }; 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(); } }; 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, sizeType: ["original", "compressed"], sourceType: ["album", "camera"], mediaType: ["image", "video"], camera: "back", uploadIcon: React.createElement(n, { size: 20, color: "#006DFC" }), uploadLabel: "", previewType: "picture", fit: "cover", name: "file", accept: "*", disabled: false, autoUpload: true, multiple: false, span: 3, maxFileSize: Number.MAX_VALUE, data: {}, headers: {}, method: "post", previewUrl: "", xhrState: 200, timeout: 1e3 * 30, preview: true, deletable: true, maxDuration: 10, previewMode: "aspectFill", beforeDelete: (file, files) => { return true; }, uploaderMessage: {} }); const InternalUploader = (props, ref) => { const { locale } = useConfig(); const _a = Object.assign(Object.assign({}, defaultProps), props), { children, uploadIcon, uploadLabel, accept, name, camera, defaultValue, value, previewType, fit, disabled, multiple, span, url, previewUrl, headers, timeout, method, xhrState, data, preview, deletable, maxCount, uploadMaxCount: propsUploadMaxCount, maxFileSize, mediaType, className, autoUpload, sizeType, sourceType, maxDuration, previewMode, imagePreview = false, tips = true, uploaderMessage, onStart, onDelete, onChange, onFileItemClick, onProgress, onSuccess, onUpdate, onFailure, onOversize, beforeXhrUpload, beforeUpload, beforeDelete, onUploadClick } = _a, restProps = __rest(_a, ["children", "uploadIcon", "uploadLabel", "accept", "name", "camera", "defaultValue", "value", "previewType", "fit", "disabled", "multiple", "span", "url", "previewUrl", "headers", "timeout", "method", "xhrState", "data", "preview", "deletable", "maxCount", "uploadMaxCount", "maxFileSize", "mediaType", "className", "autoUpload", "sizeType", "sourceType", "maxDuration", "previewMode", "imagePreview", "tips", "uploaderMessage", "onStart", "onDelete", "onChange", "onFileItemClick", "onProgress", "onSuccess", "onUpdate", "onFailure", "onOversize", "beforeXhrUpload", "beforeUpload", "beforeDelete", "onUploadClick"]); locale.uploader = Object.assign(Object.assign({}, locale.uploader), uploaderMessage); const uploadMaxCount = propsUploadMaxCount !== null && propsUploadMaxCount !== void 0 ? propsUploadMaxCount : maxCount; 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"); const chooseMaxCount = (() => { if (multiple) { if (uploadMaxCount * 1 - fileList.length > maxCount) { return maxCount; } return uploadMaxCount * 1 - fileList.length; } return 1; })(); useImperativeHandle(ref, () => ({ submit: () => { Promise.all(uploadQueue).then((res) => { res.forEach((i) => i.uploadTaro(uploadFile, getEnv())); }); }, clear: () => { clearUploadQueue(); } })); const clearUploadQueue = (index = -1) => { if (index > -1) { uploadQueue.splice(index, 1); setUploadQueue(uploadQueue); } else { setUploadQueue([]); fileList.splice(0, fileList.length); setFileList([...fileList]); } }; const _chooseImage = () => { if (disabled) { return; } onUploadClick === null || onUploadClick === void 0 ? void 0 : onUploadClick(); if (Taro.getEnv() === "WEB") { const el = document.getElementById("taroChooseImage"); if (el) { el === null || el === void 0 ? void 0 : el.setAttribute("accept", accept); } else { const obj = document.createElement("input"); obj.setAttribute("type", "file"); obj.setAttribute("id", "taroChooseImage"); obj.setAttribute("accept", accept); obj.setAttribute("style", "position: fixed; top: -4000px; left: -3000px; z-index: -300;"); document.body.appendChild(obj); } } if (getEnv() === "WEAPP" && chooseMedia) { chooseMedia({ /** 最多可以选择的文件个数 */ count: chooseMaxCount, /** 文件类型 */ mediaType, /** 图片和视频选择的来源 */ sourceType, /** 拍摄视频最长拍摄时间,单位秒。时间范围为 3s 至 30s 之间 */ maxDuration, /** 仅对 mediaType 为 image 时有效,是否压缩所选文件 */ sizeType, /** 仅在 sourceType 为 camera 时生效,使用前置或后置摄像头 */ camera, /** 接口调用失败的回调函数 */ fail: (res) => { onFailure && onFailure(res); }, /** 接口调用成功的回调函数 */ success: onChangeMedia }); } else { chooseImage({ // 选择数量 count: chooseMaxCount, // 可以指定是原图还是压缩图,默认二者都有 sizeType, sourceType, success: onChangeImage, fail: (res) => { onFailure && onFailure(res); } }); } }; const executeUpload = (fileItem, index) => { const uploadOption = new UploadOptions(); uploadOption.name = name; uploadOption.url = url; uploadOption.fileType = fileItem.type; uploadOption.formData = fileItem.formData; uploadOption.timeout = timeout * 1; uploadOption.method = method; uploadOption.xhrState = xhrState; uploadOption.headers = headers; uploadOption.taroFilePath = fileItem.path; uploadOption.beforeXhrUpload = beforeXhrUpload; uploadOption.beforeUpload = beforeUpload; uploadOption.onStart = (option) => { clearUploadQueue(index); setFileList(fileList.map((item) => { if (item.uid === fileItem.uid) { item.status = "ready"; item.message = locale.uploader.uploading; } return item; })); onStart && onStart(option); }; uploadOption.onProgress = (e, option) => { setFileList(fileList.map((item) => { if (item.uid === fileItem.uid) { item.status = UPLOADING; item.message = locale.uploader.uploading; item.percentage = e.progress; onProgress && onProgress({ e, option, percentage: item.percentage }); } return item; })); }; uploadOption.onSuccess = (responseText, option) => { const list = fileList.map((item) => { if (item.uid === fileItem.uid) { item.status = SUCCESS; item.message = locale.uploader.success; item.responseText = responseText; } return item; }); setFileList(list); onSuccess === null || onSuccess === void 0 ? void 0 : onSuccess({ responseText, option, files: list }); }; uploadOption.onFailure = (responseText, option) => { const list = fileList.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 UploaderTaro(uploadOption); if (props.autoUpload) { task.uploadTaro(uploadFile, getEnv()); } else { uploadQueue.push(new Promise((resolve, reject) => { resolve(task); })); setUploadQueue(uploadQueue); } }; const readFile = (files) => { files.forEach((file, index) => { var _a2, _b; let fileType = file.type; const filepath = file.tempFilePath || file.path; const fileItem = new FileItem(); if (file.fileType) { fileType = file.fileType; } else { const imgReg = /\.(png|jpeg|jpg|webp|gif)$/i; if (!fileType && (imgReg.test(filepath) || filepath.includes("data:image"))) { fileType = "image"; } } fileItem.path = filepath; fileItem.name = filepath; fileItem.status = "ready"; fileItem.type = fileType; fileItem.uid = `${fileItem.uid}_${index}`; fileItem.message = autoUpload ? locale.uploader.uploading : locale.uploader.uploading; if (getEnv() === "WEB") { const formData = new FormData(); for (const [key, value2] of Object.entries(data)) { formData.append(key, value2); } formData.append(name, file.originalFileObj); fileItem.name = (_a2 = file.originalFileObj) === null || _a2 === void 0 ? void 0 : _a2.name; fileItem.type = (_b = file.originalFileObj) === null || _b === void 0 ? void 0 : _b.type; fileItem.formData = formData; } else { fileItem.formData = data; } if (preview) { fileItem.url = fileType === "video" ? file.thumbTempFilePath : filepath; } fileList.push(fileItem); setFileList(fileList); executeUpload(fileItem, index); }); }; const filterFiles = (files) => { const maximum = chooseMaxCount * 1; const maxisize = props.maxFileSize * 1; const oversizes = new Array(); const filterFile = files.filter((file) => { if (file.size > maxisize) { oversizes.push(file); return false; } return true; }); if (oversizes.length) { onOversize && onOversize(files); } filterFile.length + fileList.length; if (filterFile.length > maximum) { filterFile.splice(filterFile.length - (filterFile.length - maximum)); } 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 onChangeMedia = (res) => { const { tempFiles } = res; const _files = filterFiles(tempFiles); readFile(_files); }; const onChangeImage = (res) => { const { tempFiles } = res; const _files = filterFiles(tempFiles); readFile(_files); }; const handleItemClick = (file, index) => { onFileItemClick === null || onFileItemClick === void 0 ? void 0 : onFileItemClick(file, index); }; return React.createElement( "div", Object.assign({ className: classes }, restProps), (children || previewType === "list") && React.createElement( "div", { className: "nut-uploader__slot" }, React.createElement( React.Fragment, null, children || React.createElement(Button, { size: "small", type: "primary" }, "上传文件"), uploadMaxCount > fileList.length && React.createElement(Button, { nativeType: "button", className: "nut-uploader__input", onClick: _chooseImage }) ) ), React.createElement(Preview, { fileList, previewType, deletable, onDeleteItem, handleItemClick, previewUrl, children, span, maxCount: uploadMaxCount, previewMode, imagePreview, tips, multiple }), uploadMaxCount > fileList.length && previewType === "picture" && !children && React.createElement( "div", { className: `nut-uploader__upload col-${span} ${previewType} ${disabled ? "nut-uploader__upload-disabled" : ""} ${multiple && span === 3 ? "nut-uploader__upload__multiple" : ""}` }, React.createElement( "div", { className: "nut-uploader__icon" }, uploadIcon, uploadLabel && React.createElement("span", { className: "nut-uploader__icon-tip" }, uploadLabel) ), React.createElement(Button, { nativeType: "button", className: "nut-uploader__input", onClick: _chooseImage }) ) ); }; const Uploader = React.forwardRef(InternalUploader); Uploader.defaultProps = defaultProps; Uploader.displayName = "NutUploader"; export { Uploader as U };