经验分享
JS工具函数整理
本文记录工作中封装的大量公共方法,你可以直接copy某一段方法,少量改造即可直接使用...
2023-07-12 11:00:00
121

Url参数处理

url参数query转json

/**
 * @title query转json
 * @param {string} key - json.key
 * @param {string} url - /aaa?a=1&b=2&c=3 如果不传默认读取location.href
 * @param {string[]} joinSymbol - 连接符[s1,s2,s3]
 * s1:path与query的连接符,默认?
 * s2:多个query的连接符,默认&
 * s3:query的kv连接符,默认=
 * @return {json}
 * @example "a=1&b=2&c=3" ===> QueryToJson() ===> {a:1,b:2,c:3}
 * @example QueryToJson("a") ===> 1
 */
export const QueryToJson = (key:string="",url:string="",joinSymbol=["?","&","="]) => {
  const [s1,s2,s3] = joinSymbol;
  if(!url) url = location.href;
  const queryArr = url?.split(s1)[1]?.split(s2) || [];
  if (!queryArr.length) return key ? '' : {};
  const json = queryArr.reduce((acc, curr, index) => {
    const [key,value] = curr.split(s3);
    acc[key] = value;
    return acc;
  }, {})
  if(!key) return json;
  return json[key] ? json[key] : "";
}

json转query

/**
 * @title json转query
 * @param {object} json
 * @param {Array<string>>} excludeKeys - 排除的字段
 * @param {boolean} includeEmpty - 是否包含空字符串
 * @return query
 * @example JsonToQuery({a:1,b:2,c:''}) ===> a=1&b=2
 * @example JsonToQuery({a:1,b:2,c:''},true) ===> a=1&b=2&c=
 * @example JsonToQuery({a:1,b:2,c:''},true,["b"]) ===> a=1&c=
 */
export const JsonToQuery = (json:object,excludeKeys=[],includeEmpty=false) => {
  const queryArr = [];
  for(const key in json){
    if(!(excludeKeys.includes(key))){
      if(!includeEmpty) {
        if(json[key]){
          queryArr.push(`${key}=${json[key]}`)
        }
      }else{
        queryArr.push(`${key}=${json[key]}`)
      }
    }
  }
  return queryArr.join("&");
}

字符串操作

根据指定下标返回对应文字

/**
 * 根据级别返回文字
 * @param {number} level - 级别,从1开始
 * @param {Array<string>>} textArr - 级别对应文字
 * @return {string} "省"
 * @example LevelToLabel(1,["省","市","区县","街道"]) => 省
 * */
export const LevelToLabel = (level:number,textArr:Array<string>=["省","市","区县","街道"]):string => {
  const label = textArr[level - 1];
  return label ? label : "";
}

数字操作

数字转千分位

/**
 * 数字转千分位
 * @param {number} num - 数值
 * @return {string|number}
 * @example NumberToLocal(260000) => 260,000
 * */
export const NumberToLocal = (num: number) => {
  return num ? num.toLocaleString() : 0;
};

补位函数

/**
 * 补位函数,小于9前面统一补个0
 * @param {string|number} count - 数值
 * @return {string|number} - 小于9补零返回,否则原样返回
 * */
export const RepairZero = (count:string|number) => {
  return Number(count) > 9 ? count : '0' + count;
}

数组操作

数组转对象

/**
 * 数组转对象
 * @param {Array<string>>} arr - 原数组
 * @param {(prev,curr,index)=>[string,string]} keyValCb - 回调函数,接受三个参数,(上一项,当前项,当前下标) => [key字符串,value字符串]
 * @return {JSON}
 * @example ["a=1","b=2","c=3"] ===> {a:1,b:2,c:3}
 * [1,2,3] ===> {tagId0:1,tagId1:2,tagId2:3}
 * */
export const ArrayToJson = (arr:Array<string>,keyValCb:(prev,curr,index)=>[string,string]) => {
  return arr.reduce((acc, curr, index) => {
    const [key,value] = keyValCb(acc,curr,index);
    acc[key] = value;
    return acc;
  }, {});
}

扁平化数组归纳整理为二维数组

/**
 * 将扁平化数组进行归纳整理
 * @param {Array<any>>} data - 原数组
 * @param {string} key - 根据指定key进行分类归纳
 * @return {Array<any[]>} 返回二维数组
 * @example FixFlatArr([{a:1,b:1},{a:1,b:2},{a:2,b:1}],"a") => [[{a:1,b:1},{a:1,b:2}],[{a:2,b:1}]]
 * */
