2551 lines
88 KiB
JavaScript
Executable File
Vendored
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

const localforage = require("localforage");
const dayjs = require("dayjs");
const utc = require("dayjs/plugin/utc")
const timezone = require("dayjs/plugin/timezone");
/**
* 基础函数
*/
(function (window, $, undefined) {
window.systemInfo = window.systemInfo || {};
window.modalTransferIndex = 1000;
localforage.config({name: 'DooTask', storeName: 'common'});
/**
* =============================================================================
* ******************************* 基础函数类 *******************************
* =============================================================================
*/
$.extend({
/**
* 是否数组
* @param obj
* @returns {boolean}
*/
isArray(obj) {
return typeof (obj) == "object" && Object.prototype.toString.call(obj).toLowerCase() == '[object array]' && typeof obj.length == "number";
},
/**
* 是否数组对象
* @param obj
* @returns {boolean}
*/
isJson(obj) {
return typeof (obj) == "object" && Object.prototype.toString.call(obj).toLowerCase() == "[object object]" && typeof obj.length == "undefined";
},
/**
* 是否在数组里
* @param key
* @param array
* @param regular
* @returns {boolean|*}
*/
inArray(key, array, regular = false) {
if (!this.isArray(array)) {
return false;
}
if (regular) {
return !!array.find(item => {
if (item && item.indexOf("*")) {
const rege = new RegExp("^" + item.replace(/[-\/\\^$+?.()|[\]{}]/g, '\\$&').replace(/\*/g, '.*') + "$", "g")
if (rege.test(key)) {
return true
}
}
return item == key
});
} else {
return array.includes(key);
}
},
/**
* 随机获取范围
* @param Min
* @param Max
* @returns {*}
*/
randNum(Min,Max){
let Range = Max - Min;
let Rand = Math.random();
return Min + Math.round(Rand * Range); //四舍五入
},
/**
* 获取数组最后一个值
* @param array
* @returns {boolean}
*/
last(array) {
let str = false;
if (typeof array === 'object' && array.length > 0) {
str = array[array.length - 1];
}
return str;
},
/**
* 字符串是否包含
* @param string
* @param find
* @param lower
* @returns {boolean}
*/
strExists(string, find, lower = false) {
string += "";
find += "";
if (lower !== true) {
string = string.toLowerCase();
find = find.toLowerCase();
}
return (string.indexOf(find) !== -1);
},
/**
* 字符串是否左边包含
* @param string
* @param find
* @param lower
* @returns {boolean}
*/
leftExists(string, find, lower = false) {
string += "";
find += "";
if (lower !== true) {
string = string.toLowerCase();
find = find.toLowerCase();
}
return (string.substring(0, find.length) === find);
},
/**
* 删除左边字符串
* @param string
* @param find
* @param lower
* @returns {string}
*/
leftDelete(string, find, lower = false) {
string += "";
find += "";
if (this.leftExists(string, find, lower)) {
string = string.substring(find.length)
}
return string ? string : '';
},
/**
* 字符串是否右边包含
* @param string
* @param find
* @param lower
* @returns {boolean}
*/
rightExists(string, find, lower = false) {
string += "";
find += "";
if (lower !== true) {
string = string.toLowerCase();
find = find.toLowerCase();
}
return (string.substring(string.length - find.length) === find);
},
/**
* 删除右边字符串
* @param string
* @param find
* @param lower
* @returns {string}
*/
rightDelete(string, find, lower = false) {
string += "";
find += "";
if (this.rightExists(string, find, lower)) {
string = string.substring(0, string.length - find.length)
}
return string ? string : '';
},
/**
* 取字符串中间
* @param string
* @param start
* @param end
* @returns {*}
*/
getMiddle(string, start = null, end = null) {
string = string.toString();
if (this.isHave(start) && this.strExists(string, start)) {
string = string.substring(string.indexOf(start) + start.length);
}
if (this.isHave(end) && this.strExists(string, end)) {
string = string.substring(0, string.indexOf(end));
}
return string;
},
/**
* 截取字符串
* @param string
* @param start
* @param end
* @returns {string}
*/
subString(string, start, end) {
string += "";
if (!this.isHave(end)) {
end = string.length;
}
return string.substring(start, end);
},
/**
* 随机字符
* @param len
* @returns {string}
*/
randomString(len) {
len = len || 32;
let $chars = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678oOLl9gqVvUuI1';
let maxPos = $chars.length;
let pwd = '';
for (let i = 0; i < len; i++) {
pwd += $chars.charAt(Math.floor(Math.random() * maxPos));
}
return pwd;
},
/**
* 判断是否有
* @param val
* @param {boolean} enhanced
* @returns {boolean}
*/
isHave(val, enhanced = false) {
// 基础检查
if (val === null || val === "null" || val === undefined || val === "undefined" || !val) {
return false;
}
// 增强检查
if (enhanced) {
if (Array.isArray(val)) return val.length > 0;
if (typeof val === 'object' && val.constructor === Object) return Object.keys(val).length > 0;
}
return true;
},
/**
* 判断是否为真
* @param value
* @returns {boolean}
*/
isTrue(value) {
const type = typeof value;
if (type === 'boolean') return value === true;
if (type === 'number') return value === 1;
if (type === 'string') return value.toLowerCase() === 'true' || value === '1';
return false;
},
/**
* 相当于 intval
* @param str
* @param fixed
* @returns {number}
*/
runNum(str, fixed = null) {
let _s = Number(str);
if (_s + "" === "NaN") {
_s = 0;
}
if (fixed && /^[0-9]*[1-9][0-9]*$/.test(fixed)) {
_s = _s.toFixed(fixed);
let rs = _s.indexOf('.');
if (rs < 0) {
_s += ".";
for (let i = 0; i < fixed; i++) {
_s += "0";
}
}
}
return _s;
},
/**
* 补零
* @param str
* @param length
* @param after
* @returns {*}
*/
zeroFill(str, length, after = false) {
if (after) {
return `${str}`.padEnd(length, '0')
}
return `${str}`.padStart(length, '0')
},
/**
* 检测手机号码格式
* @param str
* @returns {boolean}
*/
isMobile(str) {
return /^1([3456789])\d{9}$/.test(str);
},
/**
* 检测邮箱地址格式
* @param email
* @returns {boolean}
*/
isEmail(email) {
return /^([0-9a-zA-Z]([-.\w]*[0-9a-zA-Z])*@([0-9a-zA-Z][-\w]*\.)+[a-zA-Z]*)$/i.test(email);
},
/**
* 根据两点间的经纬度计算距离
* @param lng1
* @param lat1
* @param lng2
* @param lat2
* @returns {string|*}
*/
getDistance(lng1, lat1, lng2, lat2) {
let DEF_PI = 3.14159265359; // PI
let DEF_2PI = 6.28318530712; // 2*PI
let DEF_PI180 = 0.01745329252; // PI/180.0
let DEF_R = 6370693.5; // radius of earth
//
let ew1, ns1, ew2, ns2;
let dx, dy, dew;
let distance;
// 角度转换为弧度
ew1 = lng1 * DEF_PI180;
ns1 = lat1 * DEF_PI180;
ew2 = lng2 * DEF_PI180;
ns2 = lat2 * DEF_PI180;
// 经度差
dew = ew1 - ew2;
// 若跨东经和西经180 度,进行调整
if (dew > DEF_PI)
dew = DEF_2PI - dew;
else if (dew < -DEF_PI)
dew = DEF_2PI + dew;
dx = DEF_R * Math.cos(ns1) * dew; // 东西方向长度(在纬度圈上的投影长度)
dy = DEF_R * (ns1 - ns2); // 南北方向长度(在经度圈上的投影长度)
// 勾股定理求斜边长
distance = Math.sqrt(dx * dx + dy * dy).toFixed(0);
return distance;
},
/**
* 设置网页标题
* @param title
*/
setTile(title) {
document.title = title;
let mobile = navigator.userAgent.toLowerCase();
if (/iphone|ipad|ipod/.test(mobile)) {
let iframe = document.createElement('iframe');
iframe.style.display = 'none';
iframe.setAttribute('src', '/favicon.ico');
let iframeCallback = function () {
setTimeout(function () {
iframe.removeEventListener('load', iframeCallback);
document.body.removeChild(iframe)
}, 0)
};
iframe.addEventListener('load', iframeCallback);
document.body.appendChild(iframe)
}
},
/**
* 克隆对象
* @param value
* @param useParse
* @returns {*}
*/
cloneJSON(value, useParse = false) {
if (useParse === true) {
return $A.jsonParse($A.jsonStringify(value))
}
try {
return structuredClone(value);
} catch (e) {
if (typeof value !== 'object' || value === null) return value;
return $A.jsonParse($A.jsonStringify(value))
}
},
/**
* 将一个 JSON 字符串转换为对象已try
* @param str
* @param defaultVal
* @returns {*}
*/
jsonParse(str, defaultVal = undefined) {
if (str === null) {
return defaultVal ? defaultVal : {};
}
if (typeof str === "object") {
return str;
}
try {
return JSON.parse(str.replace(/\n/g,"\\n").replace(/\r/g,"\\r"));
} catch (e) {
return defaultVal ? defaultVal : {};
}
},
/**
* 将 JavaScript 值转换为 JSON 字符串已try
* @param json
* @param defaultVal
* @returns {string}
*/
jsonStringify(json, defaultVal = undefined) {
if (typeof json !== 'object') {
return json;
}
try{
return JSON.stringify(json);
}catch (e) {
return defaultVal ? defaultVal : "";
}
},
/**
* 监听对象尺寸发生改变
* @param obj
* @param callback
*/
resize(obj, callback) {
let myObj = $A(obj);
if (myObj.length === 0) return;
let height = parseInt(myObj.outerHeight()),
width = parseInt(myObj.outerWidth());
let inter = setInterval(()=>{
if (myObj.length === 0) clearInterval(inter);
let tmpHeight = parseInt(myObj.outerHeight()),
tmpWidth = parseInt(myObj.outerWidth());
if (height !== tmpHeight || width !== tmpWidth) {
height = tmpHeight;
width = tmpWidth;
$A.openLog && console.log(width, height);
if (typeof callback === 'function') callback();
}
}, 250);
},
/**
* 获取屏幕方向
* @returns {string}
*/
screenOrientation() {
/*try {
if (typeof window.screen.orientation === "object") {
return $A.strExists(window.screen.orientation.type, 'portrait') ? 'portrait' : 'landscape'
}
} catch (e) {
//
}*/ // 注释原因:有些设备宽和高对调了
return $A(window).width() - $A(window).height() > 50 ? "landscape" : "portrait"
},
/**
* 是否IOS
* @returns {boolean|string}
*/
isIos() {
let ua = typeof window !== 'undefined' && window.navigator.userAgent.toLowerCase();
return ua && /iphone|ipad|ipod|ios/.test(ua);
},
/**
* 是否iPad
* @returns {boolean|string}
*/
isIpad() {
let ua = typeof window !== 'undefined' && window.navigator.userAgent.toLowerCase();
return ua && /ipad/.test(ua);
},
/**
* 是否安卓
* @returns {boolean|string}
*/
isAndroid() {
let ua = typeof window !== 'undefined' && window.navigator.userAgent.toLowerCase();
return ua && ua.indexOf('android') > 0;
},
/**
* 是否微信
* @returns {boolean}
*/
isWeixin() {
let ua = typeof window !== 'undefined' && window.navigator.userAgent.toLowerCase();
return (ua.match(/MicroMessenger/i) + '' === 'micromessenger');
},
/**
* 是否Chrome
* @returns {boolean}
*/
isChrome() {
let ua = typeof window !== 'undefined' && window.navigator.userAgent.toLowerCase();
return (ua.match(/Chrome/i) + '' === 'chrome');
},
/**
* 是否桌面端
* @returns {boolean}
*/
isDesktop(){
let ua = typeof window !== 'undefined' && window.navigator.userAgent;
return !ua.match(/(phone|pad|pod|iPhone|iPod|ios|iPad|Android|Mobile|BlackBerry|IEMobile|MQQBrowser|JUC|Fennec|wOSBrowser|BrowserNG|WebOS|Symbian|Windows Phone)/i);
},
/**
* 获取对象
* @param obj
* @param keys
* @param defaultValue
* @returns {string|*}
*/
getObject(obj, keys, defaultValue = "") {
let keyArray;
if (typeof keys === 'string') {
keyArray = keys.replace(/,/g, "|").replace(/\./g, "|").split("|");
} else if (Array.isArray(keys)) {
keyArray = keys;
} else {
return defaultValue;
}
let result = obj;
for (let i = 0; i < keyArray.length; i++) {
let key = keyArray[i];
if (result == null) {
return defaultValue;
}
if (typeof key === 'string' && /^\d+$/.test(key)) {
key = parseInt(key, 10);
}
result = result[key];
}
return result === undefined ? defaultValue : result;
},
/**
* 统计数组或对象长度
* @param obj
* @returns {number}
*/
count(obj) {
try {
if (typeof obj === "undefined") {
return 0;
}
if (typeof obj === "number") {
obj += "";
}
if (typeof obj.length === 'number') {
return obj.length;
} else {
let i = 0, key;
for (key in obj) {
i++;
}
return i;
}
} catch (e) {
return 0;
}
},
/**
* 获取文本长度
* @param string
* @returns {number}
*/
stringLength(string) {
if (typeof string === "number" || typeof string === "string") {
return (string + "").length
}
return 0;
},
/**
* 获取数组长度(处理数组不存在)
* @param array
* @returns {number|*}
*/
arrayLength(array) {
if (array) {
try {
return array.length;
} catch (e) {
return 0
}
}
return 0;
},
/**
* 将数组或对象内容部分拼成字符串
* @param obj
* @returns {string}
*/
objImplode(obj) {
if (obj === null) {
return "";
}
let str = "";
$A.each(obj, (key, val) => {
if (val !== null) {
if (typeof val === "object" && this.count(val) > 0) {
str += this.objImplode(val);
} else {
str += String(val);
}
}
});
return str.replace(/\s/g, "").replace(/undefined/g, "");
},
/**
* 指定键获取url参数
* @param key
* @returns {*}
*/
urlParameter(key) {
const params = this.urlParameterAll();
return typeof key === "undefined" ? params : params[key];
},
/**
* 获取所有url参数
* @returns {{}}
*/
urlParameterAll() {
const search = window.location.search || window.location.hash || "";
const index = search.indexOf("?");
const arr = index !== -1 ? search.substring(index + 1).split("&") : [];
const params = {};
for (let i = 0; i < arr.length; i++) {
const data = arr[i].split("=");
if (data.length === 2) {
params[data[0]] = data[1];
}
}
return params;
},
/**
* 删除地址中的参数
* @param url
* @param keys
* @returns {string|*}
*/
removeURLParameter(url, keys) {
if (keys instanceof Array) {
keys.forEach((key) => {
url = $A.removeURLParameter(url, key)
});
return url;
}
try {
// 使用URL API正确解析URL各部分
const urlObj = new URL(url);
urlObj.searchParams.delete(keys);
return urlObj.toString();
} catch (e) {
// 如果URL解析失败回退到简单的字符串处理方法
const urlparts = url.split('?');
if (urlparts.length >= 2) {
const prefix = encodeURIComponent(keys) + '=';
const pars = urlparts[1].split(/[&;]/g);
for (let i = pars.length; i-- > 0;) {
if (pars[i].lastIndexOf(prefix, 0) !== -1) {
pars.splice(i, 1);
}
}
return urlparts[0] + (pars.length > 0 ? '?' + pars.join('&') : '');
}
return url;
}
},
/**
* 连接加上参数
* @param url
* @param params
* @returns {*}
*/
urlAddParams(url, params) {
if (!$A.isJson(params)) {
return url;
}
try {
// 使用URL API正确解析URL各部分
const urlObj = new URL(url);
for (let key in params) {
if (!params.hasOwnProperty(key)) {
continue;
}
urlObj.searchParams.set(key, params[key]);
}
return urlObj.toString();
} catch (e) {
// 如果URL解析失败回退到简单的字符串拼接方法
if (url) {
url = this.removeURLParameter(url, Object.keys(params))
}
url += "";
url += url.indexOf("?") === -1 ? '?' : '';
for (let key in params) {
if (!params.hasOwnProperty(key)) {
continue;
}
url += '&' + key + '=' + encodeURIComponent(params[key]);
}
return this.rightDelete(url.replace("?&", "?"), '?');
}
},
/**
* 替换url中的hash
* @param {string} url - 要修改的URL如果只提供一个参数则作为新的hash路径URL默认为当前页面
* @param {string} [path] - 新的hash路径可包含或不包含#前缀
* @returns {string} 替换hash后的URL
*/
urlReplaceHash(url, path = undefined) {
// 如果只传了一个参数将其视为pathurl默认为当前页面
if (path === undefined) {
path = url;
url = window.location.href;
}
// 确保url有值
url = url || window.location.href;
try {
// 使用URL API正确解析URL各部分
const urlObj = new URL(url);
// 确保path是字符串并格式正确
path = String(path || '');
if (path && path.startsWith('#')) {
path = path.substring(1);
}
// 设置新的hash
urlObj.hash = path;
return urlObj.toString();
} catch (e) {
// 如果URL解析失败回退到简单的字符串替换方法
if (!path) {
return url.replace(/#.*$/, '');
}
const hashPath = path.startsWith('#') ? path : '#' + path;
if (url.includes('#')) {
return url.replace(/#.*$/, hashPath);
} else {
return url + hashPath;
}
}
},
/**
* 刷新当前地址
* @returns {string}
*/
reloadUrl() {
if ($A.isEEUIApp && $A.isAndroid()) {
let url = window.location.href;
let key = '_='
let reg = new RegExp(key + '\\d+');
let timestamp = $A.dayjs().valueOf();
if (url.indexOf(key) > -1) {
url = url.replace(reg, key + timestamp);
} else {
if (url.indexOf('\?') > -1) {
let urlArr = url.split('\?');
if (urlArr[1]) {
url = urlArr[0] + '?' + key + timestamp + '&' + urlArr[1];
} else {
url = urlArr[0] + '?' + key + timestamp;
}
} else {
if (url.indexOf('#') > -1) {
url = url.split('#')[0] + '?' + key + timestamp + location.hash;
} else {
url = url + '?' + key + timestamp;
}
}
}
$A.eeuiAppSetUrl(url);
} else {
window.location.reload();
}
},
/**
* 链接字符串
* @param value 第一个参数为连接符
* @returns {string}
*/
stringConnect(...value) {
let s = null;
let text = "";
value.forEach((val) => {
if (s === null) {
s = val;
}else if (val){
if (val && text) text+= s;
text+= val;
}
});
return text;
},
/**
* 判断两个对象是否相等
* @param x
* @param y
* @returns {boolean}
*/
objEquals(x, y) {
let f1 = x instanceof Object;
let f2 = y instanceof Object;
if (!f1 || !f2) {
return x === y
}
if (Object.keys(x).length !== Object.keys(y).length) {
return false
}
for (let p in x) {
if (x.hasOwnProperty(p)) {
let a = x[p] instanceof Object;
let b = y[p] instanceof Object;
if (a && b) {
if (!this.objEquals(x[p], y[p])) {
return false;
}
} else if (x[p] != y[p]) {
return false;
}
}
}
return true;
},
/**
* 输入框内插入文本
* @param object
* @param content
*/
insert2Input(object, content) {
if (object === null || typeof object !== "object") return;
if (typeof object.length === 'number' && object.length > 0) object = object[0];
let ele = typeof object.$el === "object" ? $A(object.$el) : $A(object);
if (ele.length === 0) return;
let eleDom = ele[0];
if (eleDom.tagName != "INPUT" && eleDom.tagName != "TEXTAREA") {
if (ele.find("input").length === 0) {
ele = ele.find("textarea");
}else{
ele = ele.find("input");
}
}
if (ele.length === 0) return;
eleDom = ele[0];
if (eleDom.tagName != "INPUT" && eleDom.tagName != "TEXTAREA") return;
let text = ele.val();
let { selectionStart, selectionEnd } = eleDom;
ele.val(`${text.substring(0, selectionStart)}${content}${text.substring(selectionEnd, text.length)}`);
eleDom.dispatchEvent(new Event('input'));
setTimeout(() => {
if (eleDom.setSelectionRange) {
let pos = text.substring(0, selectionStart).length + content.length;
eleDom.focus();
eleDom.setSelectionRange(pos, pos);
}
}, 10);
},
/**
* 输入框数字限制
* @param object
* @param min
* @param max
* @returns
*/
inputNumberLimit(object, min = null, max = null) {
if (object === null || typeof object !== "object") return;
if (object && typeof object.target === "object") {
object = object.target;
}
let eleDom = null;
if (object && typeof object.$el === "object") {
eleDom = object.$el;
} else if (typeof object.length === "number" && object.length > 0) {
eleDom = object[0];
} else if (object && (object.nodeType === 1 || object.tagName)) {
eleDom = object;
}
if (!eleDom) return;
let ele = $A(eleDom);
if (ele.length === 0) return;
if (eleDom.tagName != "INPUT" && eleDom.tagName != "TEXTAREA") {
if (ele.find("input").length === 0) {
ele = ele.find("textarea");
}else{
ele = ele.find("input");
}
}
if (ele.length === 0) return;
eleDom = ele[0];
if (eleDom.tagName != "INPUT" && eleDom.tagName != "TEXTAREA") return;
let val = parseFloat(ele.val());
if (!isNaN(val)) {
if (min !== null && val < min) {
val = min;
}
if (max !== null && val > max) {
val = max;
}
ele.val(val);
eleDom.dispatchEvent(new Event('input'));
}
},
/**
* iOS上虚拟键盘引起的触控错位
*/
iOSKeyboardFixer() {
if (!this.isIos()) {
return;
}
document.body.scrollTop = document.body.scrollTop + 1;
document.body.scrollTop = document.body.scrollTop - 1;
},
/**
* 动态加载js文件
* @param url
* @returns {Promise<unknown>}
*/
loadScript(url) {
return new Promise(async (resolve, reject) => {
url = $A.originUrl(url)
if (this.rightExists(url, '.css')) {
return resolve(this.loadCss(url))
}
//
let i = 0
while (this.__loadScript[url] === "loading") {
await new Promise(r => setTimeout(r, 1000))
i++
if (i > 30) {
return reject("加载超时")
}
}
if (this.__loadScript[url] === "loaded") {
return resolve(false)
}
this.__loadScript[url] = "loading"
//
const script = document.createElement("script")
script.type = "text/javascript"
if (script.readyState) {
script.onreadystatechange = () => {
if (script.readyState === "loaded" || script.readyState === "complete") {
script.onreadystatechange = null
this.__loadScript[url] = "loaded"
resolve(true)
}
}
} else {
script.onload = () => {
this.__loadScript[url] = "loaded"
resolve(true)
}
script.onerror = (e) => {
this.__loadScript[url] = "error"
reject(e)
}
}
if (this.rightExists(url, '.js')) {
script.src = url + "?hash=" + window.systemInfo.version
} else {
script.src = url
}
if (document.head) {
document.head.appendChild(script)
} else {
document.body.appendChild(script)
}
})
},
loadScriptS(urls) {
return new Promise(resolve => {
let i = 0
const recursiveCallback = () => {
if (++i < urls.length) {
this.loadScript(urls[i]).finally(recursiveCallback)
} else {
resolve()
}
}
this.loadScript(urls[0]).finally(recursiveCallback)
})
},
__loadScript: {},
/**
* 动态加载css文件
* @param url
* @returns {Promise<unknown>}
*/
loadCss(url) {
return new Promise(async (resolve, reject) => {
url = $A.originUrl(url)
if (this.rightExists(url, '.js')) {
return resolve(this.loadScript(url))
}
//
let i = 0
while (this.__loadCss[url] === "loading") {
await new Promise(r => setTimeout(r, 1000))
i++
if (i > 30) {
return reject("加载超时")
}
}
if (this.__loadCss[url] === "loaded") {
return resolve(false)
}
this.__loadCss[url] = "loading"
//
const script = document.createElement('link')
if (script.readyState) {
script.onreadystatechange = () => {
if (script.readyState == 'loaded' || script.readyState == 'complete') {
script.onreadystatechange = null
this.__loadCss[url] = "loaded"
resolve(true)
}
}
} else {
script.onload = () => {
this.__loadCss[url] = "loaded"
resolve(true)
}
script.onerror = (e) => {
this.__loadCss[url] = "error"
reject(e)
}
}
script.rel = 'stylesheet'
if (this.rightExists(url, '.css')) {
script.href = url + "?hash=" + window.systemInfo.version
} else {
script.href = url
}
document.getElementsByTagName('head').item(0).appendChild(script)
})
},
loadCssS(urls) {
return new Promise(resolve => {
let i = 0
const recursiveCallback = () => {
if (++i < urls.length) {
this.loadCss(urls[i]).finally(recursiveCallback)
} else {
resolve()
}
}
this.loadCss(urls[0]).finally(recursiveCallback)
})
},
__loadCss: {},
/**
* 动态加载iframe
* @param url
* @param loadedRemove
* @returns {Promise<unknown>}
*/
loadIframe(url, loadedRemove = 0) {
return new Promise(async (resolve, reject) => {
url = $A.originUrl(url)
//
let i = 0
while (this.__loadIframe[url] === "loading") {
await new Promise(r => setTimeout(r, 1000))
i++
if (i > 30) {
return reject("加载超时")
}
}
if (this.__loadIframe[url] === "loaded") {
return resolve(false)
}
this.__loadIframe[url] = "loading"
//
const iframe = document.createElement("iframe")
iframe.style.display = 'none'
iframe.src = url
iframe.onload = () => {
this.__loadIframe[url] = "loaded"
resolve(true)
if (loadedRemove > 0) {
setTimeout(() => {
document.body.removeChild(iframe)
delete this.__loadIframe[url]
}, loadedRemove)
}
}
iframe.onerror = (e) => {
this.__loadIframe[url] = "error"
reject(e)
}
document.body.appendChild(iframe)
})
},
loadIframes(urls) {
return new Promise(resolve => {
let i = 0
const recursiveCallback = () => {
if (++i < urls.length) {
this.loadIframe(urls[i]).finally(recursiveCallback)
} else {
resolve()
}
}
this.loadIframe(urls[0]).finally(recursiveCallback)
})
},
__loadIframe: {},
/**
* 字节转换
* @param bytes
* @returns {string}
*/
bytesToSize(bytes) {
if (bytes === 0) return '0 B';
let k = 1024;
let sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
let i = Math.floor(Math.log(bytes) / Math.log(k));
if (typeof sizes[i] === "undefined") {
return '0 B';
}
return $A.runNum((bytes / Math.pow(k, i)), 2) + ' ' + sizes[i];
},
/**
* html代码转义
* @param sHtml
* @returns {*}
*/
html2Escape(sHtml) {
if (!sHtml || sHtml == '') {
return '';
}
return sHtml.replace(/[<>&"]/g, function (c) {
return {'<': '&lt;', '>': '&gt;', '&': '&amp;', '"': '&quot;'}[c];
});
},
/**
* 正则提取域名
* @param weburl
* @returns {string|string}
*/
getDomain(weburl) {
const urlReg = /http(s)?:\/\/([^\/]+)/i;
const domain = `${weburl}`.match(urlReg);
return ((domain != null && domain.length > 0) ? domain[2] : "");
},
/**
* 提取 URL 协议
* @param weburl
* @returns {string}
*/
getProtocol(weburl) {
try {
return new URL(weburl).protocol
} catch(e){
return ""
}
},
/**
* 滚动到View
* @param element
* @param options
*/
scrollToView(element, options) {
if (!element) {
return;
}
if (typeof options === "undefined" || options === true) {
options = {block: "start", inline: "nearest"}
} else if (options === false) {
options = {block: "end", inline: "nearest"}
}
if (typeof options.scrollMode !== "undefined" && typeof window.scrollIntoView === "function") {
window.scrollIntoView(element, options)
return;
}
try {
element.scrollIntoView(options);
} catch (e) {
if (typeof window.scrollIntoView === "function") {
window.scrollIntoView(element, options)
}
}
},
/**
* 按需滚动到View
* @param element
* @param smooth
*/
scrollIntoViewIfNeeded(element = null, smooth = false) {
if (!element) {
return;
}
if (!smooth && typeof element.scrollIntoViewIfNeeded === "function") {
element.scrollIntoViewIfNeeded()
} else {
const options = {
block: "nearest",
inline: "nearest"
}
if (smooth) {
options.behavior = 'smooth'
}
$A.scrollToView(element, options)
}
},
/**
* 给元素添加一个class过指定时间之后再去除这个class
* @param element
* @param className
* @param duration
*/
addClassWithTimeout(element, className, duration) {
if (!element || !className || !duration) return;
element.classList.add(className);
setTimeout(() => {
if (!element) return;
element.classList.remove(className);
}, duration);
},
/**
* 滚动到元素并抖动
* @param element
* @param viewIfNeeded
*/
scrollIntoAndShake(element, viewIfNeeded = true) {
if (!element) return;
const elements = Array.isArray(element) ? element : [element];
elements.forEach(el => {
if (el) {
viewIfNeeded && $A.scrollIntoViewIfNeeded(el);
$A.addClassWithTimeout(el, "common-shake", 800);
}
});
},
/**
* 等比缩放尺寸
* @param width
* @param height
* @param maxW
* @param maxH
* @returns {{width, height}|{width: number, height: number}}
*/
scaleToScale(width, height, maxW, maxH = undefined) {
const maxWidth = maxW;
const maxHeight = typeof maxH === "undefined" ? maxW : maxH;
let tempWidth;
let tempHeight;
if (width > 0 && height > 0) {
if (width / height >= maxWidth / maxHeight) {
if (width > maxWidth) {
tempWidth = maxWidth;
tempHeight = (height * maxWidth) / width;
} else {
tempWidth = width;
tempHeight = height;
}
} else {
if (height > maxHeight) {
tempHeight = maxHeight;
tempWidth = (width * maxHeight) / height;
} else {
tempWidth = width;
tempHeight = height;
}
}
return {width: parseInt(tempWidth), height: parseInt(tempHeight)};
}
return {width, height};
},
/**
* 阻止滑动穿透
* @param el
*/
scrollPreventThrough(el) {
if (!el) {
return;
}
if (el.getAttribute("data-prevent-through") === "yes") {
return;
}
el.setAttribute("data-prevent-through", "yes")
//
let targetY = null;
el.addEventListener('touchstart', function (e) {
targetY = Math.floor(e.targetTouches[0].clientY);
});
el.addEventListener('touchmove', function (e) {
// 检测可滚动区域的滚动事件,如果滑到了顶部或底部,阻止默认事件
let NewTargetY = Math.floor(e.targetTouches[0].clientY), //本次移动时鼠标的位置,用于计算
sTop = el.scrollTop, //当前滚动的距离
sH = el.scrollHeight, //可滚动区域的高度
lyBoxH = el.clientHeight; //可视区域的高度
if (sTop <= 0 && NewTargetY - targetY > 0) {
// 下拉页面到顶
e.preventDefault();
} else if (sTop >= sH - lyBoxH && NewTargetY - targetY < 0) {
// 上翻页面到底
e.preventDefault();
}
}, false);
},
/**
* 获取元素属性
* @param el
* @param attrName
* @param def
* @returns {Property<any>|string|string}
*/
getAttr(el, attrName, def = "") {
return el ? el.getAttribute(attrName) : def;
},
/**
* 排序JSON对象
* @param obj
* @param ignore
* @returns {{}}
*/
sortObject(obj, ignore = []) {
return Object.keys(obj).sort().reduce(function (result, key) {
if (!ignore.includes(key)) {
result[key] = obj[key];
}
return result;
}, {});
},
/**
* 从HTML中提取图片参数
* @param imgTag
* @returns {{original, src: (*|null), width: (number|*), height: (number|*)}}
*/
extractImageParameter(imgTag) {
const srcMatch = imgTag.match(/\s+src=(["'])([^'"]*)\1/i);
const widthMatch = imgTag.match(/\s+width=(["'])([^'"]*)\1/i);
const heightMatch = imgTag.match(/\s+height=(["'])([^'"]*)\1/i);
return {
src: srcMatch ? srcMatch[2] : null,
width: $A.runNum(widthMatch ? widthMatch[2] : 0),
height: $A.runNum(heightMatch ? heightMatch[2] : 0),
original: imgTag,
}
},
/**
* 从HTML中提取所有图片参数
* @param html
* @returns {{original, src: (*|null), width: (number|*), height: (number|*)}[]}
*/
extractImageParameterAll(html) {
const imgTags = html.match(/<img\s+[^>]*?>/g) || [];
return imgTags.map(imgTag => this.extractImageParameter(imgTag));
},
/**
* 增强版的字符串截取
* @param str
* @param length
* @param start
* @param suffix
* @returns {string}
*/
cutString(str, length, start = 0, suffix = '...') {
const chars = [...str];
// 如果长度为负数,则从末尾开始计数
if (length < 0) {
length = Math.max(chars.length + length, 0);
}
// 如果起始位置为负数,则从末尾开始计数
if (start < 0) {
start = Math.max(chars.length + start, 0);
}
// 如果截取长度为0或起始位置超出字符串长度返回空字符串
if (length === 0 || start >= chars.length) {
return '';
}
const sliced = chars.slice(start, start + length);
// 只有当实际截取的长度小于原字符串长度时才添加后缀
if (start + length < chars.length) {
return sliced.join('') + suffix;
}
return sliced.join('');
},
/**
* 获取两个数组后面的交集
* @param arr1
* @param arr2
* @returns {*}
*/
getLastSameElements(arr1, arr2) {
return arr1.slice(-(arr1.filter((item, index) =>
item === arr2[arr2.length - arr1.length + index]
).length));
},
/**
* 查找元素并在失败时重试
* @param {Function} findElementFn - 查找元素的函数
* @param {number} maxAttempts - 最大尝试次数
* @param {number} delayMs - 每次尝试之间的延迟(毫秒)
* @returns {Promise<*>}
*/
async findElementWithRetry(findElementFn, maxAttempts = 3, delayMs = 500) {
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
const element = findElementFn();
if (element) {
return element;
}
if (attempt < maxAttempts) {
await new Promise(resolve => setTimeout(resolve, delayMs));
}
}
throw new Error(`Element not found after ${maxAttempts} attempts`);
},
/**
* 轮询等待条件满足
* @param {Function} conditionFn - 返回布尔值的条件函数
* @param {number} intervalMs - 轮询间隔(毫秒,默认300ms)
* @param {number} timeoutMs - 超时时间(毫秒,默认3000ms)
* @returns {Promise<boolean>}
*/
async waitForCondition(conditionFn, intervalMs = 300, timeoutMs = 3000) {
const startTime = Date.now();
while (Date.now() - startTime < timeoutMs) {
if (conditionFn()) {
return true; // 条件满足
}
// 等待指定时间
await new Promise(resolve => setTimeout(resolve, intervalMs));
}
throw new Error('等待条件超时');
},
/**
* 执行指定次数的定时任务,返回一个可以取消的对象
* @param {Function} fn 要执行的函数
* @param {number} delay 首次执行的延迟时间(毫秒)
* @param {number} interval 间隔时间(毫秒)
* @param {number} times 执行次数
* @returns {object} 包含 clear 方法的对象
*/
repeatWithCount(fn, delay, interval = 0, times = 0) {
if (typeof fn !== 'function') {
return () => {}; // 返回空函数而不是null保持返回类型一致
}
let count = 0;
let timer = null;
const clear = () => {
if (timer) {
clearTimeout(timer);
timer = null;
}
};
const execute = () => {
if (count >= times) {
clear();
return;
}
try {
if (fn(count) === true) {
clear();
return;
}
} catch (error) {
clear();
console.error('Error in callback function:', error);
return;
}
count++;
timer = setTimeout(execute, interval);
};
// 立即开始第一次执行
timer = setTimeout(execute, delay);
// 直接返回clear函数
return clear;
},
/**
* 通过URL生成base64图片
* @param url 图片URL
* @param quality 图片质量默认0.8
* @param maxWidth 最大宽度0表示不限制
* @param maxHeight 最大高度0表示不限制
* @returns {Promise<unknown>}
*/
generateBase64Image(url, quality = 1, maxWidth = 0, maxHeight = 0) {
return new Promise(resolve => {
let canvas = document.createElement('canvas'),
ctx = canvas.getContext('2d'),
img = new Image;
img.crossOrigin = 'Anonymous';
img.onload = () => {
let width = img.width;
let height = img.height;
// 处理等比例缩放
if ((maxWidth > 0 || maxHeight > 0) && (width > 0 && height > 0)) {
// 计算宽高比
const ratio = width / height;
if (maxWidth > 0 && maxHeight > 0) {
// 同时指定了最大宽度和高度,按照最小比例缩放
if (width > maxWidth || height > maxHeight) {
const ratioWidth = maxWidth / width;
const ratioHeight = maxHeight / height;
const ratioMin = Math.min(ratioWidth, ratioHeight);
width = Math.round(width * ratioMin);
height = Math.round(height * ratioMin);
}
} else if (maxWidth > 0 && width > maxWidth) {
// 只指定了最大宽度
width = maxWidth;
height = Math.round(width / ratio);
} else if (maxHeight > 0 && height > maxHeight) {
// 只指定了最大高度
height = maxHeight;
width = Math.round(height * ratio);
}
}
canvas.width = width;
canvas.height = height;
ctx.drawImage(img, 0, 0, width, height);
let format = "png";
if ($A.rightExists(url, "jpg") || $A.rightExists(url, "jpeg")) {
format = "jpeg"
} else if ($A.rightExists(url, "webp")) {
format = "webp"
} else if ($A.rightExists(url, "git")) {
format = "git"
}
resolve(canvas.toDataURL(`image/${format}`, quality));
// 清理资源
canvas = null;
img = null;
ctx = null;
};
img.src = url;
})
},
/**
* 是否全屏(根据尺寸对比)
* @returns {boolean}
*/
isFullScreen() {
const windowWidth = $A(window).width();
const windowHeight = $A(window).height();
const screenWidth = window.screen.width;
const screenHeight = window.screen.height;
// 如果高比宽大,对调宽高
const adjustedWindowWidth = windowWidth > windowHeight ? windowWidth : windowHeight;
const adjustedWindowHeight = windowWidth > windowHeight ? windowHeight : windowWidth;
const adjustedScreenWidth = screenWidth > screenHeight ? screenWidth : screenHeight;
const adjustedScreenHeight = screenWidth > screenHeight ? screenHeight : screenWidth;
// 判断是否全屏误差1内视为全屏
const widthDiff = Math.abs(adjustedWindowWidth - adjustedScreenWidth);
const heightDiff = Math.abs(adjustedWindowHeight - adjustedScreenHeight);
return widthDiff <= 1 && heightDiff <= 1;
}
});
/**
* =============================================================================
* ***************************** localForage ******************************
* =============================================================================
*/
$.extend({
__IDBTimer: {},
async IDBTest() {
try {
if ($A.isIos()) {
await localforage.setItem('__test__', $A.dayjs().valueOf())
}
$A.openLog && console.log('IDBTest OK')
return true;
} catch (error) {
if ($A.openLog) {
console.error('IDBTest Error: ', error)
$A.modalWarning({
content: error.message,
onOk: () => {
$A.reloadUrl();
}
});
} else {
$A.reloadUrl();
}
return false;
}
},
IDBSave(key, value, delay = 100) {
if (typeof this.__IDBTimer[key] !== "undefined") {
clearTimeout(this.__IDBTimer[key])
delete this.__IDBTimer[key]
}
this.__IDBTimer[key] = setTimeout(async _ => {
await localforage.setItem(key, value)
delete this.__IDBTimer[key]
}, delay)
},
IDBDel(key) {
localforage.removeItem(key).then(_ => {})
},
IDBSet(key, value) {
return localforage.setItem(key, value)
},
IDBRemove(key) {
return localforage.removeItem(key)
},
IDBClear() {
return localforage.clear()
},
IDBValue(key) {
return localforage.getItem(key)
},
async IDBString(key, def = "") {
const value = await this.IDBValue(key)
return typeof value === "string" || typeof value === "number" ? value : def;
},
async IDBInt(key, def = 0) {
const value = await this.IDBValue(key)
return typeof value === "number" ? value : def;
},
async IDBBoolean(key, def = false) {
const value = await this.IDBValue(key)
return typeof value === "boolean" ? value : def;
},
async IDBArray(key, def = []) {
const value = await this.IDBValue(key)
return this.isArray(value) ? value : def;
},
async IDBJson(key, def = {}) {
const value = await this.IDBValue(key)
return this.isJson(value) ? value : def;
}
});
/**
* =============================================================================
* ***************************** localStorage ******************************
* =============================================================================
*/
$.extend({
setStorage(key, value) {
return this.__operationStorage(key, value);
},
getStorageValue(key) {
return this.__operationStorage(key);
},
getStorageString(key, def = '') {
let value = this.__operationStorage(key);
return typeof value === "string" || typeof value === "number" ? value : def;
},
getStorageInt(key, def = 0) {
let value = this.__operationStorage(key);
return typeof value === "number" ? value : def;
},
getStorageBoolean(key, def = false) {
let value = this.__operationStorage(key);
return typeof value === "boolean" ? value : def;
},
getStorageArray(key, def = []) {
let value = this.__operationStorage(key);
return this.isArray(value) ? value : def;
},
getStorageJson(key, def = {}) {
let value = this.__operationStorage(key);
return this.isJson(value) ? value : def;
},
existsStorage(key) {
return this.__operationStorage(key) !== null;
},
__operationStorage(key, value) {
if (!key) {
return;
}
let keyName = '__state__';
const keyArr = key.split(".");
if (keyArr.length > 1) {
const stateName = keyArr.shift();
keyName = '__state:' + stateName + '__';
key = keyArr.join(".");
}
if (typeof value === 'undefined') {
return this.__loadFromlLocal(key, null, keyName);
} else {
this.__savaToLocal(key, value, keyName);
}
},
__savaToLocal(key, value, keyName) {
try {
if (typeof keyName === 'undefined') keyName = '__seller__';
let seller = window.localStorage[keyName];
if (!seller) {
seller = {};
} else {
seller = JSON.parse(seller);
}
seller[key] = value;
window.localStorage[keyName] = JSON.stringify(seller);
} catch (e) {
}
},
__loadFromlLocal(key, def, keyName) {
try {
if (typeof keyName === 'undefined') keyName = '__seller__';
let seller = window.localStorage[keyName];
if (!seller) {
return def;
}
seller = JSON.parse(seller);
if (!seller || typeof seller[key] === 'undefined') {
return def;
}
return seller[key];
} catch (e) {
return def;
}
},
});
/**
* =============================================================================
* ***************************** sessionStorage ****************************
* =============================================================================
*/
$.extend({
setSessionStorage(key, value) {
return this.__operationSessionStorage(key, value);
},
getSessionStorageValue(key) {
return this.__operationSessionStorage(key);
},
getSessionStorageString(key, def = '') {
let value = this.__operationSessionStorage(key);
return typeof value === "string" || typeof value === "number" ? value : def;
},
getSessionStorageInt(key, def = 0) {
let value = this.__operationSessionStorage(key);
return typeof value === "number" ? value : def;
},
__operationSessionStorage(key, value) {
if (!key) {
return;
}
let keyName = '__state__';
if (key.substring(0, 5) === 'cache') {
keyName = '__state:' + key + '__';
}
if (typeof value === 'undefined') {
return this.__loadFromSession(key, '', keyName);
} else {
this.__savaToSession(key, value, keyName);
}
},
__savaToSession(key, value, keyName) {
try {
if (typeof keyName === 'undefined') keyName = '__seller__';
let seller = window.sessionStorage.getItem(keyName);
if (!seller) {
seller = {};
} else {
seller = JSON.parse(seller);
}
seller[key] = value;
window.sessionStorage.setItem(keyName, JSON.stringify(seller))
} catch (e) {
}
},
__loadFromSession(key, def, keyName) {
try {
if (typeof keyName === 'undefined') keyName = '__seller__';
let seller = window.sessionStorage.getItem(keyName);
if (!seller) {
return def;
}
seller = JSON.parse(seller);
if (!seller || typeof seller[key] === 'undefined') {
return def;
}
return seller[key];
} catch (e) {
return def;
}
},
});
/**
* =============================================================================
* ********************************* ihttp *********************************
* =============================================================================
*/
$.extend({
serializeObject(obj, parents) {
if (typeof obj === 'string') return obj;
let resultArray = [];
let separator = '&';
parents = parents || [];
let newParents;
function var_name(name) {
if (parents.length > 0) {
let _parents = '';
for (let j = 0; j < parents.length; j++) {
if (j === 0) _parents += parents[j];
else _parents += '[' + encodeURIComponent(parents[j]) + ']';
}
return _parents + '[' + encodeURIComponent(name) + ']';
}
else {
return encodeURIComponent(name);
}
}
function var_value(value) {
return encodeURIComponent(value);
}
for (let prop in obj) {
if (obj.hasOwnProperty(prop)) {
let toPush;
if (Array.isArray(obj[prop])) {
toPush = [];
for (let i = 0; i < obj[prop].length; i++) {
if (!Array.isArray(obj[prop][i]) && typeof obj[prop][i] === 'object') {
newParents = parents.slice();
newParents.push(prop);
newParents.push(i + '');
toPush.push($.serializeObject(obj[prop][i], newParents));
}
else {
toPush.push(var_name(prop) + '[]=' + var_value(obj[prop][i]));
}
}
if (toPush.length > 0) resultArray.push(toPush.join(separator));
}
else if (obj[prop] === null) {
resultArray.push(var_name(prop) + '=');
}
else if (typeof obj[prop] === 'object') {
// Object, convert to named array
newParents = parents.slice();
newParents.push(prop);
toPush = $.serializeObject(obj[prop], newParents);
if (toPush !== '') resultArray.push(toPush);
}
else if (typeof obj[prop] !== 'undefined' && obj[prop] !== '') {
// Should be string or plain value
resultArray.push(var_name(prop) + '=' + var_value(obj[prop]));
}
else if (obj[prop] === '') resultArray.push(var_name(prop));
}
}
return resultArray.join(separator);
},
// Global Ajax Setup
globalAjaxOptions: {},
ajaxSetup (options) {
if (options.type) options.method = options.type;
$.each(options, function (optionName, optionValue) {
$.globalAjaxOptions[optionName] = optionValue;
});
},
// Ajax
_jsonpRequests: 0,
ihttp(options) {
let defaults = {
method: 'GET',
data: false,
async: true,
cache: true,
user: '',
password: '',
headers: {},
xhrFields: {},
statusCode: {},
processData: true,
dataType: 'text',
contentType: 'application/x-www-form-urlencoded',
timeout: 0
};
const callbacks = ['beforeSend', 'error', 'complete', 'success', 'statusCode'];
//For jQuery guys
if (options.type) options.method = options.type;
// Merge global and defaults
$.each($.globalAjaxOptions, function (globalOptionName, globalOptionValue) {
if (callbacks.indexOf(globalOptionName) < 0) defaults[globalOptionName] = globalOptionValue;
});
// Function to run XHR callbacks and events
function fireAjaxCallback(eventName, eventData, callbackName) {
let a = arguments;
if (eventName) $(document).trigger(eventName, eventData);
if (callbackName) {
// Global callback
if (callbackName in $.globalAjaxOptions) $.globalAjaxOptions[callbackName](a[3], a[4], a[5], a[6]);
// Options callback
if (options[callbackName]) options[callbackName](a[3], a[4], a[5], a[6]);
}
}
// Merge options and defaults
$.each(defaults, function (prop, defaultValue) {
if (!(prop in options)) options[prop] = defaultValue;
});
// Default URL
if (!options.url) {
options.url = window.location.toString();
}
// Parameters Prefix
let paramsPrefix = options.url.indexOf('?') >= 0 ? '&' : '?';
// UC method
let _method = options.method.toUpperCase();
// Data to modify GET URL
if ((_method === 'GET' || _method === 'HEAD' || _method === 'OPTIONS' || _method === 'DELETE') && options.data) {
let stringData;
if (typeof options.data === 'string') {
// Should be key=value string
if (options.data.indexOf('?') >= 0) stringData = options.data.split('?')[1];
else stringData = options.data;
}
else {
// Should be key=value object
stringData = $.serializeObject(options.data);
}
if (stringData.length) {
options.url += paramsPrefix + stringData;
if (paramsPrefix === '?') paramsPrefix = '&';
}
}
// JSONP
if (options.dataType === 'json' && options.url.indexOf('callback=') >= 0) {
let callbackName = '__jsonp_' + Date.now() + ($._jsonpRequests++);
let abortTimeout;
let callbackSplit = options.url.split('callback=');
let requestUrl = callbackSplit[0] + 'callback=' + callbackName;
if (callbackSplit[1].indexOf('&') >= 0) {
let addVars = callbackSplit[1].split('&').filter(function (el) {
return el.indexOf('=') > 0;
}).join('&');
if (addVars.length > 0) requestUrl += '&' + addVars;
}
// Create script
let script = document.createElement('script');
script.type = 'text/javascript';
script.onerror = function () {
clearTimeout(abortTimeout);
fireAjaxCallback(undefined, undefined, 'error', null, 'scripterror');
fireAjaxCallback('ajaxComplete ajax:complete', {scripterror: true}, 'complete', null, 'scripterror');
};
script.src = requestUrl;
// Handler
window[callbackName] = function (data) {
clearTimeout(abortTimeout);
fireAjaxCallback(undefined, undefined, 'success', data);
script.parentNode.removeChild(script);
script = null;
delete window[callbackName];
};
document.querySelector('head').appendChild(script);
if (options.timeout > 0) {
abortTimeout = setTimeout(function () {
script.parentNode.removeChild(script);
script = null;
fireAjaxCallback(undefined, undefined, 'error', null, 'timeout');
}, options.timeout);
}
return;
}
// Cache for GET/HEAD requests
if (_method === 'GET' || _method === 'HEAD' || _method === 'OPTIONS' || _method === 'DELETE') {
if (options.cache === false) {
options.url += (paramsPrefix + '_nocache=' + Date.now());
}
}
// Create XHR
const xhr = new XMLHttpRequest();
// 添加请求开始时间记录
const requestStartTime = Date.now();
// Save Request URL
xhr.requestUrl = options.url;
xhr.requestParameters = options;
// Open XHR
xhr.open(_method, options.url, options.async, options.user, options.password);
// Create POST Data
let postData = null;
if ((_method === 'POST' || _method === 'PUT' || _method === 'PATCH') && options.data) {
if (options.processData) {
let postDataInstances = [ArrayBuffer, Blob, Document, FormData];
// Post Data
if (postDataInstances.indexOf(options.data.constructor) >= 0) {
postData = options.data;
}
else {
// POST Headers
let boundary = '---------------------------' + Date.now().toString(16);
if (options.contentType === 'multipart\/form-data') {
xhr.setRequestHeader('Content-Type', 'multipart\/form-data; boundary=' + boundary);
}
else {
xhr.setRequestHeader('Content-Type', options.contentType);
}
postData = '';
let _data = $.serializeObject(options.data);
if (options.contentType === 'multipart\/form-data') {
boundary = '---------------------------' + Date.now().toString(16);
_data = _data.split('&');
let _newData = [];
for (let i = 0; i < _data.length; i++) {
_newData.push('Content-Disposition: form-data; name="' + _data[i].split('=')[0] + '"\r\n\r\n' + _data[i].split('=')[1] + '\r\n');
}
postData = '--' + boundary + '\r\n' + _newData.join('--' + boundary + '\r\n') + '--' + boundary + '--\r\n';
}
else {
postData = _data;
}
}
}
else {
postData = options.data;
}
}
// Additional headers
if (options.headers) {
$.each(options.headers, function (headerName, headerCallback) {
xhr.setRequestHeader(headerName, headerCallback);
});
}
// Check for crossDomain
if (typeof options.crossDomain === 'undefined') {
options.crossDomain = /^([\w-]+:)?\/\/([^\/]+)/.test(options.url) && RegExp.$2 !== window.location.host;
}
if (!options.crossDomain) {
xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
}
if (options.xhrFields) {
$.each(options.xhrFields, function (fieldName, fieldValue) {
xhr[fieldName] = fieldValue;
});
}
let xhrTimeout;
// Handle XHR
xhr.onload = function (e) {
if (xhrTimeout) clearTimeout(xhrTimeout);
if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 0) {
// 计算请求耗时
const requestDuration = Date.now() - requestStartTime;
// 获取响应标头时间信息
const getResponseHeader = (header) => {
try {
return xhr.getResponseHeader(header);
} catch (e) {
return null;
}
}
const serverDate = getResponseHeader('Date');
const lastModified = getResponseHeader('Last-Modified');
const age = getResponseHeader('Age');
// 将时间信息添加到响应对象中
xhr.timeData = {
serverDate: serverDate,
lastModified: lastModified,
age: age,
duration: requestDuration
};
let responseData;
if (options.dataType === 'json') {
try {
responseData = JSON.parse(xhr.responseText);
fireAjaxCallback('ajaxSuccess ajax:success', {xhr: xhr}, 'success', responseData, xhr.status, xhr);
}
catch (err) {
console.error(err);
fireAjaxCallback('ajaxError ajax:error', {
xhr: xhr,
parseerror: true
}, 'error', xhr, 'parseerror');
}
}
else {
responseData = xhr.responseType === 'text' || xhr.responseType === '' ? xhr.responseText : xhr.response;
fireAjaxCallback('ajaxSuccess ajax:success', {xhr: xhr}, 'success', responseData, xhr.status, xhr);
}
}
else {
fireAjaxCallback('ajaxError ajax:error', {xhr: xhr}, 'error', xhr, xhr.status);
}
if (options.statusCode) {
if ($.globalAjaxOptions.statusCode && $.globalAjaxOptions.statusCode[xhr.status]) $.globalAjaxOptions.statusCode[xhr.status](xhr);
if (options.statusCode[xhr.status]) options.statusCode[xhr.status](xhr);
}
fireAjaxCallback('ajaxComplete ajax:complete', {xhr: xhr}, 'complete', xhr, xhr.status);
};
xhr.onerror = function (e) {
if (xhrTimeout) clearTimeout(xhrTimeout);
fireAjaxCallback('ajaxError ajax:error', {xhr: xhr}, 'error', xhr, xhr.status);
fireAjaxCallback('ajaxComplete ajax:complete', {xhr: xhr, error: true}, 'complete', xhr, 'error');
};
// Ajax start callback
fireAjaxCallback('ajaxStart ajax:start', {xhr: xhr}, 'start', xhr);
fireAjaxCallback(undefined, undefined, 'beforeSend', xhr);
// Timeout
if (options.timeout > 0) {
xhr.onabort = function () {
if (xhrTimeout) clearTimeout(xhrTimeout);
};
xhrTimeout = setTimeout(function () {
xhr.abort();
fireAjaxCallback('ajaxError ajax:error', {xhr: xhr, timeout: true}, 'error', xhr, 'timeout');
fireAjaxCallback('ajaxComplete ajax:complete', {
xhr: xhr,
timeout: true
}, 'complete', xhr, 'timeout');
}, options.timeout);
}
// Send XHR
xhr.send(postData);
// Return XHR object
return xhr;
}
});
/**
* =============================================================================
* *********************************** ajaxc *******************************
* =============================================================================
*/
$.extend({
ajaxc(params) {
if (!params) return false;
if (typeof params.url === 'undefined') return false;
if (typeof params.data === 'undefined') params.data = {};
if (typeof params.cache === 'undefined') params.cache = false;
if (typeof params.method === 'undefined') params.method = 'GET';
if (typeof params.timeout === 'undefined') params.timeout = 30000;
if (typeof params.dataType === 'undefined') params.dataType = 'json';
if (typeof params.before === 'undefined') params.before = () => { };
if (typeof params.complete === 'undefined') params.complete = () => { };
if (typeof params.after === 'undefined') params.after = () => { };
if (typeof params.success === 'undefined') params.success = () => { };
if (typeof params.error === 'undefined') params.error = () => { };
if (typeof params.header == 'undefined') params.header = {};
const key = $A.randomString(16);
//
params.before();
$A.__ajaxList.push({
key,
id: params.requestId || null,
url: params.url,
request: $A.ihttp({
url: params.url,
data: params.data,
cache: params.cache,
headers: params.header,
method: params.method.toUpperCase(),
contentType: "OPTIONS",
crossDomain: true,
dataType: params.dataType,
timeout: params.timeout,
success: function (data, status, xhr) {
$A.__ajaxList = $A.__ajaxList.filter(val => val.key !== key);
params.complete();
params.success(data, status, xhr);
params.after(true);
},
error: function (xhr, status) {
$A.__ajaxList = $A.__ajaxList.filter(val => val.key !== key);
params.complete();
params.error(xhr, status);
params.after(false);
}
})
});
},
ajaxcCancel(requestId) {
if (!requestId) {
return 0;
}
let num = 0;
$A.__ajaxList.forEach((val, index) => {
if (val.id === requestId) {
num++;
if (val.request) {
val.request.abort();
}
}
});
if (num > 0) {
$A.__ajaxList = $A.__ajaxList.filter(val => val.id !== requestId);
}
return num;
},
__ajaxList: [],
});
/**
* =============================================================================
* *********************************** time ********************************
* =============================================================================
*/
dayjs.extend(utc);
dayjs.extend(timezone);
$.extend({
/**
* 时间对象
* @param v
* @returns {*|dayjs.Dayjs}
*/
dayjs(v = undefined) {
if (/^\d{13,}$/.test(v)) {
return dayjs(Number(v));
}
if (/^\d{10,}$/.test(v)) {
return dayjs(Number(v) * 1000);
}
if (v === null) {
v = 0
}
return dayjs(v);
},
/**
* 时间对象(减去时区差)
* @param v
* @returns {*|dayjs.Dayjs}
*/
daytz(v = undefined) {
const t = $A.dayjs(v)
if ($A.timezoneDifference) {
return t.subtract($A.timezoneDifference, "hour")
}
return t;
},
/**
* 更新时区
* @param tz
* @returns {number}
*/
updateTimezone(tz = undefined) {
if (typeof tz !== "undefined") {
$A.timezoneName = tz;
}
if (!$A.timezoneName) {
return $A.timezoneDifference = 0;
}
const local = $A.daytz().startOf('hour');
const server = local.tz($A.timezoneName);
return $A.timezoneDifference = local.startOf('hour').diff(server.format("YYYY-MM-DD HH:mm:ss"), 'hour')
},
timezoneName: null,
timezoneDifference: 0,
/**
* 对象中有Date格式的转成指定格式
* @param value 支持类型dayjs、Date、string
* @param format 默认格式YYYY-MM-DD HH:mm:ss
* @returns {*}
*/
newDateString(value, format = "YYYY-MM-DD HH:mm:ss") {
if (value === null) {
return value;
}
if (value instanceof dayjs || value instanceof Date || $A.isDateString(value)) {
value = $A.dayjs(value).format(format);
} else if ($A.isJson(value)) {
value = Object.assign({}, value)
for (let key in value) {
if (!value.hasOwnProperty(key)) continue;
value[key] = $A.newDateString(value[key], format);
}
} else if ($A.isArray(value)) {
value = Object.assign([], value)
value.forEach((val, index) => {
value[index] = $A.newDateString(val, format);
});
}
return value;
},
/**
* 对象中有Date格式的转成时间戳
* @param value
* @returns {number|*}
*/
newTimestamp(value) {
if (value === null) {
return value;
}
if (value instanceof dayjs || value instanceof Date || $A.isDateString(value)) {
value = $A.dayjs(value).unix();
} else if ($A.isJson(value)) {
value = Object.assign({}, value)
for (let key in value) {
if (!value.hasOwnProperty(key)) continue;
value[key] = $A.newTimestamp(value[key]);
}
} else if ($A.isArray(value)) {
value = Object.assign([], value)
value.forEach((val, index) => {
value[index] = $A.newTimestamp(val);
});
}
return value;
},
/**
* 判断是否是日期格式
* 支持格式YYYY-MM-DD HH:mm:ss、YYYY-MM-DD HH:mm、YYYY-MM-DD HH、YYYY-MM-DD
* @param value
* @returns {boolean}
*/
isDateString(value) {
return typeof value === "string" && /^\d{4}[/-]\d{2}[/-]\d{2}(\s+\d{2}(:\d{2}(:\d{2})?)?)?$/i.test(value);
},
/**
* 秒数倒计时格式00:00:00, 00:00, 0s
* @param s
* @returns {string}
*/
secondsToTime(s) {
let pre = '';
if (s < 0) {
pre = '-';
s = -s;
}
let duration
const days = Math.floor(s / 86400);
const hours = Math.floor((s % 86400) / 3600);
const minutes = Math.floor(((s % 86400) % 3600) / 60);
const seconds = Math.floor(((s % 86400) % 3600) % 60);
if (days > 0) {
if (hours > 0) duration = days + "d," + $A.zeroFill(hours, 2) + "h";
else if (minutes > 0) duration = days + "d," + $A.zeroFill(minutes, 2) + "min";
else if (seconds > 0) duration = days + "d," + $A.zeroFill(seconds, 2) + "s";
else duration = days + "d";
}
else if (hours > 0) duration = $A.zeroFill(hours, 2) + ":" + $A.zeroFill(minutes, 2) + ":" + $A.zeroFill(seconds, 2);
else if (minutes > 0) duration = $A.zeroFill(minutes, 2) + ":" + $A.zeroFill(seconds, 2);
else if (seconds > 0) duration = $A.zeroFill(seconds, 2) + "s";
return pre + duration;
},
/**
* 格式化时间(本地时间自动减去时区差)
* @param date
* @returns {string}
*/
timeFormat(date) {
const local = $A.daytz(),
time = $A.dayjs(date);
if (local.format("YYYY-MM-DD") === time.format("YYYY-MM-DD")) {
return time.format("HH:mm")
}
if (local.clone().subtract(1, 'day').format('YYYY-MM-DD') === time.format("YYYY-MM-DD")) {
return `${$A.L('昨天')} ${time.format("HH:mm")}`
}
if (local.year() === time.year()) {
return time.format("MM-DD")
}
return time.format("YYYY-MM-DD") || '';
},
/**
* 倒计时
* @param s 开始时间自动减去时区差
* @param e
* @returns {string}
*/
countDownFormat(s, e) {
s = $A.daytz(s)
e = $A.dayjs(e)
const diff = e.diff(s, 'second');
if (diff == 0) {
return '0s';
}
if (Math.abs(diff) < 86400 * 7) {
return $A.secondsToTime(diff);
}
return $A.timeFormat(e)
},
});
/**
* =============================================================================
* *********************************** sort ********************************
* =============================================================================
*/
$.extend({
/**
* 计算排序值 (日期格式)
* @param v1
* @param v2
* @returns {number}
*/
sortDay(v1, v2) {
if (v1 === v2) return 0;
return ($A.dayjs(v1).valueOf() || 0) - ($A.dayjs(v2).valueOf() || 0);
},
/**
* 计算排序值 (数字格式)
* @param v1
* @param v2
* @returns {number}
*/
sortFloat(v1, v2) {
if (v1 === v2) return 0;
return (parseFloat(v1) || 0) - (parseFloat(v2) || 0);
}
});
window.$A = $;
})(window, window.$ = window.jQuery = require('jquery'));