js笔记

获取当前时间

function nowTime() {
  const now = new Date();
  const year = now.getFullYear();
  const month = now.getMonth();
  const date = now.getDate() >= 10 ? now.getDate() : "0" + now.getDate();
  const hour = now.getHours() >= 10 ? now.getHours() : "0" + now.getHours();
  const miu =
    now.getMinutes() >= 10 ? now.getMinutes() : "0" + now.getMinutes();
  const sec =
    now.getSeconds() >= 10 ? now.getSeconds() : "0" + now.getSeconds();
  return (
    +year +
    "-" +
    (month + 1) +
    "-" +
    date +
    " " +
    hour +
    ":" +
    miu +
    ":" +
    sec
  );
}

获取本月最后一天日期

function endTime() {
  const now = new Date();
  const year = now.getFullYear();
  const month = now.getMonth() + 1;
  const endOfMonth = new Date(year, month, 0).getDate();
  return (
    +year +
    "-" +
    month +
    "-" +
    endOfMonth
  );
}

获取两天的时间差(IE不兼容)

function dayDif(startTime, endTime) {
  startTime = new Date(startTime);
  endTime = new Date(endTime);
  let date3 = endTime.getTime() - startTime.getTime();
  let day = Math.floor(date3 / (24 * 3600 * 1000));
  let leave1 = date3 % (24 * 3600 * 1000);
  let hour = Math.floor(leave1 / (3600 * 1000));
  let leave2 = leave1 % (3600 * 1000);
  let minute = Math.floor(leave2 / (60 * 1000));
  let leave3 = leave2 % (60 * 1000);
  let second = Math.round(leave3 / 1000);
  return {
    day,
    hour,
    minute,
    second,
  };
}

获取两天的时间差(兼容IE)

function cuclDayDif(begintime, endtime) {
      begintime = begintime.replace("-", "/");
      begintime = begintime.replace("-", "/");
      let date1 = new Date(begintime);
      endtime = endtime.replace("-", "/");
      endtime = endtime.replace("-", "/");
      let date2 = new Date(endtime);
      let sec = date2.getTime() - date1.getTime();
      let second = 1000,
        minute = 60 * second,
        hour = 60 * minute,
        day = 24 * hour;

      let days = Math.floor(sec / day);
      sec -= days * day;
      let hours = Math.floor(sec / hour);
      sec -= hours * hour;
      let minutes = Math.floor(sec / minute);
      sec -= minutes * minute;
      let seconds = Math.floor(sec / second);
      return {
        day: days,
        hour: hours,
        minute: minutes,
        second: seconds,
      };
    }

注意:计算2022-08-12 12:23:54 到 2022-08-25的时间差时,25号的时间不能写成24:00:00,要写23:59:59

根据传入天数获取对应日期

function GetDateStr(AddDayCount = 0) {
  let dd = new Date();
  dd.setDate(dd.getDate() + AddDayCount);
  let y = dd.getFullYear();
  let m = dd.getMonth() + 1;
  let d = Number(dd.getDate()) < 10 ? `0${dd.getDate()}` : dd.getDate();
  return y + "-" + m + "-" + d;
}

GetDateStr(-2); //前天
GetDateStr(-1); //昨天
GetDateStr(0);  //今天
GetDateStr(1);  //明天
GetDateStr(2);  //后天
GetDateStr(3);  //大后天

获取星期

function GetWeek() {
  let tempDate = new Date();
  let days = tempDate.getDay();
  let week = "";

  switch (days) {
    case 1:
      week = "星期一";
      break;
    case 2:
      week = "星期二";
      break;
    case 3:
      week = "星期三";
      break;
    case 4:
      week = "星期四";
      break;
    case 5:
      week = "星期五";
      break;
    case 6:
      week = "星期六";
      break;
    case 0:
      week = "星期日";
      break;
  }
  return week;
}

格式化时间

export const dateFormater = (formater, time) => {
    let date = time ? new Date(time) : new Date(),
        Y = date.getFullYear() + '',
        M = date.getMonth() + 1,
        D = date.getDate(),
        H = date.getHours(),
        m = date.getMinutes(),
        s = date.getSeconds();
    return formater.replace(/YYYY|yyyy/g, Y)
        .replace(/YY|yy/g, Y.substr(2, 2))
        .replace(/MM/g,(M<10 ? '0' : '') + M)
        .replace(/DD/g,(D<10 ? '0' : '') + D)
        .replace(/HH|hh/g,(H<10 ? '0' : '') + H)
        .replace(/mm/g,(m<10 ? '0' : '') + m)
        .replace(/ss/g,(s<10 ? '0' : '') + s)
}
// dateFormater('YYYY-MM-DD HH:mm:ss')
// dateFormater('YYYYMMDDHHmmss')

计算两个日期之间的间隔时间

export const dayDif = (date1, date2) => {
    Math.ceil(Math.abs(date1.getTime() - date2.getTime()) / 86400000);
}
dayDif(new Date("2021-11-3"), new Date("2022-2-1"))  // 90

检测给出的日期位于今年的第几天

export const dayOfYear = (date) => {
	Math.floor((date - new Date(date.getFullYear(), 0, 0)) / 1000 / 60 / 60 / 24);
}

dayOfYear(new Date());   // 307

生成指定范围随机数

export const randomNum = (min, max) => Math.floor(Math.random() * (max - min + 1)) + min;

数字千分位分隔

export const number_format(number, decimals, dec_point, thousands_sep) {
  /*
  * 参数说明:
  * number:要格式化的数字
  * decimals:保留几位小数
  * dec_point:小数点符号
  * thousands_sep:千分位符号
  * */
  number = (number + '').replace(/[^0-9+-Ee.]/g, '');
  var n = !isFinite(+number) ? 0 : +number,
    prec = !isFinite(+decimals) ? 0 : Math.abs(decimals),
    sep = (typeof thousands_sep === 'undefined') ? ',' : thousands_sep,
    dec = (typeof dec_point === 'undefined') ? '.' : dec_point,
    s = '',
    toFixedFix = function (n, prec) {
      var k = Math.pow(10, prec);
      return '' + Math.ceil(n * k) / k;
    };
  s = (prec ? toFixedFix(n, prec) : '' + Math.round(n)).split('.');
  var re = /(-?\d+)(\d{3})/;
  while (re.test(s[0])) {
    s[0] = s[0].replace(re, "$1" + sep + "$2");
  }
  if ((s[1] || '').length < prec) {
    s[1] = s[1] || '';
    s[1] += new Array(prec - s[1].length + 1).join('0');
  }
  return s.join(dec);
}

var num=number_format(1234567.089, 2, ".", ",");//1,234,567.09
console.log(num);

数组乱序

export const arrScrambling = (arr) => {
    for (let i = 0; i < arr.length; i++) {
      const randomIndex = Math.round(Math.random() * (arr.length - 1 - i)) + i;
      [arr[i], arr[randomIndex]] = [arr[randomIndex], arr[i]];
    }
    return arr;
}