// 方法1
export const FixFlatArr = (data,key) => {
  const result = [];
  data.forEach(item => {
    const value = item[key];
    if(!(data.find(d => d.find(e => e[key] === value)))){
      result.push(data.filter(e => e[key] === value));
    }
  })
  return result;
}

// 方法2
export const FixFlatArr = (data = [], key = "id") => {
  const arrFields = [...new Set(data.map(e => e[key]))];
  return arrFields.map(v => data.filter(e => e[key] == v));
}

从一个字符串数组中过滤出首字母相同且最多的项

const arrs = ['adwafda','Addddw','swdA','adawf','sDWW','Addfawf','Afffff'];
/**
* param arr:Array			原数组
* param isRel:Boolean	是否区分大小写
*/
function filterArrByStartStrMax(arr=[],isRel=true){
  // 先拿到数组中所有首字母,并全部转为小写
  const firstStrArr = arr.map(str => {
    return isRel ? str.charAt(0) : str.charAt(0).toLowerCase();
  });
  // 定义一个json存储每个首字母出现的次数
  const obj = {};
  for(let i = 0;i<firstStrArr.length;i++){
    obj[firstStrArr[i]] = obj[firstStrArr[i]] ? obj[firstStrArr[i]] + 1 : 1;
  };
  
  // 获取出现次数最多的字母的次数
  const maxCount = Math.max(...(Object.values(obj)));
  // 定义变量存储出现次数最多的字母
  const maxS = Object.entries(obj).find(e => e[1] === maxCount)[0];
  
  // 过滤原数组
  return arr.filter(item => {
    if(!isRel) item = item.toLowerCase();
    return item.startsWith(maxS);
  })
}
filterArrByStartStrMax(arrs);// ['Addddw', 'Addfawf', 'Afffff']

数组扁平化

/**
* 返回扁平化数组
* @param data:Array
* @param childField:string
*/
function flatArr(data,childField=""){
  const result = [];
  const deepFn = (arr) => {
    arr.forEach(e => {
      if(e[childField] && e[childField].length > 0 ){
        deepFn(e[childField])
      }else{
        result.push(e)
      }
    })
  }
  deepFn(data);
  return result;
}

合并多个数组为一个

/**
* @param {object} obj
* @returns {Array<object>}
* @example AssignArr({ a:[1,2,3],b:[4,5,6],c:[7,8,9] }) => [{a:1,b:4,c:7},{a:2,b:5,c:8},{a:3,b:6,c:9}]
*/
export const AssignArr = (obj) => {
  const keys = Object.keys(obj);
  const values = Object.values(obj);
  return values[0].map((value,index) => {
    return keys.reduce((acc,key) => {
      acc[key] = obj[key][index];
      return acc;
    },{})
  })
}

数组对象排序

/**
* 对象数组排序,根据已知字段的顺序,对数组进行排序
* @param {any[]} dataSource - 原数组
* @param {string[]} order - 已知排序
* @param {string} field - 已知排序的字段名
* @returns {any[]}
* @example SortArr([{id:"aaa"},{id:"ccc"},{id:"bbb"}],["aaa","bbb","ccc"],"id") => [{id:"aaa"},{id:"bbb"},{id:"ccc"}] 
*/
export const SortArr = (dataSource:any[] = [],order:string[] = [], field:string="id") => {
  return dataSource.sort((a,b) => {
      return order.indexOf(a[field]) - order.indexOf(b[field]);
  })
}

对象数组去重

/**
* 对象数组去重,根据指定字段去重对象数组
* @param {{[key:string]:any}[]} dataSource - 原数组
* @param {string} key- 字段名
* @returns {any[]} 如果指定字段存在多次,去重后只返回第一次出现的结果
* @example ReRepeat([{id:1,name:"张三"},{id:2,name:"李四"},{id:1,name:"王五"}],"id") => [{id:1,name:"张三"},{id:2,name:"李四"}]
*/
export const ReRepeat = (dataSource:{[key:string]:any}[],key:string) => {
  const map = new Map();
  return dataSource.filter(item => !map.has(item[key]) && map.set(item[key],item));
};

生成树结构

/**
 * 生成树结构
 * @param {object} data - 原数组
 * @param {[string,string]} relation - 父子关系字段 
 * @param {boolean=false} moreSon - 允许多个不同父存在相同子
(是否相同子出现在不同父中,如果为true,则parentCode值应为逗号拼接的多个父dictCode)
 * @return {object} tree
 * */
