import { Record } from "./type"; const useClipboard = window.isSecureContext && 0; /** * 深度获取 * @param dataIndex string | string[] * @param data Object * @returns unknow */ export function getValueByModel(dataIndex: string| string[], data: Record): any { if(!data) return data; if (Array.isArray(dataIndex) && dataIndex.length === 1) { return data[dataIndex[0]]; } else if(Array.isArray(dataIndex)){ let key = dataIndex[0]; return getValueByModel(dataIndex.slice(1), data[key]); } else{ return data[dataIndex]; } } /** * 深度设置 * @param dataIndex string | string[] * @param data Object * @param newValue unknow * @returns */ export function setValueByModel (dataIndex: string| string[], data: Record, newValue: unknown) { if(Array.isArray(dataIndex) && dataIndex.length === 1) { data[dataIndex[0]] = newValue; } else if(Array.isArray(dataIndex)){ let key = dataIndex[0] if (data[key] === undefined) data[key] = {} // 自动填补数据层级,对深层级的 赋值 或 粘贴 场景及其重要! data[key] = setValueByModel(dataIndex.slice(1), data[key], newValue); } else{ data[dataIndex] = newValue; } return data; } /** * @param {*} fn 要执行的函数 * @param {*} interval 时间间隔 * @param {*} options 可选参数: leading第一次是否执行, trailing 最后一次是否执行 * @returns */ export function throttle( fn: Function, interval: number, options: { leading: boolean, trailing: boolean } = { leading: true, trailing: true } ) { // 1.记录上一次的开始时间 const { leading,trailing} = options let lastTime = 0 let timer: any = null // 2.事件触发时, 真正执行的函数 const _throttle = function (...args: any[]) { return new Promise((resolve, reject) => { //promise方式返回返回值 // 2.1.获取当前事件触发时的时间 const nowTime = new Date().getTime() if (!lastTime && !leading) lastTime = nowTime // 2.2.使用当前触发的时间和之前的时间间隔以及上一次开始的时间, 计算出还剩余多长事件需要去触发函数 const remainTime = interval - (nowTime - lastTime) if (remainTime <= 0) { if (timer) { clearTimeout(timer) timer = null } // 2.3.真正触发函数 const result = fn.apply(this, args) resolve(result) // 2.4.保留上次触发的时间 lastTime = nowTime return } if (trailing && !timer) { timer = setTimeout(() => { const result = fn.apply(this, args) resolve(result) timer = null lastTime = !leading ? 0 : new Date().getTime() }, remainTime) } }) } _throttle.cancel = function () { if (timer) clearTimeout(timer) timer = null lastTime = 0 } return _throttle } /** * 数据深拷贝 * @param obj 拷贝对象 * @returns */ export function deepCopy(obj: any) { if (obj === null || typeof obj !== 'object') { return obj; } if (obj instanceof Date) { return new Date(obj.getTime()); } if (obj instanceof Array) { return obj.reduce((arr, item, i) => { arr[i] = deepCopy(item); return arr; }, []); } if (obj instanceof Object) { return Object.keys(obj).reduce((newObj: any, key) => { newObj[key] = deepCopy(obj[key]); return newObj; }, {}); } } export function getCopyPasteTextarea(): HTMLTextAreaElement { const id = 'copy-paste-textarea' let textarea = document.querySelector('#' + id) as HTMLTextAreaElement | null; if (!textarea) { // 创建输入框 textarea = document.createElement('textarea'); textarea.id = id; textarea.style.position = 'fixed' textarea.style.width = '1px' textarea.style.height = '1px' textarea.style.opacity = '0' textarea.style.zIndex = '-1' textarea.style.transform = 'translate3d(500vw, 500vh, 0)' document.body.appendChild(textarea); } return textarea; } export function awaitTime(time: number) { return new Promise((reslove) => { setTimeout(() => reslove(true), time) }) } /** * 剪切板写入 * @param text * @returns */ export async function writeTextToClipboard (text: string) { // console.group("粘贴板写入数据") try{ useClipboard ? await navigator.clipboard.writeText(text) : await Promise.reject(); // console.info("粘贴板写入成功") } catch(e) { // 创建输入框 const textarea = getCopyPasteTextarea(); textarea.value = text textarea.select() await awaitTime(0) // 复制命令 const result = document.execCommand('copy', true) // console.info("execCommand copy 执行: ", result) } finally { // console.groupEnd() } } /** * 读取剪切板 * @returns */ export async function readTextToClipboard(){ // console.group("粘贴板读取数据") const currentActiveElement: Element | null = document.activeElement try { const clipText = useClipboard ? await navigator.clipboard.readText() : await Promise.reject(); // console.info('clipboard', clipText) return clipText; } catch(e) { // 创建输入框 const textarea = getCopyPasteTextarea(); textarea?.select() await awaitTime(0) // 粘贴命令 // const result = document.execCommand('paste', false) const text: string = textarea.value; // console.info('execCommand paste 执行: ', result, text) const warpSplit = "\r"; const enterSplit = "\n"; return text.replace(new RegExp(enterSplit, 'g'), warpSplit) } finally { // console.groupEnd() currentActiveElement?.select?.(); } } /** * 验证小位数 * @param value 验证值 * @param options { maxDecimal 最大位数, minDecimal 最小位数, message 错误提示 } */ export const validatorDecimal = function ( value: string | number, options: { maxDecimal: number; minDecimal?: number; message: string | ((value: string | number) => string); }, ): Promise { const { maxDecimal, minDecimal, message } = options; const getMessage = (): string => { if (typeof message === 'string') return message; return message(value); }; let decimal: number = 0; const splitValue: string[] = String(value).split('.'); if (splitValue.length > 1) { decimal = splitValue.at(-1)?.length ?? 0; } if (maxDecimal !== undefined && decimal > maxDecimal) return Promise.reject(getMessage()); if (minDecimal !== undefined && decimal < minDecimal) return Promise.reject(getMessage()); return Promise.resolve(); }; /** * 判断是否需要显示提示 * @param textElement 装载文本容器 * @param parentElement 防止溢出装载文本容器的父容器 * @returns */ export const computedOpenTooltipVisible = (textElement?: HTMLElement, parentElement?: HTMLElement, offset: number = 0) => { if(!textElement || !parentElement) return true; return textElement.clientWidth > parentElement.clientWidth; } export const isUndeOrNull = (value: unknown) => { return value === undefined || value === null; } /** * 保留小数位, 不足小数位将自动补0(截断) * @param value 值 * @param precision 小数位 * @returns */ export function splitFillPrecision(value: unknown, precision?: number){ try { if (isUndeOrNull(precision)) return value; if (isUndeOrNull(value) || value === '') return value; const arr = String(value).split('.'); // 整数 if (precision === 0) return arr[0]; const fill = new Array(precision).fill(0) if (arr[1]) { const afters = arr[1]?.split(''); fill.forEach((_, i) => { if (afters[i]) fill[i] = afters[i]; }) } return arr[0] + '.' + fill.join(''); } catch(e) { return value; } } /** * 保留小数位,不足小数位将自动补0(四舍五入) * @param value 值 * @param precision 小数位 * @returns */ export function toFixedFillPrecision(value: unknown, precision?: number){ if (isUndeOrNull(precision)) return value; if (isUndeOrNull(value) || value === '') return value; return Number(value).toFixed(precision); } /** * 事件委派(事件委托)封装函数 * @param {HTMLElement|string} parent - 父元素(可以是DOM元素或选择器字符串) * @param {string} selector - 目标子元素的CSS选择器(如 '.btn'、'li') * @param {string} eventType - 事件类型(如 'click'、'mouseover') * @param {Function} handler - 事件触发后的回调函数(this指向匹配的子元素,参数为事件对象) * @param {AddEventListenerOptions|boolean} [options=false] - 事件配置(可选,如 { capture: true, once: true }) * @returns {Function} - 取消事件监听的函数 */ export function delegateEvent( parent: string | HTMLElement | Element, selector: string, eventType: MouseEvent['type'], handler: (target: HTMLElement | Element, e: MouseEvent) => void, options: boolean = false ) { // 处理父元素:如果是字符串选择器,转为DOM元素 const parentEl = typeof parent === 'string' ? document.querySelector(parent) : parent; // 校验参数合法性 if (!parentEl) { console.error('父元素不存在,请检查选择器或DOM元素'); return () => {}; // 返回空函数,避免调用取消函数时报错 } if (typeof selector !== 'string' || selector.trim() === '') { console.error('目标子元素选择器必须是非空字符串'); return () => {}; } if (typeof handler !== 'function') { console.error('回调函数必须是function类型'); return () => {}; } // 兼容不同浏览器的matches方法 const matches = function (el, sel) { return ( el.matches || el.webkitMatchesSelector || el.mozMatchesSelector || el.msMatchesSelector ).call(el, sel); }; // 事件处理函数:核心的委派逻辑 const eventHandler = function (e) { // 从事件目标开始,向上遍历,直到找到匹配selector的元素或父元素 let target = e.target; while (target && target !== parentEl) { if (matches(target, selector)) { // 将回调函数的this绑定到匹配的子元素,参数传入事件对象 handler(target, e); break; // 找到匹配元素后停止遍历 } target = target.parentNode; } }; // 给父元素绑定事件 parentEl.addEventListener(eventType, eventHandler, options); // 返回取消监听的函数,方便外部手动销毁 return function () { parentEl.removeEventListener(eventType, eventHandler, options); }; } /** * 是否是一个数字,包括字符串类型的数字 * @param val 检测的值 * @returns */ export function isNumber(val: unknown) { if (isUndeOrNull(val) || typeof val === 'boolean') return false if (typeof val === 'string' && val.trim() === '' ) return false return !isNaN(Number(val)) }