常用工具函数
驼峰转小写下划线
javascript
/**
* 小驼峰转小写下划线 userNameInfo
* @param { string } v 需要转换的字符串,如:userNameInfo
* @return user_name_info
*/
export const toUnderline = (v) => {
return v.replace(/[A-Z]/g,(current) => `_${current.toLowerCase()}`)
}
下划线命名转小驼峰
javascript
/**
* 下划线命名转小驼峰 user_name_info
* @param { string } v 需要转换的字符串,如:user_name_info
* @return userNameInfo
*/
export const getCamelCase = (v) => {
return v.replace(/_[a-z]/g,(current) => current.split('_')[1].toUpperCase())
}
生成随机Hex颜色
javascript
/**
* 生成随机Hex颜色
* @return 返回色值,如:#1eb31
*/
export const getColorHexColor = () => `#${Math.floor(Math.random() * 0xfffff).toString(16)}`
console.log(getColorHexColor()) // #1eb31
javascript
/**
* 生成随机Hex颜色
* @return 返回色值,如:#1eb31
*/
getColorHexColor() {
return '#'+Math.random().toString(16).substr(2, 6)
}
判断变量数据类型
javascript
/**
* 判断变量数据类型
* @param { any } obj 需要判断数据类型的变量
* @return 数据类型
*/
const getObjType = (obj) => {
const toString = Object.prototype.toString
const map = {
'[object Boolean]': 'boolean',
'[object Number]': 'number',
'[object String]': 'string',
'[object Function]': 'function',
'[object Array]': 'array',
'[object Date]': 'date',
'[object RegExp]': 'regExp',
'[object Undefined]': 'undefined',
'[object Null]': 'null',
'[object Object]': 'object'
}
// 如果是节点
if (obj instanceof Element) {
return 'element'
}
// 通过toString.call 判断是哪个类型
// 判断的值为类型[object Boolean], 通过对象取值返回
return map[toString.call(obj)]
}
console.log(getObjType(true)) // boolean
深拷贝
javascript
/**
* 深拷贝
* @param { any } v 需要深拷贝的数据
* @return 返回年月日时分秒字符串
*/
export const deepClone = (v) => {
let type = getObjType(v);
let obj = null;
// 如果是对象&数组则先清空再做后续操作
if(type === 'array'){
obj = []
for(let i = 0; i < v.length ; i++){
// 将每一个值都放到deepClone中遍历,如果是非对象&数组则push到obj中
obj.push(deepClone(v[i]))
}
} else if(type === 'object'){
obj = {}
for(let k in v){
obj[k] = deepClone(v[k])
}
} else {
// 非对象/数组,直接返回
return v
}
return obj
}
// 使用
let o = {id:1,id2:2,id3:[{val1:3,val2:3}]}
let c = deepClone(o);
c.id3[0].val1 = 9
// { id: 1,id2: 2, id3: { val1: 9, val2: 3 }] }
// { id: 1,id2: 2, id3: { val1: 3, val2: 3 }] }
// 这个案例也解释了为什么data是一个函数的问题,这样不会造成数据污染
如果上面的深拷贝还不明白,那么下面这个例子你应该会明白
javascript
const deepClone = (v) => {
let obj = {};
if(typeof v == "object"){
obj = {}
} else {
return v
}
for(let k in v){
// 递归,将返回的值添加到obj
// 函数的内的数据是相互隔离的
// obj原本的值不会变,每次调用deepClone都是独立的
obj[k] = deepClone(v[k])
}
return obj
}
let o = {id:1,id2:true,id3:3}
let s = deepClone(o)
s.id = 9
// console.log(s) // {id:9,id2:true,id3:3}
// console.log(o) // {id:1,id2:true,id3:3}
保留指定的小数位数
javascript
/**
* 保留指定位数小数
* @param { number } v 需要转换的数值
* @param { number } s 需要保留的位数,如:2就是保留两位小数
* @return 转换后的结果
*/
export const getDecimal = (v, s) => {
let size = '1'.padEnd((s + 1), 0)
return parseInt(v * parseInt(size)) / size
}
字符串转时间戳
javascript
/**
* 时间字符串转时间戳
* @param { string } str 时间字符串
* @return 返回时间戳
*/
export const getTimes = (str) => {
return new Date(str.substring(0, 19).replace(/-/g, '/')).getTime();
}
时间戳转 年月日时分秒
javascript
/**
* 时间戳转 年月日时分秒
* @param { number } timestamp 时间戳
* @return 返回年月日时分秒字符串
*/
export const getTimestamp = (timestamp) => {
let date = new Date(timestamp)
let Year = String(date.getFullYear())
let Moth = String(date.getMonth() + 1).padStart(2,'0')
let Day = String(date.getDate()).padStart(2,'0')
let Hour = String(date.getHours()).padStart(2,'0')
let Minute = String(date.getMinutes()).padStart(2,'0')
let Seconds = String(date.getSeconds()).padStart(2,'0')
return `${Year}-${Moth}-${Day} ${Hour}:${Minute}:${Seconds}`
}
获取当月第一天、当前天、最后一天
javascript
/**
* 获取当前月份的第一天、当天和最后一天
* @param {string} 例如: '2023-09';参数可传可不传,不传获取当月,传则获取指定月
* @return {array} [当月第一天,当前天,最后一天]
**/
export const getFirstandLastDay = (YearandMonty = null) => {
let date = YearandMonty ? new Date(YearandMonty) : new Date();
let year = String(date.getFullYear());
let month = String(date.getMonth() + 1).padStart(2,'0')
let day = String(date.getDate()).padStart(2,'0')
// new Date第三个参数默认1,也就是当月第一天,如果传0则是当月最后一天
let lastDay = new Date(year,month,0).getDate();
let firstDate = `${year}-${month}-01`
let theDay = `${year}-${month}-${day}`
let lastDate = `${year}-${month}-${lastDay}`
return [firstDate, theDay, lastDate];
}
数组去重
javascript
/**
* 数组去重
* @param { array } v 需要去重的数组
* @return
*/
export const arrSet = (v) => {
return [...new Set(v)]
}
数组对象去重
原理:将数组对象的每个值变成基本类型,去比对基本类型的存储单元位置实现去重效果,去重完成后,将字符串反序列化即可拿到去重后的值。
javascript
/**
* 数组对象去重
* @param { array } v 需要去重的数组
* @return
*/
export const arrObjSet = (v) => {
return [...new Set(v.map(el=> JSON.stringify(el)))].map(el=> JSON.parse(el))
}
数组对象根据指定key去重
javascript
/**
* 数组对象根据指定key去重
* @param { array } v 需要去重的数组
* @param { string } key 根据指定key去重
* @return
*/
export const somethingSet = (v, k) => {
let res = new Set();
// 根据Set来判断,先向set中添加,因为本来就没有,此时表达式中返回的是true
// 一旦set中同样的值已经加过一次,那么has就会返回false,此时表达式返回的也就是false
// filter根据表达式中的状态true判断是否过滤,所以filter过滤出来的永远都是唯一值
return v.filter(item => !res.has(item[k]) && res.add(item[k]))
}
有了上面的思路,我们用正常的手段也可以实现同样的效果
javascript
let obj = [
{id : 1 ,name : 'a'},
{id : 2 ,name : 'b'},
{id : 3 ,name : 'c'},
{id : 4 ,name : 'b'},
{id : 5 ,name : 'c'},
{id : 6 ,name : 'd'},
]
somethingSet(obj,'name') // (4) [{…}, {…}, {…}, {…}]
const somethingSet = (v, k) => {
let res = {};
return v.filter(item => res[item[k]] != item[k] && (res[item[k]] = item[k]))
}
密码强度检测
javascript
/**
* 检测密码强度 false小于6位
* 数字 || 小写英文 || 大写英文 || ._- 分别为一级
* 符合条件累加,最大值4级
* @param { string } v 需要校验的值
* @return 返回密码强度等级
*/
export const pwdStrength = (v) => {
if (v.length < 6) {
return false
}
let i = 0;
if (/[0-9]/.test(v)) {
++i;
}
if (/[a-z]/.test(v)) {
++i;
}
if (/[A-Z]/.test(v)) {
++i;
}
if (/[_|\-|.]/.test(v)) {
++i;
}
return i;
}
排序-根据条件对数组对象排序
javascript
/**
* 排序-根据条件对数组对象排序
* v:数组对象,k:key,type:0升序|1降序
* @param { array } v 需要排序的数组
* @param { string } key 根据指定key排序
* @param { number } type 0升序|1降序
* @return 排序结果
*/
export const toSort = (v, k, type) => {
v.sort(function(a,b){
if(type == 0){
return a[k] - b[k]
} else {
return b[k] - a[k]
}
})
return v;
}
原生写法-根据条件对数组对象排序
javascript
/**
* 排序-根据条件对数组对象排序
* arr:数组对象,k:key,type:0升序|1降序
* @param { array } arr 需要转换的数组
* @param { string } key 根据执行key排序
* @param { number } type 升序还是降序
* @return 排序结果
*/
export const toCycling = (arr, key, type) => {
const run = (j) => {
let [A, B] = [arr[j], arr[j + 1]];
[arr[j + 1], arr[j]] = [A,B];
}
for (let i = 0; i < arr.length - 1; i++) {
for (let j = 0; j < arr.length - 1 - i; j++) {
if (arr[j][key] > arr[j + 1][key] && type == 0) {
run(j)
} else if(arr[j][key] < arr[j + 1][key] && type == 1){
run(j)
}
}
}
return arr;
}
比对对象差异值,返回新对象的差异值
javascript
/**
* 比对新对象和旧对象的差异值,返回新对象的差异值
* @param { object } newObj 新对象
* @param { object } oldObj 旧对象
* @return { object } obj 新对象的差异值
*/
export const Difference = (newObj,oldObj) => {
let diff = {};
for(let k in newObj){
if(newObj[k] != oldObj[k]){
diff[k] = newObj[k]
}
}
return diff;
}
获取省市区树形结构的所有code
javascript
// 示例数据
let area = [{
"code": "420000",
"name": "湖北省",
"icon" : 'icon',
"children": [{
"code": "420100",
"name": "武汉市",
"children": [{
"code": "420101",
"name": "市辖区"
}, {
"code": "420102",
"name": "江岸区"
}, {
"code": "420103",
"name": "江汉区"
}, ]
}]
},
// 其它省......
]
javascript
/**
* 获取树形数据的所有节点code
* @param {array} area 树形数据
* @return {array} 所有节点的code
*/
export const getTreeCode = (area) => {
let allCode = []
shell(area)
function shell(area) {
area.forEach((item) => {
bicycle(item)
})
function bicycle(area) {
allCode.push(area.code)
if (area?.children?.length > 0) {
shell(area.children)
}
}
}
return allCode
}
递归适用于深层数据查找,这种查找是单行的,如果要获取所有节点数据则需要遍历递归,拿到所有分支的数据。
判断code在树形数据中是否存在
javascript
/**
* 判断code在树形数据中是否存在
* @param {array} area 树形数据
* @param {string} code 需要判断的code
* @return {boolean} true\false
*/
export const treeCodeExist = (area, code) => {
let exist = false;
shell(area)
function shell(area) {
area.forEach((item) => {
bicycle(item)
})
function bicycle(area) {
if(code == area.code){
return exist = true
}
if (area?.children?.length > 0) {
shell(area.children)
}
}
}
return exist
}
判断开始时间和结束时间是否大于12个月
javascript
/**
* 判断开始时间和结束时间是否大于12个月
* @param {string} startTime 开始时间,如:'2023-09-14'
* @param {string} endTime 结束时间,如:'2023-12-10'
* @return {boolean} true\false
*/
const dateLimit = (startTime,endTime) => {
let start = startTime.split('-');
let end = endTime.split('-');
let y = end[0] - start[0];
let m = 0;
if(y > 0){
let allMonth = y * 12;
m = allMonth - Number(start[1]) + Number(end[1]);
} else {
m = end[1] - start[1];
}
if(m === 12){
if(end[2] > start[2])
{ return true } else
{ return false }
}
if(m > 12)
{ return true } else
{ return false }
}
删除数组中多个指定元素
a = [1,3,4,2,5] / b = [2,3]
预期结果:[1,4,5]
这里要用到倒叙删除,因为数组的length是动态的,在循环中正序删除会导致下标不准。
javascript
/***
* @param {array} arr 原数组
* @param {array} ids 需要删除的id数组
* @return {array} 删除的数组
*/
const arrMoreDeletion = (arr,ids) => {
for(let i = arr.length -1 ; i >= 0 ; i--){
for(let j = 0; j < ids.length ; j++){
// 如果是数组对象这里就改为arr[i].id
if(arr[i] == ids[j]){
arr.splice(i,1)
}
}
}
return arr;
}
有条件数组交叉合并
将两个乱序的数组按照梳子的组合规律依次插入排列,例如将下面的数据组合成:
[ {花园评分}, {花园描述}, {小区评分}, {小区描述} ] 这种依次排列的数组
javascript
let list = [
{id:1,name:'花园评分',value:''},
{id:2,name:'小区评分',value:''},
{id:3,name:'街道评分',value:''},
{id:4,name:'小区描述',value:''},
{id:6,name:'花园描述',value:''},
{id:7,name:'公园评分',value:''},
{id:8,name:'街道描述',value:''},
{id:9,name:'公园描述',value:''}
]
let arr = list.filter(el => el.name.includes('评分'))
let arr2 = list.filter(el => el.name.includes('描述'))
let info = []; // 最终结果
arr.forEach(el => {
info.push(el);
let text = el.name.replace('评分','');
for(let i = 0; i < arr2.length ; i++){
if(arr2[i].name.includes(text)){
info.push(arr2[i]);
break;
}
}
})
无条件数组交叉合并
以A组为基础插入B组数据,如果A组数据少于B组,那么B组多出来的数据依次向下排列。
javascript
let listA = [
{ name: 'A组01' },
{ name: 'A组02' },
{ name: 'A组03' }
]
let listB = [
{ name: 'B组01' },
{ name: 'B组02' },
{ name: 'B组03' }
]
/***
* @param {array} listA 数组A
* @param {array} listB 数组B
* @return {array} 数组A\B的交叉数组
*/
const crossList = (listA = [] , listB = []) => {
let info = []; // 最终结果
let a = listA.length;
let b = listB.length;
let state = a >= b; // 比较A组是否 >= B组
listA.forEach((el, index) => {
info.push(el); // 添加A组每一项
if (index + 1 <= b) {
info.push(listB[index]); // 在A组遍历中,同时添加B组数据
}
});
// B组大于A组,将B组的数据在数组末尾依次排列
if (!state) {
let diff = b - a;
let arr = []
for (let i = 0; i < diff; i++) {
arr.push(listB.reverse()[i]); // 反转,从尾部向前添加
}
info.push(...arr.reverse()); // 二次反转,改为正常顺序添加
}
return info;
}
crossList(listA , listB);
生成随机字符串
javascript
/***
* @param {number} num 需要生成多少位随机字符
* @return {string} 生成的随机字符
*/
export const randomString = (num) => {
let str = "",
arr = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'];
let index = null;
for (let i = 0; i < num; i++) {
index = Math.round(Math.random() * (arr.length - 1));
str += arr[index];
}
return str;
}
过滤所有节点树中的子节点
所有的子节点和父节点在一维数组中,过滤出所有节点的子项,这里的子项也就是children的最后一层
场景:后端返回所有勾选和半勾选节点,比如返回杭州市并且返回杭州市下的某几个区,我们要拿这样的数据回显树勾选节点,如果直接回显,由于返回了杭州市,杭州市下的所有区会全部勾选,所以需要过滤子节点,通过子节点反向勾选父节点,如果子节点其中某几个节点勾选,父节点半选,如果子节点全部勾选,父节点就会自动勾选。
原理:根据返回的fiLevel层级分组,例如:1、2、3组(省、市、区)然后遍历所有层级,通过fvParentId比对是否有children,最后过滤出没有children的节点。
javascript
let treeAll = [
{
"id":3425,
"fiAreaCode":"330000",
"fiDeptId":null,
"fiLevel":1,
"fvParentId":"0",
"fiAffiliation":0,
"fvAreaName":"浙江省",
"fvEstateType":null
},
{
"id":3426,
"fiAreaCode":"330100",
"fiDeptId":null,
"fiLevel":2,
"fvParentId":"330000",
"fiAffiliation":0,
"fvAreaName":"杭州市",
"fvEstateType":null
},
{
"id":3457,
"fiAreaCode":"310005",
"fiDeptId":null,
"fiLevel":3,
"fvParentId":"330100",
"fiAffiliation":0,
"fvAreaName":"临平区",
"fvEstateType":null
},
{
"id":3458,
"fiAreaCode":"310009",
"fiDeptId":null,
"fiLevel":3,
"fvParentId":"330100",
"fiAffiliation":0,
"fvAreaName":"上城区",
"fvEstateType":null
},
{
"id":3425,
"fiAreaCode":"33001200",
"fiDeptId":null,
"fiLevel":1,
"fvParentId":"0",
"fiAffiliation":0,
"fvAreaName":"我的省",
"fvEstateType":null
},
{
"id":3425,
"fiAreaCode":"3300123400",
"fiDeptId":null,
"fiLevel":1,
"fvParentId":"0",
"fiAffiliation":0,
"fvAreaName":"我的省2",
"fvEstateType":null
},
]
function shiftArea(treeAll) {
let treeObj = {};
// 层级分组
treeAll.forEach(item => {
let level = item.fiLevel;
if(!treeObj[level]){
treeObj[level] = [];
}
treeObj[level].push(item);
})
let all = []; // 保存所有节点
// 遍历所有节点
Object.values(treeObj).forEach(item => {
// 对节点深层遍历
item.forEach(item2 => {
item2.chindren = []; // 子级默认为空数组
// 设置children子级节点
treeAll.forEach(item3 => {
if(item2.fiAreaCode == item3.fvParentId){
item2.chindren.push(item3)
}
})
all.push(item2)
})
})
// 过滤出所有子级节点
let outcome = all.filter(item => item.chindren.length == 0)
return outcome
}
shiftArea(treeAll)
获取数值在数组中的近似值
javascript
/***
* @param {array} arr 数组,如:[23, 30, 35, 47, 16, 21]
* @param {number} num 当前值,如37
* @return {string} 当前值在数组中最接近的值
*/
const closest = (arr, num) => {
var ret = arr[0];
var distance = Math.abs(ret - num);
for(var i = 1; i < arr.length; i++){
var newDistance = Math.abs(arr[i] - num);
if(newDistance < distance){
distance = newDistance;
ret = arr[i];
}
}
return ret;
}
let arr = [23, 30, 35, 47, 16, 21]
let num = 37
console.log(closest(arr,num)); // 35
根据上限值计算涨幅和减幅
javascript
/**
* 根据当前值和上限值计算涨幅和减幅,例如:
* 上限100,当前45,那么45 = 100涨幅122.22222222222223%
* 上限100,当前145,145 = 100,减幅31.03448275862069%
* @param {Object} a 当前值
* @param {Object} max 上限值
* @returns {Object || Boolean} false表示无任何涨幅或不符合规则,Object.type:1涨幅 2减幅
*/
getPercent(a,max) {
// 无涨幅或不符合规则
if(a == 0 || !a) return false
if (a < max) {
// 涨幅
// 即 max 相对于 a 增加了百分之多少
const increase = ((max - a) / a) * 100;
return {
percent: increase,
type: 1, // 涨幅
}
} else if (a > max) {
// 减幅
// 即 a 相对于 max 减少了百分之多少
const decrease = ((a - max) / a) * 100;
return {
percent: decrease,
type: 2, // 减幅
}
} else {
// 没有任何涨幅,与max对等
return false
}
}
根据涨幅和减幅计算实际值
javascript
/**
* 根据涨幅和减幅计算实际值,调用上面的计算涨幅函数
* @param {Number} num 要计算的值
* @param {Object} extent
* @returns {Number} 计算后的值,保留两位小数
*/
getValue(num,extent) {
if(!extent) return num
if(extent.type == 1){
let n = num * (1 + (extent.percent / 100)); // 根据涨幅公式计算最终值
return parseInt(n * 100) / 100
} else {
let n = item.value * (1 - (extent.percent / 100)); // 根据涨幅公式计算最终值
return parseInt(n * 100) / 100
}
}
秒转时分秒
javascript
/**
* 根据涨幅和减幅计算实际值,调用上面的计算涨幅函数
* @param {Number} num 要计算的值
* @param {Object} extent
* @returns {Number} 计算后的值,保留两位小数
*/
const formatTime = (seconds) => {
let hours = Math.floor(seconds / 3600);
let minutes = Math.floor((seconds - (hours * 3600)) / 60);
let remainingSeconds = seconds - (hours * 3600) - (minutes * 60);
// 添加前导零
hours = String(hours).padStart(2, '0');
minutes = String(minutes).padStart(2, '0');
remainingSeconds = String(remainingSeconds).padStart(2, '0');
return `${hours}小时${minutes}分${remainingSeconds}秒`
}
console.log(formatTime(661)); // 输出: 00小时11分01秒
console.log(formatTime(7200)); // 输出: 02小时00分00秒
过滤出前后指定N年符合区间的时间
javascript
/**
* 从当前时间开始,过滤出前后指定N年符合区间的时间
* @param {string} time 当前时间
* @param {monthStar} 指定月份第一天
* @param {monthEnd} 指定月份最后一天
* @param {diff} 前后多少年
* @returns {Boolean} 是否符合:treu / false
*/
const getTimeFrame = (time,monthStar,monthEnd,diff) => {
const getTimestamp = (str) => new Date(str.substring(0, 19).replace(/-/g, '/')).getTime();
let date = new Date();
let year = date.getFullYear();
let star = year - diff;
let end = year + diff
let arr = [];
for(let i = 0 ; i < end - star ; i ++){
arr.push({star:`${star+i}-${monthStar}`,end:`${star+i}-${monthEnd}`})
}
let state = arr.some(el => {
return getTimestamp(time) >= getTimestamp(el.star) && getTimestamp(time) <= getTimestamp(el.end)
})
return state
}
// 从当前时间开始,过滤出前后10年符合区间的时间
getTimeFrame('2024-12-01','12-01','12-31',10); // true