export const MakeTree = (data: Array<any>, relation = ["dictCode","parentCode"],moreSon=false) => {
  const [sonField, parentField] = relation;

  // 删除 所有 children,以防止多次调用
  data.forEach((item) => {
    delete item.children;
  });

  // 将数据存储为 以 dictCode 为 KEY 的 map 索引数据列
  const map = {};
  data.forEach((item) => {
    map[item[sonField]] = item;
  });

  const val = [];

  const setVal = (parent,item) => {
    // 如果找到索引,那么说明此项不在顶级当中,那么需要把此项添加到,他对应的父级中
    if (parent) {
      parent.children || (parent.children = []);
      parent.children.push(item);
    } else {
      //如果没有在map中找到对应的索引dictCode,那么直接把 当前的item添加到 val结果集中,作为顶级
      val.push(item);
    }
  }

  data.forEach((item) => {
    if(moreSon) {
        // 以当前遍历项的parentCode,去map对象中找到索引的dictCode
        for(const parentCode of item[parentField].split(",")){
            const parent = map[parentCode ];
            setVal(parent,item)
        }
    } else {
        // 以当前遍历项的parentCode,去map对象中找到索引的dictCode
        const parent = map[item[parentField]];
        setVal(parent,item)
    }
  });
  return val;
};

获取树节点指定级别数据集合

/**
 * 获取树节点指定级别数据集合
 * @param {any[]} treeData - 树形数据
 * @param {number} level - 指定级别
 * @param {string} childField - 子节点字段名
 * @return {any[]}
 * */
export const GetDatasByTreeLevel = (treeData=[],level=0,childField="children") => {
  const result = [];
  // 递归获取指定级别数据
  // dataSource:需要递归处理的数据
  // currentLevel:从哪个级别开始
  const deepGetMenus = (dataSource,currentLevel) => {
    if(currentLevel === level) {
      result.push(...dataSource);
      return;
    }
    dataSource.forEach(data => {
      deepGetMenus(data[childField] || [],currentLevel + 1);
    })
  };
  deepGetMenus(treeData,0);
  return result;
}

根据起止数值生成数组

/**
 * 生成一组从start到end的数值数组
 * @param {number} start - 开始数值
 * @param {number} end - 结束数值
 * @param {boolean=true} includeEnd - 是否包含结束数值
 * @return {number[]}
 * */
makeArrByNum(start,end,includeEnd=true) {
    const result = [];
    for(let i = start; i < end; i++){
        result.push(i);
    }
    if(includeEnd) result.push(end);
    return result;
}

时间处理

根据指定时间转为*[秒|分钟|小时|天|月]前

/**
 * @title 时间转换
 * @description 传入时间如果如果在1天内,则返回n小时前,如果大于1天小于10天,则返回n天前,如果大于时间,则返回指定格式
 * @param {string} date 时间格式
 * @param {object} options 具体参数 { m:60,h:24,d:10,format:YYYY-MM-DD }
 *    justnow指的是分钟之内返回刚刚    刚刚
 *    minute指的是分钟之内返回分钟     56分钟前
 *    hour指的是小时之内返回小时       5小时前
 *    day天数之内返回天数             3天前
 *    month月份之内返回月份           2月前
 *    format代表大于d返回的格式       YYYY/MM/DD
 * @return {string}
 * */
export interface TransformDateOptions {
    justnow:number;
    minute:number;
    hour:number;
    day:number;
    month:number;
    format:string;
}
export const TransformDate:(date:string,options?:TransformDateOptions) => string = (date,options) => {
    const defaultOptions = {
        justnow: 60,
        minute: 60,
        hour: 24,
        day: 30,
        month: 12,
        format: "YYYY/MM/DD"
    }
    options = Object.assign({},defaultOptions,options);
    const specifiedDate = dayjs(date);
    const diffInSecond = Math.abs(specifiedDate.diff(dayjs(),"second"));
    const diffInMinute = Math.abs(specifiedDate.diff(dayjs(),"minute"));
    const diffInHours = Math.abs(specifiedDate.diff(dayjs(),"hours"));
    const diffInDay = Math.abs(specifiedDate.diff(dayjs(),"day"));
    const diffInMonth = Math.abs(specifiedDate.diff(dayjs(),"month"));

    if(diffInSecond < options.justnow) return `${diffInSecond} 秒前`;
    if(diffInMinute <= options.minute) return `${diffInMinute} 分钟前`;
    if(diffInHours <= options.hour) return `${diffInHours} 小时前`;
    if(diffInDay <= options.day) return `${diffInDay} 天前`;
    if(diffInMonth <= options.month) return `${diffInMonth} 个月前`;
    return specifiedDate.format(options.format);
}

