1. 字符串操作
1.1. 数字千分位展示
将数字或数字字符串转换为千分位展示,支持保留小数点,如123,456,789.666
/**
* 数字千分位展示并显示n位小数
* @param {Number | String} num 需要格式化的值
* @param {Number} precision 保留几位小数,不传小数不处理
* @returns {String} 返回格式化的值
*/
function formatNumber(num, precision) {
// 判断是否为数字
if (!isNaN(parseFloat(num)) && isFinite(num)) {
let parts;
num = Number(num);
// 处理小数点位数
num = (
typeof precision !== "undefined" ? num.toFixed(precision) : num
).toString();
// 分离数字的小数部分和整数部分
parts = num.split(".");
parts[0] = parts[0].toString().replace(/(?!^)(?=(\d{3})+$)/g, ",");
return parts.join(".");
}
return "";
}
1.2. 生成随机字符串
生成随机字符串,可以指定生成字符串的字典
/**
* 生成指定个数的随机字符串
* @param {Number} n 生成的字符串个数
* @param {String} str 生戯后的字符中可含有的字符
* @returns {String}
*/
function randomStr(
n,
str = "abcdefghigklmnopqrstuvexyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890_-+%$"
) {
let res = "";
const len = str.length;
for (let i = 0; i < n; i++) {
res += str[Math.floor(Math.random() * len)];
}
return res;
}
2. 数据结构相关
2.1. 二维数组转树形数组
根据parent_id将数组转换为树
/**
* 二维数组转树形数组
* @param {Array} data 原二维数组
* @param {Number} parent_id 父节点id
* @returns {Array} 树形数组 [{id:1,children:[{id:2},{id:3,children:[{id:4}]}]}]
*/
export function convertToTree (data, key = 'parent_id', value = 0) {
data = JSON.parse(JSON.stringify(data))
const tree = []
let temp
for (let i = 0; i < data.length; i++) {
const item = data[i]
if (item[key] === value) {
temp = convertToTree(data, key, item.id)
if (temp.length > 0) {
item.children = temp
}
tree.push(item)
}
}
return tree
}
3. 浏览器相关
3.1. localStorage
浏览器本地存储获取、设置、更新、删除,更新操作只支持Object
/**
* 获取localStorage
* @param {String} key localStorage键名
* @returns {Object} 格式化的json数据
*/
export function getLocalStorage (key) {
const data = localStorage.getItem(key)
let result
try {
result = data ? JSON.parse(data) : {}
} catch {
result = ''
}
return result
}
/**
* 设置localStorage
* @param {String} key localStorage键名
* @param {Object} data 数据
*/
export function setLocalStorage (key, data) {
const format = typeof data === 'object' ? JSON.stringify(data) : data
localStorage.setItem(key, format)
}
/**
* 更新localStorage
* @param {String} key localStorage键名
* @param {Object} data 数据
*/
export function updateLocalStorage (key, data) {
const source = JSON.parse(localStorage.getItem(key))
if (typeof source === 'object' && typeof data === 'object') {
const format = Object.assign(source, data)
localStorage.setItem(key, JSON.stringify(format))
} else {
localStorage.setItem(key, data)
}
}
/**
* 移除localStorage
* @param {String} key localStorage键名
*/
export function removeLocalStorage(key) {
localStorage.removeItem(key)
}
3.2. WebSocket
实现连接、发送数据、发送心跳包、接收消息、断线重连、消息队列、关闭连接,并提供wss、ws协议适配
// websocket 公共类
// 实现连接、发送数据、发送心跳包、接收消息、断线重连、关闭连接
const sleep = (duration) => new Promise(resolve => setTimeout(resolve, duration));
class SocketClient {
/**
* @param {String} options.url websocket地址
* @param {String} options.protocol 协议
* @param {Number} options.heartBeatTimeout 发送心跳包的间隔,单位ms
* @param {Boolean} options.automaticReconnect 是否断线重连
* @param {Function} options.openHandler onopen回调
* @param {Function} options.errorHandler onerror回调
* @param {Function} options.closeHandler onclose回调
* @param {Function} options.messageHandler onmessage回调
*/
constructor(options) {
this.isClosing = false
this.reconnectCount = 0
this.options = options
this.socketInstance = null
this.messageQueue = []
}
/**
* CONNECTING: 0, 正在链接中
* OPEN: 1, 已经链接并且可以通讯
* CLOSING: 2, 连接正在关闭
* CLOSED: 3 连接已关闭或者没有链接成功
*/
get readyState() {
return this.socketInstance?.readyState
}
/**
* 获取服务端地址
* @param {String} address
* @returns {String}
*/
formatServerUrl(address) {
if (!/^ws|http(s)?:\/\//.test(address)) {
// 如果开头没有指定协议
const { protocol, host } = location
let targetProtocol = protocol === 'https:' ? `wss://` : 'ws://'
if (address.startsWith('/')) {
// 如果开头是/,则同源
targetProtocol += host
}
return targetProtocol + address
}
return address
}
/**
* 建立socket连接
*/
connect() {
const { url, protocol, heartBeatTimeout, openHandler, errorHandler, closeHandler } = this.options
this.socketInstance = new WebSocket(this.formatServerUrl(url), protocol)
this.socketInstance.onopen = () => {
console.log('connect!')
this.reconnectCount = 0
this.isClosing = false
this.listenMessage()
// 发送心跳包
if (heartBeatTimeout && !isNaN(+heartBeatTimeout)) {
this.startHeartbeat(heartBeatTimeout)
}
// 在重新连接后,发送消息队列中存储的消息
while (this.messageQueue.length > 0) {
const message = this.messageQueue.shift();
this.sendMessage(message);
}
typeof openHandler === 'function' && openHandler()
}
this.socketInstance.onerror = () => {
console.error('WebSocket Error!')
typeof errorHandler === 'function' && errorHandler()
}
this.socketInstance.onclose = () => {
console.log('close!')
this.stopHeartbeat()
// 断线重连
this.reconnect()
typeof closeHandler === 'function' && closeHandler()
}
}
/**
* 发送信息
* @param {String|ArrayBuffer|Blob|ArrayBufferView} message
*/
sendMessage(message) {
if (this.readyState === WebSocket.OPEN) {
this.socketInstance.send(message)
} else {
if (this.messageQueue.length < 10) {
this.messageQueue.push(message)
} else {
console.log('消息队列已满,请等待连接成功后再发送')
}
}
}
/**
* 发送心跳包
* @param {Number} timeout 发送心跳包的间隔,单位ms
*/
startHeartbeat(timeout) {
this.stopHeartbeat()
if (this.readyState !== WebSocket.OPEN) {
return
}
this.heartBeatIntervalId = setInterval(() => {
this.sendMessage('heartbeat')
}, timeout)
}
/**
* 停止心跳包定时器
*/
stopHeartbeat() {
clearInterval(this.heartBeatIntervalId)
}
/**
* 断线重连
*/
async reconnect() {
const { automaticReconnect } = this.options
if (automaticReconnect && !this.isClosing && this.reconnectCount < 4 && this.readyState === WebSocket.CLOSED) {
// 重连4次,超过后不再重试,每次等待时间参考SignalR
const timeout = [0, 2000, 10000, 30000][this.reconnectCount] || 10000
await sleep(timeout)
console.log('reconnecting...')
this.connect()
this.reconnectCount++
}
}
listenMessage() {
const { messageHandler } = this.options
this.socketInstance.onmessage = msg => {
typeof messageHandler === 'function' && messageHandler(msg)
}
}
close() {
this.stopHeartbeat()
this.isClosing = true
this.socketInstance.close()
}
}
4. 其他工具集
4.1. 操作dom
添加、移除、判断是否包含classname
/**
* 判断元素是否有className
* @param {Object[Element]} el
* @param {String} cls className
* @returns {Boolean}
*/
export function hasClass (el, cls) {
if (!el || !cls) { return false }
if (cls.includes(' ')) { throw new Error('className should not contain space.') }
if (el.classList) {
return el.classList.contains(cls)
} else {
return (' ' + el.className + ' ').includes(' ' + cls + ' ')
}
}
/**
* 给元素添加className
* @param {Object[Element]} el
* @param {String} cls className
*/
export function addClass (el, cls) {
if (!el) { return }
let curClass = el.className
const classes = (cls || '').split(' ')
for (let i = 0, j = classes.length; i < j; i++) {
const clsName = classes[i]
if (!clsName) { continue }
if (el.classList) {
el.classList.add(clsName)
} else if (!hasClass(el, clsName)) {
curClass += ' ' + clsName
}
}
if (!el.classList) {
el.setAttribute('class', curClass)
}
}
/**
* 给元素移除className
* @param {Object[Element]} el
* @param {String} cls className
*/
export function removeClass (el, cls) {
if (!el || !cls) { return }
const classes = cls.split(' ')
let curClass = ' ' + el.className + ' '
for (let i = 0, j = classes.length; i < j; i++) {
const clsName = classes[i]
if (!clsName) { continue }
if (el.classList) {
el.classList.remove(clsName)
} else if (hasClass(el, clsName)) {
curClass = curClass.replace(' ' + clsName + ' ', ' ')
}
}
if (!el.classList) {
el.setAttribute('class', trim(curClass))
}
}
4.2. 睡眠函数
等待一定时间后再执行
export function sleep (wait) {
return new Promise(resolve => setTimeout(resolve, wait))
}
还没有评论,快来抢第一吧