diff --git a/src/utils/commons.js b/src/utils/commons.js index 9456393..1b5f3bb 100644 --- a/src/utils/commons.js +++ b/src/utils/commons.js @@ -1,55 +1,105 @@ +/** + * @deprecated + * 废弃, 请使用 cloneDeep 或者 structuredClone + */ export function copy(obj) { - return JSON.parse(JSON.stringify(obj)) + return JSON.parse(JSON.stringify(obj)); +} + +/** + * @param {Date} date + */ +export function formatDate(date) { + if (isEmpty(date)) { + return 'NaN'; + } + + const year = date.getFullYear(); + const month = date.getMonth() + 1; + const day = date.getDate(); + + const monthStr = ('' + month).padStart(2, 0); + const dayStr = ('' + day).padStart(2, 0); + const formatted = year + '-' + monthStr + '-' + dayStr; + + return formatted; +} + +/** + * @param {Date} date + */ +export function formatTime(date) { + const hours = date.getHours(); + const minutes = date.getMinutes(); + + const hoursStr = ('' + hours).padStart(2, 0); + const minutesStr = ('' + minutes).padStart(2, 0); + const formatted = hoursStr + ':' + minutesStr; + + return formatted; } +/** + * @param {Date} date + */ +export function formatDatetime(date) { + const year = date.getFullYear(); + const month = date.getMonth() + 1; + const day = date.getDate(); + + const monthStr = ('' + month).padStart(2, 0); + const dayStr = ('' + day).padStart(2, 0); + + const hours = date.getHours(); + const minutes = date.getMinutes(); + + const hoursStr = ('' + hours).padStart(2, 0); + const minutesStr = ('' + minutes).padStart(2, 0); + + const formatted = year + '-' + monthStr + '-' + dayStr + ' ' + hoursStr + ':' + minutesStr; + + return formatted; +} export function splitArray2Parts(arr, size) { - const result = [] + const result = []; for (let i = 0; i < arr.length; i += size) { - result.push(arr.slice(i, i + size)) + result.push(arr.slice(i, i + size)); } - return result + return result; } export function camelCase(name) { - return name.substr(0, 1).toLowerCase() + name.substr(1) + return name.substr(0, 1).toLowerCase() + name.substr(1); } export class UrlBuilder { - constructor(url) { - this.url = url - this.paramList = [] - } - - append(name, value) { - if (isNotEmpty(value)) { - this.paramList.push({ name: name, value: value }) - } - return this - } - - build() { - this.paramList.forEach((e, i, a) => { - if (i === 0) { - this.url += "?" - } else { - this.url += "&" - } - this.url += e.name + "=" + e.value - }) - return this.url - } -} + constructor(url) { + this.url = url; + this.paramList = []; + } -export function isNotEmpty(val) { - return val !== undefined && val !== null && val !== "" -} + append(name, value) { + if (isNotEmpty(value)) { + this.paramList.push({ name: name, value: value }); + } + return this; + } -// export function isEmpty(val) { -// return val === undefined || val === null || val === "" -// } + build() { + this.paramList.forEach((e, i, a) => { + if (i === 0) { + this.url += '?'; + } else { + this.url += '&'; + } + this.url += e.name + '=' + e.value; + }); + return this.url; + } +} export function prepareUrl(url) { - return new UrlBuilder(url) + return new UrlBuilder(url); } // export function debounce(fn, delay = 500) { @@ -64,33 +114,41 @@ export function prepareUrl(url) { // } export function throttle(fn, delay, atleast) { - let timeout = null, - startTime = new Date() - return function () { - let curTime = new Date() - clearTimeout(timeout) - if (curTime - startTime >= atleast) { - fn() - startTime = curTime - } else { - timeout = setTimeout(fn, delay) - } - } + let timeout = null, + startTime = new Date(); + return function () { + let curTime = new Date(); + clearTimeout(timeout); + if (curTime - startTime >= atleast) { + fn(); + startTime = curTime; + } else { + timeout = setTimeout(fn, delay); + } + }; } export function clickUrl(url) { - const httpLink = document.createElement("a") - httpLink.href = url - httpLink.target = "_blank" - httpLink.click() + const httpLink = document.createElement('a'); + httpLink.href = url; + httpLink.target = '_blank'; + httpLink.click(); } export function escape2Html(str) { - var temp = document.createElement("div") - temp.innerHTML = str - var output = temp.innerText || temp.textContent - temp = null - return output + var temp = document.createElement('div'); + temp.innerHTML = str; + var output = temp.innerText || temp.textContent; + temp = null; + return output; +} + +export function formatPrice(price) { + return Math.ceil(price).toLocaleString(); +} + +export function formatPercent(number) { + return Math.round(number * 100) + '%'; } /** @@ -104,11 +162,16 @@ export function isEmpty(val) { // return val === undefined || val === null || val === ""; return [Object, Array].includes((val || {}).constructor) && !Object.entries(val || {}).length; } + +export function isNotEmpty(val) { + return val !== undefined && val !== null && val !== ''; +} + /** * 数组排序 */ export const sortBy = (key) => { - return (a, b) => (a[key] > b[key] ? 1 : b[key] > a[key] ? -1 : 0); + return (a, b) => (getNestedValue(a, key) > getNestedValue(b, key) ? 1 : getNestedValue(b, key) > getNestedValue(a, key) ? -1 : 0); }; /** @@ -120,23 +183,23 @@ export const sortKeys = (obj) => .reduce((a, k2) => ({ ...a, [k2]: obj[k2] }), {}); export function sortObjectsByKeysMap(objects, keyOrder) { - if (!objects) return {} // Handle null/undefined input - if (!keyOrder || keyOrder.length === 0) return objects + if (!objects) return {}; // Handle null/undefined input + if (!keyOrder || keyOrder.length === 0) return objects; - const objectMap = new Map(Object.entries(objects)) - const sortedMap = new Map() + const objectMap = new Map(Object.entries(objects)); + const sortedMap = new Map(); for (const key of keyOrder) { if (objectMap.has(key)) { - sortedMap.set(key, objectMap.get(key)) - objectMap.delete(key) // Optimization: Remove from original map after adding + sortedMap.set(key, objectMap.get(key)); + objectMap.delete(key); // Optimization: Remove from original map after adding } } // Add remaining keys for (const [key, value] of objectMap) { - sortedMap.set(key, value) + sortedMap.set(key, value); } - return Object.fromEntries(sortedMap) + return Object.fromEntries(sortedMap); } /** @@ -151,6 +214,14 @@ export const sortArrayByOrder = (items, keyName, keyOrder) => { return keyOrder.indexOf(a[keyName]) - keyOrder.indexOf(b[keyName]); }); }; + +/** 数组去掉重复 */ +export function unique(arr) { + const x = new Set(arr); + return [...x]; +} +export const uniqWith = (arr, fn) => arr.filter((element, index) => arr.findIndex((step) => fn(element, step)) === index); + /** * 合并Object, 递归地 */ @@ -232,25 +303,52 @@ export function omit(object, keysToOmit) { * 去除无效的值: undefined, null, '', [] * * 只删除 null undefined: 用 flush 方法; */ -export const omitEmpty = _object => { - Object.keys(_object).forEach(key => (_object[key] == null || _object[key] === '' || _object[key].length === 0) && delete _object[key]); +export const omitEmpty = (_object) => { + Object.keys(_object).forEach((key) => (_object[key] == null || _object[key] === '' || _object[key].length === 0) && delete _object[key]); return _object; }; /** * 深拷贝 */ -export function cloneDeep(value) { +export function cloneDeep(value, visited = new WeakMap()) { // return structuredClone(value); - if (typeof value !== 'object' || value === null) { + + // 处理循环引用 + if (visited.has(value)) { + return visited.get(value); + } + + // 特殊对象和基本类型处理 + if (value instanceof Date) { + return new Date(value); + } + if (value instanceof RegExp) { + return new RegExp(value.source, value.flags); + } + if (value === null || typeof value !== 'object') { return value; } - const result = Array.isArray(value) ? [] : {}; + // 创建一个新的WeakMap项以避免内存泄漏 + let result; + if (Array.isArray(value)) { + result = []; + visited.set(value, result); + } else { + result = {}; + visited.set(value, result); + } + + for (const key of Object.getOwnPropertySymbols(value)) { + // 处理Symbol属性 + result[key] = cloneDeep(value[key], visited); + } for (const key in value) { if (Object.prototype.hasOwnProperty.call(value, key)) { - result[key] = cloneDeep(value[key]); + // 处理普通属性 + result[key] = cloneDeep(value[key], visited); } } @@ -325,6 +423,7 @@ export function objectMapper(input, keyMap) { let value = input[key]; if (map.transform) value = map.transform(value); mappedObj[map.key || key] = value; + // mappedObj[map.key || map] = value; } } }); @@ -352,7 +451,7 @@ export function at(obj, path) { const indexes = path.split('.').map((i) => i); result = [obj]; for (let i = 0; i < indexes.length; i++) { - result = [result[0][indexes[i]]]; + result = [result[0]?.[indexes[i]] || undefined]; } } return result; @@ -426,20 +525,26 @@ export const cartesianProductArray = (arr, sep = '_', index = 0, prefix = '') => return result; }; -export const stringToColour = (str='', withFlag = true) => { - var hash = 0 +export const stringToColour = (str = '', withFlag = true) => { + var hash = 0; if (str.length === 0) return hash; for (let i = 0; i < str.length; i++) { - hash = str.charCodeAt(i) + ((hash << 5) - hash) + hash = str.charCodeAt(i) + ((hash << 5) - hash); } - var colour = withFlag ? '#' : '' + var colour = withFlag ? '#' : ''; for (let i = 0; i < 3; i++) { - var value = (hash >> (i * 8)) & 0xff - value = (value % 150) + 50 - colour += ('00' + value.toString(16)).substr(-2) + var value = (hash >> (i * 8)) & 0xff; + value = (value % 150) + 50; + colour += ('00' + value.toString(16)).substr(-2); } - return colour -} + return colour; +}; + +export const TagColorStyle = (tag, outerStyle = false) => { + const color = stringToColour(tag); + const outerStyleObj = outerStyle ? { borderColor: `${color}66`, backgroundColor: `${color}0D` } : {}; + return { color: `${color}`, ...outerStyleObj }; +}; export const debounce = (func, wait, immediate) => { var timeout; @@ -453,7 +558,7 @@ export const debounce = (func, wait, immediate) => { if (!immediate) func.apply(context, args); }, wait); }; -} +}; export const removeFormattingChars = (str) => { const regex = /[\r\n\t\v\f]/g; @@ -461,13 +566,10 @@ export const removeFormattingChars = (str) => { // Replace more than four consecutive spaces with a single space str = str.replace(/\s{4,}/g, ' '); return str; -} +}; export const olog = (text, ...args) => { - console.log( - `%c ${text} `, - 'background:#fb923c ; padding: 1px; border-radius: 3px; color: #fff',...args - ); + console.log(`%c ${text} `, 'background:#fb923c ; padding: 1px; border-radius: 3px; color: #fff', ...args); }; export const sanitizeFilename = (str) => { @@ -480,7 +582,7 @@ export const sanitizeFilename = (str) => { // Trim leading and trailing hyphens str = str.replace(/^-+|-+$/g, ''); return str; -} +}; export const formatBytes = (bytes, decimals = 2) => { if (bytes === 0) return ''; @@ -548,7 +650,7 @@ export const clearAllCaches = async (cb) => { try { // 1. Clear the service worker cache if ('caches' in window) { - // if (navigator.serviceWorker) { + // if (navigator.serviceWorker) { const cacheNames = await caches.keys(); await Promise.all(cacheNames.map((name) => caches.delete(name))); } @@ -572,10 +674,9 @@ export const clearAllCaches = async (cb) => { } else { console.log('No service worker registered'); } - if (typeof cb === 'function' ) { + if (typeof cb === 'function') { cb(); } - } catch (error) { console.error('Error clearing caches or unregistering service worker:', error); } @@ -597,18 +698,15 @@ export const loadScript = (src) => { }); }; -export const TagColorStyle = (tag, outerStyle = false) => { - const color = stringToColour(tag); - const outerStyleObj = outerStyle ? { borderColor: `${color}66`, backgroundColor: `${color}0D` } : {}; - return { color: `${color}`, ...outerStyleObj }; +//格式化为冒号时间,2010转为20:10 +export const formatColonTime = (text) => { + const hours = text.substring(0, 2); + const minutes = text.substring(2); + return `${hours}:${minutes}`; }; -// 数组去掉重复 -export function unique(arr) { - const x = new Set(arr); - return [...x]; -} -export const uniqWith = (arr, fn) => arr.filter((element, index) => arr.findIndex((step) => fn(element, step)) === index); +// 生成唯一 36 位数字,用于新增记录 ID 赋值,React key 属性等 +export const generateId = () => new Date().getTime().toString(36) + Math.random().toString(36).substring(2, 9); /** * Creates a new tree node object. @@ -617,63 +715,62 @@ export const uniqWith = (arr, fn) => arr.filter((element, index) => arr.findInde * @param {string|null} parent - The key of the parent node, or null if it's a root. * @returns {object} A plain JavaScript object representing the tree node. */ -function createTreeNode(key, name, parent = null, keyMap={}, _raw={}) { - return { - key: key, - title: name, - parent: parent, - icon: _raw?.icon, - iconIndex: _raw?.[keyMap.iconIndex], - _raw: _raw, - children: [], - parentTitle: '', - parentIconIndex: '', - }; +function createTreeNode(key, name, parent = null, keyMap = {}, _raw = {}) { + return { + key: key, + title: name, + parent: parent, + icon: _raw?.icon, + iconIndex: _raw?.[keyMap.iconIndex], + _raw: _raw, + children: [], + parentTitle: '', + parentIconIndex: '', + }; } /** * Builds a tree structure from a flat list of nodes. * @returns {Array} An array of root tree nodes. */ -export const buildTree = (list, keyMap={ rootKeys: [], ignoreKeys: [] }) => { +export const buildTree = (list, keyMap = { rootKeys: [], ignoreKeys: [] }) => { if (!list || list.length === 0) { - return [] + return []; } - const nodeMap = new Map() - const treeRoots = [] + const nodeMap = new Map(); + const treeRoots = []; list.forEach((item) => { - const node = createTreeNode(item[keyMap.key], item[keyMap.name], item[keyMap.parent], keyMap, item) - nodeMap.set(item[keyMap.key], node) - }) + const node = createTreeNode(item[keyMap.key], item[keyMap.name], item[keyMap.parent], keyMap, item); + nodeMap.set(item[keyMap.key], node); + }); list.forEach((item) => { - const node = nodeMap.get(item[keyMap.key]) + const node = nodeMap.get(item[keyMap.key]); if (keyMap.rootKeys.includes(item[keyMap.parent]) || item[keyMap.parent] === null || item[keyMap.parent] === undefined) { // This is a root node - treeRoots.push(node) + treeRoots.push(node); } else { - const parentNode = nodeMap.get(item[keyMap.parent]) + const parentNode = nodeMap.get(item[keyMap.parent]); if (keyMap.ignoreKeys.includes(item[keyMap.parent])) { const grandParentNode = nodeMap.get(parentNode.parent); node.rawParent = node.parent; node.parent = parentNode.parent; node.parentTitle = parentNode.title; node.parentIconIndex = parentNode.iconIndex; - grandParentNode.children.push(node) + grandParentNode.children.push(node); } else if (keyMap.ignoreKeys.includes(item[keyMap.key])) { // - } - else if (parentNode) { + } else if (parentNode) { node.parentTitle = parentNode.title; node.parentIconIndex = parentNode.iconIndex; - parentNode.children.push(node) + parentNode.children.push(node); } else { - console.warn(`Parent with key '${item[keyMap.parent]}' not found for node '${item[keyMap.key]}'. This node will be treated as a root.`) - treeRoots.push(node) + console.warn(`Parent with key '${item[keyMap.parent]}' not found for node '${item[keyMap.key]}'. This node will be treated as a root.`); + treeRoots.push(node); } } - }) + }); - return treeRoots -} + return treeRoots; +};