网络操作

文件下载

/**
 * 文件下载
 * @param {string} href - 文件地址
 * @param {string} title - 文件名称
 * */
export const DownloadFile = (href:string,title:string) => {
  const xhr = new window.XMLHttpRequest();
  xhr.open("GET",href,true);
  xhr.responseType = "blob";
  xhr.onload = () => {
    const url = window.URL.createObjectURL(xhr.response);
    const link = document.createElement("a");
    link.href = url;
    link.id = `download-${Date.now()}`;
    link.setAttribute("download", title);
    link.setAttribute("target", "blank");
    document.body.appendChild(link);
    link.click();
    document.getElementById(link.id).remove();
  }
  xhr.send();
}

跨域获取Get请求页面内容

/**
 * 跨域获取Get请求页面内容
 * @param {string} url - 请求地址
 * @param {Function} callback - 请求成功回调
 * */
export const MakeCorsRequest = (url, callback) => {
  let xmlHttp = new XMLHttpRequest();
  // 设置跨域访问请求对象
  if ("withCredentials" in xmlHttp) {
    // "withCredentials"属性是XMLHTTPRequest2中独有的
    xmlHttp.open("GET", url, true);
  } else if (typeof window.XDomainRequest != "undefined") {
    // 检测是否XDomainRequest可用
    xmlHttp = new window.XDomainRequest();
    xmlHttp.open("GET", url);
  } else {
    console.log("抱歉,不支持CORS");
    return;
  }

  // 设置计时器,防止超时
  let timedout = false;
  const timer = setTimeout(function () {
    timedout = true;
    xmlHttp.abort();
  }, 5000);

  xmlHttp.onreadystatechange = function () {
    if (xmlHttp.readyState != 4) return;
    if (timedout) return;
    clearTimeout(timer);
    if (xmlHttp.status === 200) {
      callback(xmlHttp.responseText);
    }
    if (xmlHttp.status === 404) {
      console.log("路径请求错误");
    }
  };

  xmlHttp.onerror = function () {
    console.log("抱歉,请求错误");
  };

  xmlHttp.send();
}

递归生成目录

/**
 * 递归创建目录
 * @params {string} path - /a/b/c/1.png
 * @example MakeDir("/a/b/c/1.png") ==> 会在当前目录生成 ./a/b/c
*/
export const MakeDir = (path:string) => {
  // 将path以/分割转为数组,并且读取非文件地址
  const dirArr = path.split("/").filter(dir => !!dir && !(/.+\..+$/g).test(dir));
  // 递归创建目录方法
  const deepFn = (dirs) => {
    for(let i = 0; i <dirs.length; i ++) {
        try {
            fs.readdirSync(path.join("./",dirs[i]));
        }catch (err) {
            fs.mkdirSync(path.join("./",dirs[i]))
        }
        if(dirs[i + 1]){
            dirs.splice(0,2,`${dirs[i]}/${dirs[i+1]}`);
            deepFn(dirs)
        }
    }
  }
}

枚举常量类

定义枚举

export enum DETECTION {
  // 流量
  LIUL  = 10,
  // 甲烷浓度
  JWND  = 20,
  // 管道压力
  GDYL  = 30,
  // 液位
  YEWE  = 40
}

定义常量

export const DETECTION_TYPE = {
  [DETECTION.LIUL]:{ LABEL:"流量" },
  [DETECTION.JWND]:{ LABEL:"甲烷浓度" },
  [DETECTION.GDYL]:{ LABEL:"管道压力" },
  [DETECTION.YEWE]:{ LABEL:"液位" }
}

定义方法

/**
 * 根据嵌套对象的key和value返回嵌套的对象
 * @param {object} data - js对象
 * @param {string} key - 嵌套对象的key
 * @param {string} value - 嵌套对象的key对应的value
 * @return {array} - [key,data]
 * @example GET_DATA(DETECTION_TYPE,"LABEL","流量") => ["10",{ LABEL:"流量" }]
 * */
