@mijadesign/mjui-react-taro
Version:
京东风格的轻量级移动端 React 组件库,支持一套代码生成 H5 和小程序
700 lines (699 loc) • 24.6 kB
JavaScript
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
};