数组扁平化

export const flatten = (arr) => {
  let result = [];

  for(let i = 0; i < arr.length; i++) {
    if(Array.isArray(arr[i])) {
      result = result.concat(flatten(arr[i]));
    } else {
      result.push(arr[i]);
    }
  }
  return result;
}

数组中获取随机数

export const sample = arr => arr[Math.floor(Math.random() * arr.length)];

生成随机字符串

export const randomString = (len) => {
    let chars = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz123456789';
    let strLen = chars.length;
    let randomStr = '';
    for (let i = 0; i < len; i++) {
        randomStr += chars.charAt(Math.floor(Math.random() * strLen));
    }
    return randomStr;
};

生成随机颜色

export const randomColor= () =>{
	let str = "#";
    let random = 0;
    let aryNum = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F"];
    for(let i = 0; i < 6; i++) {
        random = parseInt(Math.random() * 16);
        str += aryNum[random];
    }
    return str;
}

字符串首字母大写

export const fistLetterUpper = (str) => {
    return str.charAt(0).toUpperCase() + str.slice(1);
};

手机号中间四位变成*

export const telFormat = (tel) => {
  	tel = String(tel); 
    return tel.substr(0,3) + "****" + tel.substr(7);
};

驼峰命名转换成短横线命名

export const getKebabCase = (str) => {
    return str.replace(/[A-Z]/g, (item) => '-' + item.toLowerCase())
}

短横线命名转换成驼峰命名

export const getCamelCase = (str) => {
    return str.replace( /-([a-z])/g, (i, item) => item.toUpperCase())
}

全角转换为半角

export const toCDB = (str) => {
  let result = "";
  for (let i = 0; i < str.length; i++) {
    code = str.charCodeAt(i);
    if (code >= 65281 && code <= 65374) {
      result += String.fromCharCode(str.charCodeAt(i) - 65248);
    } else if (code == 12288) {
      result += String.fromCharCode(str.charCodeAt(i) - 12288 + 32);
    } else {
      result += str.charAt(i);
    }
  }
  return result;
}

半角转换为全角

export const toDBC = (str) => {
  let result = "";
  for (let i = 0; i < str.length; i++) {
    code = str.charCodeAt(i);
    if (code >= 33 && code <= 126) {
      result += String.fromCharCode(str.charCodeAt(i) + 65248);
    } else if (code == 32) {
      result += String.fromCharCode(str.charCodeAt(i) + 12288 - 32);
    } else {
      result += str.charAt(i);
    }
  }
  return result;
}

判断字符串是否为空

export const isEmptyString = (string) =>{
	if (string == undefined ||
		typeof string == "undefined" ||
		!string ||
		string == null ||
		string == '' ||
		/^\s+$/gi.test(string)) {
		return true;
	} else {
		return false;
	}
}

数字转化为大写金额

export const digitUppercase = (n) => {
    const fraction = ['角', '分'];
    const digit = [
        '零', '壹', '贰', '叁', '肆',
        '伍', '陆', '柒', '捌', '玖'
    ];
    const unit = [
        ['元', '万', '亿'],
        ['', '拾', '佰', '仟']
    ];
    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;
    }
    return s.replace(/(零.)*零元/, '元')
        .replace(/(零.)+/g, '零')
        .replace(/^整$/, '零元整');
};

数字转化为中文数字

export const intToChinese = (value) => {
 const str = String(value);
 const len = str.length-1;
 const idxs = ['','十','百','千','万','十','百','千','亿','十','百','千','万','十','百','千','亿'];
 const num = ['零','一','二','三','四','五','六','七','八','九'];
 return str.replace(/([1-9]|0+)/g, ( $, $1, idx, full) => {
    let pos = 0;
    if($1[0] !== '0'){
      pos = len-idx;
      if(idx == 0 && $1[0] == 1 && idxs[len-idx] == '十'){
      	 return idxs[len-idx];
      }
     	return num[$1[0]] + idxs[len-idx];
    } else {
     	let left = len - idx;
     	let right = len - idx + $1.length;
     	if(Math.floor(right / 4) - Math.floor(left / 4) > 0){
      		pos = left - left % 4;
     	}
     	if( pos ){
      		return idxs[pos] + num[$1[0]];
     	} else if( idx + $1.length >= len ){
      		return '';
     	}else {
      		return num[$1[0]]
     	}
    }
   });
}

存储loalStorage

export const loalStorageSet = (key, value) => {
    if (!key) return;
    if (typeof value !== 'string') {
        value = JSON.stringify(value);
    }
    window.localStorage.setItem(key, value);
};

获取localStorage

export const loalStorageGet = (key) => {
    if (!key) return;
    return window.localStorage.getItem(key);
};

删除localStorage

export const loalStorageRemove = (key) => {
    if (!key) return;
    window.localStorage.removeItem(key);
};

存储sessionStorage

export const sessionStorageSet = (key, value) => {
  	if (!key) return;
    if (typeof value !== 'string') {
    		value = JSON.stringify(value);
    }
    window.sessionStorage.setItem(key, value)
};

获取sessionStorage

export const sessionStorageGet = (key) => {
  	if (!key) return;
    return window.sessionStorage.getItem(key)
};

删除sessionStorage

export const sessionStorageRemove = (key) => {
  	if (!key) return;
    window.sessionStorage.removeItem(key)
};

设置cookie

export const setCookie = (key, value, expire) => {
    const d = new Date();
    d.setDate(d.getDate() + expire);
    document.cookie = `${key}=${value};expires=${d.toUTCString()}`
};

读取cookie

export const getCookie = (key) => {
    const cookieStr = unescape(document.cookie);
       const arr = cookieStr.split('; ');
       let cookieValue = '';
       for (let i = 0; i < arr.length; i++) {
           const temp = arr[i].split('=');
           if (temp[0] === key) {
               cookieValue = temp[1];
               break
       }
    }
    return cookieValue
};

删除cookie

export const delCookie = (key) => {
    document.cookie = `${encodeURIComponent(key)}=;expires=${new Date()}`
};

校验身份证号码

export const checkCardNo = (value) => {
    let reg = /(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)/;
    return reg.test(value);
};

校验是否包含中文

export const haveCNChars => (value) => {
    return /[\u4e00-\u9fa5]/.test(value);
}

校验是否为中国大陆的邮政编码

export const isPostCode = (value) => {
    return /^[1-9][0-9]{5}$/.test(value.toString());
}

校验是否为IPv6地址

export const isIPv6 = (str) => {
    return Boolean(str.match(/:/g)?str.match(/:/g).length<=7:false && /::/.test(str)?/^([\da-f]{1,4}(:|::)){1,6}[\da-f]{1,4}$/i.test(str):/^([\da-f]{1,4}:){7}[\da-f]{1,4}$/i.test(str));
}

