UNPKG

zgg-ui

Version:

2.0.1 正式版本 基于 Vue 3、UniApp 和 TypeScript 开发的多端适配移动端 UI 组件库,旨在提供丰富的组件集合,帮助开发者快速构建多端响应式的移动应用界面

707 lines (673 loc) 22.3 kB
//全局方法 import { getCurrentInstance, ref } from 'vue' import type { ComponentInternalInstance } from 'vue' import zTest from '../libs/zTest' // #ifdef APP-NVUE const dom = uni.requireNativePlugin('dom') // #endif /** * 节流防抖 */ let timer: ReturnType<typeof setTimeout> | null = null let timeout: ReturnType<typeof setTimeout> | null = null let flag = ref(false) const z = { /**非nvue获取当前的节点信息 */ getDomInfo( selector: string, instance?: ComponentInternalInstance | null ): Promise<UniApp.NodeInfo> { if (!instance) { instance = getCurrentInstance() } let query: UniApp.SelectorQuery | null = null // #ifndef MP-ALIPAY || APP-PLUS query = uni.createSelectorQuery().in(instance) // #endif // #ifdef APP-PLUS query = uni.createSelectorQuery().in((instance as any).ctx.$scope) // #endif // #ifdef MP-ALIPAY query = uni.createSelectorQuery().in(null) // #endif return new Promise((resolve, reject) => { if (query) { query .select(selector) .boundingClientRect((res) => { const selectRes: UniApp.NodeInfo = res as UniApp.NodeInfo if (selectRes) { resolve(selectRes) } else { reject(new Error(`未找到对应节点: ${selector}`)) } }) .exec() } else { reject(new Error('未找到对应的SelectorQuery实例')) } }) }, getDomInfos( selector: string, instance?: ComponentInternalInstance | null ): Promise<UniApp.NodeInfo[]> { return new Promise((resolve, reject) => { if (!instance) { instance = getCurrentInstance() } let query: UniApp.SelectorQuery | null = null // #ifndef MP-ALIPAY || APP-PLUS query = uni.createSelectorQuery().in(instance) // #endif // #ifdef APP-PLUS query = uni.createSelectorQuery().in((instance as any).ctx.$scope) // #endif // #ifdef MP-ALIPAY query = uni.createSelectorQuery().in(null) // #endif if (query) { query .selectAll(selector) .boundingClientRect((res) => { const selectRes: UniApp.NodeInfo[] = res as UniApp.NodeInfo[] if (selectRes && selectRes.length > 0) { resolve(selectRes) } else { reject(new Error(`未找到对应节点: ${selector}`)) } }) .exec() } else { reject(new Error('未找到对应的SelectorQuery实例')) } }) }, /**nvue获取当前节点的信息 */ getDomInfoNvue(ref: any) { return new Promise((resolve) => { dom.getComponentRect(ref, (res: any) => { resolve(res.size) }) }) }, /** * 对象深拷贝 * @param obj 需要深层复制的对象 * @returns newObj 复制的对象 */ deepClone<T>(obj: T): T { if (obj === null || typeof obj !== 'object') { return obj } if (Array.isArray(obj)) { const newArr: any[] = [] for (let i = 0; i < obj.length; i++) { newArr[i] = z.deepClone(obj[i]) } return newArr as T } const newObj: Record<string, any> = {} for (const key in obj) { if (obj.hasOwnProperty(key)) { newObj[key] = z.deepClone(obj[key]) } } return newObj as T }, /** *对象深度合并 * @param target 目标对象 * @param source 合并对象 * @returns target */ deepMerge( target: { [index: string]: any }, source: { [index: string]: any } ) { target = z.deepClone(target) if (typeof target !== 'object' || typeof source !== 'object') return target for (const prop in source) { if (!Object.prototype.hasOwnProperty.call(source, prop)) continue if (prop in target) { if (typeof target[prop] !== 'object') { target[prop] = source[prop] } else { if (typeof source[prop] !== 'object') { target[prop] = source[prop] } else { if (target[prop].concat && source[prop].concat) { target[prop] = target[prop].concat(source[prop]) } else { target[prop] = z.deepMerge(target[prop], source[prop]) } } } } else { target[prop] = source[prop] } } return target }, /** * 判断是否带有单位,若没有则添加px为单位返回 * @param value 参数值 * @returns */ addUnit(value: number | string): string { // 如果传入的值是数字类型,添加 'px' 单位并返回 if (typeof value === 'number') { return `${value}px` } else if (typeof value === 'string') { // 如果传入的是字符串,先尝试将其转换为数字 const numericValue = zTest.number(value) // 如果转换后的结果是有效数字,则添加 'px' 单位并返回 if (numericValue) { return `${value}px` } // 如果转换后的结果不是有效数字,则直接返回原字符串 return value } else { // 其他情况直接返回传入的值的字符串形式 return value } }, /** * 通过传入主题标记(primary,success,error,info,warning),返回背景和颜色class,以背景为颜色对象 * @param value 主题标记 */ getBGColorClass(value: string): string { switch (value) { case 'primary': return 'bgprimary' case 'success': return 'bgsuccess' case 'error': return 'bgerror' case 'info': return 'bginfo' case 'warning': return 'bgwarning' default: return value } }, /** * 通过传入主题标记(primary,success,error,info,warning),返回背景和颜色class,以内容为颜色对象 * @param value 主题标记 */ getColorClass(value: string): string { switch (value) { case 'primary': return 'cprimary' case 'success': return 'csuccess' case 'cerror': return 'cerror' case 'info': return 'cinfo' case 'warning': return 'cwarning' default: return value } }, /** * 阻塞代码,延时效果 * @param value 延时时间 单位ms */ sleep(value = 30): Promise<void> { return new Promise<void>((resolve) => { setTimeout(() => { resolve() }, value) }) }, /** * 判断数据类型 * @param value */ getType(value: any): string { const type = typeof value if (type !== 'object') { return type } if (Array.isArray(value)) { return 'array' } if (value === null) { return 'null' } if (value instanceof Date) { return 'date' } if (value instanceof RegExp) { return 'regexp' } return 'object' }, /** * 字段赋值 * @param obj1 obj2 * 相同字段赋值,obj2赋值给obj1,相同字段覆盖,不同字段不加入obj1 */ assignObject(obj1: any, obj2: any): void { for (let key in obj1) { if (obj2.hasOwnProperty(key)) { obj1[key] = obj2[key] } } }, /** * 节流 * @param func 要执行的回调函数 wait 延时的时间 immediate 是否立即执行 * 在一定时间内,只能触发一次 */ throttle( func: () => void, wait: number = 500, immediate: boolean = true ): void { if (immediate) { if (!flag.value) { flag.value = true // 在wait毫秒内开始时执行 typeof func === 'function' && func() timer = setTimeout(() => { flag.value = false }, wait) } } else if (!flag.value) { flag.value = true // 在wait毫秒内的结束处执行 timer = setTimeout(() => { flag.value = false typeof func === 'function' && func() }, wait) } }, /** * 防抖原理:一定时间内,只有最后一次操作,再过wait毫秒后才执行函数 * * @param func 要执行的回调函数 wait 延时的时间 immediate 是否立即执行 * @return null */ debounce( func: () => void, wait: number = 500, immediate: boolean = false ): void { // 清除定时器 if (timeout !== null) clearTimeout(timeout) // 立即执行,此类情况一般用不到 if (immediate) { const callNow = !timeout timeout = setTimeout(() => { timeout = null }, wait) if (callNow) typeof func === 'function' && func() } else { // 设置定时器,当最后一次操作后,timeout不会再被清除,所以在延时wait毫秒后执行func回调方法 timeout = setTimeout(() => { typeof func === 'function' && func() }, wait) } }, /** * @param str 需要去除空格的字符串 * @param position {'both' | 'left' | 'right' | 'all'} [postion='both'] 去除空格的位置,both(左右)|left|right|all,默认both * @returns {string} 去除空格后的字符串 */ trim( str: string, postion: 'both' | 'left' | 'right' | 'all' = 'both' ): string { str = String(str) if (postion === 'both') { return str.replace(/^\s+|\s+$/g, '') } if (postion === 'left') { return str.replace(/^\s*/, '') } if (postion === 'right') { return str.replace(/\s*$/, '') } if (postion === 'all') { return str.replace(/\s+/g, '') } return str }, /** * 把对象转字符串,或者字符串转对象 * @param customStyle 需要转换的目标 * @param 转换的目的,object-转为对象,string-转为字符串 * @returns {Record<string, string> | string} */ addStyle( customStyle: Record<string, string> | string, target: 'object' | 'string' = 'object' ): Record<string, string> | string { // 字符串转字符串,对象转对象情形,直接返回 if ( !customStyle || (typeof customStyle === 'object' && target === 'object') || (target === 'string' && typeof customStyle === 'string') ) { return customStyle } // 字符串转对象 if (target === 'object') { customStyle = this.trim(customStyle as string) // 将字符串转为数组形式 const styleArray = customStyle.split(';') const style: Record<string, string> = {} // 将数组拼接成对象 for (const item of styleArray) { // 'font-size:20px;color:red;',如此最后字符串有";"的话,会导致styleArray最后一个元素为空字符串,这里需要过滤 if (item) { const [key, value] = item.split(':') if (key && value) { style[this.trim(key)] = this.trim(value) } } } return style } // 对象转字符串 let string = '' for (const key in customStyle as Record<string, string>) { if (customStyle.hasOwnProperty(key)) { // 驼峰转为中划线的形式,否则css内联样式,无法识别驼峰样式属性名 const kebabKey = key.replace(/([A-Z])/g, '-$1').toLowerCase() string += `${kebabKey}:${(customStyle as Record<string, string>)[key]};` } } // 去除两端空格 return this.trim(string) }, /** * 用于获取用户传递值的px值 * @param value 用户传递值的px值 unit 是否返回带单位的字符串,默认为false * @returns 转换后的px值,带单位或不带单位 */ getPx(value: number | string, unit: boolean = false): number | string { if (zTest.number(value)) { return unit ? `${value}px` : Number(value) } // 如果带有rpx,先取出其数值部分,再转为px值 if (/(rpx|upx)$/.test(value.toString())) { const numericValue = parseInt(value.toString()) const pxValue = uni.upx2px(numericValue) return unit ? `${pxValue}px` : pxValue } const numericValue = parseInt(value.toString()) return unit ? `${numericValue}px` : numericValue }, /** * @description 对名字脱敏,用星号替换中间字符 * @param {string} name 需要脱敏的名字 * @returns {string} 脱敏后的名字 */ formatName(name: string): string { let value = '' if (name.length === 2) { value = name.substring(0, 1) + '*' } else if (name.length > 2) { let char = '' for (let i = 0, len = name.length - 2; i < len; i++) { char += '*' } value = name.substring(0, 1) + char + name.substring(name.length - 1) } else { value = name } return value }, /** * @description 数字格式化 * @param {number|string} number 要格式化的数字 * @param {number} [decimals=0] 保留几位小数 * @param {string} [decimalPoint='.'] 小数点符号 * @param {string} [thousandsSeparator=','] 千分位符号 * @returns {string} 格式化后的数字 */ priceFormat( number: number | string, decimals: number = 0, decimalPoint: string = '.', thousandsSeparator: string = ',' ): string { // 将number转换为字符串,并去除非数字、加号、减号、小数点和E/e字符 number = `${number}`.replace(/[^0-9+-Ee.]/g, '') // 将number转换为数字,如果转换失败则默认为0 const n = !isFinite(+number) ? 0 : +number // 保留的小数位数,转换为绝对值并确保其为有限数字 const prec = !isFinite(+decimals) ? 0 : Math.abs(decimals) // 千分位符号 const sep = thousandsSeparator // 小数点符号 const dec = decimalPoint let s: string[] = [] // 对数字进行四舍五入处理,并转化为字符串 const round = (num: number, precision: number): number => { const factor = Math.pow(10, precision) return Math.round(num * factor) / factor } s = (prec ? round(n, prec).toString() : Math.round(n).toString()).split('.') // 正则表达式匹配负号和千分位 const re = /(-?\d+)(\d{3})/ while (re.test(s[0])) { s[0] = s[0].replace(re, `$1${sep}$2`) } // 如果小数位数不足,补全0 if ((s[1] || '').length < prec) { s[1] = s[1] || '' s[1] += new Array(prec - s[1].length + 1).join('0') } // 返回格式化后的字符串 return s.join(dec) }, /** * @description 格式化时间 * @param {string | number | null} dateTime 需要格式化的时间戳 * @param {string} formatStr 格式化规则 yyyy:mm:dd|yyyy:mm|yyyy年mm月dd日|yyyy年mm月dd日 hh时MM分等,可自定义组合 默认yyyy-mm-dd * @returns {string} 返回格式化后的字符串 */ timeFormat( dateTime: string | number | null = null, formatStr: string = 'yyyy-mm-dd' ): string { let date: Date // 若传入时间为假值,则取当前时间 if (!dateTime) { date = new Date() } // 若为unix秒时间戳,则转为毫秒时间戳(逻辑有点奇怪,但不敢改,以保证历史兼容) else if ( typeof dateTime === 'number' && /^\d{10}$/.test(dateTime.toString().trim()) ) { date = new Date(dateTime * 1000) } // 若用户传入字符串格式时间戳,new Date无法解析,需做兼容 else if (typeof dateTime === 'string' && /^\d+$/.test(dateTime.trim())) { date = new Date(Number(dateTime)) } // 处理平台性差异,在Safari/Webkit中,new Date仅支持/作为分割符的字符串时间 // 处理 '2022-07-10 01:02:03',跳过 '2022-07-10T01:02:03' else if ( typeof dateTime === 'string' && dateTime.includes('-') && !dateTime.includes('T') ) { date = new Date(dateTime.replace(/-/g, '/')) } // 其他都认为符合 RFC 2822 规范 else { date = new Date(dateTime) } const timeSource: { [key: string]: string } = { y: date.getFullYear().toString(), // 年 m: (date.getMonth() + 1).toString().padStart(2, '0'), // 月 d: date.getDate().toString().padStart(2, '0'), // 日 h: date.getHours().toString().padStart(2, '0'), // 时 M: date.getMinutes().toString().padStart(2, '0'), // 分 s: date.getSeconds().toString().padStart(2, '0'), // 秒 // 有其他格式化字符需求可以继续添加,必须转化成字符串 } for (const key in timeSource) { const match = new RegExp(`${key}+`).exec(formatStr) if (match) { const [ret] = match // 年可能只需展示两位 const beginIndex = key === 'y' && ret.length === 2 ? 2 : 0 formatStr = formatStr.replace(ret, timeSource[key].slice(beginIndex)) } } return formatStr }, /** * @description console.error提示 * @param {*} err 错误内容 */ error(err: any) { // 开发环境才提示,生产环境不会提示 if (process.env.NODE_ENV === 'development') { console.error(`错误提示:${err}`) } }, /** * 从对象中获取属性,支持通过点分割的键路径访问(例如 'a.b.c')。 * @param obj 要获取属性的对象。 * @param key 属性键,使用点分割表示多层属性访问路径。 * @returns 属性的值,如果找不到则返回 undefined。 */ getProperty(obj: any, key: string): any { if (!obj) { return undefined } if (typeof key !== 'string' || key === '') { return undefined } if (key.includes('.')) { const keys = key.split('.') let currentObj = obj for (let i = 0; i < keys.length; i++) { if (!currentObj) { return undefined } currentObj = currentObj[keys[i]] } return currentObj } else { return obj[key] } }, /** * 设置对象的属性值,支持通过点分割的键路径进行设置。 * 如果路径不存在,将逐层创建对象。 * @param obj 要设置属性的对象。 * @param key 属性键,使用点分割表示多层属性访问路径。 * @param value 要设置的值。 */ setProperty(obj: any, key: string, value: any): void { if (!obj) { return } const setValue = function (_obj: any, keys: string[], v: any): void { if (keys.length === 1) { _obj[keys[0]] = v return } const k = keys[0] if (!_obj[k] || typeof _obj[k] !== 'object') { _obj[k] = {} } setValue(_obj[k], keys.slice(1), v) } if (typeof key !== 'string' || key === '') { return } else if (key.includes('.')) { const keys = key.split('.') setValue(obj, keys, value) } else { obj[key] = value } }, /** * 显示消息提示框 * @param title 提示的内容,长度与 icon 取值有关。 * @param duration 提示的延迟时间,单位毫秒,默认:2000 */ toast(title: string, duration: number = 2000): void { uni.showToast({ title: String(title), icon: 'none', duration, }) }, /** * 检查变量是否为 undefined 或 null 或 '',如果是,则返回默认值;否则返回变量本身。 * * @param variable - 要检查的变量。 * @param defaultValue - 如果变量为 undefined 或 null 或 '' 时返回的默认值。默认值为 undefined。 * @returns 如果变量为 undefined 或 null 或 '',则返回 defaultValue;否则返回变量。 */ isEmptyVariableInDefault: <T = any>( variable: any, defaultValue: any = undefined ): T => { return variable === undefined || variable === null || variable === '' ? defaultValue : variable }, /** * 检查两个变量中的一个是否为 undefined 或 null 或 ''。如果第一个变量为 undefined 或 null 或 '',则检查第二个变量; * 如果第二个变量也为 undefined 或 null 或 '',则返回默认值;否则返回第一个不为 undefined 或 null 或 '' 的变量。 * * @param variable1 - 要检查的第一个变量。 * @param variable2 - 要检查的第二个变量。 * @param defaultValue - 如果两个变量都为 undefined 或 null 或 '' 时返回的默认值。默认值为 undefined。 * @returns 如果第一个变量为 undefined 或 null 或 '',则返回第二个变量的值(如果第二个变量也为 undefined 或 null 或 '',则返回 defaultValue);否则返回第一个变量。 */ isEmptyDoubleVariableInDefault: <T = any>( variable1: any, variable2: any, defaultValue: any = undefined ): T => { return z.isEmptyVariableInDefault( variable1, z.isEmptyVariableInDefault(variable2, defaultValue) ) }, /** * 截取指定长度的数值 * @param value 待截取的数值 * @param len 截取的长度 * @param prefixZero 如果只有一位是否添加0 * @returns 截取后的数值字符串 */ formatNumber(value: string | number, len = 2, prefixZero = true): string { let number: number | string = 0 // 判断传入的值是什么类型 if (typeof value === 'string') { // 如果为空字符串直接返回 if (value === '') return value number = Number(value) } else if (typeof value === 'number') { number = value } if (Number.isNaN(number) || number === 0) return prefixZero ? '00' : '0' const maxNumber = Math.pow(10, len) - 1 if (number > maxNumber) return `${maxNumber}+` number = String(number) return prefixZero ? `00${number}`.slice(Math.max(0, number.length > 2 ? 2 : number.length)) : number }, } export default z