/* * @Descripttion: 工具集 * @version: 2.0 * @LastEditors: sakuya * @LastEditTime: 2026年1月15日 */ import CryptoJS from 'crypto-js' import sysConfig from '@/config' const tool = {} /** * 检查是否为有效的值(非null、非undefined、非空字符串、非空数组、非空对象) * @param {*} value - 要检查的值 * @returns {boolean} */ tool.isValid = function (value) { if (value === null || value === undefined) { return false } if (typeof value === 'string' && value.trim() === '') { return false } if (Array.isArray(value) && value.length === 0) { return false } if (typeof value === 'object' && Object.keys(value).length === 0) { return false } return true } /** * 防抖函数 * @param {Function} func - 要执行的函数 * @param {number} wait - 等待时间(毫秒) * @param {boolean} immediate - 是否立即执行 * @returns {Function} */ tool.debounce = function (func, wait = 300, immediate = false) { let timeout return function (...args) { const context = this clearTimeout(timeout) if (immediate && !timeout) { func.apply(context, args) } timeout = setTimeout(() => { timeout = null if (!immediate) { func.apply(context, args) } }, wait) } } /** * 节流函数 * @param {Function} func - 要执行的函数 * @param {number} wait - 等待时间(毫秒) * @param {Object} options - 配置选项 { leading: boolean, trailing: boolean } * @returns {Function} */ tool.throttle = function (func, wait = 300, options = {}) { let timeout let previous = 0 const { leading = true, trailing = true } = options return function (...args) { const context = this const now = Date.now() if (!previous && !leading) { previous = now } const remaining = wait - (now - previous) if (remaining <= 0 || remaining > wait) { if (timeout) { clearTimeout(timeout) timeout = null } previous = now func.apply(context, args) } else if (!timeout && trailing) { timeout = setTimeout(() => { previous = leading ? Date.now() : 0 timeout = null func.apply(context, args) }, remaining) } } } /** * 深拷贝对象(支持循环引用) * @param {*} obj - 要拷贝的对象 * @param {WeakMap} hash - 用于检测循环引用 * @returns {*} */ tool.deepClone = function (obj, hash = new WeakMap()) { if (obj === null || typeof obj !== 'object') { return obj } if (hash.has(obj)) { return hash.get(obj) } const clone = Array.isArray(obj) ? [] : {} hash.set(obj, clone) for (const key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { clone[key] = tool.deepClone(obj[key], hash) } } return clone } /* localStorage */ tool.data = { set(key, data, datetime = 0) { //加密 if (sysConfig.LS_ENCRYPTION == 'AES') { data = tool.crypto.AES.encrypt(JSON.stringify(data), sysConfig.LS_ENCRYPTION_key) } let cacheValue = { content: data, datetime: parseInt(datetime) === 0 ? 0 : new Date().getTime() + parseInt(datetime) * 1000, } return localStorage.setItem(key, JSON.stringify(cacheValue)) }, get(key) { try { const value = JSON.parse(localStorage.getItem(key)) if (value) { let nowTime = new Date().getTime() if (nowTime > value.datetime && value.datetime != 0) { localStorage.removeItem(key) return null } //解密 if (sysConfig.LS_ENCRYPTION == 'AES') { value.content = JSON.parse(tool.crypto.AES.decrypt(value.content, sysConfig.LS_ENCRYPTION_key)) } return value.content } return null } catch { return null } }, remove(key) { return localStorage.removeItem(key) }, clear() { return localStorage.clear() }, } /*sessionStorage*/ tool.session = { set(table, settings) { const _set = JSON.stringify(settings) return sessionStorage.setItem(table, _set) }, get(table) { const data = sessionStorage.getItem(table) try { return JSON.parse(data) } catch { return null } }, remove(table) { return sessionStorage.removeItem(table) }, clear() { return sessionStorage.clear() }, } /*cookie*/ tool.cookie = { /** * 设置cookie * @param {string} name - cookie名称 * @param {string} value - cookie值 * @param {Object} config - 配置选项 */ set(name, value, config = {}) { const cfg = { expires: null, path: null, domain: null, secure: false, httpOnly: false, sameSite: 'Lax', ...config, } let cookieStr = `${name}=${encodeURIComponent(value)}` if (cfg.expires) { const exp = new Date() exp.setTime(exp.getTime() + parseInt(cfg.expires) * 1000) cookieStr += `;expires=${exp.toUTCString()}` } if (cfg.path) { cookieStr += `;path=${cfg.path}` } if (cfg.domain) { cookieStr += `;domain=${cfg.domain}` } if (cfg.secure) { cookieStr += `;secure` } if (cfg.sameSite) { cookieStr += `;SameSite=${cfg.sameSite}` } document.cookie = cookieStr }, /** * 获取cookie * @param {string} name - cookie名称 * @returns {string|null} */ get(name) { const arr = document.cookie.match(new RegExp('(^| )' + name + '=([^;]*)(;|$)')) if (arr != null) { return decodeURIComponent(arr[2]) } return null }, /** * 删除cookie * @param {string} name - cookie名称 */ remove(name) { const exp = new Date() exp.setTime(exp.getTime() - 1) document.cookie = `${name}=;expires=${exp.toUTCString()}` }, } /* Fullscreen */ /** * 切换全屏状态 * @param {HTMLElement} element - 要全屏的元素 */ tool.screen = function (element) { const isFull = !!(document.webkitIsFullScreen || document.mozFullScreen || document.msFullscreenElement || document.fullscreenElement) if (isFull) { if (document.exitFullscreen) { document.exitFullscreen() } else if (document.msExitFullscreen) { document.msExitFullscreen() } else if (document.mozCancelFullScreen) { document.mozCancelFullScreen() } else if (document.webkitExitFullscreen) { document.webkitExitFullscreen() } } else { if (element.requestFullscreen) { element.requestFullscreen() } else if (element.msRequestFullscreen) { element.msRequestFullscreen() } else if (element.mozRequestFullScreen) { element.mozRequestFullScreen() } else if (element.webkitRequestFullscreen) { element.webkitRequestFullscreen() } } } /* 复制对象(浅拷贝) */ /** * 浅拷贝对象 * @param {*} obj - 要拷贝的对象 * @returns {*} - 拷贝后的对象 */ tool.objCopy = function (obj) { if (obj === null || typeof obj !== 'object') { return obj } return JSON.parse(JSON.stringify(obj)) } /* 日期格式化 */ /** * 格式化日期 * @param {Date|string|number} date - 日期对象、时间戳或日期字符串 * @param {string} fmt - 格式化字符串,默认 "yyyy-MM-dd hh:mm:ss" * @returns {string} - 格式化后的日期字符串 */ tool.dateFormat = function (date, fmt = 'yyyy-MM-dd hh:mm:ss') { if (!date) return '' const dateObj = new Date(date) if (isNaN(dateObj.getTime())) return '' const o = { 'M+': dateObj.getMonth() + 1, // 月份 'd+': dateObj.getDate(), // 日 'h+': dateObj.getHours(), // 小时 'm+': dateObj.getMinutes(), // 分 's+': dateObj.getSeconds(), // 秒 'q+': Math.floor((dateObj.getMonth() + 3) / 3), // 季度 S: dateObj.getMilliseconds(), // 毫秒 } if (/(y+)/.test(fmt)) { fmt = fmt.replace(RegExp.$1, (dateObj.getFullYear() + '').substr(4 - RegExp.$1.length)) } for (const k in o) { if (new RegExp('(' + k + ')').test(fmt)) { fmt = fmt.replace(RegExp.$1, RegExp.$1.length == 1 ? o[k] : ('00' + o[k]).substr(('' + o[k]).length)) } } return fmt } /* 千分符 */ /** * 格式化数字,添加千分位分隔符 * @param {number|string} num - 要格式化的数字 * @param {number} decimals - 保留小数位数,默认为0 * @returns {string} - 格式化后的字符串 */ tool.groupSeparator = function (num, decimals = 0) { if (num === null || num === undefined || num === '') return '' const numStr = Number(num).toFixed(decimals) const parts = numStr.split('.') parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ',') return parts.join('.') } /* 常用加解密 */ tool.crypto = { //MD5加密 MD5(data) { return CryptoJS.MD5(data).toString() }, //BASE64加解密 BASE64: { encrypt(data) { return CryptoJS.enc.Base64.stringify(CryptoJS.enc.Utf8.parse(data)) }, decrypt(cipher) { return CryptoJS.enc.Base64.parse(cipher).toString(CryptoJS.enc.Utf8) }, }, //AES加解密 AES: { encrypt(data, secretKey, config = {}) { if (secretKey.length % 8 != 0) { console.warn('[SCUI error]: 秘钥长度需为8的倍数,否则解密将会失败。') } const result = CryptoJS.AES.encrypt(data, CryptoJS.enc.Utf8.parse(secretKey), { iv: CryptoJS.enc.Utf8.parse(config.iv || ''), mode: CryptoJS.mode[config.mode || 'ECB'], padding: CryptoJS.pad[config.padding || 'Pkcs7'], }) return result.toString() }, decrypt(cipher, secretKey, config = {}) { const result = CryptoJS.AES.decrypt(cipher, CryptoJS.enc.Utf8.parse(secretKey), { iv: CryptoJS.enc.Utf8.parse(config.iv || ''), mode: CryptoJS.mode[config.mode || 'ECB'], padding: CryptoJS.pad[config.padding || 'Pkcs7'], }) return CryptoJS.enc.Utf8.stringify(result) }, }, } /* 树形数据转扁平数组 */ /** * 将树形结构转换为扁平数组 * @param {Array} tree - 树形数组 * @param {Object} config - 配置项 { children: "children" } * @returns {Array} - 扁平化后的数组 */ tool.treeToList = function (tree, config = { children: 'children' }) { const result = [] tree.forEach((item) => { const tmp = { ...item } const childrenKey = config.children || 'children' if (tmp[childrenKey] && tmp[childrenKey].length > 0) { result.push({ ...item }) const childrenRoutes = tool.treeToList(tmp[childrenKey], config) result.push(...childrenRoutes) } else { result.push(tmp) } }) return result } /* 获取父节点数据(保留原有函数名) */ /** * 根据ID获取父节点数据 * @param {Array} list - 数据列表 * @param {number|string} targetId - 目标ID * @param {Object} config - 配置项 { pid: "parent_id", idField: "id", field: [] } * @returns {*} - 父节点数据或指定字段 */ tool.get_parents = function (list, targetId = 0, config = { pid: 'parent_id', idField: 'id', field: [] }) { let res = null list.forEach((item) => { if (item[config.idField || 'id'] === targetId) { if (config.field && config.field.length > 1) { res = {} config.field.forEach((field) => { res[field] = item[field] }) } else if (config.field && config.field.length === 1) { res = item[config.field[0]] } else { res = item } } }) return res } /* 获取数据字段 */ /** * 从数据对象中提取指定字段 * @param {Object} data - 数据对象 * @param {Array} fields - 字段名数组 * @returns {*} - 提取的字段数据 */ tool.getDataField = function (data, fields = []) { if (!data || typeof data !== 'object') { return data } if (fields.length === 0) { return data } if (fields.length === 1) { return data[fields[0]] } else { const result = {} fields.forEach((field) => { result[field] = data[field] }) return result } } // 兼容旧函数名 tool.tree_to_list = tool.treeToList tool.get_data_field = tool.getDataField export default tool