UNPKG

sard-uniapp

Version:

sard-uniapp 是一套基于 Uniapp + Vue3 框架开发的兼容多端的 UI 组件库

668 lines (667 loc) 20.4 kB
import { toKebabCase } from './case'; import { isPlainObject, isPrimitive } from './is'; /** * @description: 确保目标是一个数组 * @param {any} target * @return {array} */ export function toArray(target) { return Array.isArray(target) ? target : [target]; } /** * @description: 限定数值范围 * @param {number} n 被限定的值 * @param {number} min 最小值 * @param {number} max 最大值 * @return {number} 限定后的值 */ export function minmax(n, min, max) { return n < min ? min : n > max ? max : n; } /** * @description: 获取小数位数 * @param {number | string} n 要操作的数值 * @return {number} */ export function getDecimalsLength(n) { n = n.toString().split('.')[1]; return n ? n.length : 0; } /** * @description: 把一个数四舍五入到指定位数小数 * @param {number} n 要操作的数值 * @param {number} precision 精准度,即小数个数 * @return {number} */ export function round(n, precision = 0) { return Math.round(+(n + 'e' + precision)) / 10 ** precision; } /** * @description: 把一个数舍入到指定数的倍数 * @param {number} n 要舍入的数值 * @param {number} m 结果值的因数 * @return {number} */ export function mround(n, m) { return round(n - (n % m) + Math.round((n % m) / m) * m, getDecimalsLength(m)); } /** * @description: 生成唯一ID,用于设置元素的ID,以便获取 * @param {string} prefix * @return {string} */ export function uniqid(prefix = '__sar_') { return prefix + (~~(Math.random() * 10e8)).toString(36); } /** * @description: 获取阻尼值 * @param {number} value * @param {number} min * @param {number} max * @param {number} damping * @return {number} */ export function getDampingValue(value, min, max, damping) { if (value < min) { return min + (value - min) * damping; } if (value > max) { return max + (value - max) * damping; } return value; } /** * @description: 获取矩形阻尼值 * @param {number} offset 当前的偏移量 * @param {number} areaSize 范围值 * @param {number} viewSize 范围内某个视图尺寸值 * @param {number} damping 阻尼系数 * @return {number} */ export function getRectDampingValue(offset, areaSize, viewSize, damping) { const diff = areaSize - viewSize; let min, max; if (diff < 0) { min = diff; max = 0; } else { min = 0; max = diff; } return getDampingValue(offset, min, max, damping); } /** * @description: 获取范围值 * @param {number} offset 当前的偏移量 * @param {number} areaSize 范围值 * @param {number} viewSize 范围内某个视图尺寸值 * @return {number} */ export function getInBoundValue(offset, areaSize, viewSize) { const diff = areaSize - viewSize; const [min, max] = [0, diff].sort((a, b) => a - b); return minmax(offset, min, max); } /** * @description: 获取溢出值范围 * @param {number} overflow 最大溢出值 * @param {number} areaSize 范围值 * @param {number} viewSize 范围内某个视图尺寸值 * @return {*} */ export function getOverflowRangeInArea(overflow, areaSize, viewSize) { if (areaSize > viewSize) { return [-overflow, areaSize + overflow]; } else { return [areaSize - viewSize - overflow, overflow]; } } /** * @description: 扩散性遍历 * @param {any[]} array 要遍历的数组 * @param {(el: any, spreadIndex: number, index: number) => any} callback 回调函数,接收当前元素、扩散性下标、迭代下标作为参数, * 如果返回true,则中止遍历 * @param {number} currIndex 遍历开始的下标 * @param {number} direction 开始遍历的方向 * @return {number} 扩散性下标、或开始下标 */ export function spreadEach(array, callback, startIndex = 0, direction = -1) { const len = array.length; let spreadIndex = startIndex; let edge = 0; direction = -direction; for (let i = 0; i < len; i++) { if (edge < 0) { spreadIndex = len - 1 - i; } else if (edge > 0) { spreadIndex = i; } else { spreadIndex = spreadIndex + direction * i; edge = spreadIndex === 0 ? 1 : spreadIndex === len - 1 ? -1 : 0; direction = -direction; } if (typeof callback === 'function') { if (callback(array[spreadIndex], spreadIndex, i)) { return spreadIndex; } } } return startIndex; } /** * @description: 深度克隆对象,仅克隆数组和无格式对象,其他类型会被直接返回 * @param {any} target * @return {any} */ export function deepClone(target) { if (Array.isArray(target)) { return target.map((item) => { return deepClone(item); }); } if (isPlainObject(target)) { const obj = {}; Object.keys(target).forEach((k) => { obj[k] = deepClone(target[k]); }); return obj; } return target; } /** * @description: 深拷贝其他对象到第一个对象 * @param args 任意长度参数对象 * @return 返回第一个参数对象 */ export function extend(...args) { const target = args[0], l = args.length; let i = 1, options, name, src, copy, copyIsArray, clone; for (; i < l; i++) { options = args[i]; if (options !== null && options !== undefined) { for (name in options) { src = target[name]; copy = options[name]; // 防止有环 if (target === copy) { continue; } // 深复制 if (copy && (isPlainObject(copy) || (copyIsArray = Array.isArray(copy)))) { if (copyIsArray) { copyIsArray = false; clone = src && Array.isArray(src) ? src : []; } else { clone = isPlainObject(src) ? src : {}; } // 只克隆对象,不移动 target[name] = extend(clone, copy); // 不添加未定义的值 } else if (copy !== undefined) { target[name] = copy; } } } } return target; } /** * @description: 判断两数组是否相等,浅比较 * @param {any[]} arr1 * @param {any[]} arr2 * @return {boolean} */ export function arrayEqual(arr1, arr2) { return arr1.length === arr1.length && arr1.every((el, i) => el === arr2[i]); } /** * @description: 判断一个对象是否为看得到的空 * @param {any} target * @return {boolean} */ export function isVisibleEmpty(target) { return (target === null || target === undefined || (typeof target === 'string' && target.trim() === '')); } /** * @description: 判断一个值是否为空 * @param {any} value * @return {boolean} */ export function isEmptyValue(value, whitespace = true) { return (value === null || value === undefined || value === '' || (typeof value === 'string' && !whitespace && value.trim() === '') || (Array.isArray(value) && value.length === 0)); } /** * @description: 判断一个值渲染到DOM中时是否可见 * @param {any} value * @return {boolean} */ export function isRenderVisible(value) { return (value !== null && value !== undefined && (typeof value !== 'string' || value.trim() !== '')); } /** * @description: 判断是否绑定为空值 * @param {any} target * @return {boolean} */ export function isEmptyBinding(target) { return target === null || target === undefined || target === ''; } export function debounce(func, wait, options = {}) { let lastArgs, lastThis, maxWait, result, timerId, lastCallTime; let lastInvokeTime = 0; let leading = false; let maxing = false; let trailing = true; // Bypass `requestAnimationFrame` by explicitly setting `wait=0`. const useRAF = !wait && wait !== 0 && typeof requestAnimationFrame === 'function'; if (typeof func !== 'function') { throw new TypeError('Expected a function'); } wait = +wait || 0; if (isPlainObject(options)) { leading = !!options.leading; maxing = 'maxWait' in options; maxWait = maxing ? Math.max(+options.maxWait || 0, wait) : maxWait; trailing = 'trailing' in options ? !!options.trailing : trailing; } function invokeFunc(time) { const args = lastArgs; const thisArg = lastThis; lastArgs = lastThis = undefined; lastInvokeTime = time; result = func.apply(thisArg, args || []); return result; } function startTimer(pendingFunc, wait) { if (useRAF) { cancelAnimationFrame(timerId); return requestAnimationFrame(pendingFunc); } return setTimeout(pendingFunc, wait); } function cancelTimer(id) { if (useRAF) { return cancelAnimationFrame(id); } clearTimeout(id); } function leadingEdge(time) { // Reset any `maxWait` timer. lastInvokeTime = time; // Start the timer for the trailing edge. timerId = startTimer(timerExpired, wait); // Invoke the leading edge. return leading ? invokeFunc(time) : result; } function remainingWait(time) { const timeSinceLastCall = time - lastCallTime; const timeSinceLastInvoke = time - lastInvokeTime; const timeWaiting = wait - timeSinceLastCall; return maxing ? Math.min(timeWaiting, maxWait - timeSinceLastInvoke) : timeWaiting; } function shouldInvoke(time) { const timeSinceLastCall = time - lastCallTime; const timeSinceLastInvoke = time - lastInvokeTime; // Either this is the first call, activity has stopped and we're at the // trailing edge, the system time has gone backwards and we're treating // it as the trailing edge, or we've hit the `maxWait` limit. return (lastCallTime === undefined || timeSinceLastCall >= wait || timeSinceLastCall < 0 || (maxing && timeSinceLastInvoke >= maxWait)); } function timerExpired() { const time = Date.now(); if (shouldInvoke(time)) { return trailingEdge(time); } // Restart the timer. timerId = startTimer(timerExpired, remainingWait(time)); } function trailingEdge(time) { timerId = undefined; // Only invoke if we have `lastArgs` which means `func` has been // debounced at least once. if (trailing && lastArgs) { return invokeFunc(time); } lastArgs = lastThis = undefined; return result; } function cancel() { if (timerId !== undefined) { cancelTimer(timerId); } lastInvokeTime = 0; lastArgs = lastCallTime = lastThis = timerId = undefined; } function flush() { return timerId === undefined ? result : trailingEdge(Date.now()); } function pending() { return timerId !== undefined; } function debounced(...args) { const time = Date.now(); const isInvoking = shouldInvoke(time); lastArgs = args; lastThis = this; lastCallTime = time; if (isInvoking) { if (timerId === undefined) { return leadingEdge(lastCallTime); } if (maxing) { // Handle invocations in a tight loop. timerId = startTimer(timerExpired, wait); return invokeFunc(lastCallTime); } } if (timerId === undefined) { timerId = startTimer(timerExpired, wait); } return result; } debounced.cancel = cancel; debounced.flush = flush; debounced.pending = pending; return debounced; } export function throttle(func, wait, options = {}) { let leading = true; let trailing = true; if (typeof func !== 'function') { throw new TypeError('Expected a function'); } if (isPlainObject(options)) { leading = 'leading' in options ? !!options.leading : leading; trailing = 'trailing' in options ? !!options.trailing : trailing; } return debounce(func, wait, { leading, trailing, maxWait: wait, }); } /** * @description: 获取页面范围 * @param {number} current 当前页码 * @param {number} pageCount 总页数 * @param {number} pageItemCount 要展示的页数 * @return {[number, number]} */ export function getPageRange(current, pageCount, pageItemCount) { let min = current - Math.ceil((pageItemCount - 1) / 2); let max = current + Math.floor((pageItemCount - 1) / 2); const minLack = 1 - min; const maxLack = max - pageCount; if (maxLack > 0) { min -= maxLack; } if (min < 1) { min = 1; } if (minLack > 0) { max += minLack; } if (max > pageCount) { max = pageCount; } return [min, max]; } export function treeToMap(tree, keyName, childrenName, parentName) { const map = {}; function recurse(children, parent) { children.forEach((node) => { map[node[keyName]] = node; node[parentName] = parent; if (Array.isArray(node[childrenName])) { recurse(node[childrenName], node); } }); } recurse(tree, null); return map; } /** * @description: 打乱数组 * @param {any[]} arr 要打乱的数组 * @param {boolean} inPlace 是否改变原数组 * @return {any[]} */ export function shuffle(arr, inPlace = false) { if (!inPlace) { arr = arr.slice(); } const len = arr.length; for (let i = len - 1; i >= 0; i--) { const randomIndex = ~~(Math.random() * (i + 1)); const temp = arr[randomIndex]; arr[randomIndex] = arr[i]; arr[i] = temp; } return arr; } /** * @description: 根据原始坐标尺寸和缩放后的坐标尺寸算出转换的原点 * @param {Rect} rect * @param {Rect} scaleRect * @return {[number, number]} */ export function getTransformOrigin(rect, scaleRect) { const ratio = scaleRect.width / rect.width; const originX = (rect.x + rect.width / 2 - scaleRect.x - scaleRect.width / 2) / (ratio - 1) + rect.width / 2; const originY = (rect.y + rect.height / 2 - scaleRect.y - scaleRect.height / 2) / (ratio - 1) + rect.height / 2; return [originX, originY]; } /** * @description: 将一个可选单位的字符串或数值拆分为数值和单位组成的数组 * @param {number | string} target * @return {[number, string]} */ export function splitUnit(target) { const result = /([+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|))([a-z]+|%|)$/i.exec(String(target)) || [0, '']; return [+result[1], result[2]]; } /** * @description: 链式获取对象值 * @param object * @param chain 通过点分割的字符串或者字符串数组 */ export function chainGet(object, chain) { let target = object; if (chain) { chain = typeof chain === 'string' ? chain.split('.') : chain; for (const key of chain) { if (target && typeof target === 'object') { target = target[key]; } else { return target; } } } return target; } /** * @description: 链式设置对象值 * @param object * @param chain 通过点分割的字符串或者字符串数组 * @param value 要设置的值 */ export function chainSet(object, chain, value) { let target = object; chain = typeof chain === 'string' ? chain.split('.') : chain; if (chain.length === 0) { return; } for (let i = 0, l = chain.length; i < l; i++) { if (target && typeof target === 'object') { const key = chain[i]; if (i === l - 1) { target[key] = value; return; } else { target = target[key]; } } else { return; } } } /** * @description: 移动数组中的元素 * @param array * @param fromIndex * @param toIndex * @return 移动后的新数组 */ export function arrayMove(array, fromIndex, toIndex) { if (fromIndex === toIndex || fromIndex < 0 || fromIndex >= array.length || toIndex < 0 || toIndex >= array.length) { return array; } const fromValue = array[fromIndex]; if (fromIndex > toIndex) { return [ ...array.slice(0, fromIndex), ...array.slice(fromIndex + 1, toIndex + 1), fromValue, ...array.slice(toIndex + 1), ]; } else { return [ ...array.slice(0, toIndex), fromValue, ...array.slice(toIndex, fromIndex), ...array.slice(fromIndex + 1), ]; } } // 把对象拼接成字符串,解决小程序不支持styleObject的问题 export function stringifyStyle(...args) { let result = ''; for (let i = 0; i < args.length; i++) { const arg = args[i]; if (!arg) continue; if (typeof arg === 'string') { result += arg + ';'; } else if (Array.isArray(arg)) { if (arg.length) { const string = stringifyStyle(...arg); if (string) { result += string + ';'; } } } else if (typeof arg === 'object') { for (const key in arg) { const value = arg[key]; if (value || value === 0) { result += `${toKebabCase(key)}:${value};`; } } } } return result; } // 把各种类型的参数拼接成字符串类名,以便小程序不支持classObject的问题 export function classNames(...args) { let result = ''; for (let i = 0; i < args.length; i++) { const arg = args[i]; if (!arg) continue; if (typeof arg === 'string' || typeof arg === 'number') { result += arg + ' '; } else if (Array.isArray(arg)) { if (arg.length) { const className = classNames(...arg); if (className) { result += className + ' '; } } } else if (typeof arg === 'object') { for (const key in arg) { const value = arg[key]; if (value) { result += key + ' '; } } } } return result; } export const noop = () => { }; // 将嵌套数据结构转换为多维数组 export function nestedToMulti(nested, values, fieldKeys) { const columns = []; function recurse(list, index = 0) { columns.push(list); const selectedValue = values[index]; let selectedOption = list.find((option) => option[fieldKeys.value] === selectedValue); if (!selectedOption) { selectedOption = list[0]; } if (selectedOption) { const nextList = selectedOption[fieldKeys.children]; if (Array.isArray(nextList)) { recurse(nextList, ++index); } } } recurse(nested); return columns; } // 生成两数间的一个随机整数 export function random(min, max) { return Math.round(min + Math.random() * (max - min)); } // 每n位数字添加一个分隔符 export function addSeparator(num, separator = ',', digit = 3) { return String(num).replace(new RegExp(`\\B(?=(\\d{${digit}})+(?!\\d))`, 'g'), separator); } export function getDistanceBetweenTwoPoints(c1, c2) { return Math.sqrt(Math.pow(c1.x - c2.x, 2) + Math.pow(c1.y - c2.y, 2)); } export function getMayPrimitiveOption(option, key) { return isPrimitive(option) ? option : option[key]; } export async function sleep(time) { return new Promise((resolve) => { setTimeout(resolve, time); }); }