fineui是帆软报表和BI产品线所使用的前端框架。
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

766 lines
28 KiB

import { Msg } from "../../foundation/message";
import { shortcut, Widget, some, extend, jsonDecode, appendQuery, emptyFn, i18nText } from "@/core";
/**
* 文件
*
* Created by GUY on 2016/1/27.
* @class File
* @extends Single
* @abstract
*/
const document = _global.document || {};
/**
* @description normalize input.files. create if not present, add item method if not present
* @param Object generated wrap object
* @return Object the wrap object itself
*/
const F = (item => input => {
const files = input.files || [input];
if (!files.item) {
files.item = item;
}
return files;
})(i => this[i]);
const event = {
/**
* @description add an event via addEventListener or attachEvent
* @param DOMElement the element to add event
* @param String event name without "on" (e.g. "mouseover")
* @param Function the callback to associate as event
* @return Object noswfupload.event
*/
add: document.addEventListener
? (node, name, callback) => {
node.addEventListener(name, callback, false);
return this;
}
: (node, name, callback) => {
node.attachEvent(`on${name}`, callback);
return this;
},
/**
* @description remove an event via removeEventListener or detachEvent
* @param DOMElement the element to remove event
* @param String event name without "on" (e.g. "mouseover")
* @param Function the callback associated as event
* @return Object noswfupload.event
*/
del: document.removeEventListener
? (node, name, callback) => {
node.removeEventListener(name, callback, false);
return this;
}
: (node, name, callback) => {
node.detachEvent(`on${name}`, callback);
return this;
},
/**
* @description to block event propagation and prevent event default
* @param void generated event or undefined
* @return Boolean false
*/
stop(e) {
if (!e) {
if (self.event) {
event.returnValue = !(event.cancelBubble = true);
}
} else {
e.stopPropagation ? e.stopPropagation() : (e.cancelBubble = true);
e.preventDefault ? e.preventDefault() : (e.returnValue = false);
}
return false;
},
};
const sendFile = (toString => {
const split = "onabort.onerror.onloadstart.onprogress".split("."),
length = split.length,
CRLF = "\r\n";
let xhr = new XMLHttpRequest(),
sendFile;
const multipart = (boundary, name, file) =>
"--".concat(
boundary,
CRLF,
"Content-Disposition: form-data; name=\"",
name,
"\"; filename=\"",
_global.encodeURIComponent(file.fileName),
"\"",
CRLF,
"Content-Type: application/octet-stream",
CRLF,
CRLF,
file.getAsBinary(),
CRLF,
"--",
boundary,
"--",
CRLF
);
const isFunction = Function => toString.call(Function) === "[object Function]";
// FireFox 3+, Safari 4 beta (Chrome 2 beta file is buggy and will not work)
if (xhr.upload || xhr.sendAsBinary) {
sendFile = (handler, maxSize, width, height) => {
const current = handler.current;
if (-1 < maxSize && maxSize < handler.file.fileSize) {
if (isFunction(handler.onerror)) {
handler.onerror();
}
return;
}
const xhr = new XMLHttpRequest(),
upload = xhr.upload || {
addEventListener(event, callback) {
this[`on${event}`] = callback;
},
};
for (let i = 0; i < length; i++) {
upload.addEventListener(
split[i].substring(2),
// eslint-disable-next-line no-loop-func
(event => rpe => {
if (isFunction(handler[event])) {
handler[event](rpe, xhr);
}
})(split[i]),
false
);
}
upload.addEventListener(
"load",
rpe => {
if (handler.onreadystatechange === false) {
if (isFunction(handler.onload)) {
handler.onload(rpe, xhr);
}
} else {
const callback = () => {
if (xhr.readyState === 4) {
if (isFunction(handler.onload)) {
handler.onload(rpe, xhr);
}
} else {
setTimeout(callback, 15);
}
};
setTimeout(callback, 15);
}
},
false
);
xhr.open(
"post",
appendQuery(handler.url, {
filename: _global.encodeURIComponent(handler.file.fileName),
}),
true
);
if (!xhr.upload) {
const rpe = { loaded: 0, total: handler.file.fileSize || handler.file.size, simulation: true };
rpe.interval = setInterval(() => {
rpe.loaded += 1024 / 4;
if (rpe.total <= rpe.loaded) {
rpe.loaded = rpe.total;
}
upload.onprogress(rpe);
}, 100);
xhr.onabort = () => {
upload.onabort({});
};
xhr.onerror = () => {
upload.onerror({});
};
xhr.onreadystatechange = () => {
switch (xhr.readyState) {
case 2:
case 3:
if (rpe.total <= rpe.loaded) {
rpe.loaded = rpe.total;
}
upload.onprogress(rpe);
break;
case 4:
clearInterval(rpe.interval);
rpe.interval = 0;
rpe.loaded = rpe.total;
upload.onprogress(rpe);
if (199 < xhr.status && xhr.status < 400) {
upload.onload({});
const attachO = jsonDecode(xhr.responseText);
attachO.filename = handler.file.fileName;
if (handler.file.type.indexOf("image") !== -1) {
attachO.attach_type = "image";
}
handler.attach_array[current] = attachO;
} else {
upload.onerror({});
}
break;
default:
break;
}
};
upload.onloadstart(rpe);
} else {
xhr.onreadystatechange = () => {
switch (xhr.readyState) {
case 4: {
const attachO = jsonDecode(xhr.responseText);
if (handler.file.type.indexOf("image") !== -1) {
attachO.attach_type = "image";
}
attachO.filename = handler.file.fileName;
if (handler.maxLength === 1) {
handler.attach_array[0] = attachO;
// handler.attach_array.push(attachO);
} else {
handler.attach_array[current] = attachO;
}
break;
}
default:
break;
}
};
if (isFunction(upload.onloadstart)) {
upload.onloadstart();
}
}
if (handler.file.getAsBinary) {
const boundary = `AjaxUploadBoundary${new Date().getTime()}`;
xhr.setRequestHeader("Content-Type", `multipart/form-data; boundary=${boundary}`);
xhr[xhr.sendAsBinary ? "sendAsBinary" : "send"](multipart(boundary, handler.name, handler.file));
} else {
const form = new FormData();
form.append("FileData", handler.file);
xhr.send(form);
}
return handler;
};
} else {
// Internet Explorer, Opera, others
sendFile = (handler, maxSize, width, height) => {
const current = handler.current;
let iframe, form;
const url = handler.url.concat(-1 === handler.url.indexOf("?") ? "?" : "&", "AjaxUploadFrame=true"),
rpe = {
loaded: 1,
total: 100,
simulation: true,
interval: setInterval(() => {
if (rpe.loaded < rpe.total) {
++rpe.loaded;
}
if (isFunction(handler.onprogress)) {
handler.onprogress(rpe, {});
}
}, 100),
},
target = ["AjaxUpload", new Date().getTime(), String(Math.random()).substring(2)].join("_");
const onload = () => {
iframe.onreadystatechange = iframe.onload = iframe.onerror = null;
form.parentNode.removeChild(form);
form = null;
clearInterval(rpe.interval);
// rpe.loaded = rpe.total;
const responseText = (iframe.contentWindow.document || iframe.contentWindow.contentDocument).body
.innerHTML;
try {
const attachO = jsonDecode(responseText);
if (handler.file.type.indexOf("image") !== -1) {
attachO.attach_type = "image";
}
// attachO.fileSize = responseText.length;
try {
// decodeURIComponent特殊字符可能有问题, catch一下,保证能正常上传
attachO.filename = _global.decodeURIComponent(handler.file.fileName);
} catch (e) {
attachO.filename = handler.file.fileName;
}
if (handler.maxLength === 1) {
handler.attach_array[0] = attachO;
} else {
handler.attach_array[current] = attachO;
}
} catch (e) {
if (isFunction(handler.onerror)) {
handler.onerror(rpe, event || _global.event);
}
}
if (isFunction(handler.onload)) {
handler.onload(rpe, { responseText });
}
};
try {
// IE < 8 does not accept enctype attribute ...
// eslint-disable-next-line no-unused-vars
const form = document.createElement("<form enctype=\"multipart/form-data\"></form>"),
// eslint-disable-next-line no-unused-vars
iframe =
handler.iframe ||
(handler.iframe = document.createElement(
`<iframe id="${target}" name="${target}" src="${url}"></iframe>`
));
} catch (e) {
const form = document.createElement("form"),
iframe = handler.iframe || (handler.iframe = document.createElement("iframe"));
form.setAttribute("enctype", "multipart/form-data");
iframe.setAttribute("name", (iframe.id = target));
iframe.setAttribute("src", url);
}
iframe.style.position = "absolute";
iframe.style.left = iframe.style.top = "-10000px";
iframe.onload = onload;
iframe.onerror = event => {
if (isFunction(handler.onerror)) {
handler.onerror(rpe, event || _global.event);
}
};
iframe.onreadystatechange = () => {
if (/loaded|complete/i.test(iframe.readyState)) {
onload();
// wei : todo,将附件信息放到handler.attach
} else if (isFunction(handler.onloadprogress)) {
if (rpe.loaded < rpe.total) {
++rpe.loaded;
}
handler.onloadprogress(rpe, {
readyState:
{
loading: 2,
interactive: 3,
loaded: 4,
complete: 4,
}[iframe.readyState] || 1,
});
}
};
form.setAttribute("action", `${handler.url}&filename=${_global.encodeURIComponent(handler.file.fileName)}`);
form.setAttribute("target", iframe.id);
form.setAttribute("method", "post");
form.appendChild(handler.file);
form.style.display = "none";
if (isFunction(handler.onloadstart)) {
handler.onloadstart(rpe, {});
}
const d = document.body || document.documentElement;
d.appendChild(iframe);
d.appendChild(form);
form.submit();
return handler;
};
}
xhr = null;
return sendFile;
})(Object.prototype.toString);
const sendFiles = (handler, maxSize, width, height) => {
const length = handler.files.length,
onload = handler.onload,
onloadstart = handler.onloadstart;
handler.current = 0;
handler.total = 0;
handler.sent = 0;
while (handler.current < length) {
handler.total += handler.files[handler.current].fileSize || handler.files[handler.current].size;
handler.current++;
}
handler.current = 0;
if (length && handler.files[0].fileSize !== -1) {
handler.file = handler.files[handler.current];
const callback = (rpe, xhr) => {
handler.onloadstart = null;
handler.sent += handler.files[handler.current].fileSize || handler.files[handler.current].size;
if (++handler.current < length) {
handler.file = handler.files[handler.current];
sendFile(handler, maxSize, width, height).onload = callback;
} else if (onload) {
handler.onloadstart = onloadstart;
handler.onload = onload;
handler.onload(rpe, xhr);
}
};
sendFile(handler, maxSize, width, height).onload = callback;
} else if (length) {
handler.total = length * 100;
handler.file = handler.files[handler.current];
const callback = (rpe, xhr) => {
handler.onloadstart = null;
handler.sent += 100;
if (++handler.current < length) {
if (/\b(chrome|safari)\b/i.test(navigator.userAgent)) {
handler.iframe.parentNode.removeChild(handler.iframe);
handler.iframe = null;
}
setTimeout(() => {
handler.file = handler.files[handler.current];
sendFile(handler, maxSize, width, height).onload = callback;
}, 15);
} else if (onload) {
setTimeout(() => {
handler.iframe.parentNode.removeChild(handler.iframe);
handler.iframe = null;
handler.onloadstart = onloadstart;
handler.onload = onload;
handler.onload(rpe, xhr);
}, 15);
}
};
sendFile(handler, maxSize, width, height).onload = callback;
}
return handler;
};
const r1 = /\.([^.]+)$/; // .png
const r2 = /\/([^/]+)$/; // image/png
/**
* 校验文件类型是否合法,同时兼容旧版形式
* @param fileName
* @param fileType
* @returns {boolean}
*/
const fileTypeValidate = (fileName, fileType) => {
if (!fileType) {
return true;
}
let mimes = fileType.split(",");
if (mimes[0] === fileType) {
mimes = `${fileType}`.split(";");
}
return some(mimes, (index, mime) => {
let matches;
matches = mime.match(r1);
if (matches) {
return fileName.toLowerCase().endsWith(matches[0]);
}
matches = mime.match(r2);
if (matches) {
return matches[1] === "*" ? true : fileName.toLowerCase().endsWith(`.${matches[1]}`);
}
});
};
@shortcut()
export class File extends Widget {
static xtype = "bi.file";
static EVENT_CHANGE = "EVENT_CHANGE";
static EVENT_UPLOADSTART = "EVENT_UPLOADSTART";
static EVENT_ERROR = "EVENT_ERROR";
static EVENT_PROGRESS = "EVENT_PROGRESS";
static EVENT_UPLOADED = "EVENT_UPLOADED";
_defaultConfig() {
const conf = super._defaultConfig(...arguments);
return extend(conf, {
baseCls: `${conf.baseCls || ""} bi-file display-block`,
tagName: "input",
attributes: {
type: "file",
},
name: "",
url: "",
multiple: true,
accept: "", // .png,.pdf,image/jpg,image/* 等
maxSize: -1, // 1024 * 1024 单位b
maxLength: -1, // 无限制, 与multiple配合使用
errorText: emptyFn,
});
}
render() {
const { multiple, name, title, accept } = this.options;
if (multiple === true) {
this.element.attr("multiple", "multiple");
}
this.element.attr("name", name || this.getName());
this.element.attr("title", title || "");
this.element.attr("accept", accept);
}
created() {
const { maxSize, url, accept } = this.options;
// create the noswfupload.wrap Object
// wrap.maxSize 文件大小限制
// wrap.maxLength 文件个数限制
const _wrap = (this.wrap = this._wrap(this.element[0], maxSize));
// fileType could contain whatever text but filter checks *.{extension}
// if present
// handlerszhe
_wrap.onloadstart = (...args) => {
this.fireEvent(File.EVENT_UPLOADSTART, ...args);
};
_wrap.onprogress = (rpe, xhr) => {
// percent for each bar
// fileSize is -1 only if browser does not support file info access
// this if splits recent browsers from others
if (_wrap.file.fileSize !== -1) {
// simulation property indicates when the progress event is fake
if (rpe.simulation) {
// empty
} else {
// empty
}
} else {
// if fileSIze is -1 browser is using an iframe because it does
// not support
// files sent via Ajax (XMLHttpRequest)
// We can still show some information
}
this.fireEvent(File.EVENT_PROGRESS, {
file: _wrap.file,
total: rpe.total,
loaded: rpe.loaded,
simulation: rpe.simulation,
});
};
// generated if there is something wrong during upload
_wrap.onerror = () => {
// just inform the user something was wrong
this.fireEvent(File.EVENT_ERROR);
};
// generated when every file has been sent (one or more, it does not
// matter)
_wrap.onload = (rpe, xhr) => {
// just show everything is fine ...
// ... and after a second reset the component
setTimeout(() => {
_wrap.clean(); // remove files from list
_wrap.hide(); // hide progress bars and enable input file
// enable again the submit button/element
}, 100);
if (200 > xhr.status || xhr.status > 399) {
Msg.toast(i18nText("BI-Upload_File_Error"), { level: "error" });
this.fireEvent(File.EVENT_ERROR);
return;
}
const error = some(_wrap.attach_array, (index, attach) => {
if (attach.errorCode) {
Msg.toast(i18nText(attach.errorMsg), { level: "error" });
this.fireEvent(File.EVENT_ERROR, attach);
return true;
}
});
!error && this.fireEvent(File.EVENT_UPLOADED);
};
_wrap.url = url;
_wrap.fileType = accept; // 文件类型限制
_wrap.attach_array = [];
_wrap.attach_names = [];
_wrap.attachNum = 0;
}
_events(wrap) {
const { maxLength, errorText } = this.options;
const callback = () => {
event.del(wrap.dom.input, "change", callback);
const input = wrap.dom.input.cloneNode(true);
const files = F(wrap.dom.input);
if (maxLength !== -1 && maxLength < files.length) {
this.fireEvent(File.EVENT_ERROR, {
errorType: 2,
});
} else {
for (let i = 0; i < files.length; i++) {
const item = files.item(i);
const tempFile = item.value || item.name;
const value = item.fileName || (item.fileName = tempFile.split("\\").pop()),
size = item.fileSize || item.size;
const validateFileType = fileTypeValidate(value, wrap.fileType);
if (!validateFileType) {
// 文件类型不支持
Msg.toast(
errorText({
errorType: 0,
file: item,
}) || i18nText("BI-Upload_File_Type_Error", wrap.fileType),
{ level: "error" }
);
this.fireEvent(File.EVENT_ERROR, {
errorType: 0,
file: item,
});
} else if (wrap.maxSize !== -1 && size && wrap.maxSize < size) {
// 文件大小不支持
Msg.toast(
errorText({
errorType: 1,
file: item,
}) || i18nText("BI-Upload_File_Size_Error", Math.ceil(wrap.maxSize / 1024 / 1024)),
{ level: "error" }
);
this.fireEvent(File.EVENT_ERROR, {
errorType: 1,
file: item,
});
} else {
wrap.files.unshift(item);
}
}
}
wrap.files.length > 0 &&
this.fireEvent(File.EVENT_CHANGE, {
files: wrap.files,
});
input.value = "";
wrap.dom.input.parentNode.replaceChild(input, wrap.dom.input);
wrap.dom.input = input;
event.add(wrap.dom.input, "change", callback);
};
event.add(wrap.dom.input, "change", callback);
return wrap;
}
_wrap() {
const { multiple, maxSize, maxLength } = this.options;
// be sure input accept multiple files
const input = this.element[0];
if (multiple === true) {
this.element.attr("multiple", "multiple");
}
input.value = "";
// wrap Object
return this._events({
// DOM namespace
dom: {
input, // input file
disabled: false, // internal use, checks input file state
},
name: input.name, // name to send for each file ($_FILES[{name}] in the server)
// maxSize is the maximum amount of bytes for each file
maxSize: maxSize ? maxSize >> 0 : -1,
maxLength,
files: [], // file list
// remove every file from the noswfupload component
clean() {
this.files = [];
},
// upload one file a time (which make progress possible rather than all files in one shot)
// the handler is an object injected into the wrap one, could be the wrap itself or
// something like {onload:function(){alert("OK")},onerror:function(){alert("Error")}, etc ...}
upload(handler) {
if (handler) {
for (const key in handler) {
this[key] = handler[key];
}
}
sendFiles(this, this.maxSize);
return this;
},
// hide progress bar (total + current) and enable files selection
hide() {
if (this.dom.disabled) {
this.dom.disabled = false;
this.dom.input.removeAttribute("disabled");
}
},
// show progress bar and disable file selection (used during upload)
// total and current are pixels used to style bars
// totalProp and currentProp are properties to change, "height" by default
show(total, current, totalProp, currentProp) {
if (!this.dom.disabled) {
this.dom.disabled = true;
this.dom.input.setAttribute("disabled", "disabled");
}
},
});
}
setUrl(v) {
this.options.url = v;
if (this.wrap) {
this.wrap.url = v;
}
}
setMaxFileLength(v) {
this.options.maxLength = v;
if (this.wrap) {
this.wrap.maxLength = v;
}
}
select() {
this.wrap && Widget._renderEngine.createElement(this.wrap.dom.input).click();
}
upload(handler) {
this.wrap && this.wrap.upload(handler);
}
getValue() {
return this.wrap ? this.wrap.attach_array : [];
}
getQueue() {
return this.wrap.files;
}
reset() {
if (this.wrap) {
this.wrap.attach_array = [];
this.wrap.attach_names = [];
this.wrap.attachNum = 0;
}
}
sendFiles(files) {
if (!this.wrap) return;
this.wrap.dom.input.files = files;
const event = new CustomEvent("change");
this.wrap.dom.input.dispatchEvent(event);
}
_setEnable(enable) {
super._setEnable(...arguments);
if (enable === true) {
this.element.removeAttr("disabled");
} else {
this.element.attr("disabled", "disabled");
}
}
}