校验是否为邮箱地址

export const isEmail = (value) {
    return /^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+$/.test(value);
}

校验是否为中国大陆手机号

export const isTel = (value) => {
    return /^1[3,4,5,6,7,8,9][0-9]{9}$/.test(value.toString());
}

校验是否包含emoji表情

export const isEmojiCharacter = (value) => {
  	value = String(value);
    for (let i = 0; i < value.length; i++) {
        const hs = value.charCodeAt(i);
        if (0xd800 <= hs && hs <= 0xdbff) {
            if (value.length > 1) {
                const ls = value.charCodeAt(i + 1);
                const uc = ((hs - 0xd800) * 0x400) + (ls - 0xdc00) + 0x10000;
                if (0x1d000 <= uc && uc <= 0x1f77f) {
                    return true;
                }
            }
        } else if (value.length > 1) {
            const ls = value.charCodeAt(i + 1);
            if (ls == 0x20e3) {
                return true;
            }
        } else {
            if (0x2100 <= hs && hs <= 0x27ff) {
                return true;
            } else if (0x2B05 <= hs && hs <= 0x2b07) {
                return true;
            } else if (0x2934 <= hs && hs <= 0x2935) {
                return true;
            } else if (0x3297 <= hs && hs <= 0x3299) {
                return true;
            } else if (hs == 0xa9 || hs == 0xae || hs == 0x303d || hs == 0x3030
                    || hs == 0x2b55 || hs == 0x2b1c || hs == 0x2b1b
                    || hs == 0x2b50) {
                return true;
            }
        }
    }
  	return false;
}

判断今天是否为本月最后一天

export const isLastDayOfMonth = () =>{
	var flag = false;
	var date = new Date();

	var year = date.getFullYear();
	var month = date.getMonth() + 1;
	var today = 31;

	var new_year = year; //取当前的年份
	var new_month = month++; //取下一个月的第一天,方便计算(最后一天不固定)
	if (month > 12) { //如果当前大于12月,则年份转到下一年
		new_month -= 12; //月份减
		new_year++; //年份增
	}
	var new_date = new Date(new_year, new_month, 1); //取当年当月中的第一天

	var month_last_day = (new Date(new_date.getTime() - 1000 * 60 * 60 * 24)).getDate();

	if (today == month_last_day) {
		flag = true;
	}
	return flag;
}

车牌号的校验

export const isCarNo(carNo)=>{
	const carNoReg = /^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领A-Z]{1}[A-Z]{1}[A-Z0-9]{4}[A-Z0-9挂学警港澳]{1}$/;
	return carNoReg.test(carNo);
}

整数的校验

export const isInt (number)=>{
	const intReg = /^[-+]?\d*$/;
	return intReg.test(number);
}

小数的校验

export const isFloat (number)=>{
	const floatReg = /^[-\+]?\d+(\.\d+)?$/;
	return floatReg.test(number);
}

文件拓展名的校验

export const checkFileName (arr,filename)=>{
	arr = arr.map(name => `.${name}`).join('|');
	const filenameReg = new RegExp(`(${arr})$`);
	return filenameReg.test(filename);
}

获取URL参数列表

export const GetRequest = () => {
    let url = location.search;
    const paramsStr = /.+\?(.+)$/.exec(url)[1]; // 将 ? 后面的字符串取出来
    const paramsArr = paramsStr.split('&'); // 将字符串以 & 分割后存到数组中
    let paramsObj = {};
    // 将 params 存到对象中
    paramsArr.forEach(param => {
      if (/=/.test(param)) { // 处理有 value 的参数
        let [key, val] = param.split('='); // 分割 key 和 value
        val = decodeURIComponent(val); // 解码
        val = /^\d+$/.test(val) ? parseFloat(val) : val; // 判断是否转为数字
        if (paramsObj.hasOwnProperty(key)) { // 如果对象有 key,则添加一个值
          paramsObj[key] = [].concat(paramsObj[key], val);
        } else { // 如果对象没有这个 key,创建 key 并设置值
          paramsObj[key] = val;
        }
      } else { // 处理没有 value 的参数
        paramsObj[param] = true;
      }
    })
    return paramsObj;
};

检测URL是否有效

export const getUrlState = (URL) => {
  let xmlhttp = new ActiveXObject("microsoft.xmlhttp");
  xmlhttp.Open("GET", URL, false);
  try {
    xmlhttp.Send();
  } catch (e) {
  } finally {
    let result = xmlhttp.responseText;
    if (result) {
      if (xmlhttp.Status == 200) {
        return true;
      } else {
        return false;
      }
    } else {
      return false;
    }
  }
}

键值对拼接成URL参数

export const params2Url = (obj) => {
     let params = []
     for (let key in obj) {
       params.push(`${key}=${obj[key]}`);
     }
     return encodeURIComponent(params.join('&'))
}

修改URL中的参数

export const replaceParamVal => (paramName, replaceWith) {
   const oUrl = location.href.toString();
   const re = eval('/('+ paramName+'=)([^&]*)/gi');
   location.href = oUrl.replace(re,paramName+'='+replaceWith);
   return location.href;
}

删除URL中指定参数

