常用内容集锦 - 正则、方法、网站

175 阅读4分钟

常用正则

  /**
   * 统一社会信用代码(同纳税人识别号)
   * 由 登记管理部门代码(1)、机构类别代码(2)、登记管理机关行政区划码(3-8)、主体标识码(组织机构代码)(9-17)、校验码(18) 五部分组成
   */
  USCC: /^[1-9ANY][\d][\d]{6}[A-Z0-9]{9}[A-Z0-9]$/,
  // 纳税人识别号
  TIN: /^[1-9ANY][\d][\d]{6}[A-Z0-9]{9}[A-Z0-9]$/,
  // 移动电话
  mobilePhone: /^1[3-9]\d{9}$/,
  // 固定电话
  landlinePhone: /^\d{7,8}$/,
  // 固定电话-2(携带区号,支持0755-86668888、0755-8666888、075586668888、07558666888)
  landlinePhone_2: /^0\d{3}(?:-?\d{8}|-?\d{7})$/,
  // 身份证号码
  idCard: /^\d{17}[0-9Xx]$/,
  // 身份证号码-2(含老式15位身份证号判别)
  idCard_2: /^\d{17}[0-9Xx]|\d{15}$/,
  // 电子邮箱
  email: /^[A-Za-z0-9]+([-._][A-Za-z0-9]+)*@[A-Za-z0-9]+(-[A-Za-z0-9]+)*(\.[A-Za-z]{2,6}|[A-Za-z]{2,4}\.[A-Za-z]{2,3})$/,
  // 银行卡号(此处不做发卡行标识代码(前6位)限制)
  bankCardNum: /^[0-9]{16,19}$/,

  // 仅包含中文和英文
  onlyChineseAndEnglish: /^[\u0391-\uFFE5A-Za-z]+$/,
  // 中文
  chinese: /^[\u0391-\uFFE5]+$/,
  // 英文
  english: /^[a-zA-Z]+$/,
  // 小写英文
  lowerCaseEnglish: /^[a-z]+$/,
  // 大写英文
  upperCaseEnglish: /^[A-Z]+$/,
  // 正数
  positiveNum: /^(?!(0[0-9]{0,}$))[0-9]{1,}[.]{0,}[0-9]{0,}$/,
  // 正整数
  positiveInteger: /^[+]{0,1}(\d+)$/,
  // IP地址
  ipAddress: /^(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])$/,
  // 端口号
  portNum: /^([0-9]|[1-9]\d{1,3}|[1-5]\d{4}|6[0-5]{2}[0-3][0-5])$/,
  // 网址
  url: /^(https?|ftp):\/\/([a-zA-Z0-9.-]+(:[a-zA-Z0-9.&%$-]+)*@)*((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3}|([a-zA-Z0-9-]+\.)*[a-zA-Z0-9-]+\.(com|edu|gov|int|mil|net|org|biz|arpa|info|name|pro|aero|coop|museum|[a-zA-Z]{2}))(:[0-9]+)*(\/($|[a-zA-Z0-9.,?'\\+&%$#=~_-]+))*$/,

  // 限定字母、数字、-、_的编码
  code: /^[a-zA-Z0-9-_]*$/,

推荐两个网站,分别用于 正则可视化正则测试(网站效果如下)

image.png

image.png

常用方法

数值处理

数值格式化(例如:10k、100M)

/**
 * @description 数字格式化(Number formatting)
 * @example 10000 => 10k
 * @param {Number} num
 * @param {Number} digits
 */
export function numberFormatter(num, digits) {
  const si = [
    { value: 1e18, symbol: 'E' },
    { value: 1e15, symbol: 'P' },
    { value: 1e12, symbol: 'T' },
    { value: 1e9, symbol: 'G' },
    { value: 1e6, symbol: 'M' },
    { value: 1e3, symbol: 'k' },
  ]

  for (let i = 0; i < si.length; i++) {
    if (num >= si[i].value) {
      return (
        (num / si[i].value)
          .toFixed(digits)
          .replace(/\.0+$|(\.[0-9]*[1-9])0+$/, '$1') + si[i].symbol
      )
    }
  }
  return num.toString()
}

设置「千分分隔符」(例如:1,000.00)

/**
 * @description 带 千位分隔符 的数字
 * @example 10000 => "10,000"
 * @param {Number} num
 * @param {Number} [digits=2] 小数位数
 * @param {String} suffix 后缀
 * @param {String} prefix 前缀
 */
export function toThousandFilter(num, digits = 2, suffix, prefix) {
  if ([null, undefined, ''].includes(num)) return num

  const numStr = parseFloat(num)
    .toFixed(digits)
    .replace(/(\d)(?=(\d{3})+(?!\d))/g, '$1,')

  return `${prefix ?? ''}${numStr}${suffix ?? ''}`
}

转化「中文大写」(例如:壹仟元整)

/**
 * @description 阿拉伯数字转化为中文大写形式
 * @param {Number} money 金额
 * @example 100 => 壹佰元整、111.11 => 壹佰壹拾壹元壹角壹分
 * @remark 最大值:999999999999999.9999
 */
function parseAmountInWords(money) {
  //  将数字金额转换为大写金额
  var cnNums = ['零', '壹', '贰', '叁', '肆', '伍', '陆', '柒', '捌', '玖'] // 汉字的数字
  var cnIntRadice = ['', '拾', '佰', '仟'] // 基本单位
  var cnIntUnits = ['', '万', '亿', '兆'] // 对应整数部分扩展单位
  var cnDecUnits = ['角', '分', '毫', '厘'] // 对应小数部分单位
  var cnInteger = '整' // 整数金额时后面跟的字符
  var cnIntLast = '元' // 整数完以后的单位
  // 最大处理的数字
  var maxNum = 999999999999999.9999
  var integerNum // 金额整数部分
  var decimalNum // 金额小数部分
  // 输出的中文金额字符串
  var chineseStr = ''

  /* var parts // 分离金额后用的数组,预定义*/
  if (money === '') {
    return ''
  }

  money = parseFloat(money)
  if (money >= maxNum) {
    // 超出最大处理数字
    return '超出最大处理数字'
  }
  if (money === 0) {
    chineseStr = cnNums[0] + cnIntLast + cnInteger
    return chineseStr
  }

  // 四舍五入保留两位小数,转换为字符串
  money = Math.round(money * 100).toString()
  integerNum = money.substr(0, money.length - 2)
  const endTwoNum = money.substr(money.length - 2, money.length - 1) // 判断倒数第二位是否是0
  if (!endTwoNum || endTwoNum === 0) {
    decimalNum = '0' + money.substr(money.length - 1)
  } else {
    decimalNum = money.substr(money.length - 2)
  }

  // 获取整型部分转换
  if (parseInt(integerNum, 10) > 0) {
    var zeroCount = 0
    var IntLen = integerNum.length
    for (var i = 0; i < IntLen; i++) {
      var n = integerNum.substr(i, 1)
      var p = IntLen - i - 1
      var q = p / 4
      var m = p % 4
      if (n === '0') {
        zeroCount++
      } else {
        if (zeroCount > 0) {
          chineseStr += cnNums[0]
        }
        // 归零
        zeroCount = 0
        chineseStr += cnNums[parseInt(n)] + cnIntRadice[m]
      }
      if (m === 0 && zeroCount < 4) {
        chineseStr += cnIntUnits[q]
      }
    }
    chineseStr += cnIntLast
  }
  // 小数部分
  if (decimalNum !== '') {
    var decLen = decimalNum.length
    for (let i = 0; i < decLen; i++) {
      const n = decimalNum.substr(i, 1)
      if (n !== '0') {
        chineseStr += cnNums[Number(n)] + cnDecUnits[i]
      }
    }
  }
  if (chineseStr === '') {
    chineseStr += cnNums[0] + cnIntLast + cnInteger
  } else if (decimalNum === '' || /^0*$/.test(decimalNum)) {
    chineseStr += cnInteger
  }

  return chineseStr
}

时间处理

带单位过去时长(例如:1 minute、2 hours)

/**
 * @description 描述时间过去的长度
 * @param {Number, String} time 先前时间
 * @return {String} 时间过去长短(带单位)
 */
function timeAgo(time) {
  // 内部函数,用于构建 带单位时间字符串
  function pluralize(time, label) {    
    return `${time} ${label}${(time <= 1 ? undefined : 's') ?? ''}`
  }
  
  // 校正字符串类型时间值
  typeof time === 'string' && (time = Date.parse(time))
  // 转化值为NaN,说明转化失败,传入值不合法
  if (Number.isNaN(time)) {
    console.error('The value of the time is invalid.')
    return
  }
  
  // 前后时间差值(单位:s)
  const between = (Date.now() - Number(time)) / 1000
  // 各 时间单位 单位时间长度
  const timeUnitInfo = {
      minute: 60,
      hour: 60 * 60,
      day: 60 * 60 * 24,
  }
  if (between < timeUnitInfo.hour) {
    return pluralize(~~(between / timeUnitInfo.minute), 'minute')
  } else if (between < timeUnitInfo.day) {
    return pluralize(~~(between / timeUnitInfo.hour), 'hour')
  } else {
    return pluralize(~~(between / timeUnitInfo.day), 'day')
  }
}

备注信息

时间相关操作,极力推荐工具类 dayjs

其他

深拷贝

/** 
 * 深拷贝
 */
function deepClone(obj) {
  // 克隆函数的方法
  function cloneFunction() {
    const obj = [].shift.call(arguments)
    const temp = function temporary() {
      return obj.apply(this, arguments)
    };
    for (const key in obj) {
      if (Object.prototype.hasOwnProperty.call(obj, key)) {
        temp[key] = obj[key];
      }
    }
    return temp;
  }
  
  // 递归方法
  function recursion(obj) {
    if (obj === null) return null
    if (typeof obj === 'function') return cloneFunction(obj)
    if (typeof obj !== 'object') return obj
    if (obj instanceof RegExp) return new RegExp(obj)
    if (obj instanceof Date) return new Date(obj)

    const obj2 = new obj.constructor()
    for (const key in obj) {
      if (Object.prototype.hasOwnProperty.call(obj, key)) {
        obj2[key] = recursion(obj[key])
      }
    }
    return obj2
  }
  
  // 开启递归
  return recursion(obj)
}

获取「目标值路径」(不携带起始对象名称)

el-form组件 与 el-table组件 配套使用,且编辑数据的为 树形结构 时,使用常见的索引值标定prop属性,会出现 校验异常 的问题

此时,我们需要使用属性的 全路径地址 进行标定

/**
 * @description 获取参数具体地址,常用于树形结构数据校验
 * @param {Object} obj 查询对象
 * @param {String, Number} targetVal 目标值(常见ID属性)
 * @param {String[]} [path=[]] 前置路径
 * @param {String[]} [whiteList=['pid', 'parentId']] 目标值校对白名单
 */
function findFullPath(
  obj,
  targetVal,
  path = [],
  whiteList = ['pid', 'parentId']
) {
  function recursion(obj, targetVal, path) {
    if (Array.isArray(obj)) {
      // 如果obj是数组,就遍历每个元素
      for (let i = 0; i < obj.length; i++) {
        // 将当前索引添加到路径数组中
        path.push(`[${i}]`)
        // 对当前元素递归调用findPath函数,并将结果赋值给result
        const result = recursion(obj[i], targetVal, path)
        // 如果result不为空,说明找到了匹配的id,就返回result
        if (result) return result
        // 否则,就从路径数组中移除最后一个元素(即当前索引)
        path.pop()
      }
    } else if (typeof obj === 'object') {
      // 如果obj是对象,就遍历每个属性
      for (const key in obj) {
        if (!whiteList.includes(key)) {
          // 将当前键添加到路径数组中
          path.push(`.${key}`)
          // 对当前值递归调用findPath函数,并将结果赋值给result
          const result = recursion(obj[key], targetVal, path)
          // 如果result不为空,说明找到了匹配的id,就返回result
          if (result) return result
          // 否则,就从路径数组中移除最后一个元素(即当前键)
          path.pop()
        }
      }
    } else {
      // 如果obj是基本类型(如字符串、数字等),就判断是否等于目标id
      // 如果相等,就将路径数组连接成字符串并返回
      if (obj === targetVal) return path.join('')
      // 否则,就返回空字符串
      else return ''
    }
  }

  return recursion(obj, targetVal, path)
}

格式「空叶子节点」数据

当子项为空数组时,el-cascader组件仍会显示节点;此时,需要将相关数据调整为undefined

/**
 * @description 格式化空叶子节点数据。将为空的子类转化为特定数据
 * @param {String} [childAttrName='children'] 子类所对应的属性名称
 * @param {*} [replaceVal=undefined] 替换空子类的值
 */
function formatEmptyLeafNode(data, childAttrName = 'children', replaceVal = undefined) {
  // 递归方法
  function recursion(data) {
    data.forEach(item => {
      // 使用可选运算符(?.),避免child为undefined时,仍获取length
      if (item[childAttrName]?.length) {
        // 针对子类,递归调用
        recursion(item[childAttrName])
      } else {
        item[childAttrName] = replaceVal
      }
    })
  }

  // 深拷贝数据,避免污染原数据(深拷贝方法 见上方)
  const cloneData = deepClone(data)
  // 调用递归方法,处理子类为空的对象
  recursion(cloneData)

  return cloneData
}

树形结构设置「索引值」(例如:1、2.1)

el-table组件,针对树形结构,并不能自动设置层级化索引,因而需手动设置

/**
 * @description 为对象集合添加索引值
 * @param {Array} nodes 对象集合
 * @param {Boolean} [isClone=false] 是否克隆对象集合(避免对原数据造成影响)
 * @param {Boolean} [isFullPath=true] 是否为全路径索引值,例如 1.1.2
 * @param {String} [separator='.'] 分隔符
 * @param {String} [childName='children'] 子项参数名称
 * @returns 设置索引值后的对象集合
 */
function setNodesIndex(
  nodes,
  isClone = false,
  isFullPath = true,
  separator = '.',
  childName = 'children'
) {
  isClone && (nodes = JSON.parse(JSON.stringify(nodes)))

  // 内部递归函数
  function recursion(nodes, parentIndex = '') {
    nodes.forEach((node, index) => {
      // 为当前节点构建完整的索引值
      const childIndex = `${isFullPath ? parentIndex : ''}${index + 1}`
      node.index = childIndex

      // 如果有子节点,递归地为子节点设置索引值,并传递当前节点的索引作为父索引
      if (Array.isArray(node[childName])) {
        recursion(
          node[childName],
          isFullPath ? `${childIndex}${separator}` : undefined
        )
      }
    })
  }
  
  // 开启递归
  recursion(nodes)
  // 返回调整后的集合
  return nodes
}

树形结构「展平」

/**
 * @description 用于展平树结构的方法
 * @param {Array} treeData 源树结构对象
 * @param {Boolean} [delChild=true] 是否删除子元素
 * @param {String} childName 子元素属性名
 * @param {boolean} [isClone=true] 是否克隆源树结构对象
 * @returns {Array} 展平后的数组
 */
function flattenTree(
  treeData,
  delChild = true,
  childName = 'children',
  isClone = true
) {
  const resultArr = []
  // 用于递归的内部函数
  function recursion(treeData) {
    treeData.forEach((item) => {
      // 获取子节点
      const child = item[childName]
      // 删除children属性
      delChild && delete item[childName]
      // 将当前节点,插入到结果集合中
      resultArr.push(item)
      // 当child存在且长度不为0时,递归执行
      child?.length && recursion(child)
    })
  }

  // 开启递归函数
  recursion(isClone ? deepClone(treeData) : treeData)

  return resultArr
}

数据流「生成文件」

/**
 * @description 下载的统一处理方法
 * @param {Object} res 下载请求响应值
 * @param {String} filename 文件的名称
 */
function dwlUniformFun(res, filename) {
  return new Promise((resolve) => {
    const reader = new FileReader()
    reader.onload = function () {
      try {
        // 读取成功,表明 响应数据为 JSON(间接表明请求失败)
        const jsonRes = JSON.parse(reader.result)
        return resolve(jsonRes)
      } catch (err) {
        // 读取失败,表明 响应数据为 二进制流
        if (window.navigator.msSaveOrOpenBlob) {
          navigator.msSaveBlob(res, filename)
        } else {
          const link = document.createElement('a')
          const body = document.querySelector('body')

          link.href = window.URL.createObjectURL(res.data || res)
          filename && (link.download = filename)

          // fix Firefox
          link.style.display = 'none'
          body.appendChild(link)

          link.click()
          body.removeChild(link)

          window.URL.revokeObjectURL(link.href)
        }

        return resolve({ code: 0, msg: '请查收下载的内容!' })
      }
    }
    // 读取文件信息
    reader.readAsText(res.data || res)
  })
}

抽离文件名、文件类型

/**
 * @description 获取文件名和后缀
 * @param {String} path 文件路径
 * @param {Boolean} needOriginName 是否需要源文件名,默认为否
 */
function getFileNameAndSuffix(path) {
  // 获取文件全名,全名包含后缀(例如:temp.png)
  const fullName = path.split('/').reverse()[0]
  // 获取文件名和后缀
  const lastIndexOfDot = fullName.lastIndexOf('.')
  let fileName = fullName.substring(0, lastIndexOfDot)
  const suffix = fullName.substring(lastIndexOfDot + 1)

  return { fileName, suffix }
}

首字母大写(例如:Upper)

/**
 * @description 首字母大写(Upper case first char)
 * @param {String} 待转化字符串
 */
function uppercaseFirst(string) {
  return string.charAt(0).toUpperCase() + string.slice(1)
}

@click 实现单、双击监测

// 双击操作,最长点击间隔
const dblclickGap = 250;
// 点击事件计时器
let clickTimer = null;
// 末次点击时的时间戳
let clickTimestamp = 0;

handleClick() {
  const curTimestamp = Date.now()
  // 计算两次点击的时间差
  const clickGap = curTimestamp - clickTimestamp
  // 更新点击时的时间戳
  clickTimestamp = curTimestamp

  let clickType = 'click'
  if (clickGap < dblclickGap) {
    // 短时间内连续点击,触发【双击事件】
    // 清除上次点击产生的定时器
    clearTimeout(clickTimer)
    // 调整点击类型
    clickType = 'dblclick'
  }

  clickTimer = setTimeout(() => {
    if (clickType === 'click') {
      /* 此处调用单击事件 */
    } else {
      /* 此处调用双击事件 */
    }
  }, dblclickGap)
}

常用网站

less、sass在线编译

image.png 相关地址:LESS转CSSSASS转CSS

图片格式转化

image.png 相关地址:Office-ConverterVectorizer.AIICO在线制作

伪数据生成

image.png 相关地址:在线身份证生成个人虚拟信息批量生成