amis
Version:
一种MIS页面生成工具
592 lines (591 loc) • 27.9 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var tslib_1 = require("tslib");
var react_1 = tslib_1.__importDefault(require("react"));
var Item_1 = require("./Item");
var find = require("lodash/find");
var isPlainObject = require("lodash/isPlainObject");
var async_1 = require("async");
var Image_1 = tslib_1.__importDefault(require("./Image"));
var helper_1 = require("../../utils/helper");
var api_1 = require("../../utils/api");
var Button_1 = tslib_1.__importDefault(require("../../components/Button"));
var icons_1 = require("../../components/icons");
var react_dropzone_1 = tslib_1.__importDefault(require("react-dropzone"));
var id = 1;
function gennerateId() {
return id++;
}
var preventEvent = function (e) { return e.stopPropagation(); };
function getNameFromUrl(url) {
if (/(?:\/|^)([^\/]+?)$/.test(url)) {
return RegExp.$1;
}
return url;
}
var FileControl = /** @class */ (function (_super) {
tslib_1.__extends(FileControl, _super);
function FileControl(props) {
var _this = _super.call(this, props) || this;
_this.dropzone = react_1.default.createRef();
var value = props.value;
var multiple = props.multiple;
var joinValues = props.joinValues;
var delimiter = props.delimiter;
var files = [];
if (value && value instanceof Blob) {
files = [value];
}
else if (value) {
files = (Array.isArray(value)
? value
: joinValues
? (value.value || value).split(delimiter)
: [(value.value || value)])
.map(function (item) { return FileControl.valueToFile(item, props); })
.filter(function (item) { return item; });
}
_this.state = {
files: files,
uploading: false
};
_this.sendFile = _this.sendFile.bind(_this);
_this.removeFile = _this.removeFile.bind(_this);
_this.clearError = _this.clearError.bind(_this);
_this.handleDrop = _this.handleDrop.bind(_this);
_this.handleDropRejected = _this.handleDropRejected.bind(_this);
_this.startUpload = _this.startUpload.bind(_this);
_this.stopUpload = _this.stopUpload.bind(_this);
_this.toggleUpload = _this.toggleUpload.bind(_this);
_this.tick = _this.tick.bind(_this);
_this.onChange = _this.onChange.bind(_this);
_this.uploadFile = _this.uploadFile.bind(_this);
_this.uploadBigFile = _this.uploadBigFile.bind(_this);
_this.handleSelect = _this.handleSelect.bind(_this);
return _this;
}
FileControl.valueToFile = function (value, props, files) {
var file = files && typeof value === 'string' ? find(files, function (item) { return item.value === value; }) : undefined;
return value
? value instanceof File
? {
state: 'ready',
value: value,
name: value.name,
url: '',
id: gennerateId()
}
: tslib_1.__assign({}, (typeof value === 'string'
? {
state: file && file.state ? file.state : 'init',
value: value,
name: /^data:/.test(value)
? (file && file.name) || 'base64数据'
: getNameFromUrl(value),
id: gennerateId(),
url: typeof props.downloadUrl === 'string' && value && !/^data:/.test(value)
? "" + props.downloadUrl + value
: undefined
}
: value))
: undefined;
};
FileControl.prototype.componentWillReceiveProps = function (nextProps) {
var _this = this;
var props = this.props;
if (props.value !== nextProps.value) {
var value = nextProps.value;
var multiple = nextProps.multiple;
var joinValues = nextProps.joinValues;
var delimiter = nextProps.delimiter;
var files = [];
if (value) {
files = (Array.isArray(value)
? value
: joinValues && typeof value === 'string'
? value.split(delimiter)
: [value])
.map(function (item) {
var obj = FileControl.valueToFile(item, nextProps, _this.state.files);
var org;
if (obj &&
(org = find(_this.state.files, function (item) { return item.value === obj.value; }))) {
obj = tslib_1.__assign(tslib_1.__assign(tslib_1.__assign({}, org), obj), { id: obj.id || org.id });
}
return obj;
})
.filter(function (item) { return item; });
}
this.setState({
files: files
});
}
};
FileControl.prototype.handleDrop = function (files) {
var _this = this;
if (!files.length) {
return;
}
var _a = this.props, maxSize = _a.maxSize, multiple = _a.multiple, maxLength = _a.maxLength;
var allowed = multiple && maxLength ? maxLength - this.state.files.length : files.length;
var inputFiles = [];
[].slice.call(files, 0, allowed).forEach(function (file) {
if (maxSize && file.size > maxSize) {
_this.props.env.alert("\u60A8\u9009\u62E9\u7684\u6587\u4EF6 " + file.name + " \u5927\u5C0F\u4E3A " + Image_1.default.formatFileSize(file.size) + " \u8D85\u51FA\u4E86\u6700\u5927\u4E3A " + Image_1.default.formatFileSize(maxSize) + " \u7684\u9650\u5236\uFF0C\u8BF7\u91CD\u65B0\u9009\u62E9");
file.state = 'invalid';
}
else {
file.state = 'pending';
}
file.id = gennerateId();
inputFiles.push(file);
});
if (!inputFiles.length) {
return;
}
this.setState({
error: null,
files: multiple ? this.state.files.concat(inputFiles) : inputFiles
}, function () {
var autoUpload = _this.props.autoUpload;
if (autoUpload) {
_this.startUpload();
}
});
};
FileControl.prototype.handleDropRejected = function (rejectedFiles, evt) {
if (evt.type !== 'change' && evt.type !== 'drop') {
return;
}
var _a = this.props, multiple = _a.multiple, env = _a.env, accept = _a.accept;
var files = rejectedFiles.map(function (file) { return (tslib_1.__assign(tslib_1.__assign({}, file), { state: 'invalid', id: gennerateId(), name: file.name })); });
this.setState({
files: multiple
? this.state.files.concat(files)
: this.state.files.length
? this.state.files
: files.slice(0, 1)
});
env.alert("\u60A8\u6DFB\u52A0\u7684\u6587\u4EF6" + files.map(function (item) { return "\u3010" + item.name + "\u3011"; }) + "\u4E0D\u7B26\u5408\u7C7B\u578B\u7684`" + accept + "`\u8BBE\u5B9A\uFF0C\u8BF7\u4ED4\u7EC6\u68C0\u67E5\u3002");
};
FileControl.prototype.handleSelect = function () {
this.dropzone.current && this.dropzone.current.open();
};
FileControl.prototype.startUpload = function () {
if (this.state.uploading) {
return;
}
this.setState({
uploading: true,
files: this.state.files.map(function (file) {
if (file.state === 'error') {
file.state = 'pending';
}
return file;
})
}, this.tick);
};
FileControl.prototype.toggleUpload = function (e) {
e.preventDefault();
return this.state.uploading ? this.stopUpload() : this.startUpload();
};
FileControl.prototype.stopUpload = function () {
if (!this.state.uploading) {
return;
}
this.setState({
uploading: false
});
};
FileControl.prototype.tick = function () {
var _this = this;
if (this.current || !this.state.uploading) {
return;
}
var file = find(this.state.files, function (item) { return item.state === 'pending'; });
if (file) {
this.current = file;
file.state = 'uploading';
this.setState({
files: this.state.files.concat()
}, function () {
return _this.sendFile(file, function (error, file, obj) {
var files = _this.state.files.concat();
var idx = files.indexOf(file);
if (!~idx) {
return;
}
var newFile = file;
if (error) {
newFile.state = 'error';
newFile.error = error;
}
else {
newFile = obj;
}
files.splice(idx, 1, newFile);
_this.current = null;
_this.setState({
error: error ? error : null,
files: files
}, _this.tick);
}, function (progress) {
var files = _this.state.files.concat();
var idx = files.indexOf(file);
if (!~idx) {
return;
}
// file 是个非 File 对象,先不copy了直接改。
file.progress = progress;
_this.setState({
files: files
});
});
});
}
else {
this.setState({
uploading: false
}, function () {
_this.onChange();
if (_this.resolve) {
_this.resolve(_this.state.files.some(function (file) { return file.state === 'error'; }) ? '文件上传失败请重试' : null);
_this.resolve = undefined;
}
});
}
};
FileControl.prototype.sendFile = function (file, cb, onProgress) {
var _a = this.props, reciever = _a.reciever, fileField = _a.fileField, downloadUrl = _a.downloadUrl, useChunk = _a.useChunk, chunkSize = _a.chunkSize, startChunkApi = _a.startChunkApi, chunkApi = _a.chunkApi, finishChunkApi = _a.finishChunkApi, asBase64 = _a.asBase64, asBlob = _a.asBlob, data = _a.data;
if (asBase64) {
var reader_1 = new FileReader();
reader_1.readAsDataURL(file);
reader_1.onload = function () {
file.state = 'ready';
cb(null, file, {
value: reader_1.result,
name: file.name,
url: '',
state: 'ready',
id: file.id
});
};
reader_1.onerror = function (error) { return cb(error.message); };
return;
}
else if (asBlob) {
file.state = 'ready';
setTimeout(function () {
return cb(null, file, {
name: file.name,
value: file,
url: '',
state: 'ready',
id: file.id
});
}, 4);
return;
}
var fn = (useChunk === 'auto' && chunkSize && file.size > chunkSize) || useChunk === true
? this.uploadBigFile
: this.uploadFile;
fn(file, reciever, {}, {
fieldName: fileField,
chunkSize: chunkSize,
startChunkApi: startChunkApi,
chunkApi: chunkApi,
finishChunkApi: finishChunkApi,
data: data
}, onProgress)
.then(function (ret) {
if (ret.status || !ret.data) {
throw new Error(ret.msg || '上传失败, 请重试');
}
onProgress(1);
var value = ret.data.value || ret.data;
cb(null, file, tslib_1.__assign(tslib_1.__assign({}, (isPlainObject(ret.data) ? ret.data : null)), { value: value, url: typeof downloadUrl === 'string' && value
? "" + downloadUrl + value
: ret.data
? ret.data.url
: null, state: 'uploaded', id: file.id }));
})
.catch(function (error) {
cb(error.message || '上传失败, 请重试', file);
});
};
FileControl.prototype.removeFile = function (file, index) {
var files = this.state.files.concat();
files.splice(index, 1);
this.setState({
files: files
}, this.onChange);
};
FileControl.prototype.clearError = function () {
this.setState({
error: null
});
};
FileControl.prototype.onChange = function () {
var _a = this.props, multiple = _a.multiple, onChange = _a.onChange, joinValues = _a.joinValues, extractValue = _a.extractValue, valueField = _a.valueField, delimiter = _a.delimiter, resetValue = _a.resetValue, asBlob = _a.asBlob;
var files = this.state.files.filter(function (file) { return ~['uploaded', 'init', 'ready'].indexOf(file.state); });
var value = multiple ? files : files[0];
if (value) {
if (extractValue || asBlob) {
value = Array.isArray(value)
? value.map(function (item) { return item[valueField || 'value']; })
: value[valueField || 'value'];
}
else if (joinValues) {
value = Array.isArray(value)
? value.map(function (item) { return item[valueField || 'value']; }).join(delimiter || ',')
: value[valueField || 'value'];
}
}
else {
value = typeof resetValue === 'undefined' ? '' : resetValue;
}
onChange(value);
};
FileControl.prototype.uploadFile = function (file, reciever, params, config, onProgress) {
if (config === void 0) { config = {}; }
var fd = new FormData();
var api = api_1.buildApi(reciever, helper_1.createObject(config.data, params), {
method: 'post'
});
helper_1.qsstringify(tslib_1.__assign(tslib_1.__assign({}, api.data), params))
.split('&')
.forEach(function (item) {
var parts = item.split('=');
fd.append(parts[0], parts[1]);
});
fd.append(config.fieldName || 'file', file);
return this._send(api, fd, {}, onProgress);
};
FileControl.prototype.uploadBigFile = function (file, reciever, params, config, onProgress) {
if (config === void 0) { config = {}; }
var chunkSize = config.chunkSize || 5 * 1024 * 1024;
var self = this;
var startProgress = 0.2;
var endProgress = 0.9;
var progressArr;
return new Promise(function (resolve, reject) {
var state;
var startApi = api_1.buildApi(config.startChunkApi, helper_1.createObject(config.data, tslib_1.__assign(tslib_1.__assign({}, params), { filename: file.name })), {
method: 'post',
autoAppend: true
});
self._send(startApi)
.then(startChunk)
.catch(reject);
function startChunk(ret) {
onProgress(startProgress);
var tasks = getTasks(file);
progressArr = tasks.map(function () { return 0; });
if (!ret.data) {
throw new Error('接口返回错误,请仔细检查');
}
state = {
key: ret.data.key,
uploadId: ret.data.uploadId,
loaded: 0,
total: tasks.length
};
async_1.mapLimit(tasks, 3, uploadPartFile(state, config), function (err, results) {
if (err) {
reject(err);
}
else {
finishChunk(results, state);
}
});
}
function updateProgress(partNumber, progress) {
progressArr[partNumber - 1] = progress;
onProgress(startProgress +
(endProgress - startProgress) *
(progressArr.reduce(function (count, progress) { return count + progress; }, 0) / progressArr.length));
}
function finishChunk(partList, state) {
onProgress(endProgress);
var endApi = api_1.buildApi(config.finishChunkApi, helper_1.createObject(config.data, tslib_1.__assign(tslib_1.__assign({}, params), { uploadId: state.uploadId, key: state.key, filename: file.name, partList: partList })), {
method: 'post',
autoAppend: true
});
self._send(endApi)
.then(resolve)
.catch(reject);
}
function uploadPartFile(state, conf) {
return function (task, callback) {
var api = api_1.buildApi(conf.chunkApi, helper_1.createObject(config.data, params), {
method: 'post'
});
var fd = new FormData();
var blob = task.file.slice(task.start, task.stop + 1);
helper_1.qsstringify(tslib_1.__assign(tslib_1.__assign({}, api.data), params))
.split('&')
.forEach(function (item) {
var parts = item.split('=');
fd.append(parts[0], parts[1]);
});
fd.append('key', state.key);
fd.append('uploadId', state.uploadId);
fd.append('partNumber', task.partNumber.toString());
fd.append('partSize', task.partSize.toString());
fd.append(config.fieldName || 'file', blob, file.name);
return self
._send(api, fd, {}, function (progress) { return updateProgress(task.partNumber, progress); })
.then(function (ret) {
state.loaded++;
callback(null, {
partNumber: task.partNumber,
eTag: ret.data.eTag
});
})
.catch(callback);
};
}
function getTasks(file) {
var leftSize = file.size;
var offset = 0;
var partNumber = 1;
var tasks = [];
while (leftSize > 0) {
var partSize = Math.min(leftSize, chunkSize);
tasks.push({
file: file,
partNumber: partNumber,
partSize: partSize,
start: offset,
stop: offset + partSize - 1
});
leftSize -= partSize;
offset += partSize;
partNumber += 1;
}
return tasks;
}
});
};
FileControl.prototype._send = function (api, data, options, onProgress) {
var env = this.props.env;
if (!env || !env.fetcher) {
throw new Error('fetcher is required');
}
return env.fetcher(api, data, tslib_1.__assign(tslib_1.__assign({ method: 'post' }, options), { withCredentials: true, onUploadProgress: onProgress
? function (event) { return onProgress(event.loaded / event.total); }
: undefined }));
};
FileControl.prototype.validate = function () {
var _this = this;
if (this.state.uploading || this.state.files.some(function (item) { return item.state === 'pending'; })) {
return new Promise(function (resolve) {
_this.resolve = resolve;
_this.startUpload();
});
}
else if (this.state.files.some(function (item) { return item.state === 'error'; })) {
return '文件上传失败请重试';
}
};
FileControl.prototype.render = function () {
var _this = this;
var _a = this.props, btnLabel = _a.btnLabel, accept = _a.accept, disabled = _a.disabled, maxLength = _a.maxLength, multiple = _a.multiple, autoUpload = _a.autoUpload, description = _a.description, hideUploadButton = _a.hideUploadButton, className = _a.className, cx = _a.classnames, render = _a.render;
var _b = this.state, files = _b.files, uploading = _b.uploading, error = _b.error;
var hasPending = files.some(function (file) { return file.state == 'pending'; });
return (react_1.default.createElement("div", { className: cx('FileControl', className) },
react_1.default.createElement(react_dropzone_1.default, { key: "drop-zone", ref: this.dropzone, onDrop: this.handleDrop, onDropRejected: this.handleDropRejected, accept: accept, multiple: multiple }, function (_a) {
var getRootProps = _a.getRootProps, getInputProps = _a.getInputProps, isDragActive = _a.isDragActive;
return (react_1.default.createElement("div", tslib_1.__assign({}, getRootProps({
onClick: preventEvent
}), { className: cx('FileControl-dropzone', {
disabled: disabled,
'is-empty': !files.length,
'is-active': isDragActive
}) }),
react_1.default.createElement("input", tslib_1.__assign({}, getInputProps())),
isDragActive ? (react_1.default.createElement("div", { className: cx('FileControl-acceptTip') }, "\u628A\u6587\u4EF6\u62D6\u5230\u8FD9\uFF0C\u7136\u540E\u677E\u5B8C\u6210\u6DFB\u52A0\uFF01")) : (react_1.default.createElement(react_1.default.Fragment, null,
(multiple && (!maxLength || files.length < maxLength)) || !multiple ? (react_1.default.createElement(Button_1.default, { level: "default", className: cx('FileControl-selectBtn'), onClick: _this.handleSelect },
react_1.default.createElement(icons_1.Icon, { icon: "upload", className: "icon" }),
!multiple && files.length
? '重新上传'
: multiple && files.length
? '继续添加'
: '上传文件')) : null,
description
? render('desc', description, {
className: cx('FileControl-description')
})
: null,
Array.isArray(files) ? (react_1.default.createElement("ul", { className: cx('FileControl-list') }, files.map(function (file, index) { return (react_1.default.createElement("li", { key: file.id },
react_1.default.createElement("div", { className: cx('FileControl-itemInfo', {
'is-invalid': file.state === 'invalid' || file.state === 'error'
}) },
react_1.default.createElement(icons_1.Icon, { icon: "file", className: "icon" }),
file.url ? (react_1.default.createElement("a", { className: cx('FileControl-itemInfoText'), target: "_blank", href: file.url }, file.name || file.filename)) : (react_1.default.createElement("span", { className: cx('FileControl-itemInfoText') }, file.name || file.filename)),
file.state === 'invalid' || file.state === 'error' ? (react_1.default.createElement(icons_1.Icon, { icon: "fail", className: "icon" })) : null,
file.state !== 'uploading' ? (react_1.default.createElement("a", { "data-tooltip": "\u79FB\u9664", className: cx('FileControl-clear'), onClick: function () { return _this.removeFile(file, index); } },
react_1.default.createElement(icons_1.Icon, { icon: "close", className: "icon" }))) : null),
file.state === 'uploading' || file.state === 'uploaded' ? (react_1.default.createElement("div", { className: cx('FileControl-progressInfo') },
react_1.default.createElement("div", { className: cx('FileControl-progress') },
react_1.default.createElement("span", { style: {
width: (file.state === 'uploaded'
? 100
: (file.progress || 0) * 100) + "%"
} })),
file.state === 'uploaded' ? (react_1.default.createElement(icons_1.Icon, { icon: "success", className: "icon" })) : (react_1.default.createElement("span", null,
Math.round((file.progress || 0) * 100),
"%")))) : null)); }))) : null))));
}),
error ? react_1.default.createElement("div", { className: cx('FileControl-errorMsg') }, error) : null,
!autoUpload && !hideUploadButton && files.length ? (react_1.default.createElement(Button_1.default, { level: "default", disabled: !hasPending, className: cx('FileControl-uploadBtn'), onClick: this.toggleUpload }, uploading ? '暂停上传' : '开始上传')) : null));
};
FileControl.defaultProps = {
maxSize: 0,
maxLength: 0,
placeholder: '',
btnLabel: '文件上传',
reciever: '/api/upload/file',
fileField: 'file',
joinValues: true,
extractValue: false,
delimiter: ',',
downloadUrl: '',
useChunk: 'auto',
chunkSize: 5 * 1024 * 1024,
startChunkApi: '/api/upload/startChunk',
chunkApi: '/api/upload/chunk',
finishChunkApi: '/api/upload/finishChunk',
accept: 'text/plain',
multiple: false,
autoUpload: true,
hideUploadButton: false,
stateTextMap: {
init: '',
pending: '等待上传',
uploading: '上传中',
error: '上传出错',
uploaded: '已上传',
ready: ''
},
asBase64: false
};
return FileControl;
}(react_1.default.Component));
exports.default = FileControl;
var FileControlRenderer = /** @class */ (function (_super) {
tslib_1.__extends(FileControlRenderer, _super);
function FileControlRenderer() {
return _super !== null && _super.apply(this, arguments) || this;
}
FileControlRenderer = tslib_1.__decorate([
Item_1.FormItem({
type: 'file',
sizeMutable: false,
renderDescription: false
})
], FileControlRenderer);
return FileControlRenderer;
}(FileControl));
exports.FileControlRenderer = FileControlRenderer;
//# sourceMappingURL=./renderers/Form/File.js.map