export const funcUrlDel = (name) => {
  const baseUrl = location.origin + location.pathname + "?";
  const query = location.search.substr(1);
  if (query.indexOf(name) > -1) {
    const obj = {};
    const arr = query.split("&");
    for (let i = 0; i < arr.length; i++) {
      arr[i] = arr[i].split("=");
      obj[arr[i][0]] = arr[i][1];
    }
    delete obj[name];
    return baseUrl + JSON.stringify(obj).replace(/[\"\{\}]/g,"").replace(/\:/g,"=").replace(/\,/g,"&");
  }
}

判断是移动还是PC设备

export const isMobile = () => {
  if ((navigator.userAgent.match(/(iPhone|iPod|Android|ios|iOS|iPad|Backerry|WebOS|Symbian|Windows Phone|Phone)/i))) {
		return 'mobile';
  }
  return 'desktop';
}

判断是否是苹果还是安卓移动设备

export const isAppleMobileDevice = () => {
  let reg = /iphone|ipod|ipad|Macintosh/i;
  return reg.test(navigator.userAgent.toLowerCase());
}

判断是否是安卓移动设备

export const isAndroidMobileDevice = () => {
  return /android/i.test(navigator.userAgent.toLowerCase());
}

判断是Windows还是Mac系统

export const osType = () => {
    const agent = navigator.userAgent.toLowerCase();
    const isMac = /macintosh|mac os x/i.test(navigator.userAgent);
  	const isWindows = agent.indexOf("win64") >= 0 || agent.indexOf("wow64") >= 0 || agent.indexOf("win32") >= 0 || agent.indexOf("wow32") >= 0;
    if (isWindows) {
        return "windows";
    }
    if(isMac){
        return "mac";
    }
}

判断是否是微信/QQ内置浏览器

export const broswer = () => {
    const ua = navigator.userAgent.toLowerCase();
    if (ua.match(/MicroMessenger/i) == "micromessenger") {
        return "weixin";
    } else if (ua.match(/QQ/i) == "qq") {
        return "QQ";
    }
    return false;
}

浏览器型号和版本

export const getExplorerInfo = () => {
    let t = navigator.userAgent.toLowerCase();
    return 0 <= t.indexOf("msie") ? { //ie < 11
        type: "IE",
        version: Number(t.match(/msie ([\d]+)/)[1])
    } : !!t.match(/trident\/.+?rv:(([\d.]+))/) ? { // ie 11
        type: "IE",
        version: 11
    } : 0 <= t.indexOf("edge") ? {
        type: "Edge",
        version: Number(t.match(/edge\/([\d]+)/)[1])
    } : 0 <= t.indexOf("firefox") ? {
        type: "Firefox",
        version: Number(t.match(/firefox\/([\d]+)/)[1])
    } : 0 <= t.indexOf("chrome") ? {
        type: "Chrome",
        version: Number(t.match(/chrome\/([\d]+)/)[1])
    } : 0 <= t.indexOf("opera") ? {
        type: "Opera",
        version: Number(t.match(/opera.([\d]+)/)[1])
    } : 0 <= t.indexOf("Safari") ? {
        type: "Safari",
        version: Number(t.match(/version\/([\d]+)/)[1])
    } : {
        type: t,
        version: -1
    }
}

滚动到页面顶部

export const scrollToTop = () => {
  const height = document.documentElement.scrollTop || document.body.scrollTop;
  if (height > 0) {
    window.requestAnimationFrame(scrollToTop);
    window.scrollTo(0, height - height / 8);
  }
}

滚动到页面底部

export const scrollToBottom = () => {
  window.scrollTo(0, document.documentElement.clientHeight);  
}

滚动到指定元素区域

export const smoothScroll = (element) => {
    document.querySelector(element).scrollIntoView({
        behavior: 'smooth'
    });
};

获取可视窗口高度

export const getClientHeight = () => {
    let clientHeight = 0;
    if (document.body.clientHeight && document.documentElement.clientHeight) {
        clientHeight = (document.body.clientHeight < document.documentElement.clientHeight) ? document.body.clientHeight : document.documentElement.clientHeight;
    }
    else {
        clientHeight = (document.body.clientHeight > document.documentElement.clientHeight) ? document.body.clientHeight : document.documentElement.clientHeight;
    }
    return clientHeight;
}

获取可视窗口宽度

export const getPageViewWidth = () => {
    return (document.compatMode == "BackCompat" ? document.body : document.documentElement).clientWidth;
}

打开浏览器全屏

export const toFullScreen = () => {
    let element = document.body;
    if (element.requestFullscreen) {
      element.requestFullscreen()
    } else if (element.mozRequestFullScreen) {
      element.mozRequestFullScreen()
    } else if (element.msRequestFullscreen) {
      element.msRequestFullscreen()
    } else if (element.webkitRequestFullscreen) {
      element.webkitRequestFullScreen()
    }
}

退出浏览器全屏

export const exitFullscreen = () => {
    if (document.exitFullscreen) {
      document.exitFullscreen()
    } else if (document.msExitFullscreen) {
      document.msExitFullscreen()
    } else if (document.mozCancelFullScreen) {
      document.mozCancelFullScreen()
    } else if (document.webkitExitFullscreen) {
      document.webkitExitFullscreen()
    }
}

阻止冒泡事件

export const stopPropagation = (e) => { 
    e = e || window.event; 
    if(e.stopPropagation) {    // W3C阻止冒泡方法 
        e.stopPropagation(); 
    } else { 
        e.cancelBubble = true; // IE阻止冒泡方法 
    } 
} 

防抖函数

export const debounce = (fn, wait) => {
  let timer = null;

  return function() {
    let context = this,
        args = arguments;

    if (timer) {
      clearTimeout(timer);
      timer = null;
    }

    timer = setTimeout(() => {
      fn.apply(context, args);
    }, wait);
  };
}

节流函数

export const throttle = (fn, delay) => {
  let curTime = Date.now();

  return function() {
    let context = this,
        args = arguments,
        nowTime = Date.now();

    if (nowTime - curTime >= delay) {
      curTime = Date.now();
      return fn.apply(context, args);
    }
  };
}

数据类型判断

export const getType = (value) => {
  if (value === null) {
    return value + "";
  }
  // 判断数据是引用类型的情况
  if (typeof value === "object") {
    let valueClass = Object.prototype.toString.call(value),
      type = valueClass.split(" ")[1].split("");
    type.pop();
    return type.join("").toLowerCase();
  } else {
    // 判断数据是基本数据类型的情况和函数的情况
    return typeof value;
  }
}

对象深拷贝

export const deepClone = (obj, hash = new WeakMap() => {
  // 日期对象直接返回一个新的日期对象
  if (obj instanceof Date){
  	return new Date(obj);
  } 
  //正则对象直接返回一个新的正则对象     
  if (obj instanceof RegExp){
  	return new RegExp(obj);     
  }
  //如果循环引用,就用 weakMap 来解决
  if (hash.has(obj)){
  	return hash.get(obj);
  }
  // 获取对象所有自身属性的描述
  let allDesc = Object.getOwnPropertyDescriptors(obj);
  // 遍历传入参数所有键的特性
  let cloneObj = Object.create(Object.getPrototypeOf(obj), allDesc)
  
  hash.set(obj, cloneObj)
  for (let key of Reflect.ownKeys(obj)) { 
    if(typeof obj[key] === 'object' && obj[key] !== null){
    	cloneObj[key] = deepClone(obj[key], hash);
    } else {
    	cloneObj[key] = obj[key];
    }
  }
  return cloneObj
}

验证密码

  • 仅包含数字,字母,下划线中的两种及以上的六位数组合
let  reg = /^(?!^[\d]+$)(?!^[_]+$)(?!^[a-zA-Z]+$)[\w_]{6,30}$/;
if(!reg.test(this.form.newPassword)){
	console.log("密码不符合规则")
	return;
}
  • 字符串只能是数字,字母,下划线中的两种,只能是三种类型中的两种,6位以上
let  reg =  /^(?!(\d+|[a-z]+|_+|(?!([\da-z]+|[\d_]+|[a-z_]+)$)[\da-z_]+)$)([\da-z]|[\d_]|[a-z_]){6,}$/i;
if(!reg.test(this.form.newPassword)){
	console.log("密码不符合规则")
	return;
}
  • 至少包含数字、字母、下划线三种里的两种
let  reg =  /(?:\d.*_)|(?:_.*\d)|(?:[A-Za-z].*_)|(?:_.*[A-Za-z])|(?:[A-Za-z].*\d)|(?:\d.*[A-Za-z])/;
if(!reg.test(this.form.newPassword)){
	console.log("密码不符合规则")
	return;
}

验证手机号

let  reg =  /^[1][0-9]{10}$/;
if(!reg.test(this.form.newPassword)){
	console.log("手机号不符合规则")
	return;
}

验证身份证

isCardID: function(sId) {
	var iSum = 0;
	var info = "";
	if (!/^\d{17}(\d|X)$/i.test(sId)) return "你输入的身份证长度或格式错误";
	sId = sId.replace(/X$/i, "a");
	let sBirthday = sId.substr(6, 4) + "-" + Number(sId.substr(10, 2)) + "-" + Number(sId.substr(12, 2));
	var d = new Date(sBirthday.replace(/-/g, "/"));
	if (sBirthday != (d.getFullYear() + "-" + (d.getMonth() + 1) + "-" + d.getDate())) return "您输入的身份证号非法";
	for (var i = 17; i >= 0; i--) iSum += (Math.pow(2, i) % 11) * parseInt(sId.charAt(17 - i), 11);
	if (iSum % 11 != 1) return "您输入的身份证号非法";
	return true;
}

封装主体 Axios

// src/api/axios.js
import axios from "axios";
import Qs from 'qs'

export const Axios = (url,method='get',params={},headers={})=>{
    // 根据 定义的环境状态,切换不同的 baseURL 开发环境使用代理, 生产环境可以直接使用域名全拼
    const BaseUrl = process.env.NODE_ENV==='development'? '' : process.env.BASEURL;
    let defaultHeaders = {
        'Content-Type': 'application/json;charset=UTF-8',
        // 'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8', // 指定提交方式为表单提交 或上传
        // 'Content-Type' :'multipart/form-data;charset=UTF-8',
        'Accept': 'application/json', // 通过头指定,获取的数据类型是JSON 'application/json, text/plain, */*',
        // 'Access-Control-Allow-Origin': 'true',
        // 'Access-Control-Allow-Credentials': 'true',
    }

    if(headers){
        for (let i in headers) {
            defaultHeaders[i] = headers[i];
        }
    }

    const showResState = (state) => {
        let message = ''
        // 这里只做部分常见的示例,具体根据需要进行配置
        switch (state) {
            case 400:
                message = '请求错误(400)'
                break
            case 401:
                message = '未授权,请重新登录(401)'
                break
            case 403:
                message = '拒绝访问(403)'
                break
            case 404:
                message = '请求出错(404)'
                break
            case 500:
                message = '服务器错误(500)'
                break
            case 501:
                message = '服务未实现(501)'
                break
            case 502:
                message = '网络错误(502)'
                break
            case 503:
                message = '服务不可用(503)'
                break
            default:
                message = `连接出错(${state})!`
        }
        return `${message},请检查网络或联系网站管理员!`
    }

    // 添加请求拦截器
    axios.interceptors.request.use( (config) =>  {
        // 在发送请求之前做些什么
        console.log(config)

        // header 配置 Token 判断Token是否过期 没过期则正常处理 过期则发起刷新Token的请求 拿到新的Token保存
        config.headers.Authorization = null;
        // const token = !localStorage.getItem('__auth_provider_token__')?localStorage.setItem('__auth_provider_token__',''):localStorage.getItem('__auth_provider_token__');
        // let navigate = useNavigate();
        // if(sessionStorage.getItem("__auth_provider_isLogin__") !== '1' && isAuth ){//&& !token
        //     alert('token失效');
        //     navigate('/login');
        //     return new Promise((resolve, reject) => {});
        // }
        return config;
    }, function (error) {
        // 对请求错误做些什么
        return Promise.reject(error);
    });

    // 添加响应拦截器
    axios.interceptors.response.use((res) => {
        // 对响应数据做点什么
        const status = res.status;
        let msg = ''
        if (status < 200 || status >= 300) {
            // 处理http错误,抛到业务代码
            msg = showResState(status)
            
            if (typeof res.data === 'string') {
               res.data = { msg }
            } else {
               res.data.msg = msg
            }
        }
        return res;
    }, function (error) {
        // 对响应错误做点什么
        return Promise.reject(error);
    });

    // 1. 执行异步ajax请求
    const instance = axios({
        // `baseURL` 将自动加在 `url` 前面,除非 `url` 是一个绝对 URL。
        // 它可以通过设置一个 `baseURL` 便于为 axios 实例的方法传递相对 URL
        baseURL: BaseUrl,

        // `url` 是用于请求的服务器
        url: url,

        // `method` 是创建请求时使用的方法
        method: method || 'get',

        // mode: 'cors',
        // cache: 'no-cache', // *default, no-cache, reload, force-cache, only-if-cached

        // `headers` 是即将被发送的自定义请求头
        headers: {...defaultHeaders},

        // `transformRequest` 允许在向服务器发送前,修改请求数据
        // 只能用在 'PUT', 'POST' 和 'PATCH' 这几个请求方法
        // 后面数组中的函数必须返回一个字符串,或 ArrayBuffer,或 Stream
        transformRequest: [function (data, headers) {
            // 对 data 进行任意转换处理
            return data;
        }],

        // `transformResponse` 在传递给 then/catch 前,允许修改响应数据
        transformResponse: [function (data) {
            // 对 data 进行任意转换处理
            return data;
        }],

        // `params` 是即将与请求一起发送的 URL 参数
        // 必须是一个无格式对象(plain object)或 URLSearchParams 对象
        params: method === 'get' ? params || {} : {},

        // `paramsSerializer` 是一个负责 `params` 序列化的函数
        paramsSerializer: function(params) {
            return Qs.stringify(params, {arrayFormat: 'brackets'})
        },

        // `data` 是作为请求主体被发送的数据
        // 只适用于这些请求方法 'PUT', 'POST', 和 'PATCH'
        // 在没有设置 `transformRequest` 时,必须是以下类型之一:
        // - string, plain object, ArrayBuffer, ArrayBufferView, URLSearchParams
        // - 浏览器专属:FormData, File, Blob
        // - Node 专属: Stream
        data: method === 'post' ? params || {} : {},

        // `timeout` 指定请求超时的毫秒数(0 表示无超时时间)
        // 如果请求话费了超过 `timeout` 的时间,请求将被中断
        timeout: 0,

        // `withCredentials` 表示跨域请求时是否需要使用凭证
        withCredentials: false, // default 为true则产生跨域,跨域携带cookie

        // `responseType` 表示服务器响应的数据类型,可以是 'arraybuffer', 'blob', 'document', 'json', 'text', 'stream'
        responseType: 'json', // default
    });

    return new Promise((resolve, reject) => {
        instance.then(response => {
            // 2. 如果成功了, 调用resolve(value)
            resolve(response);
        })
            .catch(error => {
                // 3. 如果失败了, 不调用reject(reason), 而是提示异常信息
                reject(error)
            }).finally(() => {
        })
    });
}

// GET 请求 get 下 params 为查询参数
export const Get = (url,params={},headers={}) => {
    return Axios(url,'get',params,headers)
}

// POST 请求 post 下 params 为body参数, 如果 post 下既需要传查询参数也需要传实体参数,则查询参数配置在 url 中
export const Post = (url,params={},headers={}) => {
    return Axios(url,'post',params,headers)
}

拦截器

在请求或响应被 then 或 catch 处理前拦截它们。

 //添加请求拦截器
axios.interceptors.request.use( (config) =>  {
    // 在发送请求之前做些什么
    console.log(config)

    // header 配置 Token 判断Token是否过期 没过期则正常处理 过期则发起刷新Token的请求 拿到新的Token保存
    config.headers.Authorization = null;
    // const token = !localStorage.getItem('__auth_provider_token__')
    //?localStorage.setItem('__auth_provider_token__','')
    //:localStorage.getItem('__auth_provider_token__');
    
    // let navigate = useNavigate();
    // if(sessionStorage.getItem("__auth_provider_isLogin__") !== '1' && isAuth && !token){
    //     alert('token失效');
    //     navigate('/login');
    //     return new Promise((resolve, reject) => {});
    // }
    return config;
}, function (error) {
    // 对请求错误做些什么
    return Promise.reject(error);
});

// 添加响应拦截器
axios.interceptors.response.use((res) => {
    // 对响应数据做点什么
    return res;
}, function (error) {
    // 对响应错误做点什么
    return Promise.reject(error);
});

状态处理

在响应拦截中,如果有需要还可以对状态码提示进行处理。现在项目一般后端都会给处理好,这个就根据自己的项目情况进行配置吧。

 //根据不同的状态码,生成不同的提示信息
const showResState = (state) => {
    let message = ''
    // 这里只做部分常见的示例,具体根据需要进行配置
    switch (state) {
        case 400:
            message = '请求错误(400)'
            break
        case 401:
            message = '未授权,请重新登录(401)'
            break
        case 403:
            message = '拒绝访问(403)'
            break
        case 404:
            message = '请求出错(404)'
            break
        case 500:
            message = '服务器错误(500)'
            break
        case 501:
            message = '服务未实现(501)'
            break
        case 502:
            message = '网络错误(502)'
            break
        case 503:
            message = '服务不可用(503)'
            break
        default:
            message = `连接出错(${state})!`
    }
    return `${message},请检查网络或联系网站管理员!`
}

// 添加响应拦截器
axios.interceptors.response.use(function (res) {
    // 对响应数据做点什么
    const status = res.status
    let msg = ''
    if (status < 200 || status >= 300) {
        // 处理http错误,抛到业务代码
        msg = showResState(status)
        if (typeof res.data === 'string') {
            res.data = { msg }
        } else {
            res.data.msg = msg
        }
    }
    return res;
}, function (error) {
    // 对响应错误做点什么
    return Promise.reject(error);
});

封装 Get 请求

 //GET 请求 get 下 params 为查询参数
export const Get = (url,params={},headers={}) => {
    return Axios(url,'get',params,headers)
}

封装 Post 请求

// POST 请求 post 下 params 为body参数, 如果 post 下既需要传查询参数也需要传实体参数,则查询参数配置在 url 中
// post 请求支持上传文件
export const Post = (url,params={},headers={}) => {
    return Axios(url,'post',params,headers)
}

封装 API

 // src/api/index.js

import {Axios,Post,Get} from './axios'; // Axios 数据请求方法

/**
 * 使用说明:
 * import { login } from '@/api/index'
 * 使用:
 * login(params).then((res)=>{
 *  // 业务处理
 * }).catch((err)=>{
 *   console.log(err)
 * });
 */

/**
 * 请求示例--Get 示例
 * @params title string
 * {title: 'cp'}
 */
export const getArticles = (params={}) => {
    return Get('/api/v1/blog/getArticles', params);
}

/**
 * 请求示例--Post 示例
 * @params code string
 * {code: 'xxxxxxx'}
 */
export const saveArticles = (params={}) => {
    return Post('/api/v1/blog/saveArticles', params);
}

/**
 * Axios 示例
 * @params title string
 * {title:'标题'}
 */
export const getAxiosDemo = (params={}) => {
    return Axios('/api/v1/blog/getArticles','get', params);
}

/**
 * 请求示例--Post 上传示例
 * @params file object
 * file
 */
export const upload = (params={}) => {
    return Post('/api/v1/article/upload', params);
}

使用

// 直接在页面中使用封装的方法
import { Axios, Get, Post } from '@/api'

// 使用 Axios 配置
Axios(
   '/api/v1/blog/getArticles',
   'get',
    {id:123}
)
.then((res: any) => {
  console.log(res)
}).catch((err:any)=>{
  console.log(err)
});

// 使用 Get 请求
Get(
   '/api/v1/blog/getArticles',
   'get',
    {id:123}
)
.then((res) => {
  console.log(res)
}).catch((err)=>{
  console.log(err)
});

// 使用 Post 请求
Post(
   '/api/v1/blog/getArticles',
    {id:123}
)
.then((res) => {
  console.log(res)
}).catch((err)=>{
  console.log(err)
});

对于在实际业务系统中,可以 对 Api 进行封装,放在一个或一组文件中,然后在页面中通过 API 接口名称进行调用,这样也便于管理 api 地址。对比下这种方式是不是很方便呢。

// 在页面中使用封装好的 API
import { getArticles } from '@/api'

getArticles({id:123}).then((res: any) => {
  console.log(res)
}).catch((err:any)=>{
  console.log(err)
});

上传

这里单独把上传文件的代码也贴出来,供参考

<script setup>
import { ref } from 'vue';

const fileRef = ref(null);

const handleUploadBtn = () => {
  fileRef.value.click();
}

const handleUpload = () => {
  let file = fileRef.value.files[0];
  if (file.size > 10 * 1024 * 1024) {
    // 文件大小超限了
    alert('请上传小于10M的图片');
    fileRef.value.value = ''; // 清空内容
    return;
  }
  let forms = new FormData();
  forms.append('file', file);
  // forms.append('filePath', `pc/client-${moment().format('YYYY-MM-DD')}/`);
  fileRef.value.value = ''; // 清空内容

  upload(forms).then((res) => {
    console.log(res);
  }).catch((err)=>{
    console.log(err);
  });
}
</script>

<template>
  <input
      type="file"
      accept="image/*"
      ref="fileRef"
      @change="handleUpload"
  />
  <button @click="handleUploadBtn">选择文件</button>
</template>

设计

封装之前先梳理下所需功能,并要做成什么样,采用什么样的规范,部分主要代码片段是以 localStorage作为示例,最后会贴出完整代码的。可以结合项目自行优化,也可以直接使用。

// 区分存储类型 type
// 自定义名称前缀 prefix
// 支持设置过期时间 expire
// 支持加密可选,开发环境下未方便调试可关闭加密

// 支持数据加密 这里采用 crypto-js 加密 也可使用其他方式

// 判断是否支持 Storage isSupportStorage

// 设置 setStorage

// 获取 getStorage

// 是否存在 hasStorage

// 获取所有key getStorageKeys

// 根据索引获取key getStorageForIndex

// 获取localStorage长度 getStorageLength

// 获取全部 getAllStorage

// 删除 removeStorage

// 清空 clearStorage

//定义参数 类型 window.localStorage,window.sessionStorage,
const config = {
    type: 'localStorage', // 本地存储类型 localStorage/sessionStorage
    prefix: 'SDF_0.0.1', // 名称前缀 建议:项目名 + 项目版本
    expire: 1, //过期时间 单位:秒
    isEncrypt: true // 默认加密 为了调试方便, 开发过程中可以不加密
}

设置 setStorage

Storage 本身是不支持过期时间设置的,要支持设置过期时间,可以效仿 Cookie 的做法,setStorage(key,value,expire) 方法,接收三个参数,第三个参数就是设置过期时间的,用相对时间,单位秒,要对所传参数进行类型检查。可以设置统一的过期时间,也可以对单个值得过期时间进行单独配置。两种方式按需配置。 代码实现:

// 设置 setStorage
export const setStorage = (key,value,expire=0) => {
    if (value === '' || value === null || value === undefined) {
        value = null;
    }

    if (isNaN(expire) || expire < 1) throw new Error("Expire must be a number");

    expire = (expire?expire:config.expire) * 60000;
    let data = {
        value: value, // 存储值
        time: Date.now(), //存值时间戳
        expire: expire // 过期时间
    }

    window[config.type].setItem(key, JSON.stringify(data));
}

获取 getStorage

首先要对 key 是否存在进行判断,防止获取不存在的值而报错。对获取方法进一步扩展,只要在有效期内获取 Storage 值,就对过期时间进行续期,如果过期则直接删除该值。并返回 null

// 获取 getStorage
export const getStorage = (key) => {
    // key 不存在判断
    if (!window[config.type].getItem(key) || JSON.stringify(window[config.type].getItem(key)) === 'null'){
        return null;
    }

    // 优化 持续使用中续期
    const storage = JSON.parse(window[config.type].getItem(key));
    console.log(storage)
    let nowTime = Date.now();
    console.log(config.expire*6000 ,(nowTime - storage.time))
    // 过期删除
    if (storage.expire && config.expire*6000 < (nowTime - storage.time)) {
        removeStorage(key);
        return null;
    } else {
        // 未过期期间被调用 则自动续期 进行保活
        setStorage(key,storage.value);
        return storage.value;
    }
}

获取所有值

// 获取全部 getAllStorage
export const getAllStorage = () => {
    let len = window[config.type].length // 获取长度
    let arr = new Array() // 定义数据集
    for (let i = 0; i < len; i++) {
        // 获取key 索引从0开始
        let getKey = window[config.type].key(i)
        // 获取key对应的值
        let getVal = window[config.type].getItem(getKey)
        // 放进数组
        arr[i] = { 'key': getKey, 'val': getVal, }
    }
    return arr
}

删除 removeStorage

// 名称前自动添加前缀
const autoAddPrefix = (key) => {
    const prefix = config.prefix ? config.prefix + '_' : '';
    return  prefix + key;
}

// 删除 removeStorage
export const removeStorage = (key) => {
    window[config.type].removeItem(autoAddPrefix(key));
}

清空 clearStorage

// 清空 clearStorage
export const clearStorage = () => {
    window[config.type].clear();
}

加密、解密

加密采用的是 crypto-js

// 安装crypto-js
npm install crypto-js
// 或
yarn add crypto-js

// 引入 crypto-js 有以下两种方式
import CryptoJS from "crypto-js";
// 或者
const CryptoJS = require("crypto-js");

对 crypto-js 设置密钥和密钥偏移量,可以采用将一个私钥经 MD5 加密生成16位密钥获得。

// 十六位十六进制数作为密钥
const SECRET_KEY = CryptoJS.enc.Utf8.parse("3333e6e143439161");
// 十六位十六进制数作为密钥偏移量
const SECRET_IV = CryptoJS.enc.Utf8.parse("e3bbe7e3ba84431a");

对加密方法进行封装

/**
 * 加密方法
 * @param data
 * @returns {string}
 */
export function encrypt(data) {
  if (typeof data === "object") {
    try {
      data = JSON.stringify(data);
    } catch (error) {
      console.log("encrypt error:", error);
    }
  }
  const dataHex = CryptoJS.enc.Utf8.parse(data);
  const encrypted = CryptoJS.AES.encrypt(dataHex, SECRET_KEY, {
    iv: SECRET_IV,
    mode: CryptoJS.mode.CBC,
    padding: CryptoJS.pad.Pkcs7
  });
  return encrypted.ciphertext.toString();
}

对解密方法进行封装

/**
 * 解密方法
 * @param data
 * @returns {string}
 */
export function decrypt(data) {
  const encryptedHexStr = CryptoJS.enc.Hex.parse(data);
  const str = CryptoJS.enc.Base64.stringify(encryptedHexStr);
  const decrypt = CryptoJS.AES.decrypt(str, SECRET_KEY, {
    iv: SECRET_IV,
    mode: CryptoJS.mode.CBC,
    padding: CryptoJS.pad.Pkcs7
  });
  const decryptedStr = decrypt.toString(CryptoJS.enc.Utf8);
  return decryptedStr.toString();
}

在存储数据及获取数据中进行使用:

这里我们主要看下进行加密和解密部分,部分方法在下面代码段中并未展示,请注意,不能直接运行。

const config = {
    type: 'localStorage', // 本地存储类型 sessionStorage
    prefix: 'SDF_0.0.1', // 名称前缀 建议:项目名 + 项目版本
    expire: 1, //过期时间 单位:秒
    isEncrypt: true // 默认加密 为了调试方便, 开发过程中可以不加密
}

// 设置 setStorage
export const setStorage = (key, value, expire = 0) => {
    if (value === '' || value === null || value === undefined) {
        value = null;
    }

    if (isNaN(expire) || expire < 0) throw new Error("Expire must be a number");

    expire = (expire ? expire : config.expire) * 1000;
    let data = {
        value: value, // 存储值
        time: Date.now(), //存值时间戳
        expire: expire // 过期时间
    }
    // 对存储数据进行加密 加密为可选配置
    const encryptString = config.isEncrypt ? encrypt(JSON.stringify(data)): JSON.stringify(data);
    window[config.type].setItem(autoAddPrefix(key), encryptString);
}

// 获取 getStorage
export const getStorage = (key) => {
    key = autoAddPrefix(key);
    // key 不存在判断
    if (!window[config.type].getItem(key) || JSON.stringify(window[config.type].getItem(key)) === 'null') {
        return null;
    }

    // 对存储数据进行解密
    const storage = config.isEncrypt ? JSON.parse(decrypt(window[config.type].getItem(key))) : JSON.parse(window[config.type].getItem(key));
    let nowTime = Date.now();
    
    // 过期删除
    if (storage.expire && config.expire * 6000 < (nowTime - storage.time)) {
        removeStorage(key);
        return null;
    } else {
        //  持续使用时会自动续期
        setStorage(autoRemovePrefix(key), storage.value);
        return storage.value;
    }
}

使用

使用的时候你可以通过 import 按需引入,也可以挂载到全局上使用,一般建议少用全局方式或全局变量,为后来接手项目继续开发维护的人,追查代码留条便捷之路!不要为了封装而封装,尽可能基于项目需求和后续的通用,以及使用上的便捷。比如获取全部存储变量,如果你项目上都未曾用到过,倒不如删减掉,留着过年也不见得有多香,不如为减小体积做点贡献!

import {isSupportStorage, hasStorage, setStorage,getStorage,getStorageKeys,getStorageForIndex,getStorageLength,removeStor

完整代码

该代码已进一步完善,需要的可以直接进一步优化,也可以将可优化或可扩展的建议,留言说明,我会进一步迭代的。可以根据自己的需要删除一些不用的方法,以减小文件大小。

/***
 * title: storage.js
 * Author: Gaby
 * Email: xxx@126.com
 * Time: 2022/6/1 17:30
 * last: 2022/6/2 17:30
 * Desc: 对存储的简单封装
 */
 
import CryptoJS from 'crypto-js';

// 十六位十六进制数作为密钥
const SECRET_KEY = CryptoJS.enc.Utf8.parse("3333e6e143439161");
// 十六位十六进制数作为密钥偏移量
const SECRET_IV = CryptoJS.enc.Utf8.parse("e3bbe7e3ba84431a");

// 类型 window.localStorage,window.sessionStorage,
const config = {
    type: 'localStorage', // 本地存储类型 sessionStorage
    prefix: 'SDF_0.0.1', // 名称前缀 建议:项目名 + 项目版本
    expire: 1, //过期时间 单位:秒
    isEncrypt: true // 默认加密 为了调试方便, 开发过程中可以不加密
}

// 判断是否支持 Storage
export const isSupportStorage = () => {
    return (typeof (Storage) !== "undefined") ? true : false
}

// 设置 setStorage
export const setStorage = (key, value, expire = 0) => {
    if (value === '' || value === null || value === undefined) {
        value = null;
    }

    if (isNaN(expire) || expire < 0) throw new Error("Expire must be a number");

    expire = (expire ? expire : config.expire) * 1000;
    let data = {
        value: value, // 存储值
        time: Date.now(), //存值时间戳
        expire: expire // 过期时间
    }
    
    const encryptString = config.isEncrypt 
    ? encrypt(JSON.stringify(data))
    : JSON.stringify(data);
    
    window[config.type].setItem(autoAddPrefix(key), encryptString);
}

// 获取 getStorage
export const getStorage = (key) => {
    key = autoAddPrefix(key);
    // key 不存在判断
    if (!window[config.type].getItem(key) || JSON.stringify(window[config.type].getItem(key)) === 'null') {
        return null;
    }

    // 优化 持续使用中续期
    const storage = config.isEncrypt 
    ? JSON.parse(decrypt(window[config.type].getItem(key))) 
    : JSON.parse(window[config.type].getItem(key));
    
    let nowTime = Date.now();

    // 过期删除
    if (storage.expire && config.expire * 6000 < (nowTime - storage.time)) {
        removeStorage(key);
        return null;
    } else {
        // 未过期期间被调用 则自动续期 进行保活
        setStorage(autoRemovePrefix(key), storage.value);
        return storage.value;
    }
}

// 是否存在 hasStorage
export const hasStorage = (key) => {
    key = autoAddPrefix(key);
    let arr = getStorageAll().filter((item)=>{
        return item.key === key;
    })
    return arr.length ? true : false;
}

// 获取所有key
export const getStorageKeys = () => {
    let items = getStorageAll()
    let keys = []
    for (let index = 0; index < items.length; index++) {
        keys.push(items[index].key)
    }
    return keys
}

// 根据索引获取key
export const getStorageForIndex = (index) => {
    return window[config.type].key(index)
}

// 获取localStorage长度
export const getStorageLength = () => {
    return window[config.type].length
}

// 获取全部 getAllStorage
export const getStorageAll = () => {
    let len = window[config.type].length // 获取长度
    let arr = new Array() // 定义数据集
    for (let i = 0; i < len; i++) {
        // 获取key 索引从0开始
        let getKey = window[config.type].key(i)
        // 获取key对应的值
        let getVal = window[config.type].getItem(getKey)
        // 放进数组
        arr[i] = {'key': getKey, 'val': getVal,}
    }
    return arr
}

// 删除 removeStorage
export const removeStorage = (key) => {
    window[config.type].removeItem(autoAddPrefix(key));
}

// 清空 clearStorage
export const clearStorage = () => {
    window[config.type].clear();
}

// 名称前自动添加前缀
const autoAddPrefix = (key) => {
    const prefix = config.prefix ? config.prefix + '_' : '';
    return  prefix + key;
}

// 移除已添加的前缀
const autoRemovePrefix = (key) => {
    const len = config.prefix ? config.prefix.length+1 : '';
    return key.substr(len)
    // const prefix = config.prefix ? config.prefix + '_' : '';
    // return  prefix + key;
}

/**
 * 加密方法
 * @param data
 * @returns {string}
 */
const encrypt = (data) => {
    if (typeof data === "object") {
        try {
            data = JSON.stringify(data);
        } catch (error) {
            console.log("encrypt error:", error);
        }
    }
    const dataHex = CryptoJS.enc.Utf8.parse(data);
    const encrypted = CryptoJS.AES.encrypt(dataHex, SECRET_KEY, {
        iv: SECRET_IV,
        mode: CryptoJS.mode.CBC,
        padding: CryptoJS.pad.Pkcs7
    });
    return encrypted.ciphertext.toString();
}

/**
 * 解密方法
 * @param data
 * @returns {string}
 */
const decrypt = (data) => {
    const encryptedHexStr = CryptoJS.enc.Hex.parse(data);
    const str = CryptoJS.enc.Base64.stringify(encryptedHexStr);
    const decrypt = CryptoJS.AES.decrypt(str, SECRET_KEY, {
        iv: SECRET_IV,
        mode: CryptoJS.mode.CBC,
        padding: CryptoJS.pad.Pkcs7
    });
    const decryptedStr = decrypt.toString(CryptoJS.enc.Utf8);
    return decryptedStr.toString();
}