export const GET_DATA = (data={},key:string,value:string) => {
  const target = Object.entries(data).find(arr => arr[1][key] === value);
  if(!target || !target.length) return [];
  return target;
}

/**
 * 返回指定嵌套对象的key集合
 * @param {object} data - js对象
 * @param {string} key - 需要的字段集合
 * @param {Array<string>} excludes - 排除掉的字符串
 * @return {Array<string>>}
 * @example GET_DATA_KEYS(DETECTION_TYPE,"LABEL",["流量"]) => ["甲烷浓度","管道压力","液位"]
 * */
export const GET_DATA_KEYS = (data={},key:string,excludes:Array<string>=[]) => {
  return Object.values(data).map(e => e[key]).filter(value => !(excludes.includes(value)));
}

Vite+Vue3

获取assets下的文件

/**
 * 获取图片
 * @param {string} src - assets下级图片地址
 * @return {string} - 构建新的图片地址
 * @example "images/arrow.png" => "http://192.168.6.192:8080/src/assets/images/arrow.png"
 * */
export const GetImage = (src:string) => {
  return new URL(`../assets/${src}`,import.meta.url).href;
}

杂项

持续获取当前时间距离指定时间还剩多久

/**
* @param h:number 距离时,< 24
* @param m:number	距离分,< 60
* @param s:number	距离秒,< 60
*/
function getTimeLong(h,m,s){
    setInterval(() => {
      const D = new Date();
      const now = `${D.getFullYear()}/${(D.getMonth() + 1) > 9 ? (D.getMonth() + 1) : '0' + (D.getMonth() + 1)}/${(D.getDate()) > 9 ? (D.getDate()) : '0' + (D.getDate())} ${(D.getHours()) > 9 ? (D.getHours() ) : '0' + (D.getHours())}:${(D.getMinutes()) > 9 ? (D.getMinutes() ) : '0' + (D.getMinutes())}:${(D.getSeconds()) > 9 ? (D.getSeconds() ) : '0' + (D.getSeconds())}`;
      const finish = `${D.getFullYear()}/${(D.getMonth() + 1) > 9 ? (D.getMonth() + 1) : '0' + (D.getMonth() + 1)}/${(D.getDate()) > 9 ? (D.getDate()) : '0' + (D.getDate())} ${h}:${m}:${s}`;
      const nowTime = new Date(now).getTime();
      const finishTime = new Date(finish).getTime();
      const ggTime = finishTime - nowTime;
        const result = parseInt((ggTime % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60))+'时'+
            parseInt((ggTime % (1000 * 60 * 60)) / (1000 * 60)) + '分' +
            (ggTime % (1000 * 60)) / 1000 + '秒';
      console.log(decodeURIComponent('%E8%B7%9D%E7%A6%BB%E4%B8%8B%E7%8F%AD%E8%BF%98%E6%9C%89%EF%BC%9A')+result)
    },1000)
}

设置页面标题

/**
 * 设置页面标题
 * @param {string} title - 设置页面标题title
 * */
export const SetTitle = (title="webxue") => {
  document.title = title;
}

全屏/取消全屏

export const Fullscreen:{ open:Function,close:Function } = {
  // 打开全屏
  open:(element:HTMLElement = document.body):void => {
    element.requestFullscreen().then();
  },
  // 关闭
  close:():void => {
    document.exitFullscreen().then();
  }
}

// 监听屏幕状态
window.addEventListener("resize",function (){
  // 开发环境由于需要打开f12,全屏状态取决于(document的高度等于屏幕高度) || (document的宽度等于屏幕宽度)
  // 生产环境忽略打开f12,全屏状态取决于(document的高度等于屏幕高度) && (document的宽度等于屏幕宽度)
  // isFullScreen = ref(false) 全局状态
  if(import.meta.env.DEV) {
    isFullScreen.value = document.body.clientHeight === window.screen.height || document.body.clientWidth === window.screen.width;
  }else{
    isFullScreen.value = document.body.clientHeight === window.screen.height && document.body.clientWidth === window.screen.width;
  }
})

函数式选择文件

/**
 * 选择文件
 * @param {string} accept
 * */
export const GetFile = (accept:string="") => {
  const fileSelectDom = document.createElement("input");
  fileSelectDom.type = "file";
  fileSelectDom.accept = accept;
  fileSelectDom.click();
  return new Promise((resolve) => {
    fileSelectDom.onchange = function () {
      resolve(fileSelectDom.files[0]);
      fileSelectDom.onchange = undefined;
    }
  })
}