UNPKG

@truenewx/tnxcore

Version:

互联网技术解决方案:Web核心扩展支持

1,487 lines (1,453 loc) 49.1 kB
// tnxcore-util.js import CryptoES from 'crypto-es'; if (window && window.location && !window.location.origin) { window.location.origin = window.location.protocol + '//' + window.location.hostname + (window.location.port ? ':' + window.location.port : ''); } // 不要在Object.prototype中添加函数,否则vue会报错 Object.assign(Number.prototype, { /** * 获取当前数值四舍五入到指定精度后的结果 * @param scale 精度,即小数点后的位数 * @returns {number} 四舍五入后的结果数值 */ halfUp(scale) { const p = Math.pow(10, scale); return Math.round(this * p) / p; }, toPercent(scale) { if (typeof scale === 'number') { return (this * 100).halfUp(scale) + '%'; } else { return (this * 100) + '%'; } } }); Object.assign(String.prototype, { firstToLowerCase() { return this.substring(0, 1).toLowerCase() + this.substring(1); }, firstToUpperCase() { return this.substring(0, 1).toUpperCase() + this.substring(1); }, format(args) { if (!(args instanceof Array)) { args = arguments; } let s = this; for (let i = 0; i < args.length; i++) { s = s.replace('{' + i + '}', args[i]); } return s; }, contains(searchString) { return this.indexOf(searchString) >= 0; }, toCharArray() { let array = []; for (let i = 0; i < this.length; i++) { array.push(this.charAt(i)); } return array; }, // 部分浏览器没有这个方法支持 replaceAll(regex, replacement) { if (typeof regex === 'string') { regex = new RegExp(regex, "gm"); } return this.replace(regex, replacement); }, allIndexOf(searchString, position) { let indexes = []; let index = this.indexOf(searchString, position); while (index >= 0) { indexes.push(index); index = this.indexOf(searchString, index + searchString.length); } return indexes; }, splitToIntArray(separator) { let array = this.split(separator); for (let i = 0; i < array.length; i++) { array[i] = parseInt(array[i]); } return array; }, /** * 按大写字母分割 * @param byNumber 是否按数字也分割 * @returns {string[]} */ splitByUpperCaseLetter(byNumber) { let array = []; let s = ''; for (let c of this) { if (!s) { s = c; } else if (/^[A-Z]$/.test(c)) { array.push(s); s = c; } else if (byNumber && /^[0-9]$/.test(c)) { if (/^[0-9]$/.test(s[s.length - 1])) { // 前一个字符也是数字 s += c; } else { array.push(s); s = c; } } else { s += c; } } if (s) { array.push(s); } return array; }, wildcardMatches(...patterns) { for (let pattern of patterns) { const regexPattern = pattern.split('*').map(s => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('.*'); const regex = new RegExp(`^${regexPattern}$`); if (regex.test(this)) { return true; } } return false; }, }); const DATE_PATTERNS = { dateTime: 'yyyy-MM-dd HH:mm:ss', date: 'yyyy-MM-dd', time: 'HH:mm:ss', timeMillisecond: 'HH:mm:ss.S', timeMinute: 'HH:mm', dateMinute: 'yyyy-MM-dd HH:mm', dateMonth: 'yyyy-MM', } Object.assign(Date.prototype, { format(pattern) { let date = { 'M+': this.getMonth() + 1, // 月份 'd+': this.getDate(), // 日 'H+': this.getHours(), // 小时 'm+': this.getMinutes(), // 分 's+': this.getSeconds(), // 秒 'q+': Math.floor((this.getMonth() + 3) / 3), // 季度 'S': this.getMilliseconds(), // 毫秒 }; if (/(y+)/.test(pattern)) { pattern = pattern.replace(RegExp.$1, (this.getFullYear() + '').substr(4 - RegExp.$1.length)); } for (let key in date) { if (new RegExp('(' + key + ')').test(pattern)) { let prefix = key === 'S' ? '000' : '00'; pattern = pattern.replace(RegExp.$1, RegExp.$1.length === 1 ? date[key] : (prefix + date[key]).substr(('' + date[key]).length)); } } return pattern; }, formatDateTime() { return this.format(DATE_PATTERNS.dateTime); }, formatDate() { return this.format(DATE_PATTERNS.date); }, formatTime() { return this.format(DATE_PATTERNS.time); }, formatTimeMillisecond() { return this.format(DATE_PATTERNS.timeMillisecond); }, formatTimeMinute() { return this.format(DATE_PATTERNS.timeMinute); }, formatDateMinute() { return this.format(DATE_PATTERNS.dateMinute); }, formatDateMonth() { return this.format(DATE_PATTERNS.dateMonth); }, plusMilliseconds(milliseconds) { return new Date(this.getTime() + milliseconds); }, plusSeconds(seconds) { return this.plusMilliseconds(seconds * 1000); }, plusMinutes(minutes) { return this.plusSeconds(minutes * 60); }, plusHours(hours) { return this.plusMinutes(hours * 60); }, plusDays(days) { return this.plusHours(days * 24); }, plusMonths(months) { let year = this.getFullYear(); let month = this.getMonth(); year += months / 12; month += months % 12; let date = new Date(this); date.setFullYear(year); date.setMonth(month); return date; }, plusYears(years) { let date = new Date(this); date.setFullYear(this.getFullYear() + years); return date; }, applyTime(hours, minutes, seconds, milliseconds) { this.setHours(hours); this.setMinutes(minutes); this.setSeconds(seconds); this.setMilliseconds(milliseconds); }, }); Object.assign(Array.prototype, { contains(element) { // 不覆盖原生的includes()方法,以免其它框架调用出错 if (typeof element === 'function') { for (let e of this) { if (element(e)) { return true; } } } else { for (let e of this) { if (e === element) { return true; } } } return false; }, containsIgnoreCase(element) { if (typeof element === 'string') { for (let e of this) { if (typeof e === 'string' && e.toLocaleLowerCase() === element.toLocaleLowerCase()) { return true; } } } return false; }, remove(element) { let index = -1; if (typeof element === 'function') { for (let i = 0; i < this.length; i++) { if (element(this[i]) === true) { index = i; break; } } } else { for (let i = 0; i < this.length; i++) { if (this[i] === element) { index = i; break; } } } if (index >= 0) { let result = this[index]; this.splice(index, 1); return result; } return undefined; }, /** * 移除当前数组中满足指定断言的元素 * @param predicate 断言元素是否该被移除 * @return {boolean} 是否实际发生了移除 */ removeIf(predicate) { let changed = false; if (typeof predicate === 'function') { for (let i = this.length - 1; i >= 0; i--) { if (predicate(this[i]) === true) { this.splice(i, 1); changed = true; } } } return changed; }, /** * 克隆 * @param deep 是否深度克隆,深度克隆会将数组中的元素也克隆 * @returns {*[]} 克隆形成的新数组 */ clone(deep) { if (deep) { let array = []; for (let e of this) { array.push(ObjectUtil.deepClone(e)); } return array; } else { return this.slice(0, this.length); } }, get(fn) { for (let e of this) { if (fn(e)) { return e; } } return undefined; }, equals(other, predicate) { if (typeof predicate !== 'function') { predicate = (a, b) => a === b; } if (Array.isArray(other) && this.length === other.length) { for (let thisElement of this) { for (let otherElement of other) { if (!predicate(thisElement, otherElement)) { return false; } } } for (let otherElement of other) { for (let thisElement of this) { if (!predicate(thisElement, otherElement)) { return false; } } } return true; } return false; }, }); Object.assign(Boolean.prototype, { toText() { if (this === true) { return '是'; } else if (this === false) { return '否'; } return undefined; } }); Object.assign(Element.prototype, { /** * 获取不是指定标签的第一个子节点 * @param tagName 标签名 * @return ChildNode 不是指定标签的第一个子节点,没有则返回undefined */ getFirstChildWithoutTagName(tagName) { const children = this.childNodes; for (let i = 0; i < children.length; i++) { if (children[i].tagName && children[i].tagName !== tagName.toUpperCase()) { return children[i]; } } return undefined; }, toggleClass(className) { if (this.classList.contains(className)) { this.classList.remove(className); } else { this.classList.add(className); } }, getParentIf(predicate) { let parent = this.parentElement; if (typeof predicate === 'function') { while (parent) { if (predicate(parent) !== false) { return parent; } parent = parent.parentElement; } return null; } return parent; }, }); export const BuildUtil = { byVite() { try { return typeof import.meta.env === 'object'; } catch (e) { return false; } }, byWebPack() { try { return typeof process.env === 'object'; } catch (e) { return false; } }, isProduction() { if (this.byVite()) { return import.meta.env.PROD; } if (this.byWebPack()) { return process.env.NODE_ENV === 'production'; } return true; }, } export const ObjectUtil = { /** * 深度合并多个对象到目标对象 * @param {Object} target 目标对象 * @param {...Object} sources 源对象 * @returns {Object} 合并后的目标对象 */ assignDeep(target, ...sources) { if (!target) { return target; } // 使用 WeakMap 来存储已经处理过的对象,防止循环引用 const processed = new WeakMap(); const isObject = obj => obj && typeof obj === 'object' && !Array.isArray(obj); const merge = (target, source) => { if (processed.has(source)) { return processed.get(source); } processed.set(source, target); for (const key in source) { try { const targetValue = target[key]; const sourceValue = source[key]; if (isObject(targetValue) && isObject(sourceValue)) { merge(targetValue, sourceValue); } else { target[key] = sourceValue; } } catch (e) { // 忽略只读属性错误 } } return target; }; for (const source of sources) { if (source) { merge(target, source); } } return target; }, toKeyValueArray(object, valueFunction) { if (object) { let array = []; Object.keys(object).forEach(key => { let value = object[key]; if (typeof valueFunction === 'function') { value = valueFunction(value); } array.push({ key: key, value: value, }); }); return array; } return undefined; }, setValue(obj, fieldPath, value) { var names = fieldPath.split('.'); for (let i = 0; i < names.length - 1; i++) { let name = names[i]; obj[name] = obj[name] || {}; obj = obj[name]; } let lastName = names[names.length - 1]; obj[lastName] = value; return obj; }, deepClone(obj) { if (obj) { let json = StringUtil.toJson(obj); return StringUtil.parseJson(json); } return obj; }, clear(obj, excludedKeys) { if (typeof obj === 'object') { excludedKeys = excludedKeys || []; let keys = Object.keys(obj); for (let key of keys) { if (!excludedKeys.contains(key)) { delete obj[key]; } } } }, isNull(value) { return value === null || value === undefined; }, isNotNull(value) { return value !== null && value !== undefined; }, isEmpty(value) { if (this.isNull(value)) { return true; } if (typeof value === 'string' || Array.isArray(value)) { return value.length === 0; } if (typeof value === 'object') { return Object.keys(value).length === 0; } return false; }, isNotEmpty(value) { if (value === null || value === undefined) { return false; } if (typeof value === 'string' || Array.isArray(value)) { return value.length > 0; } return true; }, isSimple(value) { return typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean' || value instanceof Date; }, rewriteDefined(target, sources, mapper) { if (target && sources) { if (!Array.isArray(sources)) { sources = [sources]; } let keys = Object.keys(target); for (let key of keys) { for (let i = sources.length - 1; i >= 0; i--) { let source = sources[i]; if (typeof source === 'object') { let newValue = source[key]; if (typeof mapper === 'function') { newValue = mapper(key, newValue); } if (newValue !== undefined) { let oldValue = target[key]; if (typeof oldValue === 'boolean') { newValue = newValue === true || newValue === 'true'; } else if (typeof oldValue === 'number' && typeof newValue !== 'number') { newValue = Number(newValue); } else if (typeof oldValue === 'string') { newValue = newValue ? newValue.toString() : ''; } target[key] = newValue; break; } } } } } return target; }, rewriteSimpleDefined(target, sources, ignoredKeys) { return this.rewriteDefined(target, sources, (key, value) => { if (!this.isSimple(value)) { return undefined; } if (ignoredKeys) { if (Array.isArray(ignoredKeys)) { if (ignoredKeys.contains(key)) { return undefined; } } else { if (ignoredKeys === key) { return undefined; } } } return value; }); }, } export const FunctionUtil = { before(before, target) { return function () { before.apply(this, arguments); return target.apply(this, arguments); }; }, around(target, around) { return function () { const args = [target]; for (let i = 0; i < arguments.length; i++) { args.push(arguments[i]); } return around.apply(this, args); } }, after(target, after) { return function () { let result = target.apply(this, arguments); after.apply(this, arguments); return result; }; }, /** * 最少超时回调 * @param beginTime 开始时间 * @param callback 回调函数 * @param minTimeout 最少超时时间,单位:毫秒 */ setMinTimeout(beginTime, callback, minTimeout) { if (beginTime instanceof Date) { beginTime = beginTime.getTime(); } minTimeout = minTimeout || 1500; const dTime = new Date().getTime() - beginTime; if (dTime > minTimeout) { callback(); } else { setTimeout(callback, minTimeout - dTime); } }, } export const MathUtil = { /** * 获取在[min,max)范围内的随机整数值 * @param min 最小值 * @param max 最大值 * @returns {number} 随机整数值 */ randomInt(min, max) { if (min > max) { // 最小值如果大于最大值,则互换 let temp = min; min = max; max = temp; } let result = Math.ceil(min + (max - min) * Math.random()); // 用ceil()方法以确保结果一定不小于最小值 if (result >= max) { // 确保不大于最大值 result = max; } return result; }, } export const StringUtil = { toJson: JSON.stringify, parseJson: JSON.parse, md5(s) { return CryptoES.MD5(s).toString(); }, base64: { encode: function (s) { return CryptoES.enc.Base64.stringify(CryptoES.enc.Utf8.parse(s)); }, decode: function (s) { return CryptoES.enc.Base64.parse(s).toString(CryptoES.enc.Utf8); }, }, uuid32() { let objectUrl = URL.createObjectURL(new Blob()).toString(); URL.revokeObjectURL(objectUrl); return objectUrl.substring(objectUrl.lastIndexOf('/') + 1).replaceAll('-', ''); }, random(length, chars) { if (length >= 0) { chars = chars || 'abcdefghijklmnopqrstuvwxyz0123456789'; // 默认取值范围为所有小写字母和数字 let s = ''; while (s.length < length) { s += chars.charAt(MathUtil.randomInt(0, chars.length)); } return s; } return undefined; }, getCapacityCaption(capacity, scale) { if (typeof capacity === 'number') { scale = scale || 0; const units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB']; let series = 0; for (series = 0; series < units.length; series++) { if (capacity >= 1024) { capacity = (capacity / 1024).halfUp(scale); } else { break; } } return capacity + units[series]; } return undefined; }, getPixelString(value, containerValue) { if (typeof value === 'number') { return value + 'px'; } else if (typeof value === 'string') { if (value.endsWith('%')) { let percent = parseFloat(value.substr(0, value.length - 1)); let containerNumber = this.getPixelNumber(containerValue); if (!isNaN(percent) && !isNaN(containerNumber)) { return (containerNumber * percent / 100) + 'px'; } } } return value; }, getPixelNumber(value) { if (typeof value === 'number') { return value; } if (typeof value === 'string') { if (value.toLowerCase().endsWith('px')) { return parseInt(value.substr(0, value.length - 2)); } if (value.toLowerCase().endsWith('rem')) { return parseInt(value.substr(0, value.length - 3)) * 16; } } return NaN; }, idCard(idCardNo) { let birthday; let gender; if (idCardNo.length === 15) { // 15位身份证号码 birthday = '19' + idCardNo.substr(6, 6); gender = idCardNo.substr(14, 1); } else { birthday = idCardNo.substr(6, 8); gender = idCardNo.substr(16, 1); } birthday = birthday.substr(0, 4) + '-' + birthday.substr(4, 2) + '-' + birthday.substr(6, 2); return { birthday: birthday, male: parseInt(gender) % 2 === 1, // 奇:男,偶:女 serialNo: idCardNo }; }, matchesForEach(content, keyword) { if (!keyword) { // 搜索关键字为空,则全部匹配 return true; } if (content !== undefined && content !== null) { content += ''; } if (!content) { // 搜索内容为空,则无法匹配 return false; } let index = 0; for (let c of keyword.toCharArray()) { index = content.indexOf(c, index); if (index < 0) { // 搜索关键字中有一个字符未被包含在内容中,则不匹配 return false; } } // 遍历后没有不匹配的字符,则说明整理匹配 return true; }, matchesWildcard(s, pattern) { const regex = new RegExp('^' + pattern.replace(/\*/g, '.*') + '$'); return regex.test(s); }, } export const DateUtil = { patterns: DATE_PATTERNS, toDate(value) { if (value instanceof Date) { return value; } else if (typeof value === 'string' || typeof value === 'number') { return new Date(value); } return undefined; }, format(date, pattern) { if (typeof date === 'number' || typeof date === 'string') { date = new Date(date); } if (date instanceof Date) { return date.format(pattern); } return undefined; }, formatDate(date) { return this.format(date, DATE_PATTERNS.date); }, formatTime(date) { return this.format(date, DATE_PATTERNS.time); }, formatTimeMinute(date) { return this.format(date, DATE_PATTERNS.timeMinute); }, formatDateTime(date) { return this.format(date, DATE_PATTERNS.dateTime); }, formatDateMinute(date) { return this.format(date, DATE_PATTERNS.dateMinute); }, PERMANENT_DATE_TEXT: '永久', formatPermanentableDate(date) { if (date) { if (date.permanent) { return this.PERMANENT_DATE_TEXT; } if (date.value) { return new Date(date.value).formatDate(); } } return undefined; }, /** * 将指定yyyy-MM-dd型的日期转换为yyyy-MM的月份格式 * @param date 日期 * @returns 月份 */ dateToMonth(date) { if (date instanceof Date) { return this.format(date, this.pattern.dateMonth); } if (date) { return date.substr(0, date.lastIndexOf('-')); } return date; }, createDate(year, month, day, hour, minute, second, millis) { let date = new Date(); date.setFullYear(year, (month || 1) - 1, day || 1); date.setHours(hour || 0, minute || 0, second || 0, millis || 0); return date; }, getDaysOfMonth(year, month) { // 闰月 if (month === 2 && ((year % 400 === 0) || (year % 4 === 0 && year % 100 !== 0))) { return 29; } return [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month]; }, betweenDays(earlierDate, laterDate) { earlierDate = new Date(earlierDate); laterDate = new Date(laterDate); // 如果earlierDate晚于laterDate,则返回负值 return (laterDate.getTime() - earlierDate.getTime()) / 1000 / 60 / 60 / 24; }, betweenDate(earlierDate, laterDate) { earlierDate = new Date(earlierDate); let year1 = earlierDate.getFullYear(); let month1 = earlierDate.getMonth() + 1; let day1 = earlierDate.getDate(); laterDate = new Date(laterDate); let year2 = laterDate.getFullYear(); let month2 = laterDate.getMonth() + 1; let day2 = laterDate.getDate(); let revisedMonths = 0; //修订月数 let revisedYears = 0; // 修订年数 // 计算日,不足时向月份借 let days; if (day2 >= day1) { days = day2 - day1; } else { revisedMonths = -1; days = this.getDaysOfMonth(year1, month1) + day2 - day1; } // 计算月,不足时向年份借 let months; if (month2 + revisedMonths >= month1) { months = month2 + revisedMonths - month1; } else { revisedYears = -1; months = 12 + month2 + revisedMonths - month1; } // 计算年 let years = year2 + revisedYears - year1; return { years, months, days, toMonthString() { let s = ''; if (this.years > 0) { s += this.years + '年'; } s += this.months + '个月'; return s; }, toString() { let s = ''; if (this.years > 0) { s += this.years + '年'; } if (this.months > 0) { s += this.months + '个月'; } else if (this.years > 0 && this.days > 0) { s += '零'; } if (this.days > 0) { s += this.days + '天'; } return s; } } }, today() { let date = new Date(); date.setHours(0, 0, 0, 0); return date; }, monthsToYearMonth(months) { if (months !== undefined && months !== null) { months = parseInt(months); if (!isNaN(months)) { let year = Math.floor(months / 12); let month = months % 12; return (year >= 1 ? year + '年' : '') + month + '个月'; } } return undefined; } } export const ArrayUtil = { indexOf(array, element) { if (typeof element === 'function') { for (let i = 0; i < array.length; i++) { if (element(array[i])) { return i; } } return -1; } return array.indexOf(element); }, } export const NetUtil = { concatUri(contextUri, path) { // 如果相对路径为空或/,则无需拼接 if (!path || path === '/') { return contextUri; } if (ObjectUtil.isNull(contextUri)) { return null; } if (contextUri.endsWith('/')) { contextUri = contextUri.substring(0, contextUri.length() - 1); } if (!path.startsWith('/')) { path = '/' + path; } return contextUri + path; }, /** * 从指定头信息集中获取指定头信息值 * @param headers 头信息集 * @param name 头信息名称 * @param defaultValue 默认值 * @returns {undefined|*} 头信息值 */ getHeader(headers, name, defaultValue) { if (headers && name) { return headers[name] || headers[name.toLowerCase()] || defaultValue; } return undefined; }, getParameterString() { let href = window.location.href; let index = href.indexOf('?'); if (index >= 0) { let parameterString = href.substr(index + 1); parameterString = decodeURIComponent(parameterString); index = parameterString.indexOf('#'); if (index >= 0) { parameterString = parameterString.substr(0, index); } return parameterString; } return ''; }, getParameters() { let params = {}; let parameterString = this.getParameterString(); if (parameterString) { let array = parameterString.split('&'); for (let parameter of array) { let index = parameter.indexOf('='); if (index > 0) { let paramName = parameter.substr(0, index); let paramValue = parameter.substr(index + 1); if (params[paramName]) { if (!Array.isArray(params[paramName])) { params[paramName] = [params[paramName]]; } params[paramName].push(paramValue); } else { params[paramName] = paramValue; } } } } return params; }, getParamValue(name) { let parameterString = this.getParameterString(); if (parameterString) { let value = []; let array = parameterString.split('&'); for (let parameter of array) { let index = parameter.indexOf('='); if (index > 0) { let paramName = parameter.substr(0, index); if (paramName === name) { value.push(parameter.substr(index + 1)); } } } if (value.length === 1) { return value[0]; } else if (value.length > 1) { return value; } } return undefined; }, /** * 将指定对象中的所有字段拼凑成形如a=a1&b=b1的字符串 * @param object 对象 * @returns {string} 拼凑成的字符串 */ toParameterString(object) { let s = ''; if (typeof object === 'object') { Object.keys(object).forEach(key => { let value = object[key]; if (value !== undefined && value !== null) { let toKeyValueString = function (k, v) { switch (typeof v) { case 'function': v = v(); break; case 'object': if (v instanceof Date) { v = v.formatDateTime(); } else if (typeof v.toString === 'function') { v = v.toString(); } else { v = null; } break; } if (v !== undefined && v !== null && v !== '') { return '&' + k + '=' + encodeURIComponent(v); } return ''; } if (Array.isArray(value)) { for (let v of value) { s += toKeyValueString(key, v); } } else { s += toKeyValueString(key, value); } } }); if (s.length) { // 去掉头部多余的& s = s.substr(1); } } return s; }, /** * 为指定URL附加参数 * @param url 原URL * @param params 附加的参数集 * @return {string} 新的URL */ appendParams(url, params) { if (typeof url === 'string') { let parameterString = this.toParameterString(params); if (parameterString.length) { return url += (url.contains('?') ? '&' : '?') + parameterString; } } return url; }, /** * 为指定URL附加一个随机参数,用于刷新资源 * @param url URL * @return {string} 新的URL */ appendRandomParam(url) { let params = {}; let key = '_' + StringUtil.random(8); params[key] = new Date().getTime(); return this.appendParams(url, params); }, getAnchor() { const anchor = window.location.hash; if (anchor) { const index = anchor.indexOf('#'); if (index >= 0) { return anchor.substr(index + 1); } } return ''; }, /** * 获取当前页面的地址 */ getUrl(withOrigin, withAnchor, withParameter) { let url = ''; if (withOrigin) { url += window.location.origin; } url += window.location.pathname; if (withAnchor) { url += window.location.hash; } if (withParameter) { url += this.getParameterString(); } return url; }, isIntranetHostname(hostname) { if (hostname === 'localhost' || hostname === '127.0.0.1' || hostname === '0:0:0:0:0:0:0:1') { // 本机 return true; } if (hostname.startsWith('192.168.') || hostname.startsWith('10.')) { // 192.168网段或10网段 return true; } else if (hostname.startsWith('172.')) { // 172.16-172.31网段 let seg = hostname.substring(4, hostname.indexOf('.', 4)); // 取第二节 let value = parseInt(seg); return 16 <= value && value <= 31; } return false; }, isUrl(s) { const regex = '^((https|http|ftp)?://)' + '?(([0-9a-z_!~*\'().&=+$%-]+: )?[0-9a-z_!~*\'().&=+$%-]+@)?' //ftp的user@ + '(([0-9]{1,3}.){3}[0-9]{1,3}' // IP形式的URL- 199.194.52.184 + '|' // 允许IP和DOMAIN(域名) + '([0-9a-z_!~*\'()-]+.)*' // 二级域名:www + '([0-9a-z][0-9a-z-]{0,61})?[0-9a-z].' // 一级域名 + '[a-z]{2,6})' // 域名后缀:.com + '(:[0-9]{1,4})?' // 端口:80 + '((/?)|' + '(/[0-9a-z_!~*\'().;?:@&=+$,%#-]+)+/?)$'; return new RegExp(regex).test(s); }, /** * 判断是否移动端,包括手机和平板 * @returns {boolean} 是否移动端 */ isMobile() { let userAgent = navigator.userAgent.toLowerCase(); return userAgent.contains('android') || userAgent.contains('iphone') || userAgent.contains('ipad'); }, /** * 判断是否在微信中 * @returns 是否在微信中 */ isWechat() { let userAgent = navigator.userAgent.toLowerCase(); return userAgent.contains('micromessenger'); }, getExtension(url, withDot) { if (url) { let index = url.indexOf('?'); if (index >= 0) { url = url.substring(0, index); } index = url.lastIndexOf('.'); if (index >= 0) { return url.substring(withDot ? index : index + 1).toLowerCase(); } return ''; } return undefined; }, pushState(url) { if (window.history.pushState) { if (url.startsWith('/')) { url = window.location.origin + url; } window.history.pushState({}, '', url); return true; } return false; }, replaceState(url) { if (window.history.replaceState) { if (url.startsWith('/')) { url = window.location.origin + url; } window.history.replaceState({}, '', url); return true; } return false; }, getPathVariables(pattern, url) { const variableNames = []; const regexPattern = pattern.replace(/\$\{([^}]+)\}/g, (match, variableName) => { variableNames.push(variableName); return '([^/]+)'; // 匹配任意非斜杠字符 }); const fullRegex = new RegExp(`^${regexPattern}$`); const match = url.match(fullRegex); if (!match) { return undefined; } const variables = {}; for (let i = 0; i < variableNames.length; i++) { variables[variableNames[i]] = decodeURIComponent(match[i + 1]); // match[0] 是整个匹配的字符串 } return variables; }, } export const DomUtil = { getMetaContent(name) { const meta = document.querySelector('meta[name="' + name + '"]'); if (meta) { return meta.getAttribute('content'); } return undefined; }, getDocWidth() { return document.documentElement.clientWidth; }, getDocHeight() { return document.documentElement.clientHeight; }, maxZIndex(elements) { let result = -1; if (!elements) { elements = document.body.getElementsByTagName('*'); } for (let element of elements) { const zIndex = Number(element.style.zIndex); if (result < zIndex) { result = zIndex; } } return result; }, /** * 获取最小的可位于界面顶层的ZIndex */ minTopZIndex(step) { step = step || 1; const maxValue = 2147483584; // 允许的最大值,取各浏览器支持的最大值中的最小一个(Opera) const elements = document.body.querySelectorAll('*'); const maxZIndex = this.maxZIndex(elements); // 可见DOM元素中的最高层级 if (maxZIndex > maxValue - step) { return maxValue; } else { return maxZIndex + step; } }, selectRange(element, start, length) { let end = start + length; element.setSelectionRange(start, end); element.focus(); return end; }, scrollToTop() { window.document.body.scrollIntoView(); }, scrollToBottom() { let top = window.document.body.scrollHeight; window.scroll({top: top, left: 0, behavior: 'smooth'}); }, matchesKeyEvent(event, options) { if (options) { if (options.ctrlKey === true && event.ctrlKey !== true) { return false; } if (options.altKey === true && event.altKey !== true) { return false; } if (options.shiftKey === true && event.shiftKey !== true) { return false; } if (options.key) { return options.key.toLowerCase() === event.key.toLowerCase(); } if (options.code) { return options.code.toLowerCase() === event.code.toLowerCase(); } return options.keyCode === event.keyCode; } }, /** * 替代元素键盘事件处理 * @param element 元素 * @param handler 事件处理函数 * @param options 事件匹配参数,可以包含:ctrlKey、altKey、shiftKey、key、code、keyCode */ replaceKeyEvent(element, handler, options) { let _this = this; // 屏蔽键盘按下事件 element.onkeydown = function (event) { if (_this.matchesKeyEvent(event, options)) { event.preventDefault(); event.returnValue = false; } }; // 替换键盘松开事件 element.onkeyup = function (event) { if (_this.matchesKeyEvent(event, options)) { event.preventDefault(); event.returnValue = false; handler(); } }; }, observeHeightChange(element, callback) { let oldHeight = window.getComputedStyle(element).getPropertyValue('height'); const MutationObserver = window.MutationObserver || window.webkitMutationObserver || window.MozMutationObserver; let observer = new MutationObserver(function (mutations) { let height = window.getComputedStyle(element).getPropertyValue('height'); if (height !== oldHeight) { oldHeight = height; callback(); } }); observer.observe(element, { childList: true, // 直接子节点的变动(增、改、删) attributes: true, // 属性的变动 characterData: true, // 节点内容或节点文本的变动 subtree: true, // 是否应用于所有后代节点 }); return observer; }, /** * 获取指定元素在垂直居中于页面时的top * @param element 元素 */ getTopVerticallyCenteredOnPage(element) { const height = element.offsetHeight; const docHeight = this.getDocHeight(); // 对话框高度占文档高度的比例 const heightRatio = height / docHeight; // 为了获得更好的视觉舒适度,根据高度比确定对话框中线位置:从33vh->50vh const baseline = 33 + (50 - 33) * heightRatio; const baseTop = docHeight * baseline / 100; let top = baseTop - height / 2; top = Math.max(top, 8); // 至少顶部留8px空隙 return top; }, } export const BomUtil = { _ctrlKeyObservers: {}, /** * 获取监听Ctrl快捷键时的当前地址,默认返回当前页面的锚点,应用可根据实际需要覆写本方法 * @returns {string} 监听Ctrl快捷键时的当前地址 */ getObserveCtrlKeyCurrentPage() { return util.net.getAnchor(); }, /** * 侦听窗口级的Ctrl键盘事件 * @param page 注册页面标识,一般用页面地址 * @param key 注册键盘按键,如:'s' * @param observer 侦听函数,无参数 */ observeCtrlKey(page, key, observer) { if (Object.keys(this._ctrlKeyObservers).length === 0) { let _this = this; // 必须拦截onkeydown才能阻止浏览器默认事件,拦截onkeyup时浏览器的默认onkeydown仍会执行 window.onkeydown = function (event) { if (event.ctrlKey) { let _key = event.key + '@' + _this.getObserveCtrlKeyCurrentPage(); let _observer = _this._ctrlKeyObservers[_key]; if (_observer) { _observer(); event.returnValue = false; } } } } let pages = Array.isArray(page) ? page : [page]; for (let p of pages) { this._ctrlKeyObservers[key.toLowerCase() + '@' + p] = observer; } }, _opened: {}, _openedIntervalId: null, /** * 唯一地打开指定地址的窗口 * @param url 地址 */ openUniquely(url) { // 先开启侦听 if (this._openedIntervalId === null) { this._openedIntervalId = setInterval(function () { const opened = window.tnx.util.bom._opened; Object.keys(opened).forEach(url => { let win = opened[url]; if (win && win.closed) { delete opened[url]; } }); }, 1000); } let win = this._opened[url]; if (win && !win.closed) { win.focus(); return win; } win = window.open(url); this._opened[url] = win; return win; }, closeWindow() { return new Promise((resolve, reject) => { if (window.opener) { window.close(); resolve(window.opener); } else { reject(); } }); }, copyToClipboard(text, callback) { if (navigator.clipboard) { navigator.clipboard.writeText(text).then(callback).catch(error => { console.error(error); }); } else { console.error('当前浏览器不支持 navigator.clipboard'); } }, readFromClipboard(callback) { if (navigator.clipboard) { navigator.clipboard.readText().then(callback).catch(error => { console.error(error); }); } else { console.error('当前浏览器不支持 navigator.clipboard'); } }, } export const FileUtil = { extensions: { image: ['bmp', 'jpg', 'jpeg', 'png', 'gif', 'svg', 'ico'], video: ['mp4', 'webm', 'mpg', 'mpeg', 'mov', 'avi', 'rm', '3gp', '3gpp', 'mkv', 'rmvb', 'wmv'], audio: ['mp3', 'wav', 'wma', 'acc', 'mid', 'cda', 'aif', 'aiff', 'ra', 'ape'], archive: ['zip', 'rar', '7z', 'war', 'jar', 'gz', 'tar'], binary: ['exe', 'msi', 'bin', 'dll', 'sys', 'com'], }, isImage(extension) { return this.extensions.image.contains(extension.toLowerCase()); }, isVideo(extension) { return this.extensions.video.contains(extension.toLowerCase()); }, isAudio(extension) { return this.extensions.audio.contains(extension.toLowerCase()); }, isArchive(extension) { return this.extensions.archive.contains(extension.toLowerCase()); }, isBinary(extension) { return this.extensions.binary.contains(extension.toLowerCase()); }, } export const util = { build: BuildUtil, object: ObjectUtil, function: FunctionUtil, math: MathUtil, string: StringUtil, date: DateUtil, array: ArrayUtil, net: NetUtil, dom: DomUtil, bom: BomUtil, file: FileUtil, }; export default util;