import chinaMapJSON from '@/assets/js/china_area' import { NotURLPrefixRegExp } from '@/utils/constant' import { env, androidOrIOS } from '@/utils/prototype/modules/platform' import qs from 'qs' /* * 时间格式化函数(将时间格式化为,2019年08月12日,2019-08-12,2019/08/12的形式) * pattern参数(想要什么格式的数据就传入什么格式的数据) * · 'yyyy-MM-dd' ---> 输出如2019-09-20 * · 'yyyy-MM-dd HH:mm' ---> 输出如2019-09-20 18:20 * · 'yyyy-MM-dd HH:mm:ss' ---> 输出如2019-09-20 06:20:23 * · 'yyyy/MM/dd' ---> 输出如2019/09/20 * · 'yyyy年MM月dd日' ---> 输出如2019年09月20日 * · 'yyyy年MM月dd日 HH时mm分' ---> 输出如2019年09月20日 18时20分 * · 'yyyy年MM月dd日 HH时mm分ss秒' ---> 输出如2019年09月20日 18时20分23秒 * · 'yyyy年MM月dd日 HH时mm分ss秒 EE' ---> 输出如2019年09月20日 18时20分23秒 周二 * · 'yyyy年MM月dd日 HH时mm分ss秒 EEE' ---> 输出如2019年09月20日 18时20分23秒 星期二 * 参考: https://www.cnblogs.com/mr-wuxiansheng/p/6296646.html */ export function dateFormatter(date = '', fmt = 'yyyy-MM-dd HH:mm:ss') { // 将传入的date转为时间对象 if (!date) return '' // 处理ios不兼容'2022-6-6'类似的'-'问题 if (typeof data === 'string') { date = date.replace(/-/g, '/') } date = new Date(date) const o = { 'y+': date.getFullYear(), 'M+': date.getMonth() + 1, // 月份 'd+': date.getDate(), // 日 // 12小时制 'h+': date.getHours() % 12 === 0 ? 12 : date.getHours() % 12, // 小时 // 24小时制 'H+': date.getHours(), // 小时 'm+': date.getMinutes(), // 分 's+': date.getSeconds(), // 秒 'q+': Math.floor((date.getMonth() + 3) / 3), // 季度 S: date.getMilliseconds(), // 毫秒 'E+': date.getDay() // 周 } const week = ['日', '一', '二', '三', '四', '五', '六'] if (/(y+)/.test(fmt)) { fmt = fmt.replace( RegExp.$1, (date.getFullYear() + '').substr(4 - RegExp.$1.length) ) } if (/(E+)/.test(fmt)) { fmt = fmt.replace( RegExp.$1, (RegExp.$1.length > 1 ? (RegExp.$1.length > 2 ? '星期' : '周') : '') + week[date.getDay()] ) } for (const k in o) { if (new RegExp('(' + k + ')').test(fmt)) { fmt = fmt.replace( RegExp.$1, RegExp.$1.length === 1 ? o[k] : ('00' + o[k]).substr(('' + o[k]).length) ) } } return fmt } // 金额处理 // 分转元 export function fen2Yuan(v) { if (!v) return 0 return v / 100 } // 元转分 export function yuan2Fen(v) { return (v * 10000) / 100 } // 金额大写,链接:https://juejin.im/post/5a2a7a5051882535cd4abfce // upDigit(1682) result:"人民币壹仟陆佰捌拾贰元整" // upDigit(-1693) result:"欠壹仟陆佰玖拾叁元整" export function upPrice(n) { const fraction = ['角', '分', '厘'] const digit = ['零', '壹', '贰', '叁', '肆', '伍', '陆', '柒', '捌', '玖'] const unit = [ ['元', '万', '亿'], ['', '拾', '佰', '仟'] ] // const head = n < 0 ? '欠人民币' : '人民币' const head = '' n = Math.abs(n) let s = '' for (let i = 0; i < fraction.length; i++) { s += ( digit[Math.floor(n * 10 * Math.pow(10, i)) % 10] + fraction[i] ).replace(/零./, '') } s = s || '整' n = Math.floor(n) for (let i = 0; i < unit[0].length && n > 0; i++) { let p = '' for (let j = 0; j < unit[1].length && n > 0; j++) { p = digit[n % 10] + unit[1][j] + p n = Math.floor(n / 10) } s = p.replace(/(零.)*零$/, '').replace(/^$/, '零') + unit[0][i] + s // s = p + unit[0][i] + s; } return ( head + s .replace(/(零.)*零元/, '元') .replace(/(零.)+/g, '零') .replace(/^整$/, '零元整') ) } // 金额3位逗号分隔 ------------> /** * @param s 要格式化的数字(四舍五入) * @param n 保留几位小数(不传或者传-1 --> 如果为整数,则不保留小数。如果为浮点数,则保留两位小数) * @param comma 是否小数点前每3位添加逗号 */ export function newFormat(s = 0, n = -1, comma = false) { n = n === -1 ? 0 : n if (n > 20 || n < -1) { n = 2 } s = Number(s) return s.toLocaleString('zh-CN', { style: 'decimal', useGrouping: comma, minimumFractionDigits: n, maximumFractionDigits: n }) } export function formatPrice(s, n = -1, comma = false) { // 如果不传s或者s为空,则直接返回0 if (!s) return 0 if (n !== -1) n = n > 0 && n <= 20 ? n : 2 const intS = parseInt(String(s)) let point = '.' let left = [] let right = '' s = parseFloat((s + '').replace(/[^\d.-]/g, '')) // 没传n或者n为-1,默认(如果为整数,则不保留小数。如果为浮点数,则保留两位小数) if (n === -1) { if (s === intS) { n = 0 right = '' point = '' } else { n = 2 s = s.toFixed(n) right = s.split('.')[1] } s = s + '' left = s.split('.')[0].split('').reverse() } else { s = parseFloat((s + '').replace(/[^\d.-]/g, '')).toFixed(n) + '' left = s.split('.')[0].split('').reverse() right = s.split('.')[1] } if (comma) { let t = '' for (let i = 0; i < left.length; i++) { t += left[i] + ((i + 1) % 3 === 0 && i + 1 !== left.length ? ',' : '') } return t.split('').reverse().join('') + point + right } return left.reverse().join('') + point + right } /** * 格式化金钱的函数 * @param {number} s 金额必传 * @param {int:0-100} n 保留小数的位数(int:0-100) * @param {Boolean} withoutComma 传true则表示不使用,分割返回值 */ export function utilsFormatMoney(s, n, withoutComma) { // 如果不传s或者s为空,则直接返回0 if (!s) return 0 if (n === undefined || n === null) { n = -1 } else { n = n > 0 && n <= 20 ? n : 2 } var intS = parseInt(s) var point = '.' var left var right s = parseFloat((s + '').replace(/[^\d\.-]/g, '')) // 没传n,默认(如果为整数,则不保留小数。如果为浮点数,则保留两位小数) if (n === -1) { if (s === intS) { n = 0 right = '' point = '' } else { n = 2 s = s.toFixed(n) right = s.split('.')[1] } s = s + '' left = s.split('.')[0].split('').reverse() } else { s = parseFloat((s + '').replace(/[^\d\.-]/g, '')).toFixed(n) + '' left = s.split('.')[0].split('').reverse() right = s.split('.')[1] } // 默认进行,拼接 if (!withoutComma) { t = '' for (i = 0; i < left.length; i++) { t += left[i] + ((i + 1) % 3 == 0 && i + 1 != left.length ? ',' : '') } return t.split('').reverse().join('') + point + right } return left.reverse().join('') + point + right } export const debounce = (func, delay = 200, immediate) => { let timer = null return function () { const context = this const args = arguments if (timer) clearTimeout(timer) if (immediate) { const doNow = !timer timer = setTimeout(function () { timer = null }, delay) if (doNow) { func.apply(context, args) } } else { timer = setTimeout(function () { func.apply(context, args) }, delay) } } } // 时间戳转换 多少秒、多少分、多少小时前、多少天前 超出10天显示年月日 // 传入一个时间戳 export function dateFromNow(originTime, useOld = false) { if (!originTime) return // 原始时间 - 传入的时间戳 const originTimeStamp = +new Date(originTime) // 当前时间戳 const nowTimeStamp = +new Date() // 时间戳相差多少 const diffTimeStamp = nowTimeStamp - originTimeStamp const postfix = diffTimeStamp > 0 ? '前' : '后' // 求绝对值 ms(毫秒) const diffTimeStampAbsMs = Math.abs(diffTimeStamp) const diffTimeStampAbsS = Math.round(diffTimeStampAbsMs / 1000) // 10天的秒数 const days11 = 11 * 24 * 60 * 60 const dataMap = { zh: ['天', '小时', '分钟', '秒'], number: [24 * 60 * 60, 60 * 60, 60, 1] } let timeString = '' // 10天前 const tenDaysAgo = diffTimeStampAbsS > days11 // 是否是当天 const isCurrentDay = dateFormatter(originTimeStamp, 'yyyy.MM.dd') === dateFormatter(nowTimeStamp, 'yyyy.MM.dd') let condition = !isCurrentDay if (useOld) { condition = tenDaysAgo } if (condition) { // 不是当天,则使用正常日期显示 const originDate = new Date(originTimeStamp) const nowDate = new Date() // 是否同年 const sameYear = originDate.getFullYear() === nowDate.getFullYear() // 如果是当年,则不显示年 const patternString = sameYear ? 'MM-dd' : 'yyyy-MM-dd' timeString = dateFormatter(originDate, patternString) } else { for (let i = 0; i < dataMap.number.length; i++) { const inm = Math.floor(diffTimeStampAbsS / dataMap.number[i]) if (inm !== 0) { timeString = inm + dataMap.zh[i] + postfix break } } } return timeString } // 金额类型转换 export function moneyUnit(m, type = 'string', lv = 0) { const mUnit = { levelArr: ['元', '万', '亿', '万亿'], test(num, type, lv) { if (num === 0) { if (type === 'string') { return '0元' } if (type === 'lv') { return this.levelArr[lv] } if (type === 'number') { return 0 } if (type === 'index') { return lv } if (type === 'transfer') { return 0 } } const result = num / Math.pow(10000, lv) if (result > 10000 && lv < 2) { return this.test(num, type, lv + 1) } else { if (type === 'string') { return ( String(Math.floor(result * 100) / 100).replace('.00', '') + this.levelArr[lv] ) } if (type === 'lv') { return this.levelArr[lv] } if (type === 'number') { return String(Math.floor(result * 100) / 100).replace('.00', '') } if (type === 'index') { return lv } } }, // 需要传入固定的lv(此时lv为 levelArr 中的一个) transfer(num, lvString) { const index = this.levelArr.indexOf(lvString) if (index === -1 || index === 0) { return num } else { return (num / Math.pow(10000, index)).toFixed(2) + lvString } } } if (m === undefined || m === null) { return '' } else { if (type === 'transfer') { return mUnit.transfer(m, lv) } else { return mUnit.test(m, type, lv) } } } /** * 通用关键字高亮替换 * @param {String} value 要高亮的字符串 * @param {String|Array} oldChar 要被替换的字符串(或数组) * @param {String|Array} newChar 要替换成的字符串(或数组) * * 比如:要将 - `剑鱼标讯工具函数` 字符串中的 `工具` 高亮 * 则此时 value -> `剑鱼标讯工具函数` * oldChar -> `工具` * newChar -> `工具` * * 批量高亮----- * 比如:要将 - `剑鱼标讯工具函数` 字符串中的 `工具` `剑鱼` 高亮 * 则此时 value -> `剑鱼标讯工具函数`批量高亮 * oldChar -> ['工具', '剑鱼'] * newChar -> ['', ''] * * 注意:此时newChar为一个长度为2的数组,数组中为高亮标签的起始标签和结束标签 * */ export function replaceKeyword( value, oldChar, newChar = ['', ''] ) { if (!oldChar || !newChar) return value // oldChar的字符串数组 let oldCharArr = [] if (Array.isArray(oldChar)) { oldCharArr = oldChar.concat() } else { oldCharArr.push(oldChar) } // 数组去重 oldCharArr = Array.from(new Set(oldCharArr)) for (let i = 0; i < oldCharArr.length; i++) { if (!oldCharArr[i]) { continue } else { oldCharArr[i] = oldCharArr[i] .replace(/([$()*+.[\]?/\\^{}|])/g, '\\$1') .replace(/\s+/g, '') } } // 数组去空 const lastArr = oldCharArr .filter((item) => !!item) .sort((a, b) => b.length - a.length) const regExp = new RegExp(`(${lastArr.join('|')})`, 'gmi') if (lastArr.length === 0) { return value } if (Array.isArray(newChar)) { // 批量高亮 return value.replace(regExp, newChar.join('$1')) } else { // 普通单个高亮 return value.replace(regExp, newChar) } } // 获取随机字符串 // 不传参数则获取长度不固定的字符串 export const getRandomString = (len) => { let randomString = '' if (len) { /** 默认去掉了容易混淆的字符oOLl,9gq,Vv,Uu,I1 **/ const $chars = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678' const maxPos = $chars.length for (let i = 0; i < len; i++) { randomString += $chars.charAt(Math.floor(Math.random() * maxPos)) } } else { // Math.random() 生成随机数字, eg: 0.123456 // .toString(36) 转化成36进制 : "0.4fzyo82mvyr" // .substring(2) 去掉前面两位 : "yo82mvyr" // .slice(-8) 截取最后八位 : "yo82mvyr" randomString = Math.random().toString(36).substring(2) } return randomString } // 随机整数 min <= X <= max export const getRandomNumber = (min, max) => { return Math.round(Math.random() * (max - min)) + min } export const copyText = async function (text) { try { await navigator.clipboard.writeText(text) } catch (error) { const input = document.createElement('input') // js创建一个input输入框 input.value = text // 将需要复制的文本赋值到创建的input输入框中 document.body.appendChild(input) // 将输入框暂时创建到实例里面 input.select() // 选中输入框中的内容 document.execCommand('copy') // 执行复制操作 document.body.removeChild(input) // 最后删除实例中临时创建的input输入框,完成复制操作 } } // FROM: https://www.jianshu.com/p/90ed8b728975 // 比较两个对象是否相等 // 返回true为相等,返回false为不相等 /* eslint-disable */ export const deepCompare = function (x, y) { let i, l, leftChain, rightChain function compare2Objects(x, y) { let p // remember that NaN === NaN returns false // and isNaN(undefined) returns true if ( isNaN(x) && isNaN(y) && typeof x === 'number' && typeof y === 'number' ) { return true } // Compare primitives and functions. // Check if both arguments link to the same object. // Especially useful on the step where we compare prototypes if (x === y) { return true } // Works in case when functions are created in constructor. // Comparing dates is a common scenario. Another built-ins? // We can even handle functions passed across iframes if ( (typeof x === 'function' && typeof y === 'function') || (x instanceof Date && y instanceof Date) || (x instanceof RegExp && y instanceof RegExp) || (x instanceof String && y instanceof String) || (x instanceof Number && y instanceof Number) ) { return x.toString() === y.toString() } // At last checking prototypes as good as we can if (!(x instanceof Object && y instanceof Object)) { return false } // eslint-disable-next-line no-prototype-builtins if (x.isPrototypeOf(y) || y.isPrototypeOf(x)) { return false } if (x.constructor !== y.constructor) { return false } if (x.prototype !== y.prototype) { return false } // Check for infinitive linking loops if (leftChain.indexOf(x) > -1 || rightChain.indexOf(y) > -1) { return false } // Quick checking of one object being a subset of another. // todo: cache the structure of arguments[0] for performance for (p in y) { // eslint-disable-next-line no-prototype-builtins if (y.hasOwnProperty(p) !== x.hasOwnProperty(p)) { return false } else if (typeof y[p] !== typeof x[p]) { return false } } for (p in x) { // eslint-disable-next-line no-prototype-builtins if (y.hasOwnProperty(p) !== x.hasOwnProperty(p)) { return false } else if (typeof y[p] !== typeof x[p]) { return false } switch (typeof x[p]) { case 'object': case 'function': leftChain.push(x) rightChain.push(y) if (!compare2Objects(x[p], y[p])) { return false } leftChain.pop() rightChain.pop() break default: if (x[p] !== y[p]) { return false } break } } return true } if (arguments.length < 1) { return true // Die silently? Don't know how to handle such case, please help... // throw "Need two or more arguments to compare"; } for (i = 1, l = arguments.length; i < l; i++) { leftChain = [] // Todo: this can be cached rightChain = [] if (!compare2Objects(arguments[0], arguments[i])) { return false } } return true } /* eslint-disable */ // 保留几位小数 /* eslint-disable */ Number.prototype.fixed = function (len) { len = isNaN(len) ? 0 : len const num = Math.pow(10, len) return Math.round(this * num) / num } /* eslint-disable */ // 计算时间间隔差函数 [年个数, 月个数] export const getDateSub = function (start, end) { let startTime = new Date(start * 1000) let endTime = new Date(end * 1000) let startYear = startTime.getFullYear() let startMonth = startTime.getMonth() let startDay = startTime.getDate() let endYear = endTime.getFullYear() let endMonth = endTime.getMonth() let endDay = endTime.getDate() let finalMonthNum = 0 let finalYearNum = 0 if (startYear === endYear) { if (startMonth === endMonth) { finalMonthNum = 1 } else { if (endDay > startDay) { finalMonthNum = endMonth - startMonth + 1 } else { finalMonthNum = endMonth - startMonth } } } else { if (startMonth === endMonth) { if (endDay <= startDay) { finalMonthNum = (endYear - startYear) * 12 } else { finalMonthNum = (endYear - startYear) * 12 + 1 } } else if (endMonth > startMonth) { if (endDay <= startDay) { finalMonthNum = (endYear - startYear) * 12 + (endMonth - startMonth) } else { finalMonthNum = (endYear - startYear) * 12 + (endMonth - startMonth) + 1 } } else { if (endDay <= startDay) { finalMonthNum = (endYear - startYear - 1) * 12 + (12 - startMonth + endMonth) } else { finalMonthNum = (endYear - startYear - 1) * 12 + (12 - startMonth + endMonth) + 1 } } finalYearNum = Math.floor(finalMonthNum / 12) if (finalYearNum > 0) { finalMonthNum = finalMonthNum - finalYearNum * 12 } } return [finalYearNum, finalMonthNum] } export function recoveryPageData(key, defaultValues = {}) { return JSON.parse(localStorage.getItem(key) || JSON.stringify(defaultValues)) } export function defaultLocalPageData(key, defaultValues = {}) { return JSON.parse(localStorage.getItem(key) || JSON.stringify(defaultValues)) } export function getPic(link) { if (NotURLPrefixRegExp.test(link)) { return import.meta.env.VITE_APP_IMAGE_BASE + link } return link } // 通过公司全称截取短名称 export function getShortName(comName) { const areaMap = chinaMapJSON || [] let shortName = comName // 1. 循环省份城市进行替换 areaMap.forEach(function (item) { const p = item.name.replace(/[省市]/, '') if (shortName.indexOf(p) !== -1) { shortName = shortName.replace(item.name, '').replace(p, '') } item.city.forEach(function (iitem) { const c = iitem.name.replace(/[省市]/, '') if (shortName.indexOf(c) !== -1) { shortName = shortName.replace(iitem.name, '').replace(c, '') } iitem.area.forEach(function (iiitem) { if (shortName.indexOf(iiitem) !== -1) { shortName = shortName.replace(iiitem, '') } }) }) }) const matchRes = shortName.match(/[\u4e00-\u9fa5]{4}/gm) let name = matchRes ? matchRes[0] : shortName.slice(0, 4) if (name.length < 4) { name = name.slice(0, 4) } return name } /** * 分发函数到$refs子组件 * @param fnName 函数名称 * @param config 配置 * @param config.params 参数或获取参数的函数 * @param config.default 找不到子组件函数时默认函数 */ export function transferMethodsOfRefs(fnName, config) { const defaultConfig = Object.assign( { default: () => {} }, config ) let params = defaultConfig?.params if (typeof params !== 'undefined') { params = typeof defaultConfig?.params === 'function' ? defaultConfig?.params() : defaultConfig?.params } Object.keys(this.$refs).forEach((v) => { if (!this.$refs[v]) { console.warn(`Error: 分发${fnName}事件到子组件错误 没有找到组件实例`) return defaultConfig.default(params) } const tempFn = this.$refs[v][fnName] if (typeof tempFn === 'function') { try { tempFn(params) } catch (e) { console.warn(`Error: 分发${fnName}事件到子组件错误`, e) defaultConfig.default(params) } } else { defaultConfig.default(params) } }) } /** * 获取 URL + Query 拼接后的链接 * @param link * @param query * @returns {string} */ export function getFormatURL(link, query = {}) { const queryStr = qs.stringify(query) || '' const queryArr = [link] if (queryStr) { queryArr.push(queryStr) } return queryArr.join('?') } /** * 返回对象指定的keys * @param obj * @param keys 需要匹配的Key * @param exclude 是否使用排除模式,默认采用 include 匹配 * @returns {{}} */ export function filterObjOfKeys(obj, keys = [], exclude = false) { const result = {} let needKeys = Object.keys(obj) if (exclude) { needKeys = needKeys.filter((v) => !keys.includes(v)) } else if (keys.length) { needKeys = [].concat(keys) } needKeys.forEach((v) => (result[v] = obj[v])) return result } /** * 获取IOS版本号 * @returns {number} * @constructor */ export function IosVersion() { let result = 0 try { result = navigator.userAgent .toLowerCase() .match(/cpu iphone os (.*?) like mac os/)[1] .replace(/_/g, '.') } catch (e) { console.warn(e) } return result } /** * 获取url中的参数 * @returns {String} url */ export function resolveUrlQueryParams(url) { const map = {} if (!url) return map const query = url.split('?')[1] if (!query) return map return qs.parse(query) } /** * ios版本是否小于14 * 即ios13以及ios13以下版本 * @returns {boolean} */ export function iOSVersionLt14() { return androidOrIOS() === 'ios' && String(IosVersion()).split('.')[0] < 14 } /** * 上报荟聚埋点数据 * @param trackInfo * @param trackInfo.id - 事件ID (必须提前在荟聚后台定义ID) * @param trackInfo.date - 事件时间 * @param trackInfo.data - 事件自定义属性 */ export function setTrack(trackInfo) { const { id = 'c_jyclick', data = {} } = trackInfo try { clab_tracker.track( id, Object.assign( {}, { c_platform: env.platform, date: new Date() }, data ) ) } catch (e) { console.warn(e) } } // ios或者h5返回回调 export function iosBackInvoke(callback) { let isPageHide = false window.addEventListener('pageshow', function () { if (isPageHide) { callback && callback() } }) window.addEventListener('pagehide', function () { isPageHide = true }) } // ios或者h5返回刷新 export function iosBackRefresh() { iosBackInvoke(() => { location.reload() }) } // 此函数仅仅在h5下会被执行 export function fixH5BackRefresh() { const ua = navigator.userAgent.toLowerCase() // 判断是不是华为/荣耀浏览器 const huawei = ua.includes('huawei') || ua.includes('honor') if (huawei) { window.addEventListener('visibilitychange', function () { const v = document.visibilityState if (v === 'hidden') { // do something } else if (v === 'visible') { location.reload() } }) } else { iosBackRefresh() } } // vite动态获取图片 export function getAssetsFile(url) { return new URL(`../assets/image/${url}`, import.meta.url).href } // 去关注微信公众号页面 export function toWxGzhProfile () { window.location.href = 'https://mp.weixin.qq.com/mp/profile_ext?action=home&__biz=Mzk0MjIyMzY2Nw==&scene=110#wechat_redirect' } // 数组对象根据某一个值进行冒泡排序 // arr 数组 // value 字符串 export function bSort (arr, value) { const len = arr.length for (let i = 0; i < len - 1; i++) { for (let j = 0; j < len - 1 - i; j++) { // 相邻元素两两对比,元素交换,大的元素交换到后面 if (arr[j][value] > arr[j + 1][value]) { let temp = arr[j] arr[j] = arr[j + 1] arr[j + 1] = temp } } } return arr } // 月份数转换为“年+月”的组合格式 export function formatMonthsToYearsMonths(months, lang = 'zh') { const units = { zh: { year: '年', month: '个月' }, en: { year: ' year', month: ' month', plural: 's' }, }; const { year, month, plural } = units[lang] || units.zh; const years = Math.floor(months / 12); const remainingMonths = months % 12; let result = []; if (years > 0) { const yearUnit = lang === 'en' && years > 1 ? year + plural : year; result.push(`${years}${yearUnit}`); } if (remainingMonths > 0) { const monthUnit = lang === 'en' && remainingMonths > 1 ? month + plural : month; result.push(`${remainingMonths}${monthUnit}`); } return result.join(' ') || `0${month}`; }