常用正则
/**
* 统一社会信用代码(同纳税人识别号)
* 由 登记管理部门代码(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-_]*$/,
推荐两个网站,分别用于 正则可视化、正则测试(网站效果如下)


常用方法
数值处理
数值格式化(例如: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在线编译
图片格式转化
相关地址:Office-Converter、Vectorizer.AI、ICO在线制作
伪数据生成
相关地址:在线身份证生成、个人虚拟信息批量生成
相关地址: