uview-plus组件库全面升级更新,订单结算判断支付方式是否可用代码调整,公众号关注二维码修改

This commit is contained in:
2025-10-21 10:44:31 +08:00
parent 5d98b7efc2
commit 5f3a307fec
395 changed files with 31264 additions and 2477 deletions

View File

@@ -0,0 +1,42 @@
// @ts-nocheck
import {isNumeric} from '../isNumeric'
import {isDef} from '../isDef'
/**
* 给一个值添加单位(像素 px
* @param value 要添加单位的值,可以是字符串或数字
* @returns 添加了单位的值,如果值为 null 则返回 null
*/
// #ifndef UNI-APP-X && APP
export function addUnit(value?: string | number): string | null {
if (!isDef(value)) {
return null;
}
value = String(value); // 将值转换为字符串
// 如果值是数字,则在后面添加单位 "px",否则保持原始值
return isNumeric(value) ? `${value}px` : value;
}
// #endif
// #ifdef UNI-APP-X && APP
function addUnit(value: string): string|null
function addUnit(value: number): string|null
function addUnit(value: string|number|null): string|null {
if (!isDef(value)) {
return null;
}
value = `${value}` //value.toString(); // 将值转换为字符串
// 如果值是数字,则在后面添加单位 "px",否则保持原始值
return isNumeric(value) ? `${value}px` : value;
}
export {addUnit}
// #endif
// console.log(addUnit(100)); // 输出: "100px"
// console.log(addUnit("200")); // 输出: "200px"
// console.log(addUnit("300px")); // 输出: "300px"(已经包含单位)
// console.log(addUnit()); // 输出: undefined值为 undefined
// console.log(addUnit(null)); // 输出: undefined值为 null

View File

@@ -0,0 +1,82 @@
export function cubicBezier(p1x : number, p1y : number, p2x : number, p2y : number):(x: number)=> number {
const ZERO_LIMIT = 1e-6;
// Calculate the polynomial coefficients,
// implicit first and last control points are (0,0) and (1,1).
const ax = 3 * p1x - 3 * p2x + 1;
const bx = 3 * p2x - 6 * p1x;
const cx = 3 * p1x;
const ay = 3 * p1y - 3 * p2y + 1;
const by = 3 * p2y - 6 * p1y;
const cy = 3 * p1y;
function sampleCurveDerivativeX(t : number) : number {
// `ax t^3 + bx t^2 + cx t` expanded using Horner's rule
return (3 * ax * t + 2 * bx) * t + cx;
}
function sampleCurveX(t : number) : number {
return ((ax * t + bx) * t + cx) * t;
}
function sampleCurveY(t : number) : number {
return ((ay * t + by) * t + cy) * t;
}
// Given an x value, find a parametric value it came from.
function solveCurveX(x : number) : number {
let t2 = x;
let derivative : number;
let x2 : number;
// https://trac.webkit.org/browser/trunk/Source/WebCore/platform/animation
// first try a few iterations of Newton's method -- normally very fast.
// http://en.wikipedia.org/wikiNewton's_method
for (let i = 0; i < 8; i++) {
// f(t) - x = 0
x2 = sampleCurveX(t2) - x;
if (Math.abs(x2) < ZERO_LIMIT) {
return t2;
}
derivative = sampleCurveDerivativeX(t2);
// == 0, failure
/* istanbul ignore if */
if (Math.abs(derivative) < ZERO_LIMIT) {
break;
}
t2 -= x2 / derivative;
}
// Fall back to the bisection method for reliability.
// bisection
// http://en.wikipedia.org/wiki/Bisection_method
let t1 = 1;
/* istanbul ignore next */
let t0 = 0;
/* istanbul ignore next */
t2 = x;
/* istanbul ignore next */
while (t1 > t0) {
x2 = sampleCurveX(t2) - x;
if (Math.abs(x2) < ZERO_LIMIT) {
return t2;
}
if (x2 > 0) {
t1 = t2;
} else {
t0 = t2;
}
t2 = (t1 + t0) / 2;
}
// Failure
return t2;
}
return function (x : number) : number {
return sampleCurveY(solveCurveX(x));
}
// return solve;
}

View File

@@ -0,0 +1,3 @@
import {cubicBezier} from './bezier';
export let ease = cubicBezier(0.25, 0.1, 0.25, 1);
export let linear = cubicBezier(0,0,1,1);

View File

@@ -0,0 +1,12 @@
// @ts-nocheck
// #ifdef UNI-APP-X && APP
// export * from './uvue.uts'
export { Timeline, Animation } from './uvue.uts'
// #endif
// #ifndef UNI-APP-X && APP
// export * from './vue.ts'
export { Timeline, Animation } from './vue.ts'
// #endif

View File

@@ -0,0 +1,108 @@
// @ts-nocheck
import type { ComponentPublicInstance, Ref } from 'vue'
import { ease, linear } from './ease';
import { Timeline, Animation } from './';
export type UseTransitionOptions = {
duration ?: number
immediate ?: boolean
context ?: ComponentPublicInstance
}
// #ifndef UNI-APP-X && APP
import { ref, watch } from '@/uni_modules/lime-shared/vue'
export function useTransition(percent : Ref<number>|(() => number), options : UseTransitionOptions) : Ref<number> {
const current = ref(0)
const { immediate, duration = 300 } = options
let tl:Timeline|null = null;
let timer = -1
const isFunction = typeof percent === 'function'
watch(isFunction ? percent : () => percent.value, (v) => {
if(tl == null){
tl = new Timeline()
}
tl.start();
tl.add(
new Animation(
current.value,
v,
duration,
0,
ease,
nowValue => {
current.value = nowValue
clearTimeout(timer)
if(current.value == v){
timer = setTimeout(()=>{
tl?.pause();
tl = null
}, duration)
}
}
)
);
}, { immediate })
return current
}
// #endif
// #ifdef UNI-APP-X && APP
type UseTransitionReturnType = Ref<number>
export function useTransition(source : any, options : UseTransitionOptions) : UseTransitionReturnType {
const outputRef : Ref<number> = ref(0)
const immediate = options.immediate ?? false
const duration = options.duration ?? 300
const context = options.context //as ComponentPublicInstance | null
let tl:Timeline|null = null;
let timer = -1
const watchFunc = (v : number) => {
if(tl == null){
tl = new Timeline()
}
tl!.start();
tl!.add(
new Animation(
outputRef.value,
v,
duration,
0,
ease,
nowValue => {
outputRef.value = nowValue
clearTimeout(timer)
if(outputRef.value == v){
timer = setTimeout(()=>{
tl?.pause();
tl = null
}, duration)
}
}
),
null
);
}
if (context != null && typeof source == 'string') {
context.$watch(source, watchFunc, { immediate } as WatchOptions)
} else if(typeof source == 'function'){
watch(source, watchFunc, { immediate })
}
// #ifdef APP-ANDROID
else if(isRef(source) && source instanceof Ref<number>) {
watch(source as Ref<number>, watchFunc, { immediate })
}
// #endif
// #ifndef APP-ANDROID
else if(isRef(source) && typeof source.value == 'number') {
watch(source, watchFunc, { immediate })
}
// #endif
const stop = ()=>{
}
return outputRef //as UseTransitionReturnType
}
// #endif

View File

@@ -0,0 +1,119 @@
// @ts-nocheck
import { raf, cancelRaf} from '../raf'
export class Timeline {
state : string
animations : Set<Animation> = new Set<Animation>()
delAnimations : Animation[] = []
startTimes : Map<Animation, number> = new Map<Animation, number>()
pauseTime : number = 0
pauseStart : number = Date.now()
tickHandler : number = 0
tickHandlers : number[] = []
tick : (() => void) | null = null
constructor() {
this.state = 'Initiated';
}
start() {
if (!(this.state == 'Initiated')) return;
this.state = 'Started';
let startTime = Date.now();
this.pauseTime = 0;
this.tick = () => {
let now = Date.now();
this.animations.forEach((animation : Animation) => {
let t:number;
const ani = this.startTimes.get(animation)
if (ani == null) return
if (ani < startTime) {
t = now - startTime - animation.delay - this.pauseTime;
} else {
t = now - ani - animation.delay - this.pauseTime;
}
if (t > animation.duration) {
this.delAnimations.push(animation)
// 不能在 foreach 里面 对 集合进行删除操作
// this.animations.delete(animation);
t = animation.duration;
}
if (t > 0) animation.run(t);
})
// 不能在 foreach 里面 对 集合进行删除操作
while (this.delAnimations.length > 0) {
const animation = this.delAnimations.pop();
if (animation == null) return
this.animations.delete(animation);
}
// cancelAnimationFrame(this.tickHandler);
if (this.state != 'Started') return
this.tickHandler = raf(()=>{
this.tick!()
})
this.tickHandlers.push(this.tickHandler)
}
if(this.tick != null) {
this.tick!()
}
}
pause() {
if (!(this.state === 'Started')) return;
this.state = 'Paused';
this.pauseStart = Date.now();
cancelRaf(this.tickHandler);
// cancelRaf(this.tickHandler);
}
resume() {
if (!(this.state === 'Paused')) return;
this.state = 'Started';
this.pauseTime += Date.now() - this.pauseStart;
this.tick!();
}
reset() {
this.pause();
this.state = 'Initiated';
this.pauseTime = 0;
this.pauseStart = 0;
this.animations.clear()
this.delAnimations.clear()
this.startTimes.clear()
this.tickHandler = 0;
}
add(animation : Animation, startTime ?: number | null) {
if (startTime == null) startTime = Date.now();
this.animations.add(animation);
this.startTimes.set(animation, startTime);
}
}
export class Animation {
startValue : number
endValue : number
duration : number
timingFunction : (t : number) => number
delay : number
template : (t : number) => void
constructor(
startValue : number,
endValue : number,
duration : number,
delay : number,
timingFunction : (t : number) => number,
template : (v : number) => void) {
this.startValue = startValue;
this.endValue = endValue;
this.duration = duration;
this.timingFunction = timingFunction;
this.delay = delay;
this.template = template;
}
run(time : number) {
let range = this.endValue - this.startValue;
let progress = time / this.duration
if(progress != 1) progress = this.timingFunction(progress)
this.template(this.startValue + range * progress)
}
}

View File

@@ -0,0 +1,123 @@
// @ts-nocheck
const TICK = Symbol('tick');
const TICK_HANDLER = Symbol('tick-handler');
const ANIMATIONS = Symbol('animations');
const START_TIMES = Symbol('start-times');
const PAUSE_START = Symbol('pause-start');
const PAUSE_TIME = Symbol('pause-time');
const _raf = typeof requestAnimationFrame !== 'undefined' ? requestAnimationFrame : function(cb: Function) {return setTimeout(cb, 1000/60)}
const _caf = typeof cancelAnimationFrame !== 'undefined' ? cancelAnimationFrame: function(id: any) {clearTimeout(id)}
// const TICK = 'tick';
// const TICK_HANDLER = 'tick-handler';
// const ANIMATIONS = 'animations';
// const START_TIMES = 'start-times';
// const PAUSE_START = 'pause-start';
// const PAUSE_TIME = 'pause-time';
// const _raf = function(callback):number|null {return setTimeout(callback, 1000/60)}
// const _caf = function(id: number):void {clearTimeout(id)}
export class Timeline {
state: string
constructor() {
this.state = 'Initiated';
this[ANIMATIONS] = new Set();
this[START_TIMES] = new Map();
}
start() {
if (!(this.state === 'Initiated')) return;
this.state = 'Started';
let startTime = Date.now();
this[PAUSE_TIME] = 0;
this[TICK] = () => {
let now = Date.now();
this[ANIMATIONS].forEach((animation) => {
let t: number;
if (this[START_TIMES].get(animation) < startTime) {
t = now - startTime - animation.delay - this[PAUSE_TIME];
} else {
t = now - this[START_TIMES].get(animation) - animation.delay - this[PAUSE_TIME];
}
if (t > animation.duration) {
this[ANIMATIONS].delete(animation);
t = animation.duration;
}
if (t > 0) animation.run(t);
})
// for (let animation of this[ANIMATIONS]) {
// let t: number;
// console.log('animation', animation)
// if (this[START_TIMES].get(animation) < startTime) {
// t = now - startTime - animation.delay - this[PAUSE_TIME];
// } else {
// t = now - this[START_TIMES].get(animation) - animation.delay - this[PAUSE_TIME];
// }
// if (t > animation.duration) {
// this[ANIMATIONS].delete(animation);
// t = animation.duration;
// }
// if (t > 0) animation.run(t);
// }
this[TICK_HANDLER] = _raf(this[TICK]);
};
this[TICK]();
}
pause() {
if (!(this.state === 'Started')) return;
this.state = 'Paused';
this[PAUSE_START] = Date.now();
_caf(this[TICK_HANDLER]);
}
resume() {
if (!(this.state === 'Paused')) return;
this.state = 'Started';
this[PAUSE_TIME] += Date.now() - this[PAUSE_START];
this[TICK]();
}
reset() {
this.pause();
this.state = 'Initiated';
this[PAUSE_TIME] = 0;
this[PAUSE_START] = 0;
this[ANIMATIONS] = new Set();
this[START_TIMES] = new Map();
this[TICK_HANDLER] = null;
}
add(animation: any, startTime?: number) {
if (arguments.length < 2) startTime = Date.now();
this[ANIMATIONS].add(animation);
this[START_TIMES].set(animation, startTime);
}
}
export class Animation {
startValue: number
endValue: number
duration: number
timingFunction: (t: number) => number
delay: number
template: (t: number) => void
constructor(startValue: number, endValue: number, duration: number, delay: number, timingFunction: (t: number) => number, template: (v: number) => void) {
timingFunction = timingFunction || (v => v);
template = template || (v => v);
this.startValue = startValue;
this.endValue = endValue;
this.duration = duration;
this.timingFunction = timingFunction;
this.delay = delay;
this.template = template;
}
run(time: number) {
let range = this.endValue - this.startValue;
let progress = time / this.duration
if(progress != 1) progress = this.timingFunction(progress)
this.template(this.startValue + range * progress)
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,71 @@
// @ts-nocheck
import _areaList from './city-china.json';
export const areaList = _areaList
// #ifndef UNI-APP-X
type UTSJSONObject = Record<string, string>
// #endif
// #ifdef UNI-APP-X
type Object = UTSJSONObject
// #endif
type AreaList = {
province_list : Map<string, string>;
city_list : Map<string, string>;
county_list : Map<string, string>;
}
// type CascaderOption = {
// text : string;
// value : string;
// children ?: CascaderOption[];
// };
const makeOption = (
label : string,
value : string,
children ?: UTSJSONObject[],
) : UTSJSONObject => ({
label,
value,
children,
});
export function useCascaderAreaData() : UTSJSONObject[] {
const city = areaList['city_list'] as UTSJSONObject
const county = areaList['county_list'] as UTSJSONObject
const province = areaList['province_list'] as UTSJSONObject
const provinceMap = new Map<string, UTSJSONObject>();
Object.keys(province).forEach((code) => {
provinceMap.set(code.slice(0, 2), makeOption(`${province[code]}`, code, []));
});
const cityMap = new Map<string, UTSJSONObject>();
Object.keys(city).forEach((code) => {
const option = makeOption(`${city[code]}`, code, []);
cityMap.set(code.slice(0, 4), option);
const _province = provinceMap.get(code.slice(0, 2));
if (_province != null) {
(_province['children'] as UTSJSONObject[]).push(option)
}
});
Object.keys(county).forEach((code) => {
const _city = cityMap.get(code.slice(0, 4));
if (_city != null) {
(_city['children'] as UTSJSONObject[]).push(makeOption(`${county[code]}`, code, null));
}
});
// #ifndef APP-ANDROID || APP-IOS
return Array.from(provinceMap.values());
// #endif
// #ifdef APP-ANDROID || APP-IOS
const obj : UTSJSONObject[] = []
provinceMap.forEach((value, code) => {
obj.push(value)
})
return obj
// #endif
}

View File

@@ -0,0 +1,10 @@
// @ts-nocheck
// #ifndef UNI-APP-X && APP
// export * from './vue.ts'
export { arrayBufferToFile } from './vue.ts'
// #endif
// #ifdef UNI-APP-X && APP
// export * from './uvue.uts'
export { arrayBufferToFile } from './uvue.uts'
// #endif

View File

@@ -0,0 +1,10 @@
// @ts-nocheck
// import {platform} from '../platform'
/**
* buffer转路径
* @param {Object} buffer
*/
// @ts-nocheck
export function arrayBufferToFile(buffer: ArrayBuffer, name?: string, format?:string):Promise<(File|string)> {
console.error('[arrayBufferToFile] 当前环境不支持')
}

View File

@@ -0,0 +1,63 @@
// @ts-nocheck
import {platform} from '../platform'
/**
* buffer转路径
* @param {Object} buffer
*/
// @ts-nocheck
export function arrayBufferToFile(buffer: ArrayBuffer | Blob, name?: string, format?:string):Promise<(File|string)> {
return new Promise((resolve, reject) => {
// #ifdef MP
const fs = uni.getFileSystemManager()
//自定义文件名
if (!name && !format) {
reject(new Error('ERROR_NAME_PARSE'))
}
const fileName = `${name || new Date().getTime()}.${format.replace(/(.+)?\//,'')}`;
let pre = platform()
const filePath = `${pre.env.USER_DATA_PATH}/${fileName}`
fs.writeFile({
filePath,
data: buffer,
success() {
resolve(filePath)
},
fail(err) {
console.error(err)
reject(err)
}
})
// #endif
// #ifdef H5
const file = new File([buffer], name, {
type: format,
});
resolve(file)
// #endif
// #ifdef APP-PLUS
const bitmap = new plus.nativeObj.Bitmap('bitmap' + Date.now())
const base64 = uni.arrayBufferToBase64(buffer)
bitmap.loadBase64Data(base64, () => {
if (!name && !format) {
reject(new Error('ERROR_NAME_PARSE'))
}
const fileNmae = `${name || new Date().getTime()}.${format.replace(/(.+)?\//,'')}`;
const filePath = `_doc/uniapp_temp/${fileNmae}`
bitmap.save(filePath, {},
() => {
bitmap.clear()
resolve(filePath)
},
(error) => {
bitmap.clear()
reject(error)
})
}, (error) => {
bitmap.clear()
reject(error)
})
// #endif
})
}

View File

@@ -0,0 +1,13 @@
// @ts-nocheck
// 未完成
export function base64ToArrayBuffer(base64 : string) {
const [, format, bodyData] = /data:image\/(\w+);base64,(.*)/.exec(base64) || [];
if (!format) {
new Error('ERROR_BASE64SRC_PARSE')
}
if(uni.base64ToArrayBuffer) {
return uni.base64ToArrayBuffer(bodyData)
} else {
}
}

View File

@@ -0,0 +1,11 @@
// @ts-nocheck
// #ifndef UNI-APP-X && APP
// export * from './vue.ts'
export { base64ToPath } from './vue.ts'
// #endif
// #ifdef UNI-APP-X && APP
// export * from './uvue.uts'
export { base64ToPath } from './uvue.uts'
// #endif

View File

@@ -0,0 +1,22 @@
// @ts-nocheck
import { processFile, type ProcessFileOptions } from '@/uni_modules/lime-file-utils'
/**
* base64转路径
* @param {Object} base64
*/
export function base64ToPath(base64: string, filename: string | null = null):Promise<string> {
return new Promise((resolve,reject) => {
processFile({
type: 'toDataURL',
path: base64,
filename,
success(res: string){
resolve(res)
},
fail(err){
reject(err)
}
} as ProcessFileOptions)
})
}

View File

@@ -0,0 +1,75 @@
// @ts-nocheck
import {platform} from '../platform'
/**
* base64转路径
* @param {Object} base64
*/
export function base64ToPath(base64: string, filename?: string):Promise<string> {
const [, format] = /^data:image\/(\w+);base64,/.exec(base64) || [];
return new Promise((resolve, reject) => {
// #ifdef MP
const fs = uni.getFileSystemManager()
//自定义文件名
if (!filename && !format) {
reject(new Error('ERROR_BASE64SRC_PARSE'))
}
// const time = new Date().getTime();
const name = filename || `${new Date().getTime()}.${format}`;
let pre = platform()
const filePath = `${pre.env.USER_DATA_PATH}/${name}`
fs.writeFile({
filePath,
data: base64.split(',')[1],
encoding: 'base64',
success() {
resolve(filePath)
},
fail(err) {
console.error(err)
reject(err)
}
})
// #endif
// #ifdef H5
// mime类型
let mimeString = base64.split(',')[0].split(':')[1].split(';')[0];
//base64 解码
let byteString = atob(base64.split(',')[1]);
//创建缓冲数组
let arrayBuffer = new ArrayBuffer(byteString.length);
//创建视图
let intArray = new Uint8Array(arrayBuffer);
for (let i = 0; i < byteString.length; i++) {
intArray[i] = byteString.charCodeAt(i);
}
resolve(URL.createObjectURL(new Blob([intArray], {
type: mimeString
})))
// #endif
// #ifdef APP-PLUS
const bitmap = new plus.nativeObj.Bitmap('bitmap' + Date.now())
bitmap.loadBase64Data(base64, () => {
if (!filename && !format) {
reject(new Error('ERROR_BASE64SRC_PARSE'))
}
// const time = new Date().getTime();
const name = filename || `${new Date().getTime()}.${format}`;
const filePath = `_doc/uniapp_temp/${name}`
bitmap.save(filePath, {},
() => {
bitmap.clear()
resolve(filePath)
},
(error) => {
bitmap.clear()
reject(error)
})
}, (error) => {
bitmap.clear()
reject(error)
})
// #endif
})
}

View File

@@ -0,0 +1,21 @@
/**
* 将字符串转换为 camelCase 或 PascalCase 风格的命名约定
* @param str 要转换的字符串
* @param isPascalCase 指示是否转换为 PascalCase 的布尔值,默认为 false
* @returns 转换后的字符串
*/
export function camelCase(str: string, isPascalCase: boolean = false): string {
// 将字符串分割成单词数组
let words: string[] = str.split(/[\s_-]+/);
// 将数组中的每个单词首字母大写(除了第一个单词)
let camelCased: string[] = words.map((word, index):string => {
if (index == 0 && !isPascalCase) {
return word.toLowerCase(); // 第一个单词全小写
}
return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase();
});
// 将数组中的单词拼接成一个字符串
return camelCased.join('');
};

View File

@@ -0,0 +1,67 @@
// @ts-nocheck
// #ifndef UNI-APP-X && APP
// #ifdef MP-ALIPAY
interface My {
SDKVersion: string
}
declare var my: My
// #endif
function compareVersion(v1:string, v2:string) {
let a1 = v1.split('.');
let a2 = v2.split('.');
const len = Math.max(a1.length, a2.length);
while (a1.length < len) {
a1.push('0');
}
while (a2.length < len) {
a2.push('0');
}
for (let i = 0; i < len; i++) {
const num1 = parseInt(a1[i], 10);
const num2 = parseInt(a2[i], 10);
if (num1 > num2) {
return 1;
}
if (num1 < num2) {
return -1;
}
}
return 0;
}
function gte(version: string) {
let {SDKVersion} = uni.getSystemInfoSync();
// #ifdef MP-ALIPAY
SDKVersion = my.SDKVersion
// #endif
return compareVersion(SDKVersion, version) >= 0;
}
// #endif
/** 环境是否支持canvas 2d */
export function canIUseCanvas2d(): boolean {
// #ifdef MP-WEIXIN
return gte('2.9.0');
// #endif
// #ifdef MP-ALIPAY
return gte('2.7.0');
// #endif
// #ifdef MP-TOUTIAO
return gte('1.78.0');
// #endif
// #ifdef UNI-APP-X && WEB || UNI-APP-X && APP
return true;
// #endif
// #ifndef MP-WEIXIN || MP-ALIPAY || MP-TOUTIAO
return false
// #endif
}

View File

@@ -0,0 +1,111 @@
// @ts-nocheck
import { isString } from "../isString";
import { isNumber } from "../isNumber";
/**
* 将金额转换为中文大写形式
* @param {number | string} amount - 需要转换的金额,可以是数字或字符串
* @returns {string} 转换后的中文大写金额
*/
export function capitalizedAmount(amount : number) : string
export function capitalizedAmount(amount : string) : string
export function capitalizedAmount(amount : any | null) : string {
try {
let _amountStr :string;
let _amountNum :number = 0;
// 如果输入是字符串,先将其转换为数字,并去除逗号
if (typeof amount == 'string') {
_amountNum = parseFloat((amount as string).replace(/,/g, ''));
}
if(isNumber(amount)) {
_amountNum = amount as number
}
// 判断输入是否为有效的金额 || isNaN(amount)
if (amount == null) throw new Error('不是有效的金额!');
let result = '';
// 处理负数情况
if (_amountNum < 0) {
result = '欠';
_amountNum = Math.abs(_amountNum);
}
// 金额不能超过千亿以上
if (_amountNum >= 10e11) throw new Error('计算金额过大!');
// 保留两位小数并转换为字符串
_amountStr = _amountNum.toFixed(2);
// 定义数字、单位和小数单位的映射
const digits = ['零', '壹', '贰', '叁', '肆', '伍', '陆', '柒', '捌', '玖'];
const units = ['', '拾', '佰', '仟'];
const bigUnits = ['', '万', '亿'];
const decimalUnits = ['角', '分'];
// 分离整数部分和小数部分
const amountArray = _amountStr.split('.');
let integerPart = amountArray[0]; // string| number[]
const decimalPart = amountArray[1];
// 处理整数部分
if (integerPart != '0') {
let _integerPart = integerPart.split('').map((item):number => parseInt(item));
// 将整数部分按四位一级进行分组
const levels = _integerPart.reverse().reduce((prev:string[][], item, index):string[][] => {
// const level = prev?.[0]?.length < 4 ? prev[0] : [];
const level = prev.length > 0 && prev[0].length < 4 ? prev[0]: []
const value = item == 0 ? digits[item] : digits[item] + units[index % 4];
level.unshift(value);
if (level.length == 1) {
prev.unshift(level);
} else {
prev[0] = level;
}
return prev;
}, [] as string[][]);
// 将分组后的整数部分转换为中文大写形式
result += levels.reduce((prev, item, index):string => {
let _level = bigUnits[levels.length - index - 1];
let _item = item.join('').replace(/(零)\1+/g, '$1');
if (_item == '零') {
_level = '';
_item = '';
} else if (_item.endsWith('零')) {
_item = _item.slice(0, _item.length - 1);
}
return prev + _item + _level;
}, '');
} else {
result += '零';
}
// 添加元
result += '元';
// 处理小数部分
if (decimalPart != '00') {
if (result == '零元') result = '';
for (let i = 0; i < decimalPart.length; i++) {
const digit = parseInt(decimalPart.charAt(i));
if (digit != 0) {
result += digits[digit] + decimalUnits[i];
}
}
} else {
result += '整';
}
return result;
} catch (error : Error) {
return error.message;
}
};

View File

@@ -0,0 +1,91 @@
## 0.4.22025-09-26
- feat: 增加`cssToObj`
## 0.4.12025-06-13
- fix测试hbx4.71更新
## 0.4.02025-06-04
- fix测试hbx4.71更新
## 0.3.92025-06-04
- fix: 因hbx4.71更新导致mac用户下载的文件被重命名故退回4.66
## 0.3.82025-06-03
- fix: 修复`while`在vue2报错的问题
## 0.3.72025-05-21
- fix: 修复`merge`在vue2报错的问题
## 0.3.62025-05-17
- fix: 修复`raf`导错目录的问题
## 0.3.52025-05-16
- feat: 增加`isIP`
- feat: 增加`merge`
- feat: 增加`isByteLength`
- feat: 增加`isRegExp`
## 0.3.42025-04-27
- fix: 修复exif缺少isBase64的问题
## 0.3.32025-04-14
- fix: 修复4.61上的类型问题
## 0.3.22025-04-14
- fix: 修复4.61上的类型问题
## 0.3.12025-03-28
- fix: 修复getRect在nvue上的问题
## 0.3.02025-02-27
- fix: 修复部分函数无法在uniappx上运行的问题
## 0.2.92025-02-19
- chore: 更新文档
## 0.2.82025-02-11
- chore: 更新文档
## 0.2.72025-01-17
- fix: 针对canvas 平台判断优化
## 0.2.62025-01-09
- feat: 增加`areaData`中国省市区数据
## 0.2.52025-01-07
- fix: animation在app上类型问题
## 0.2.42025-01-04
- feat: getRect类型问题
## 0.2.32025-01-01
- chore: unitConvert使用uni.rpx2px
## 0.2.22024-12-11
- chore: 动画使用`requestAnimationFrame`
## 0.2.12024-11-20
- feat: 增加`characterLimit`
## 0.2.02024-11-14
- fix: vue2的类型问题
## 0.1.92024-11-14
- feat: 增加`shuffle`
## 0.1.82024-10-08
- fix: vue2 条件编译 // #ifdef APP-IOS || APP-ANDROID 会生效
## 0.1.72024-09-23
- fix: raf 类型跟随版本变更
## 0.1.62024-07-24
- fix: vue2 app ts需要明确的后缀所有补全
- chore: 减少依赖
## 0.1.52024-07-21
- feat: 删除 Hooks
- feat: 兼容uniappx
## 0.1.42023-09-05
- feat: 增加 Hooks `useIntersectionObserver`
- feat: 增加 `floatAdd`
- feat: 因为本人插件兼容 vue2 需要使用 `composition-api`故增加vue文件代码插件的条件编译
## 0.1.32023-08-13
- feat: 增加 `camelCase`
## 0.1.22023-07-17
- feat: 增加 `getClassStr`
## 0.1.12023-07-06
- feat: 增加 `isNumeric` 区别于 `isNumber`
## 0.1.02023-06-30
- fix: `clamp`忘记导出了
## 0.0.92023-06-27
- feat: 增加`arrayBufferToFile`
## 0.0.82023-06-19
- feat: 增加`createAnimation``clamp`
## 0.0.72023-06-08
- chore: 更新注释
## 0.0.62023-06-08
- chore: 增加`createImage``lime-watermark``lime-qrcode`提供依赖
## 0.0.52023-06-03
- chore: 更新注释
## 0.0.42023-05-22
- feat: 增加`range`,`exif`,`selectComponent`
## 0.0.32023-05-08
- feat: 增加`fillZero`,`debounce`,`throttle`,`random`
## 0.0.22023-05-05
- chore: 更新文档
## 0.0.12023-05-05
-

View File

@@ -0,0 +1,63 @@
// @ts-nocheck
/**
* 计算字符串字符的长度并可以截取字符串。
* @param char 传入字符串maxcharacter条件下一个汉字表示两个字符
* @param max 规定最大字符串长度
* @returns 当没有传入maxCharacter/maxLength 时返回字符串字符长度当传入maxCharacter/maxLength时返回截取之后的字符串和长度。
*/
export type CharacterLengthResult = {
length : number;
characters : string;
}
// #ifdef APP-ANDROID
type ChartType = any
// #endif
// #ifndef APP-ANDROID
type ChartType = string | number
// #endif
export function characterLimit(type : string, char : ChartType, max : number) : CharacterLengthResult {
const str = `${char}`;
if (str.length == 0) {
return {
length: 0,
characters: '',
} as CharacterLengthResult
}
if (type == 'maxcharacter') {
let len = 0;
for (let i = 0; i < str.length; i += 1) {
let currentStringLength : number// = 0;
const code = str.charCodeAt(i)!
if (code > 127 || code == 94) {
currentStringLength = 2;
} else {
currentStringLength = 1;
}
if (len + currentStringLength > max) {
return {
length: len,
characters: str.slice(0, i),
} as CharacterLengthResult
}
len += currentStringLength;
}
return {
length: len,
characters: str,
} as CharacterLengthResult
} else if (type == 'maxlength') {
const length = str.length > max ? max : str.length;
return {
length: length,
characters: str.slice(0, length),
} as CharacterLengthResult
}
return {
length: str.length,
characters: str,
} as CharacterLengthResult
};

View File

@@ -0,0 +1,16 @@
// @ts-nocheck
/**
* 将一个值限制在指定的范围内
* @param val 要限制的值
* @param min 最小值
* @param max 最大值
* @returns 限制后的值
*/
export function clamp(val: number, min: number, max: number): number {
return Math.max(min, Math.min(max, val));
}
// console.log(clamp(5 ,0, 10)); // 输出: 5在范围内不做更改
// console.log(clamp(-5 ,0, 10)); // 输出: 0小于最小值被限制为最小值
// console.log(clamp(15 ,0, 10)); // 输出: 10大于最大值被限制为最大值

View File

@@ -0,0 +1,53 @@
// @ts-nocheck
// #ifdef UNI-APP-X && APP
import { isNumber } from '../isNumber'
import { isString } from '../isString'
import { isDef } from '../isDef'
// #endif
/**
* 获取对象的类名字符串
* @param obj - 需要处理的对象
* @returns 由对象属性作为类名组成的字符串
*/
export function classNames<T>(obj : T) : string {
let classNames : string[] = [];
// #ifdef APP-ANDROID || APP-HARMONY
if (obj instanceof UTSJSONObject) {
(obj as UTSJSONObject).toMap().forEach((value, key) => {
if (isDef(value)) {
if (isNumber(value)) {
classNames.push(key);
}
if (isString(value) && value !== '') {
classNames.push(key);
}
if (typeof value == 'boolean' && (value as boolean)) {
classNames.push(key);
}
}
})
}
// #endif
// #ifndef APP-ANDROID || APP-HARMONY
// 遍历对象的属性
for (let key in obj) {
// 检查属性确实属于对象自身且其值为true
if ((obj as any).hasOwnProperty(key) && obj[key]) {
// 将属性名添加到类名数组中
classNames.push(key);
}
}
// #endif
// 将类名数组用空格连接成字符串并返回
return classNames.join(' ');
}
// 示例
// const obj = { foo: true, bar: false, baz: true };
// const classNameStr = stringify(obj);
// console.log(classNameStr); // 输出: "foo baz"

View File

@@ -0,0 +1,12 @@
// @ts-nocheck
// #ifdef APP-ANDROID
// export * from './uvue.uts'
export { cloneDeep } from './uvue.uts'
// #endif
// #ifndef APP-ANDROID
// export * from './vue.ts'
export { cloneDeep } from './vue.ts'
// #endif

View File

@@ -0,0 +1,39 @@
// @ts-nocheck
/**
* 深度克隆一个对象或数组
* @param obj 要克隆的对象或数组
* @returns 克隆后的对象或数组
*/
export function cloneDeep<T>(obj : any) : T {
if (obj instanceof Set) {
const set = new Set<any>();
obj.forEach((item : any) => {
set.add(item)
})
return set as T;
}
if (obj instanceof Map) {
const map = new Map<any, any>();
obj.forEach((value : any, key : any) => {
map.set(key, value)
})
return map as T;
}
if (obj instanceof RegExp) {
return new RegExp(obj) as T;
}
if (Array.isArray(obj)) {
return (obj as any[]).map((item : any):any => item) as T;
}
if (obj instanceof Date) {
return new Date(obj.getTime()) as T;
}
if (typeof obj == 'object') {
return UTSJSONObject.assign<T>({}, toRaw(obj))!
}
return obj as T
}

View File

@@ -0,0 +1,103 @@
// @ts-nocheck
/**
* 深度克隆一个对象或数组
* @param obj 要克隆的对象或数组
* @returns 克隆后的对象或数组
*/
export function cloneDeep<T>(obj: any): T {
// 如果传入的对象为空,返回空
if (obj === null) {
return null as unknown as T;
}
// 如果传入的对象是 Set 类型,则将其转换为数组,并通过新的 Set 构造函数创建一个新的 Set 对象
if (obj instanceof Set) {
return new Set([...obj]) as unknown as T;
}
// 如果传入的对象是 Map 类型,则将其转换为数组,并通过新的 Map 构造函数创建一个新的 Map 对象
if (obj instanceof Map) {
return new Map([...obj]) as unknown as T;
}
// 如果传入的对象是 WeakMap 类型,则直接用传入的 WeakMap 对象进行赋值
if (obj instanceof WeakMap) {
let weakMap = new WeakMap();
weakMap = obj;
return weakMap as unknown as T;
}
// 如果传入的对象是 WeakSet 类型,则直接用传入的 WeakSet 对象进行赋值
if (obj instanceof WeakSet) {
let weakSet = new WeakSet();
weakSet = obj;
return weakSet as unknown as T;
}
// 如果传入的对象是 RegExp 类型,则通过新的 RegExp 构造函数创建一个新的 RegExp 对象
if (obj instanceof RegExp) {
return new RegExp(obj) as unknown as T;
}
// 如果传入的对象是 undefined 类型,则返回 undefined
if (typeof obj === 'undefined') {
return undefined as unknown as T;
}
// 如果传入的对象是数组,则递归调用 cloneDeep 函数对数组中的每个元素进行克隆
if (Array.isArray(obj)) {
return obj.map(cloneDeep) as unknown as T;
}
// 如果传入的对象是 Date 类型,则通过新的 Date 构造函数创建一个新的 Date 对象
if (obj instanceof Date) {
return new Date(obj.getTime()) as unknown as T;
}
// 如果传入的对象是普通对象,则使用递归调用 cloneDeep 函数对对象的每个属性进行克隆
if (typeof obj === 'object') {
const newObj: any = {};
for (const [key, value] of Object.entries(obj)) {
newObj[key] = cloneDeep(value);
}
const symbolKeys = Object.getOwnPropertySymbols(obj);
for (const key of symbolKeys) {
newObj[key] = cloneDeep(obj[key]);
}
return newObj;
}
// 如果传入的对象是基本数据类型(如字符串、数字等),则直接返回
return obj;
}
// 示例使用
// // 克隆一个对象
// const obj = { name: 'John', age: 30 };
// const clonedObj = cloneDeep(obj);
// console.log(clonedObj); // 输出: { name: 'John', age: 30 }
// console.log(clonedObj === obj); // 输出: false (副本与原对象是独立的)
// // 克隆一个数组
// const arr = [1, 2, 3];
// const clonedArr = cloneDeep(arr);
// console.log(clonedArr); // 输出: [1, 2, 3]
// console.log(clonedArr === arr); // 输出: false (副本与原数组是独立的)
// // 克隆一个包含嵌套对象的对象
// const person = {
// name: 'Alice',
// age: 25,
// address: {
// city: 'New York',
// country: 'USA',
// },
// };
// const clonedPerson = cloneDeep(person);
// console.log(clonedPerson); // 输出: { name: 'Alice', age: 25, address: { city: 'New York', country: 'USA' } }
// console.log(clonedPerson === person); // 输出: false (副本与原对象是独立的)
// console.log(clonedPerson.address === person.address); // 输出: false (嵌套对象的副本也是独立的)

View File

@@ -0,0 +1,22 @@
// @ts-nocheck
/**
* 在给定数组中找到最接近目标数字的元素。
* @param arr 要搜索的数字数组。
* @param target 目标数字。
* @returns 最接近目标数字的数组元素。
*/
export function closest(arr: number[], target: number):number {
return arr.reduce((pre: number, cur: number):number =>
Math.abs(pre - target) < Math.abs(cur - target) ? pre : cur
);
}
// 示例
// // 定义一个数字数组
// const numbers = [1, 3, 5, 7, 9];
// // 在数组中找到最接近目标数字 6 的元素
// const closestNumber = closest(numbers, 6);
// console.log(closestNumber); // 输出结果: 5

View File

@@ -0,0 +1,407 @@
<template>
<view id="shared" style="height: 500px; width: 300px; background-color: aqua;">
</view>
</template>
<script lang="ts">
// #ifdef WEB
import validator from 'validator'
// #endif
import { getRect, getAllRect } from '@/uni_modules/lime-shared/getRect'
import { camelCase } from '@/uni_modules/lime-shared/camelCase'
import { canIUseCanvas2d } from '@/uni_modules/lime-shared/canIUseCanvas2d'
import { clamp } from '@/uni_modules/lime-shared/clamp'
import { cloneDeep } from '@/uni_modules/lime-shared/cloneDeep'
import { closest } from '@/uni_modules/lime-shared/closest'
import { debounce } from '@/uni_modules/lime-shared/debounce'
import { fillZero } from '@/uni_modules/lime-shared/fillZero'
import { floatAdd } from '@/uni_modules/lime-shared/floatAdd'
import { floatMul } from '@/uni_modules/lime-shared/floatMul'
import { floatDiv } from '@/uni_modules/lime-shared/floatDiv'
import { floatSub } from '@/uni_modules/lime-shared/floatSub'
import { getClassStr } from '@/uni_modules/lime-shared/getClassStr'
import { getCurrentPage } from '@/uni_modules/lime-shared/getCurrentPage'
import { getStyleStr } from '@/uni_modules/lime-shared/getStyleStr'
import { hasOwn } from '@/uni_modules/lime-shared/hasOwn'
import { isBase64 } from '@/uni_modules/lime-shared/isBase64'
import { isBrowser } from '@/uni_modules/lime-shared/isBrowser'
import { isDef } from '@/uni_modules/lime-shared/isDef'
import { isEmpty } from '@/uni_modules/lime-shared/isEmpty'
import { isFunction } from '@/uni_modules/lime-shared/isFunction'
import { isNumber } from '@/uni_modules/lime-shared/isNumber'
import { isNumeric } from '@/uni_modules/lime-shared/isNumeric'
import { isObject } from '@/uni_modules/lime-shared/isObject'
import { isPromise } from '@/uni_modules/lime-shared/isPromise'
import { isString } from '@/uni_modules/lime-shared/isString'
import { kebabCase } from '@/uni_modules/lime-shared/kebabCase'
import { raf, doubleRaf } from '@/uni_modules/lime-shared/raf'
import { random } from '@/uni_modules/lime-shared/random'
import { range } from '@/uni_modules/lime-shared/range'
import { sleep } from '@/uni_modules/lime-shared/sleep'
import { throttle } from '@/uni_modules/lime-shared/throttle'
import { toArray } from '@/uni_modules/lime-shared/toArray'
import { toBoolean } from '@/uni_modules/lime-shared/toBoolean'
import { toNumber } from '@/uni_modules/lime-shared/toNumber'
import { unitConvert } from '@/uni_modules/lime-shared/unitConvert'
import { getCurrentInstance } from '@/uni_modules/lime-shared/vue'
import { capitalizedAmount } from '@/uni_modules/lime-shared/capitalizedAmount'
import { obj2url } from '@/uni_modules/lime-shared/obj2url'
import { isURL, type IsURLOptions } from '@/uni_modules/lime-shared/isURL'
import { isIP } from '@/uni_modules/lime-shared/isIP'
import { isDate, type IsDateOptions } from '@/uni_modules/lime-shared/isDate'
import { isEmail } from '@/uni_modules/lime-shared/isEmail'
import { isRegExp } from '@/uni_modules/lime-shared/isRegExp'
import { isValidDomain, type IsValidDomainOptions } from '@/uni_modules/lime-shared/isValidDomain'
import { merge } from '@/uni_modules/lime-shared/merge'
import { isByteLength, type IsByteLengthOptions } from '@/uni_modules/lime-shared/isByteLength'
// #ifdef VUE2
type UTSJSONObject = any
// #endif
const context = getCurrentInstance()
// getRect('#shared', context!).then(res =>{
// console.log('res', res.bottom)
// })
// getAllRect('#shared', context).then(res =>{
// console.log('res', res)
// })
// console.log('camelCase::', camelCase("hello world"));
// console.log('camelCase::', camelCase("my_name_is_john", true));
// console.log('canIUseCanvas2d::', canIUseCanvas2d());
// console.log('clamp::', clamp(5 ,0, 10));
// console.log('cloneDeep::', cloneDeep<UTSJSONObject>({a:5}));
// console.log('closest::', closest([1, 3, 5, 7, 9], 6));
// const saveData = (data: any) => {
// // 模拟保存数据的操作
// console.log(`Saving data: ${data}`);
// }
// const debouncedSaveData = debounce(saveData, 500);
// debouncedSaveData('Data 1');
// debouncedSaveData('Data 2');
// console.log('fillZero', fillZero(1))
// console.log('floatAdd', floatAdd(0.1, 0.2), floatAdd(1.05, 0.05), floatAdd(0.1, 0.7), floatAdd(0.0001, 0.0002), floatAdd(123.456 , 789.012))
// console.log('floatMul', floatMul(0.1, 0.02), floatMul(1.0255, 100))
// console.log('floatDiv', floatDiv(10.44, 100), floatDiv(1.0255, 100), floatDiv(5.419909340994699, 0.2))
// console.log('floatSub', floatSub(0.4, 0.1), floatSub(1.0255, 100))
// const now = () : number => System.nanoTime() / 1_000_000.0
// console.log('capitalizedAmount', capitalizedAmount(0.4))
// console.log('capitalizedAmount', capitalizedAmount(100))
// console.log('capitalizedAmount', capitalizedAmount(100000000))
// console.log('capitalizedAmount', capitalizedAmount('2023.04'))
// console.log('capitalizedAmount', capitalizedAmount(-1024))
// console.log('now', now(), Date.now())
// console.log('getClassStr', getClassStr({hover: true}))
// console.log('getStyleStr', getStyleStr({ color: 'red', fontSize: '16px', backgroundColor: '', border: null }))
// console.log('hasOwn', hasOwn({a: true}, 'key'))
// console.log('isBase64::', isBase64("SGVsbG8sIFdvcmxkIQ=="));
// console.log('isBrowser::', isBrowser);
// console.log('isDef::', isDef('6'));
// console.log('isEmpty::', isEmpty({a: true}));
// const b = () =>{}
// console.log('isFunction::', isFunction(b));
// console.log('isNumber::', isNumber('6'));
// console.log('isNumeric::', isNumeric('6'));
// console.log('isObject::', isObject({}));
// const promise = ():Promise<boolean> => {
// return new Promise((resolve) => {
// resolve(true)
// })
// }
// const a = promise()
// console.log('isPromise::', isPromise(a));
// console.log('isString::', isString('null'));
// console.log('kebabCase::', kebabCase('my love'));
// console.log('raf::', raf(()=>{
// console.log('raf:::1')
// }));
// console.log('doubleRaf::', doubleRaf(()=>{
// console.log('doubleRaf:::1')
// }));
// console.log('random', random(0, 10))
// console.log('random', random(0, 1, 2))
// console.log('range', range(0, 10, 2))
// console.log('sleep', sleep(300).then(res => {
// console.log('log')
// }))
// const handleScroll = (a: string) => {
// console.log("Scroll event handled!", a);
// }
// // // 使用节流函数对 handleScroll 进行节流,间隔时间为 500 毫秒
// const throttledScroll = throttle(handleScroll, 500);
// throttledScroll('5');
// const page = getCurrentPage()
// console.log('getCurrentPage::', page)
// console.log('toArray', toArray<number>(5))
// console.log('toBoolean', toBoolean(5))
// console.log('toNumber', toNumber('5'))
// console.log('unitConvert', unitConvert('5'))
// uni.getImageInfo({
// src: '/static/logo.png',
// success(res) {
// console.log('res', res)
// }
// })
// --------------------------
// IPv4 验证示例
// --------------------------
// 标准IPv4格式
// console.log(isIP('192.168.1.1', 4)); // true
// console.log(isIP('255.255.255.255', { version: 4 })); // true
// // 边界值验证
// console.log(isIP('0.0.0.0', 4)); // true
// console.log(isIP('223.255.255.255', '4')); // true
// // 非法IPv4案例
// console.log(isIP('256.400.999.1', 4)); // false数值超限
// console.log(isIP('192.168.01', 4)); // false段数不足
// // --------------------------
// // IPv6 验证示例
// // --------------------------
// // 标准IPv6格式
// console.log(isIP('2001:0db8:85a3:0000:0000:8a2e:0370:7334', 6)); // true
// console.log(isIP('fe80::1%eth0', { version: 6 })); // true带区域标识
// // 压缩格式验证
// console.log(isIP('2001:db8::1', '6')); // true双冒号压缩
// console.log(isIP('::1', 6)); // true本地环回简写
// // IPv4混合格式
// console.log(isIP('::ffff:192.168.1.1', 6)); // trueIPv4映射地址
// // 非法IPv6案例
// console.log(isIP('2001::gggg::1', 6)); // false非法字符
// console.log(isIP('fe80::1%', 6)); // false空区域标识
// // --------------------------
// // 自动版本检测
// // --------------------------
// // 有效地址检测
// console.log(isIP('172.16.254.1')); // true自动识别IPv4
// console.log(isIP('2001:db8:3333:4444:5555:6666:7777:8888')); // true自动识别IPv6
// // 无效地址检测
// console.log(isIP('192.168.1.256')); // false无效IPv4
// console.log(isIP('2001::gggg::1')); // false无效IPv6
// // --------------------------
// // 特殊场景
// // --------------------------
// // 带前后空格处理
// console.log(isIP(' 203.0.113.50 ', 4)); // true自动trim
// console.log(isIP(' fe80::1%1 ', 6)); // true自动trim
// // 非法版本指定
// console.log(isIP('192.168.1.1', 5)); // false不存在IPv5
// console.log(isIP('2001:db8::1', 'ipv6')); // false版本参数格式错误
// // --------------------------
// // 边界案例
// // --------------------------
// // 最小/最大有效值
// console.log(isIP('0.0.0.0', 4)); // true
// console.log(isIP('255.255.255.255', 4)); // true
// console.log(isIP('0000:0000:0000:0000:0000:0000:0000:0000', 6)); // true
// // 超长地址验证
// console.log(isIP('192.168.1.1.1', 4)); // falseIPv4段数过多
// console.log(isIP('2001:db8:1:2:3:4:5:6:7', 6)); // falseIPv6段数过多
// const original = { color: 'red' };
// const merged = merge({ ...original }, { color: 'blue', size: 'M' });
// console.log('original', original); // 输出: { color: 'red' } (保持不变)
// console.log('merged', merged); // 输出: { color: 'red', size: 'M' }
type ColorType = {
color?: string,
size?: string,
}
const merged2 = merge({ color: 'red' }, { size: 'M' } as ColorType);
console.log('merged2:::', merged2)
// // 使用配置对象参数
// console.log(isByteLength('hello', { min: 3, max: 7 } as ByteLengthOptions)); // true (5字节)
// console.log(isByteLength('hello', { min: 6 } as ByteLengthOptions)); // false (5 < 6)
// console.log(isByteLength('hello', { max: 4 } as ByteLengthOptions)); // false (5 > 4)
// // 使用独立参数(旧式调用)
// console.log(isByteLength('hello', 3, 7)); // true
// console.log(isByteLength('hello', 6)); // false
// console.log(isByteLength('hello', null, 4)); // false
// =====================
// 多字节字符处理示例
// =====================
// 中文字符UTF-8 每个汉字3字节
// console.log(isByteLength('中国', { min: 6 })); // true (2字 × 3字节 = 6)
// console.log(isByteLength('中国', { max: 5 })); // false (6 > 5)
// // 表情符号多数占用4字节
// console.log(isByteLength('🌟', { min: 4, max: 4 })); // true
// console.log(isByteLength('👨👩👧👦', { max: 15 })); // false (家庭表情占25字节)
// // 混合字符集
// console.log(isByteLength('aé🌟', { min: 7 })); // true
// // URL编码字符
// console.log(isByteLength('%20', { min: 3 })); // true实际字节长度3
// console.log(isByteLength('%E2%82%AC', { max: 3 })); // false欧元符号编码为3字节
// // 构造函数创建的正则表达式
// console.log(isRegExp(new RegExp('hello'))); // true
// console.log(isRegExp(new RegExp('\\d+', 'gi'))); // true
// // 字面量正则表达式
// console.log(isRegExp(/abc/)); // true
// console.log(isRegExp(/^[0-9]+$/gi)); // true
// // 字符串(含正则格式字符串)
// console.log(isRegExp('/abc/')); // false
// console.log(isRegExp('new RegExp("abc")')); // false
// console.log(isEmail('"John"@example.com')) // false实际有效
// console.log(isEmail('中国@例子.中国')) // false实际有效
// // 简单键值对
// console.log(obj2url({ name: '张三', age: 25 }));
// // "name=%E5%BC%A0%E4%B8%89&age=25"
// // 包含布尔值
// console.log(obj2url({ active: true, admin: false }));
// // "active=true&admin=false"
// // 数字处理
// console.log(obj2url({ page: 1, limit: 10 }));
// // "page=1&limit=10"
// 基础验证
// console.log("example.com =>", isValidDomain("example.com")); // true
// console.log("sub.example.co.uk =>", isValidDomain("sub.example.co.uk")); // true
// // 缺少TLD的情况
// console.log("localhost =>", isValidDomain("localhost")); // false
// console.log("localhost (不要求TLD) =>", isValidDomain("localhost", { requireTld: false } as DomainOptions)); // true
// // 带结尾点号的情况
// console.log("example.com. =>", isValidDomain("example.com.")); // false
// console.log("example.com. (允许结尾点号) =>", isValidDomain("example.com.", { allowTrailingDot: true } as DomainOptions)); // true
// // 带下划线的情况
// console.log("my_site.com =>", isValidDomain("my_site.com")); // false
// console.log("my_site.com (允许下划线) =>", isValidDomain("my_site.com", { allowUnderscore: true } as DomainOptions)); // true
// // 非法字符测试
// console.log("含有空格的域名 =>", isValidDomain("exa mple.com")); // false
// console.log("含有!的域名 =>", isValidDomain("exa!mple.com")); // false
// // 长度测试
// const longPart = "a".repeat(64);
// console.log(`超长部分 (${longPart.length}字符) =>`, isValidDomain(`${longPart}.com`)); // false
// // 连字符测试
// console.log("以连字符开头 =>", isValidDomain("-example.com")); // false
// console.log("以连字符结尾 =>", isValidDomain("example-.com")); // false
// // 国际化域名测试
// console.log("中文域名 =>", isValidDomain("中国.中国")); // true
// console.log("日文域名 =>", isValidDomain("ドメイン.テスト")); // true
// // 基础格式验证
// console.log("1. 标准日期格式验证:");
// console.log("2023/12/31 =>", isDate("2023/12/31")); // true
// console.log("1999-01-01 =>", isDate("1999-01-01")); // true
// console.log("02.28.2023 =>", isDate("02.28.2023", { delimiters: ['.'], format: 'MM.DD.YYYY' } as DateOptions)); // true (自定义分隔符)
// // 严格模式验证
// console.log("2. 严格模式验证:");
// console.log("严格匹配格式:", isDate("2023/02/28", { strictMode: true, format: "YYYY/MM/DD" }as DateOptions)); // true
// console.log("长度不符:", isDate("2023/2/28", { strictMode: true, format: "YYYY/MM/DD" }as DateOptions)); // false
// console.log("错误分隔符:", isDate("2023-02-28", { strictMode: true, format: "YYYY/MM/DD" }as DateOptions)); // false
// // 两位年份处理
// console.log("3. 两位年份验证:");
// console.log("23 -> 2023:", isDate("23/12/31", { format: "YY/MM/DD" } as DateOptions)); // true → 2023-12-31
// console.log("87 -> 1987:", isDate("87-01-01", { format: "YY-MM-DD" } as DateOptions)); // true → 1987-01-01
// console.log("负数年份:", isDate("-100/12/31", { format: "YYYY/MM/DD" } as DateOptions)); // false
// // 日期有效性验证
// console.log("4. 无效日期检测:");
// console.log("闰年2020-02-29:", isDate("2020/02/29")); // true
// console.log("非闰年2023-02-29:", isDate("2023/02/29")); // false
// console.log("月份溢出:", isDate("2023/13/01")); // false
// console.log("日期溢出:", isDate("2023/12/32")); // false
// // Date对象验证
// console.log("5. Date对象验证:");
// console.log("有效Date对象:", isDate(new Date())); // true
// console.log("无效Date对象:", isDate(new Date("invalid")), new Date("invalid")); // false
// console.log("严格模式Date对象:", isDate(new Date(), { strictMode: true } as DateOptions)); // false
// // 自定义格式验证
// console.log("6. 自定义格式测试:");
// console.log("MM-DD-YYYY:", isDate("12-31-2023", { format: "MM-DD-YYYY" } as DateOptions)); // true
// console.log("DD.MM.YY:", isDate("31.12.23", { format: "DD.MM.YY", delimiters: ['.'] } as DateOptions)); // true
// console.log("中文分隔符:", isDate("2023年12月31日", {
// format: "YYYY年MM月DD日",
// strictMode: true,
// delimiters: ['年', '月', '日']
// } as DateOptions )); // true
// 示例测试
// console.log("示例1 标准HTTP URL:", isURL("http://example.com")); // true
// console.log("示例2 需要端口时缺少端口:", isURL("https://example.com", { requirePort: true } as URLOptions)); // false
// console.log("示例3 协议相对URL:", isURL("//example.com", { allowProtocolRelativeUrls: true })); // true
console.log("示例4 IPv6地址:", isURL("http://[2001:db8::1]:8080", {})); // true
// console.log("示例5 带认证信息被禁用:", isURL("user:pass@example.com", { disallowAuth: true })); // false
console.log("示例6 查询参数被禁用:", isURL("http://example.com?q=test", { allowQueryComponents: true })); // false
console.log("示例7 非字符串输入:", isURL(null, {})); // false
console.log("示例8 邮件协议被排除:", isURL("mailto:test@example.com", {})); // false
console.log("示例9 自定义协议:", isURL("ftp://files.example.com", { protocols: ["ftp"] })); // true
console.log("示例10 白名单检查:", isURL("http://trusted.com", { hostWhitelist: ["trusted.com"] })); // true
// #ifdef WEB
// console.log('validator', validator.isURL())
console.log("示例4 IPv6地址:", validator.isURL("http://[2001:db8::1]:8080", {})); // true
console.log("示例6 查询参数被禁用:", validator.isURL("http://example.com?q=test", { allow_query_components: true })); // false
console.log("示例8 邮件协议被排除:", validator.isURL("mailto:test@example.com", {})); // false
console.log("示例9 自定义协议:", isURL("ftp://files.example.com", { protocols: ["ftp"] })); // true
// #endif
export default {
}
</script>
<style>
</style>

View File

@@ -0,0 +1,11 @@
// @ts-nocheck
// #ifndef UNI-APP-X
export * from './type.ts'
// export * from './vue.ts'
export { createAnimation } from './vue.ts'
// #endif
// #ifdef UNI-APP-X
// export * from './uvue.ts'
export { createAnimation } from './uvue.uts'
// #endif

View File

@@ -0,0 +1,25 @@
export type CreateAnimationOptions = {
/**
* 动画持续时间单位ms
*/
duration ?: number;
/**
* 定义动画的效果
* - linear: 动画从头到尾的速度是相同的
* - ease: 动画以低速开始,然后加快,在结束前变慢
* - ease-in: 动画以低速开始
* - ease-in-out: 动画以低速开始和结束
* - ease-out: 动画以低速结束
* - step-start: 动画第一帧就跳至结束状态直到结束
* - step-end: 动画一直保持开始状态,最后一帧跳到结束状态
*/
timingFunction ?: string //'linear' | 'ease' | 'ease-in' | 'ease-in-out' | 'ease-out' | 'step-start' | 'step-end';
/**
* 动画延迟时间,单位 ms
*/
delay ?: number;
/**
* 设置transform-origin
*/
transformOrigin ?: string;
}

View File

@@ -0,0 +1,5 @@
// @ts-nocheck
// export * from '@/uni_modules/lime-animateIt'
export function createAnimation() {
console.error('当前环境不支持,请使用lime-animateIt')
}

View File

@@ -0,0 +1,148 @@
// @ts-nocheck
// nvue 需要在节点上设置ref或在export里传入
// const animation = createAnimation({
// ref: this.$refs['xxx'],
// duration: 0,
// timingFunction: 'linear'
// })
// animation.opacity(1).translate(x, y).step({duration})
// animation.export(ref)
// 抹平nvue 与 uni.createAnimation的使用差距
// 但是nvue动画太慢
import { type CreateAnimationOptions } from './type'
// #ifdef APP-NVUE
const nvueAnimation = uni.requireNativePlugin('animation')
type AnimationTypes = 'matrix' | 'matrix3d' | 'rotate' | 'rotate3d' | 'rotateX' | 'rotateY' | 'rotateZ' | 'scale' | 'scale3d' | 'scaleX' | 'scaleY' | 'scaleZ' | 'skew' | 'skewX' | 'skewY' | 'translate' | 'translate3d' | 'translateX' | 'translateY' | 'translateZ'
| 'opacity' | 'backgroundColor' | 'width' | 'height' | 'left' | 'right' | 'top' | 'bottom'
interface Styles {
[key : string] : any
}
interface StepConfig {
duration?: number
timingFunction?: string
delay?: number
needLayout?: boolean
transformOrigin?: string
}
interface StepAnimate {
styles?: Styles
config?: StepConfig
}
interface StepAnimates {
[key: number]: StepAnimate
}
// export interface CreateAnimationOptions extends UniApp.CreateAnimationOptions {
// ref?: string
// }
type Callback = (time: number) => void
const animateTypes1 : AnimationTypes[] = ['matrix', 'matrix3d', 'rotate', 'rotate3d', 'rotateX', 'rotateY', 'rotateZ', 'scale', 'scale3d',
'scaleX', 'scaleY', 'scaleZ', 'skew', 'skewX', 'skewY', 'translate', 'translate3d', 'translateX', 'translateY',
'translateZ'
]
const animateTypes2 : AnimationTypes[] = ['opacity', 'backgroundColor']
const animateTypes3 : AnimationTypes[] = ['width', 'height', 'left', 'right', 'top', 'bottom']
class LimeAnimation {
ref : any
context : any
options : UniApp.CreateAnimationOptions
// stack : any[] = []
next : number = 0
currentStepAnimates : StepAnimates = {}
duration : number = 0
constructor(options : CreateAnimationOptions) {
const {ref} = options
this.ref = ref
this.options = options
}
addAnimate(type : AnimationTypes, args: (string | number)[]) {
let aniObj = this.currentStepAnimates[this.next]
let stepAnimate:StepAnimate = {}
if (!aniObj) {
stepAnimate = {styles: {}, config: {}}
} else {
stepAnimate = aniObj
}
if (animateTypes1.includes(type)) {
if (!stepAnimate.styles.transform) {
stepAnimate.styles.transform = ''
}
let unit = ''
if (type === 'rotate') {
unit = 'deg'
}
stepAnimate.styles.transform += `${type}(${args.map((v: number) => v + unit).join(',')}) `
} else {
stepAnimate.styles[type] = `${args.join(',')}`
}
this.currentStepAnimates[this.next] = stepAnimate
}
animateRun(styles: Styles = {}, config:StepConfig = {}, ref: any) {
const el = ref || this.ref
if (!el) return
return new Promise((resolve) => {
const time = +new Date()
nvueAnimation.transition(el, {
styles,
...config
}, () => {
resolve(+new Date() - time)
})
})
}
nextAnimate(animates: StepAnimates, step: number = 0, ref: any, cb: Callback) {
let obj = animates[step]
if (obj) {
let { styles, config } = obj
// this.duration += config.duration
this.animateRun(styles, config, ref).then((time: number) => {
step += 1
this.duration += time
this.nextAnimate(animates, step, ref, cb)
})
} else {
this.currentStepAnimates = {}
cb && cb(this.duration)
}
}
step(config:StepConfig = {}) {
this.currentStepAnimates[this.next].config = Object.assign({}, this.options, config)
this.currentStepAnimates[this.next].styles.transformOrigin = this.currentStepAnimates[this.next].config.transformOrigin
this.next++
return this
}
export(ref: any, cb?: Callback) {
ref = ref || this.ref
if(!ref) return
this.duration = 0
this.next = 0
this.nextAnimate(this.currentStepAnimates, 0, ref, cb)
return null
}
}
animateTypes1.concat(animateTypes2, animateTypes3).forEach(type => {
LimeAnimation.prototype[type] = function(...args: (string | number)[]) {
this.addAnimate(type, args)
return this
}
})
// #endif
export function createAnimation(options : CreateAnimationOptions) {
// #ifndef APP-NVUE
return uni.createAnimation({ ...options })
// #endif
// #ifdef APP-NVUE
return new LimeAnimation(options)
// #endif
}

View File

@@ -0,0 +1,73 @@
// @ts-nocheck
// #ifndef UNI-APP-X && APP
import type { ComponentInternalInstance } from '@/uni_modules/lime-shared/vue'
import { getRect } from '@/uni_modules/lime-shared/getRect'
import { canIUseCanvas2d } from '@/uni_modules/lime-shared/canIUseCanvas2d'
export const isCanvas2d = canIUseCanvas2d()
// #endif
export function createCanvas(canvasId : string, component : ComponentInternalInstance) {
// #ifdef UNI-APP-X
uni.createCanvasContextAsync({
canvasId,
component,
success(context : CanvasContext) {
},
fail(error : UniError) {
}
})
// #endif
// #ifndef UNI-APP-X
const isCanvas2d = canIUseCanvas2d()
getRect('#' + canvasId, context, isCanvas2d).then(res => {
if (res.node) {
res.node.width = res.width
res.node.height = res.height
return res.node
} else {
const ctx = uni.createCanvasContext(canvasId, context)
if (!ctx._drawImage) {
ctx._drawImage = ctx.drawImage
ctx.drawImage = function (...args) {
const { path } = args.shift()
ctx._drawImage(path, ...args)
}
}
if (!ctx.getImageData) {
ctx.getImageData = function () {
return new Promise((resolve, reject) => {
uni.canvasGetImageData({
canvasId,
x: parseInt(arguments[0]),
y: parseInt(arguments[1]),
width: parseInt(arguments[2]),
height: parseInt(arguments[3]),
success(res) {
resolve(res)
},
fail(err) {
reject(err)
}
}, context)
})
}
return {
getContext(type: string) {
if(type == '2d') {
return ctx
}
},
width: res.width,
height: res.height,
createImage
}
}
}
})
// #endif
}

View File

@@ -0,0 +1,71 @@
// @ts-nocheck
// #ifndef UNI-APP-X && APP
import {isBrowser} from '../isBrowser'
class Image {
currentSrc: string | null = null
naturalHeight: number = 0
naturalWidth: number = 0
width: number = 0
height: number = 0
tagName: string = 'IMG'
path: string = ''
crossOrigin: string = ''
referrerPolicy: string = ''
onload: () => void = () => {}
onerror: () => void = () => {}
complete: boolean = false
constructor() {}
set src(src: string) {
console.log('src', src)
if(!src) {
return this.onerror()
}
src = src.replace(/^@\//,'/')
this.currentSrc = src
uni.getImageInfo({
src,
success: (res) => {
const localReg = /^\.|^\/(?=[^\/])/;
// #ifdef MP-WEIXIN || MP-BAIDU || MP-QQ || MP-TOUTIAO
res.path = localReg.test(src) ? `/${res.path}` : res.path;
// #endif
this.complete = true
this.path = res.path
this.naturalWidth = this.width = res.width
this.naturalHeight = this.height = res.height
this.onload()
},
fail: () => {
this.onerror()
}
})
}
get src() {
return this.currentSrc
}
}
interface UniImage extends WechatMiniprogram.Image {
complete?: boolean
naturalHeight?: number
naturalWidth?: number
}
/** 创建用于 canvas 的 img */
export function createImage(canvas?: any): HTMLImageElement | UniImage {
if(canvas && canvas.createImage) {
return (canvas as WechatMiniprogram.Canvas).createImage()
} else if(this && this['tagName'] == 'canvas' && !('toBlob' in this) || canvas && !('toBlob' in canvas)){
return new Image()
} else if(isBrowser) {
return new window.Image()
}
return new Image()
}
// #endif
// #ifdef UNI-APP-X && APP
export function createImage():Image{
// console.error('当前环境不支持')
return new Image()
}
// #endif

View File

@@ -0,0 +1,45 @@
// @ts-nocheck
// #ifndef UNI-APP-X
type UTSJSONObject = Record<string, any>
// #endif
/**
* 将 CSS 字符串转换为样式对象
* @param css CSS 字符串,例如 "color: red; font-size: 16px;"
* @returns CSSProperties 对象
*/
export function cssToObj(css : string | UTSJSONObject | null) : UTSJSONObject {
// #ifdef APP-ANDROID
if(css == null) return {}
// #endif
// #ifndef APP-ANDROID
if(!css) return {}
// #endif
if(typeof css == 'object') return css as UTSJSONObject
const style : UTSJSONObject = {};
(css as string).split(';').forEach(decl => {
// #ifdef APP-ANDROID
const res = decl.split(':').map(s => s.trim());
if(res.length > 1) {
const [prop, val] = res;
if (prop != '' && val != '') {
const camelProp = prop!.replace(/-([a-z])/g, (_: string, _offset: number,c: string):string => c.toUpperCase());
style[camelProp] = val!;
}
}
// #endif
// #ifndef APP-ANDROID
const [prop, val] = decl.split(':').map(s => s.trim());
if (prop && val) {
const camelProp = prop.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
style[camelProp] = val;
}
// #endif
});
return style;
}

View File

@@ -0,0 +1,11 @@
// @ts-nocheck
// #ifdef UNI-APP-X && APP
// export * from './uvue.uts'
export { debounce } from './uvue.uts'
// #endif
// #ifndef UNI-APP-X && APP
// export * from './vue.ts'
export { debounce } from './vue.ts'
// #endif

View File

@@ -0,0 +1,36 @@
// @ts-nocheck
/**
* 防抖函数,通过延迟一定时间来限制函数的执行频率。
* @param fn 要防抖的函数。
* @param wait 触发防抖的等待时间,单位为毫秒。
* @returns 防抖函数。
*/
export function debounce<A extends any>(fn : (args: A)=> void, wait = 300): (args: A)=> void {
let timer = -1
return (args: A) => {
if (timer >-1) {clearTimeout(timer)};
timer = setTimeout(()=>{
fn(args)
}, wait)
}
};
// 示例
// 定义一个函数
// function saveData(data: string) {
// // 模拟保存数据的操作
// console.log(`Saving data: ${data}`);
// }
// // 创建一个防抖函数,延迟 500 毫秒后调用 saveData 函数
// const debouncedSaveData = debounce(saveData, 500);
// // 连续调用防抖函数
// debouncedSaveData('Data 1'); // 不会立即调用 saveData 函数
// debouncedSaveData('Data 2'); // 不会立即调用 saveData 函数
// 在 500 毫秒后,只会调用一次 saveData 函数,输出结果为 "Saving data: Data 2"

View File

@@ -0,0 +1,40 @@
// @ts-nocheck
type Timeout = ReturnType<typeof setTimeout> | null;
/**
* 防抖函数,通过延迟一定时间来限制函数的执行频率。
* @param fn 要防抖的函数。
* @param wait 触发防抖的等待时间,单位为毫秒。
* @returns 防抖函数。
*/
export function debounce<A extends any, R>(
fn : (...args : A) => R,
wait : number = 300) : (...args : A) => void {
let timer : Timeout = null;
return function (...args : A) {
if (timer) clearTimeout(timer); // 如果上一个 setTimeout 存在,则清除它
// 设置一个新的 setTimeout在指定的等待时间后调用防抖函数
timer = setTimeout(() => {
fn.apply(this, args); // 使用提供的参数调用原始函数
}, wait);
};
};
// 示例
// 定义一个函数
// function saveData(data: string) {
// // 模拟保存数据的操作
// console.log(`Saving data: ${data}`);
// }
// // 创建一个防抖函数,延迟 500 毫秒后调用 saveData 函数
// const debouncedSaveData = debounce(saveData, 500);
// // 连续调用防抖函数
// debouncedSaveData('Data 1'); // 不会立即调用 saveData 函数
// debouncedSaveData('Data 2'); // 不会立即调用 saveData 函数
// 在 500 毫秒后,只会调用一次 saveData 函数,输出结果为 "Saving data: Data 2"

View File

@@ -0,0 +1,18 @@
// @ts-nocheck
export function findClosestElementWithStyle(startEl: UniElement | null, styleProperty: string): UniElement | null {
let currentEl: UniElement | null = startEl;
while (currentEl != null) {
// Check if the current element has the style property with a non-empty value
const styleValue = currentEl?.style.getPropertyValue(styleProperty) ?? '';
if (styleValue.trim() != '') {
return currentEl;
}
// Move to parent element
currentEl = currentEl.parentElement;
}
// Return null if no element with the specified style was found
return null;
};

View File

@@ -0,0 +1,11 @@
// @ts-nocheck
// #ifndef UNI-APP-X && APP
// export * from './vue.ts'
export { exif } from './vue.ts'
// #endif
// #ifdef UNI-APP-X && APP
// export * from './uvue.uts'
export { exif } from './uvue.uts'
// #endif

View File

@@ -0,0 +1,7 @@
class EXIF {
constructor(){
console.error('当前环境不支持')
}
}
export const exif = new EXIF()

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,11 @@
// @ts-nocheck
/**
* 在数字前填充零,返回字符串形式的结果
* @param number 要填充零的数字
* @param length 填充零后的字符串长度默认为2
* @returns 填充零后的字符串
*/
export function fillZero(number: number, length: number = 2): string {
// 将数字转换为字符串,然后使用 padStart 方法填充零到指定长度
return `${number}`.padStart(length, '0');
}

View File

@@ -0,0 +1,36 @@
import { isNumber } from '../isNumber'
/**
* 返回两个浮点数相加的结果
* @param num1 第一个浮点数
* @param num2 第二个浮点数
* @returns 两个浮点数的相加结果
*/
export function floatAdd(num1 : number, num2 : number) : number {
// 检查 num1 和 num2 是否为数字类型
if (!(isNumber(num1) || isNumber(num2))) {
console.warn('Please pass in the number type');
return NaN;
}
let r1 : number, r2 : number, m : number;
try {
// 获取 num1 小数点后的位数
r1 = num1.toString().split('.')[1].length;
} catch (error) {
r1 = 0;
}
try {
// 获取 num2 小数点后的位数
r2 = num2.toString().split('.')[1].length;
} catch (error) {
r2 = 0;
}
// 计算需要扩大的倍数
m = Math.pow(10, Math.max(r1, r2));
// 返回相加结果
return (num1 * m + num2 * m) / m;
}

View File

@@ -0,0 +1,45 @@
import { floatMul } from '../floatMul';
import { isNumber } from '../isNumber';
/**
* 除法函数,用于处理浮点数除法并保持精度。
* @param {number} num1 - 被除数。
* @param {number} num2 - 除数。
* @returns {number} 除法运算的结果,保留正确的精度。
*/
export function floatDiv(num1:number, num2:number):number {
// 如果传入的不是数字类型则打印警告并返回NaN
if (!isNumber(num1) || !isNumber(num2)) {
console.warn('请传入数字类型');
return NaN;
}
let m1 = 0, // 被除数小数点后的位数
m2 = 0, // 除数小数点后的位数
s1 = num1.toString(), // 将被除数转换为字符串
s2 = num2.toString(); // 将除数转换为字符串
// 计算被除数小数点后的位数
try {
m1 += s1.split('.')[1].length;
} catch (error) {}
// 计算除数小数点后的位数
try {
m2 += s2.split('.')[1].length;
} catch (error) {}
// 进行除法运算并处理小数点后的位数,使用之前定义的乘法函数保持精度
// #ifdef APP-ANDROID
return floatMul(
parseFloat(s1.replace('.', '')) / parseFloat(s2.replace('.', '')),
Math.pow(10, m2 - m1),
);
// #endif
// #ifndef APP-ANDROID
return floatMul(
Number(s1.replace('.', '')) / Number(s2.replace('.', '')),
Math.pow(10, m2 - m1),
);
// #endif
}

View File

@@ -0,0 +1,44 @@
// @ts-nocheck
import {isNumber} from '../isNumber';
// #ifdef APP-ANDROID
import BigDecimal from 'java.math.BigDecimal'
// import BigDecimal from 'java.math.BigDecimal'
// import StringBuilder from 'java.lang.StringBuilder'
// import java.math.BigDecimal;
// #endif
/**
* 乘法函数,用于处理浮点数乘法并保持精度。
* @param {number} num1 - 第一个乘数。
* @param {number} num2 - 第二个乘数。
* @returns {number} 乘法运算的结果,保留正确的精度。
*/
export function floatMul(num1 : number, num2 : number) : number {
if (!(isNumber(num1) || isNumber(num2))) {
console.warn('Please pass in the number type');
return NaN;
}
let m = 0;
// #ifdef APP-ANDROID
let s1 = BigDecimal.valueOf(num1.toDouble()).toPlainString(); //new UTSNumber(num1).toString() // //`${num1.toFloat()}`// num1.toString(),
let s2 = BigDecimal.valueOf(num2.toDouble()).toPlainString(); //new UTSNumber(num2).toString() //`${num2.toFloat()}`//.toString();
// #endif
// #ifndef APP-ANDROID
let s1:string = `${num1}`// num1.toString(),
let s2:string = `${num2}`//.toString();
// #endif
try {
m += s1.split('.')[1].length;
} catch (error) { }
try {
m += s2.split('.')[1].length;
} catch (error) { }
// #ifdef APP-ANDROID
return parseFloat(s1.replace('.', '')) * parseFloat(s2.replace('.', '')) / Math.pow(10, m);
// #endif
// #ifndef APP-ANDROID
return Number(s1.replace('.', '')) * Number(s2.replace('.', '')) / Math.pow(10, m);
// #endif
}

View File

@@ -0,0 +1,32 @@
import { isNumber } from '../isNumber';
/**
* 减法函数,用于处理浮点数减法并保持精度。
* @param {number} num1 - 被减数。
* @param {number} num2 - 减数。
* @returns {number} 减法运算的结果,保留正确的精度。
*/
export function floatSub(num1 : number, num2 : number) : number {
if (!(isNumber(num1) || isNumber(num2))) {
console.warn('Please pass in the number type');
return NaN;
}
let r1:number, r2:number, m:number, n:number;
try {
r1 = num1.toString().split('.')[1].length;
} catch (error) {
r1 = 0;
}
try {
r2 = num2.toString().split('.')[1].length;
} catch (error) {
r2 = 0;
}
m = Math.pow(10, Math.max(r1, r2));
n = r1 >= r2 ? r1 : r2;
// #ifndef APP-ANDROID
return Number(((num1 * m - num2 * m) / m).toFixed(n));
// #endif
// #ifdef APP-ANDROID
return parseFloat(((num1 * m - num2 * m) / m).toFixed(n));
// #endif
}

View File

@@ -0,0 +1,53 @@
// @ts-nocheck
// #ifdef UNI-APP-X && APP
import { isNumber } from '../isNumber'
import { isString } from '../isString'
import { isDef } from '../isDef'
// #endif
/**
* 获取对象的类名字符串
* @param obj - 需要处理的对象
* @returns 由对象属性作为类名组成的字符串
*/
export function getClassStr<T>(obj : T) : string {
let classNames : string[] = [];
// #ifdef APP-ANDROID
if (obj instanceof UTSJSONObject) {
(obj as UTSJSONObject).toMap().forEach((value, key) => {
if (isDef(value)) {
if (isNumber(value)) {
classNames.push(key);
}
if (isString(value) && value !== '') {
classNames.push(key);
}
if (typeof value == 'boolean' && (value as boolean)) {
classNames.push(key);
}
}
})
}
// #endif
// #ifndef APP-ANDROID
// 遍历对象的属性
for (let key in obj) {
// 检查属性确实属于对象自身且其值为true
if ((obj as any).hasOwnProperty(key) && obj[key]) {
// 将属性名添加到类名数组中
classNames.push(key);
}
}
// #endif
// 将类名数组用空格连接成字符串并返回
return classNames.join(' ');
}
// 示例
// const obj = { foo: true, bar: false, baz: true };
// const classNameStr = getClassStr(obj);
// console.log(classNameStr); // 输出: "foo baz"

View File

@@ -0,0 +1,11 @@
// @ts-nocheck
// #ifndef UNI-APP-X && APP
// export * from './vue.ts'
export { getCurrentPage } from './vue.ts'
// #endif
// #ifdef UNI-APP-X && APP
// export * from './uvue.uts'
export { getCurrentPage } from './uvue.uts'
// #endif

View File

@@ -0,0 +1,5 @@
// @ts-nocheck
export const getCurrentPage = ():Page => {
const pages = getCurrentPages();
return pages[pages.length - 1]
};

View File

@@ -0,0 +1,6 @@
// @ts-nocheck
/** 获取当前页 */
export const getCurrentPage = () => {
const pages = getCurrentPages();
return pages[pages.length - 1] //as T & WechatMiniprogram.Page.TrivialInstance;
};

View File

@@ -0,0 +1,62 @@
// @ts-nocheck
// #ifdef APP-NVUE || APP-VUE
export const getLocalFilePath = (path : string) => {
if (typeof plus == 'undefined') return path
if (/^(_www|_doc|_documents|_downloads|file:\/\/|\/storage\/emulated\/0\/)/.test(path)) return path
if (/^\//.test(path)) {
const localFilePath = plus.io.convertAbsoluteFileSystem(path)
if (localFilePath !== path) {
return localFilePath
} else {
path = path.slice(1)
}
}
return '_www/' + path
}
// #endif
// #ifdef UNI-APP-X && APP
export { getResourcePath as getLocalFilePath } from '@/uni_modules/lime-file-utils'
// export const getLocalFilePath = (path : string) : string => {
// let uri = path
// if (uri.startsWith("http") || uri.startsWith("<svg") || uri.startsWith("data:image/svg+xml")) {
// return uri
// }
// if (uri.startsWith("file://")) {
// uri = uri.substring("file://".length)
// } else if (uri.startsWith("unifile://")) {
// uri = UTSAndroid.convert2AbsFullPath(uri)
// } else {
// uri = UTSAndroid.convert2AbsFullPath(uri)
// if (uri.startsWith("/android_asset/")) {
// uri = uri.replace("/android_asset/", "")
// }
// }
// if (new File(uri).exists()) {
// return uri
// } else {
// return null
// }
// // return UTSAndroid.convert2AbsFullPath(path)
// }
// #endif
// #ifdef APP-IOS
// export const getLocalFilePath = (path : string) : string => {
// try {
// let uri = path
// if (uri.startsWith("http") || uri.startsWith("<svg") || uri.startsWith("data:image/svg+xml")) {
// return uri
// }
// if (uri.startsWith("file://")) {
// return uri.substring("file://".length)
// } else if (path.startsWith("/var/")) {
// return path
// }
// return UTSiOS.getResourcePath(path)
// } catch (e) {
// return null
// }
// // return UTSiOS.getResourcePath(path)
// }
// #endif

View File

@@ -0,0 +1,138 @@
// @ts-nocheck
// #ifdef UNI-APP-X && APP
// export * from './uvue.uts'
export { getRect, getAllRect } from './uvue.uts'
// #endif
// #ifndef UNI-APP-X && APP
// export * from './vue.ts'
export { getRect, getAllRect } from './vue.ts'
// #endif
/**
* 获取视口滚动条位置信息
*/
export function getViewportScrollInfo() : Promise<any> {
return new Promise(resolve => {
uni.createSelectorQuery()
.selectViewport()
.scrollOffset((res) => {
resolve(res);
}).exec();
})
}
/**
```
page
╭───────────────╮ viewport
╭─│─ ─ ─ ─ ─ ─ ─ ─│─╮
│ │ ╭───────────╮ │ │
│ │ │ element │ │ │
│ │ ╰───────────╯ │ │
╰─│─ ─ ─ ─ ─ ─ ─ ─│─╯
│ │
│ │
╰───────────────╯
```
# 参数
- viewportHeight: viewport 高度
- viewportScrollTop: viewport 垂直滚动值
- elementHeight: element 高度
- elementOffsetTop: element 距离页面顶部距离
# 选项
- position: element 在视窗中的位置(start, center, end, nearest)
- startOffset: element 距离视窗顶部的偏移量
- endOffset: element 距离视窗底部的偏移量
# 结果值
- viewportScrollTop: viewport 新的垂直滚动值
*/
export type ScrollIntoViewOptions = {
/** 元素顶部需要保留的缓冲距离(默认 0 */
startOffset ?: number;
/** 元素底部需要保留的缓冲距离(默认 0 */
endOffset ?: number;
/** 滚动对齐方式start/center/end/nearest默认 nearest */
position ?: 'start' | 'center' | 'end' | 'nearest';
}
/**
* 计算元素需要滚动到可视区域的目标滚动位置
* @param viewportHeight 视口高度(像素)
* @param viewportScrollTop 当前滚动位置(像素)
* @param elementHeight 元素高度(像素)
* @param elementOffsetTop 元素相对于父容器顶部的偏移量(像素)
* @param options 配置选项
* @returns 计算后的目标滚动位置(像素)
*
* @example
* // 示例:将元素滚动到视口顶部对齐
* const scrollTop = getScrollIntoViewValue(
* 500, // 视口高度
* 200, // 当前滚动位置
* 100, // 元素高度
* 300, // 元素偏移量
* { position: 'start' }
* );
*/
export function getScrollIntoViewValue(
viewportHeight : number,
viewportScrollTop : number,
elementHeight : number,
elementOffsetTop : number,
options : ScrollIntoViewOptions = {}
) : number {
let { startOffset = 0, endOffset = 0, position = 'nearest'} = options;
// 计算元素相对于视口的上下偏移量
const elementToViewportTopOffset = elementOffsetTop - viewportScrollTop - startOffset;
const elementToViewportBottomOffset =
elementOffsetTop +
elementHeight -
viewportScrollTop -
viewportHeight +
endOffset;
// 处理 nearest 模式,自动选择最近边缘
if (position == 'nearest') {
if (elementToViewportTopOffset >= 0 && elementToViewportBottomOffset <= 0) {
return viewportScrollTop;
}
position =
Math.abs(elementToViewportTopOffset) > Math.abs(elementToViewportBottomOffset)
? 'end'
: 'start';
}
// 根据不同的对齐位置计算目标滚动位置
let nextScrollTop = 0;
switch (position) {
case 'start':
// 顶部对齐:元素顶部对齐视口顶部(考虑顶部缓冲)
nextScrollTop = elementOffsetTop - startOffset;
break;
case 'center':
// 居中对齐:元素中心对齐视口中心(考虑上下缓冲)
nextScrollTop =
elementOffsetTop -
(viewportHeight - elementHeight - endOffset - startOffset) / 2 +
startOffset;
break;
case 'end':
// 底部对齐:元素底部对齐视口底部(考虑底部缓冲)
nextScrollTop =
elementOffsetTop + elementHeight - viewportHeight + endOffset;
break;
}
return nextScrollTop;
}

View File

@@ -0,0 +1,17 @@
// @ts-nocheck
export function getRect(selector : string, context: ComponentPublicInstance):Promise<NodeInfo> {
return new Promise((resolve)=>{
uni.createSelectorQuery().in(context).select(selector).boundingClientRect(res =>{
resolve(res as NodeInfo)
}).exec();
})
}
export function getAllRect(selector : string, context: ComponentPublicInstance):Promise<NodeInfo[]> {
return new Promise((resolve)=>{
uni.createSelectorQuery().in(context).selectAll(selector).boundingClientRect(res =>{
resolve(res as NodeInfo[])
}).exec();
})
}

View File

@@ -0,0 +1,117 @@
// @ts-nocheck
// #ifdef APP-NVUE
// 当编译环境是 APP-NVUE 时,引入 uni.requireNativePlugin('dom'),具体插件用途未知
const dom = uni.requireNativePlugin('dom')
// #endif
/**
* 获取节点信息
* @param selector 选择器字符串
* @param context ComponentInternalInstance 对象
* @param node 是否获取node
* @returns 包含节点信息的 Promise 对象
*/
export function getRect(selector : string, context : ComponentInternalInstance|ComponentPublicInstance, node: boolean = false) {
// 之前是个对象,现在改成实例,防止旧版会报错
if(context== null) {
return Promise.reject('context is null')
}
if(context.context){
context = context.context
}
// #ifdef MP || VUE2
if (context.proxy) context = context.proxy
// #endif
return new Promise<UniNamespace.NodeInfo>((resolve, reject) => {
// #ifndef APP-NVUE
const dom = uni.createSelectorQuery().in(context).select(selector);
const result = (rect: UniNamespace.NodeInfo) => {
if (rect) {
resolve(rect)
} else {
reject('no rect')
}
}
if (!node) {
dom.boundingClientRect(result).exec()
} else {
dom.fields({
node: true,
size: true,
rect: true
}, result).exec()
}
// #endif
// #ifdef APP-NVUE
const refs = context.refs || context.$refs
if (/#|\./.test(selector) && refs) {
selector = selector.replace(/#|\./, '')
if (refs[selector]) {
selector = refs[selector]
if(Array.isArray(selector)) {
selector = selector[0]
}
}
}
dom.getComponentRect(selector, (res) => {
if (res.size) {
resolve(res.size)
} else {
reject('no rect')
}
})
// #endif
});
};
export function getAllRect(selector : string, context: ComponentInternalInstance|ComponentPublicInstance, node:boolean = false) {
if(context== null) {
return Promise.reject('context is null')
}
// #ifdef MP || VUE2
if (context.proxy) context = context.proxy
// #endif
return new Promise<UniNamespace.NodeInfo>((resolve, reject) => {
// #ifndef APP-NVUE
const dom = uni.createSelectorQuery().in(context).selectAll(selector);
const result = (rect: UniNamespace.NodeInfo[]) => {
if (rect) {
resolve(rect)
} else {
reject('no rect')
}
}
if (!node) {
dom.boundingClientRect(result).exec()
} else {
dom.fields({
node: true,
size: true,
rect: true
}, result).exec()
}
// #endif
// #ifdef APP-NVUE
let { context } = options
if (/#|\./.test(selector) && context.refs) {
selector = selector.replace(/#|\./, '')
if (context.refs[selector]) {
selector = context.refs[selector]
if(Array.isArray(selector)) {
selector = selector[0]
}
}
}
dom.getComponentRect(selector, (res) => {
if (res.size) {
resolve([res.size])
} else {
reject('no rect')
}
})
// #endif
});
};

View File

@@ -0,0 +1,54 @@
// @ts-nocheck
// #ifndef UNI-APP-X && APP
interface CSSProperties {
[key : string] : string | number | null
}
// #endif
// #ifdef VUE3
// #ifdef UNI-APP-X && APP
type CSSProperties = UTSJSONObject
// #endif
// #endif
/**
* 将字符串转换为带有连字符分隔的小写形式
* @param key - 要转换的字符串
* @returns 转换后的字符串
*/
export function toLowercaseSeparator(key : string):string {
return key.replace(/([A-Z])/g, '-$1').toLowerCase();
}
/**
* 获取样式对象对应的样式字符串
* @param style - CSS样式对象
* @returns 由非空有效样式属性键值对组成的字符串
*/
export function getStyleStr(style : CSSProperties) : string {
// #ifdef UNI-APP-X && APP
let styleStr = '';
style.toMap().forEach((value, key) => {
if(value !== null && value != '') {
styleStr += `${toLowercaseSeparator(key as string)}: ${value};`
}
})
return styleStr
// #endif
// #ifndef UNI-APP-X && APP
return Object.keys(style)
.filter(
(key) =>
style[key] !== undefined &&
style[key] !== null &&
style[key] !== '')
.map((key : string) => `${toLowercaseSeparator(key)}: ${style[key]};`)
.join(' ');
// #endif
}
// 示例
// const style = { color: 'red', fontSize: '16px', backgroundColor: '', border: null };
// const styleStr = getStyleStr(style);
// console.log(styleStr);
// 输出: "color: red; font-size: 16px;"

View File

@@ -0,0 +1,28 @@
/**
* 生成指定长度的伪随机字符串通常用作唯一标识符非标准GUID
*
* 此函数使用Math.random()生成基于36进制数字+小写字母的随机字符串。当长度超过11位时
* 会通过递归拼接多个随机段实现。注意该方法生成的并非标准GUID/UUID不适合高安全性场景。
*
* @param {number} [len=32] - 要生成的字符串长度默认32位
* @returns {string} 生成的伪随机字符串包含0-9和a-z字符
*
* @example
* guid(); // 返回32位字符串例如"3zyf6a5f3kb4ayy9jq9v1a70z0qdm0bk"
* guid(5); // 返回5位字符串例如"kf3a9"
* guid(20); // 返回20位字符串由两段随机字符串拼接而成
*
* @note
* 1. 由于使用Math.random(),随机性存在安全缺陷,不适用于密码学用途
* 2. 当长度>11时采用递归拼接可能略微影响性能在极端大长度情况下
* 3. 字符串补全时使用'0'填充,可能略微降低末尾字符的随机性
*/
export function guid(len:number = 32):string {
// crypto.randomUUID();
return len <= 11
? Math.random()
.toString(36)
.substring(2, 2 + len)
.padEnd(len, '0')
: guid(11) + guid(len - 11);
}

View File

@@ -0,0 +1,11 @@
// @ts-nocheck
// #ifdef UNI-APP-X && APP
// export * from './uvue.uts'
export { hasOwn } from './uvue.uts'
// #endif
// #ifndef UNI-APP-X && APP
// export * from './vue.ts'
export { hasOwn } from './vue.ts'
// #endif

View File

@@ -0,0 +1,43 @@
// @ts-nocheck
/**
* 检查对象或数组是否具有指定的属性或键
* @param obj 要检查的对象或数组
* @param key 指定的属性或键
* @returns 如果对象或数组具有指定的属性或键则返回true否则返回false
*/
function hasOwn(obj: UTSJSONObject, key: string): boolean
function hasOwn(obj: Map<string, unknown>, key: string): boolean
function hasOwn(obj: any, key: string): boolean {
if(obj instanceof UTSJSONObject){
return obj[key] != null
}
if(obj instanceof Map<string, unknown>){
return (obj as Map<string, unknown>).has(key)
}
if(typeof obj == 'object') {
const obj2 = {...toRaw(obj)}
return obj2[key] != null
}
return false
}
export {
hasOwn
}
// 示例
// const obj = { name: 'John', age: 30 };
// if (hasOwn(obj, 'name')) {
// console.log("对象具有 'name' 属性");
// } else {
// console.log("对象不具有 'name' 属性");
// }
// // 输出: 对象具有 'name' 属性
// const arr = [1, 2, 3];
// if (hasOwn(arr, 'length')) {
// console.log("数组具有 'length' 属性");
// } else {
// console.log("数组不具有 'length' 属性");
// }
// 输出: 数组具有 'length' 属性

View File

@@ -0,0 +1,30 @@
// @ts-nocheck
const hasOwnProperty = Object.prototype.hasOwnProperty
/**
* 检查对象或数组是否具有指定的属性或键
* @param obj 要检查的对象或数组
* @param key 指定的属性或键
* @returns 如果对象或数组具有指定的属性或键则返回true否则返回false
*/
export function hasOwn(obj: Object | Array<any>, key: string): boolean {
return hasOwnProperty.call(obj, key);
}
// 示例
// const obj = { name: 'John', age: 30 };
// if (hasOwn(obj, 'name')) {
// console.log("对象具有 'name' 属性");
// } else {
// console.log("对象不具有 'name' 属性");
// }
// // 输出: 对象具有 'name' 属性
// const arr = [1, 2, 3];
// if (hasOwn(arr, 'length')) {
// console.log("数组具有 'length' 属性");
// } else {
// console.log("数组不具有 'length' 属性");
// }
// 输出: 数组具有 'length' 属性

View File

@@ -0,0 +1,43 @@
// @ts-nocheck
// validator
// export * from './isString'
// export * from './isNumber'
// export * from './isNumeric'
// export * from './isDef'
// export * from './isFunction'
// export * from './isObject'
// export * from './isPromise'
// export * from './isBase64'
// export * from './hasOwn'
// // 单位转换
// export * from './addUnit'
// export * from './unitConvert'
// export * from './toNumber'
// export * from './random'
// export * from './range'
// export * from './fillZero'
// // image
// export * from './base64ToPath'
// export * from './pathToBase64'
// export * from './exif'
// // canvas
// export * from './canIUseCanvas2d'
// // page
// export * from './getCurrentPage'
// // dom
// export * from './getRect'
// export * from './selectComponent'
// export * from './createAnimation'
// // delay
// export * from './sleep'
// export * from './debounce'
// export * from './throttle'

View File

@@ -0,0 +1,23 @@
// @ts-nocheck
/**
* 判断一个字符串是否为Base64编码。
* Base64编码的字符串只包含A-Z、a-z、0-9、+、/ 和 = 这些字符。
* @param {string} str - 要检查的字符串。
* @returns {boolean} 如果字符串是Base64编码返回true否则返回false。
*/
export function isBase64(str: string): boolean {
const base64Regex = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/;
return base64Regex.test(str);
}
/**
* 判断一个字符串是否为Base64编码的data URI。
* Base64编码的data URI通常以"data:"开头后面跟着MIME类型和编码信息然后是Base64编码的数据。
* @param {string} str - 要检查的字符串。
* @returns {boolean} 如果字符串是Base64编码的data URI返回true否则返回false。
*/
export function isDataURI(str: string): boolean {
const dataUriRegex = /^data:([a-zA-Z]+\/[a-zA-Z0-9-+.]+)(;base64)?,([a-zA-Z0-9+/]+={0,2})$/;
return dataUriRegex.test(str);
}

View File

@@ -0,0 +1,24 @@
/**
* 检查一个值是否为严格的布尔值(仅限 `true` 或 `false`
*
* @example
* isBoolean(true); // true
* isBoolean(false); // true
* isBoolean(0); // false
* isBoolean(null); // false
*
* @param {unknown} value - 要检查的值
* @returns {value is boolean} 如果值是 `true` 或 `false` 则返回 `true`,否则返回 `false`
*
* @description
* 此函数使用严格相等(`===`)检查,避免隐式类型转换。
* 注意:不适用于 `Boolean` 包装对象(如 `new Boolean(true)`)。
*/
export function isBoolean(value: any|null): boolean {
// #ifdef APP-ANDROID
return value == true || value == false
// #endif
// #ifndef APP-ANDROID
return value === true || value === false
// #endif
}

View File

@@ -0,0 +1,8 @@
// @ts-nocheck
// #ifdef WEB
export const isBrowser = typeof window !== 'undefined';
// #endif
// #ifndef WEB
export const isBrowser = false;
// #endif

View File

@@ -0,0 +1,86 @@
// @ts-nocheck
// import assertString from './util/assertString';
/**
* 字节长度验证配置选项
*/
export type IsByteLengthOptions = {
/** 允许的最小字节长度 */
min ?: number;
/** 允许的最大字节长度 */
max ?: number;
}
/**
* 检查字符串字节长度是否在指定范围内
* @function
* @overload 使用配置对象
* @param str - 要检查的字符串
* @param options - 配置选项对象
* @returns 是否满足字节长度要求
*
* @overload 使用独立参数
* @param str - 要检查的字符串
* @param min - 最小字节长度
* @param max - 最大字节长度(可选)
* @returns 是否满足字节长度要求
*
* @example
* // 使用配置对象
* isByteLength('🇨🇳', { min: 4, max: 8 }); // trueunicode 国旗符号占 8 字节)
*
* @example
* // 旧式参数调用
* isByteLength('hello', 3, 7); // true实际字节长度 5
*
* @description
* 1. 使用 URL 编码计算字节长度(更准确处理多字节字符)
* 2. 同时支持两种参数格式:
* - 配置对象格式 { min, max }
* - 独立参数格式 (min, max)
* 3. 不传 max 参数时只验证最小长度
* 4. 严格空值处理,允许设置 0 值
*/
export function isByteLength(str : string, optionsOrMin ?: IsByteLengthOptions) : boolean;
export function isByteLength(str : string, optionsOrMin ?: number) : boolean;
export function isByteLength(str : string, optionsOrMin : number, maxParam : number | null) : boolean;
export function isByteLength(
str : string,
optionsOrMin ?: IsByteLengthOptions | number,
maxParam : number | null = null
) : boolean {
// assertString(str);
/** 最终计算的最小长度 */
let min: number;
/** 最终计算的最大长度 */
let max : number | null;
// 参数逻辑处理
if (optionsOrMin != null && typeof optionsOrMin == 'object') {
// 使用对象配置的情况
const options = optionsOrMin as IsByteLengthOptions;
min = Math.max(options.min ?? 0, 0); // 确保最小值为正整数
max = options.max;
} else {
// 使用独立参数的情况
min = Math.max(
typeof optionsOrMin == 'number' ? optionsOrMin : 0,
0
);
max = maxParam;
}
// URL 编码后的字节长度计算
const encoded = encodeURI(str);
const len = (encoded?.split(/%..|./).length ?? 0) - 1;
// 执行验证逻辑
// #ifndef APP-ANDROID
return len >= min && (typeof max == 'undefined' || len <= (max ?? 0));
// #endif
// #ifdef APP-ANDROID
return len >= min && (max == null || len <= max);
// #endif
}

View File

@@ -0,0 +1,189 @@
// @ts-nocheck
/**
* 日期验证配置选项
*/
export type IsDateOptions = {
/** 日期格式字符串,默认 'YYYY/MM/DD' */
format ?: string;
/** 允许的分隔符数组,默认 ['/', '-'] */
delimiters ?: string[];
/** 是否严格匹配格式,默认 false */
strictMode ?: boolean;
}
/**
* 验证日期格式字符串是否合法
* @param format - 需要验证的格式字符串
* @returns 是否合法格式
*/
function isValidFormat(format : string) : boolean {
return /(^(y{4}|y{2})[年./-](m{1,2})[月./-](d{1,2}(日)?)$)|(^(m{1,2})[./-](d{1,2})[./-]((y{4}|y{2})$))|(^(d{1,2})[./-](m{1,2})[./-]((y{4}|y{2})$))/i.test(format);
}
/**
* 将日期部分和格式部分组合成键值对数组
* @param date - 分割后的日期部分数组
* @param format - 分割后的格式部分数组
* @returns 组合后的二维数组
*/
function zip(date : string[], format : string[]) : string[][] {
const zippedArr : string[][] = [];
const len = Math.max(date.length, format.length);
for (let i = 0; i < len; i++) {
const key = i < date.length ? date[i] : ''
const value = i < format.length ? format[i] : ''
zippedArr.push([key, value])
}
return zippedArr;
}
/** 验证日期对象 */
function validateDateObject(date : Date, strictMode : boolean) : boolean {
// #ifndef APP-ANDROID
return !strictMode && Object.prototype.toString.call(date) === '[object Date]' && !isNaN(date.getTime());
// #endif
// #ifdef APP-ANDROID
return !strictMode && !isNaN(date.getTime())
// #endif
}
function escapeRegExp(str: string): string {
return str//.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
function enhancedSplit(
str: string,
delimiters: string[]
): string[] {
// 构建动态分隔符正则表达式
const escapedDelimiters = delimiters.map(d => escapeRegExp(d));
const pattern = new RegExp(
`[${escapedDelimiters.join('')}]+`, // 匹配任意允许的分隔符
'g'
);
return str.split(pattern).filter(p => p != '');
}
/**
* 验证输入是否为有效日期
* @param input - 输入值,可以是字符串或 Date 对象
* @param options - 配置选项,可以是字符串(简写格式)或配置对象
* @returns 是否为有效日期
*
* @example
* isDate('2023/12/31'); // true
* isDate(new Date()); // true
* isDate('02-29-2023', { strictMode: true }); // false2023年不是闰年
*/
export function isDate(input : Date, options ?: IsDateOptions) : boolean;
export function isDate(input : string, options ?: string | IsDateOptions) : boolean;
export function isDate(input : string | Date, options : string | IsDateOptions | null = null) : boolean {
// 处理参数重载:允许第二个参数直接传格式字符串
// Date对象验证
let format = 'YYYY/MM/DD'
let delimiters = ['/', '-']
let strictMode = false
if (options != null) {
if (typeof options == 'string') {
format = options as string
} else {
format = (options as IsDateOptions).format ?? format
delimiters = (options as IsDateOptions).delimiters ?? delimiters
strictMode = (options as IsDateOptions).strictMode ?? strictMode
}
}
if (input instanceof Date) {
return validateDateObject(input, strictMode);
}
// 字符串类型验证
if (!isValidFormat(format)) return false;
// 严格模式长度检查
if (strictMode && input.length != format.length) {
return false;
}
// 获取格式中的分隔符
const formatDelimiter = delimiters.find((d) : boolean => format.indexOf(d) != -1);
// 获取实际使用的分隔符
const dateDelimiter = strictMode
? formatDelimiter
: delimiters.find((d) : boolean => input.indexOf(d) != -1);
// 分割日期和格式
const dateParts = strictMode ? enhancedSplit(input, delimiters) : input.split(dateDelimiter ?? '');
const formatParts = strictMode ? enhancedSplit(format.toLowerCase(), delimiters) : format.toLowerCase().split(formatDelimiter ?? '');
// 组合成键值对
const dateAndFormat = zip(dateParts, formatParts);
const dateObj = new Map<string, string>();
// 解析日期组成部分
for (const [dateWord, formatWord] of dateAndFormat) {
if (dateWord == '' || formatWord == '' || dateWord.length != formatWord.length) {
return false;
}
dateObj.set(formatWord.charAt(0), dateWord)
}
// 年份处理
let fullYear = dateObj.get('y');
if (fullYear == null) return false;
// 检查年份前导负号
if (fullYear.startsWith('-')) return false;
// 两位年份转四位
if (fullYear.length == 2) {
const parsedYear = parseInt(fullYear, 10);
if (isNaN(parsedYear)) {
return false;
}
const currentYear = new Date().getFullYear();
const century = currentYear - (currentYear % 100);
fullYear = (parseInt(fullYear, 10) < (currentYear % 100))
? `${century + 100 + parseInt(fullYear, 10)}`
: `${century + parseInt(fullYear, 10)}`;
}
// 月份补零
const month = dateObj.get('m')?.padStart(2, '0') ?? '';
// 日期补零
const day = dateObj.get('d')?.padStart(2, '0') ?? '';
const isoDate = `${fullYear}-${month}-${day}T00:00:00.000Z`;
// return new Date(time).getDate() == parseInt(day);
// 构造 ISO 日期字符串验证
try {
// #ifndef APP-ANDROID
const date = new Date(isoDate);
return date.getUTCDate() === parseInt(day, 10) &&
(date.getUTCMonth() + 1) === parseInt(month, 10) &&
date.getUTCFullYear() === parseInt(fullYear, 10);
// #endif
// #ifdef APP-ANDROID
const date = new Date(isoDate);
return date.getDate() == parseInt(day, 10) &&
(date.getMonth() + 1) == parseInt(month, 10) &&
date.getFullYear() == parseInt(fullYear, 10);
// #endif
} catch {
return false;
}
}

View File

@@ -0,0 +1,23 @@
// @ts-nocheck
/**
* 检查一个值是否已定义(不为 undefined且不为 null
* @param value 要检查的值
* @returns 如果值已定义且不为 null则返回 true否则返回 false
*/
// #ifndef UNI-APP-X
export function isDef(value: unknown): boolean {
return value !== undefined && value !== null;
}
// #endif
// #ifdef UNI-APP-X
export function isDef(value : any|null) : boolean {
// #ifdef UNI-APP-X && APP
return value != null;
// #endif
// #ifndef UNI-APP-X && APP
return value != null && value != undefined;
// #endif
}
// #endif

View File

@@ -0,0 +1,11 @@
// @ts-nocheck
/**
* 验证电子邮件地址格式
* @param email - 要验证的字符串
* @returns 是否通过验证
*/
export function isEmail(email : string) : boolean {
const emailRegex = /\S+@\S+\.\S+/;
return emailRegex.test(email);
}

View File

@@ -0,0 +1,81 @@
// @ts-nocheck
import {isDef} from '../isDef'
import {isString} from '../isString'
import {isNumber} from '../isNumber'
/**
* 判断一个值是否为空。
*
* 对于字符串去除首尾空格后判断长度是否为0。
* 对于数组判断长度是否为0。
* 对于对象判断键的数量是否为0。
* 对于null或undefined直接返回true。
* 其他类型(如数字、布尔值等)默认不为空。
*
* @param {any} value - 要检查的值。
* @returns {boolean} 如果值为空返回true否则返回false。
*/
// #ifdef UNI-APP-X && APP
export function isEmpty(value : any | null) : boolean {
// 为null
if(!isDef(value)){
return true
}
// 为空字符
if(isString(value)){
return value.toString().trim().length == 0
}
// 为数值
if(isNumber(value)){
return false
}
if(typeof value == 'object'){
// 数组
if(Array.isArray(value)){
return (value as Array<unknown>).length == 0
}
// Map
if(value instanceof Map<unknown, unknown>) {
return value.size == 0
}
// Set
if(value instanceof Set<unknown>) {
return value.size == 0
}
if(value instanceof UTSJSONObject) {
return value.toMap().size == 0
}
return JSON.stringify(value) == '{}'
}
return false
}
// #endif
// #ifndef UNI-APP-X && APP
export function isEmpty(value: any): boolean {
// 检查是否为null或undefined
if (value == null || value == undefined || value == '') {
return true;
}
// 检查字符串是否为空
if (typeof value === 'string') {
return value.trim().length === 0;
}
// 检查数组是否为空
if (Array.isArray(value)) {
return value.length === 0;
}
// 检查对象是否为空
if (typeof value === 'object') {
return Object.keys(value).length === 0;
}
// 其他类型(如数字、布尔值等)不为空
return false;
}
// #endif

View File

@@ -0,0 +1,16 @@
// @ts-nocheck
/**
* 检查一个值是否为函数类型
* @param val 要检查的值
* @returns 如果值的类型是函数类型,则返回 true否则返回 false
*/
// #ifdef UNI-APP-X && APP
export const isFunction = (val: any):boolean => typeof val == 'function';
// #endif
// #ifndef UNI-APP-X && APP
export const isFunction = (val: unknown): val is Function =>
typeof val === 'function';
// #endif

View File

@@ -0,0 +1,64 @@
// @ts-nocheck
// #ifndef APP-ANDROID || APP-HARMONY || APP-IOS
type UTSJSONObject = {
version : string | number | null;
};
// #endif
// #ifdef APP-ANDROID || APP-HARMONY || APP-IOS
// type Options = UTSJSONObject
// #endif
const IPv4SegmentFormat = '(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])';
const IPv4AddressFormat = `(${IPv4SegmentFormat}\\.){3}${IPv4SegmentFormat}`;
const IPv4AddressRegExp = new RegExp(`^${IPv4AddressFormat}$`);
const IPv6SegmentFormat = '(?:[0-9a-fA-F]{1,4})';
const IPv6AddressRegExp = new RegExp(
'^(' +
`(?:${IPv6SegmentFormat}:){7}(?:${IPv6SegmentFormat}|:)|` +
`(?:${IPv6SegmentFormat}:){6}(?:${IPv4AddressFormat}|:${IPv6SegmentFormat}|:)|` +
`(?:${IPv6SegmentFormat}:){5}(?::${IPv4AddressFormat}|(:${IPv6SegmentFormat}){1,2}|:)|` +
`(?:${IPv6SegmentFormat}:){4}(?:(:${IPv6SegmentFormat}){0,1}:${IPv4AddressFormat}|(:${IPv6SegmentFormat}){1,3}|:)|` +
`(?:${IPv6SegmentFormat}:){3}(?:(:${IPv6SegmentFormat}){0,2}:${IPv4AddressFormat}|(:${IPv6SegmentFormat}){1,4}|:)|` +
`(?:${IPv6SegmentFormat}:){2}(?:(:${IPv6SegmentFormat}){0,3}:${IPv4AddressFormat}|(:${IPv6SegmentFormat}){1,5}|:)|` +
`(?:${IPv6SegmentFormat}:){1}(?:(:${IPv6SegmentFormat}){0,4}:${IPv4AddressFormat}|(:${IPv6SegmentFormat}){1,6}|:)|` +
`(?::((?::${IPv6SegmentFormat}){0,5}:${IPv4AddressFormat}|(?::${IPv6SegmentFormat}){1,7}|:))` +
')(%[0-9a-zA-Z.]{1,})?$'
);
/**
* 验证IP地址格式
* @param {string} ipAddress - 要验证的IP地址
* @param {Options|string|number} options - 配置选项或版本号
* @returns {boolean} 是否匹配有效的IP地址格式
*/
export function isIP(ipAddress : string | null, options : UTSJSONObject | string | number | null = null) : boolean {
// assertString(ipAddress);
if(ipAddress == null) return false
let version : string | number | null;
if (typeof options == 'object') {
version = (options as UTSJSONObject|null)?.['version'];
} else {
version = options;
}
const versionStr = version != null ? `${version}` : '';
if (versionStr == '') {
return isIP(ipAddress, 4) || isIP(ipAddress, 6);
}
if (versionStr == '4') {
return IPv4AddressRegExp.test(ipAddress.trim());
}
if (versionStr == '6') {
return IPv6AddressRegExp.test(ipAddress.trim());
}
return false;
}

View File

@@ -0,0 +1,26 @@
// @ts-nocheck
/**
* 检查一个值是否为数字类型
* @param value 要检查的值,可以是 number 类型或 string 类型的数字
* @returns 如果值是数字类型且不是 NaN则返回 true否则返回 false
*/
// #ifndef UNI-APP-X
export function isNumber(value: number | string | null): boolean {
return typeof value === 'number' && !isNaN(value);
}
// #endif
// #ifdef UNI-APP-X
export function isNumber(value: any|null): boolean {
// #ifdef APP-ANDROID
return ['Byte', 'UByte','Short','UShort','Int','UInt','Long','ULong','Float','Double','number'].includes(typeof value)
// #endif
// #ifdef APP-IOS
return ['Int8', 'UInt8','Int16','UInt16','Int32','UInt32','Int64','UInt64','Int','UInt','Float','Float16','Float32','Float64','Double', 'number'].includes(typeof value)
// #endif
// #ifndef APP-ANDROID || APP-IOS
return typeof value === 'number' && !isNaN(value);
// #endif
}
// #endif

View File

@@ -0,0 +1,33 @@
// @ts-nocheck
/**
* 检查一个值是否为数字类型或表示数字的字符串
* @param value 要检查的值,可以是 string 类型或 number 类型
* @returns 如果值是数字类型或表示数字的字符串,则返回 true否则返回 false
*/
// #ifndef UNI-APP-X && APP
export function isNumeric(value: string | number | undefined | null): boolean {
return /^(-)?\d+(\.\d+)?$/.test(value);
}
// #endif
// #ifdef UNI-APP-X && APP
import {isNumber} from '../isNumber';
import {isString} from '../isString';
export function isNumeric(value : any|null) : boolean {
if(value == null) {
return false
}
if(isNumber(value)) {
return true
} else if(isString(value)) {
// const regex = "-?\\d+(\\.\\d+)?".toRegex()
const regex = new RegExp("^(-)?\\d+(\\.\\d+)?$")
return regex.test(value as string) //regex.matches(value as string)
}
return false
// return /^(-)?\d+(\.\d+)?$/.test(value);
}
// #endif

View File

@@ -0,0 +1,19 @@
// @ts-nocheck
/**
* 检查一个值是否为对象类型
* @param val 要检查的值
* @returns 如果值的类型是对象类型,则返回 true否则返回 false
*/
// #ifndef APP-ANDROID
export const isObject = (val : unknown) : val is Record<any, any> =>
val !== null && typeof val === 'object';
// #endif
// #ifdef APP-ANDROID
export const isObject = (val : any | null) : boolean =>{
return val != null && typeof val == 'object';
}
// #endif

View File

@@ -0,0 +1,22 @@
// @ts-nocheck
import {isFunction} from '../isFunction'
import {isObject} from '../isObject'
/**
* 检查一个值是否为 Promise 类型
* @param val 要检查的值
* @returns 如果值的类型是 Promise 类型,则返回 true否则返回 false
*/
// #ifndef APP-ANDROID
export const isPromise = <T = any>(val: unknown): val is Promise<T> => {
// 使用 isObject 函数判断值是否为对象类型
// 使用 isFunction 函数判断值是否具有 then 方法和 catch 方法
return isObject(val) && isFunction(val.then) && isFunction(val.catch);
};
// #endif
// #ifdef APP-ANDROID
export const isPromise = (val: any): boolean => {
return val instanceof Promise<unknown>
};
// #endif

View File

@@ -0,0 +1,33 @@
// @ts-nocheck
/**
* 检测输入值是否为正则表达式对象
* @param obj - 需要检测的任意类型值
* @returns 如果检测值是正则表达式返回 true否则返回 false
*
* @example
* // 基础检测
* isRegExp(/abc/); // true
* isRegExp(new RegExp('abc')); // true
*
* @example
* // 非正则表达式检测
* isRegExp('hello'); // false
* isRegExp({}); // false
* isRegExp(null); // false
*
* @description
* 1. 通过 Object.prototype.toString 的可靠类型检测
* 2. 支持跨执行环境的可靠检测:
* - 浏览器多 iframe 环境
* - Node.js 的 vm 模块
* 3. 比 instanceof 检测更可靠
* 4. 支持 ES3+ 全环境兼容
*/
export function isRegExp(obj : any) : boolean {
// #ifndef APP-ANDROID
return Object.prototype.toString.call(obj) === '[object RegExp]';
// #endif
// #ifdef APP-ANDROID
return obj instanceof RegExp//Object.prototype.toString.call(obj) === '[object RegExp]';
// #endif
}

View File

@@ -0,0 +1,19 @@
// @ts-nocheck
/**
* 检查一个值是否为字符串类型
* @param str 要检查的值
* @returns 如果值的类型是字符串类型,则返回 true否则返回 false
*/
// #ifndef UNI-APP-X && APP
// export const isString = (str: unknown): str is string => typeof str === 'string';
export function isString (str: unknown): str is string {
return typeof str == 'string'
}
// #endif
// #ifdef UNI-APP-X && APP
export function isString (str: any|null): boolean {
return typeof str == 'string'
}
// #endif

View File

@@ -0,0 +1,198 @@
// @ts-nocheck
import { isValidDomain, type IsValidDomainOptions } from '../isValidDomain';
import { isIP } from '../isIP';
import { isRegExp } from '../isRegExp';
// import {merge} from '../merge';
/** URL 验证配置选项 */
export type IsURLOptions = {
/** 允许的协议列表(默认 ['http', 'https', 'ftp'] */
protocols ?: string[];
/** 需要顶级域名(默认 true */
requireTld ?: boolean;
/** 需要协议头(默认 false */
requireProtocol ?: boolean;
/** 需要主机地址(默认 true */
requireHost ?: boolean;
/** 需要端口号(默认 false */
requirePort ?: boolean;
/** 需要有效协议(默认 true */
requireValidProtocol ?: boolean;
/** 允许下划线(默认 false */
allowUnderscores ?: boolean;
/** 允许结尾点号(默认 false */
allowTrailingDot ?: boolean;
/** 允许协议相对地址(默认 false */
allowProtocolRelativeUrls ?: boolean;
/** 允许片段标识(默认 true */
allowFragments ?: boolean;
/** 允许查询参数(默认 true */
allowQueryComponents ?: boolean;
/** 禁用认证信息(默认 false */
disallowAuth ?: boolean;
/** 验证长度(默认 true */
validateLength ?: boolean;
/** 最大允许长度(默认 2084 */
maxAllowedLength ?: number;
/** 白名单主机列表 */
hostWhitelist ?: Array<string | RegExp>;
/** 黑名单主机列表 */
hostBlacklist ?: Array<string | RegExp>;
}
export function checkHost(host : string, matches : any[]) : boolean {
for (let i = 0; i < matches.length; i++) {
let match = matches[i];
if (host == match || (isRegExp(match) && (match as RegExp).test(host))) {
return true;
}
}
return false;
}
// 辅助函数
function isValidPort(port : number | null) : boolean {
return port != null && !isNaN(port) && port > 0 && port <= 65535;
}
function validateHost(host : string, options : IsURLOptions | null, isIPv6 : boolean) : boolean {
if (isIPv6) return isIP(host, 6);
return isIP(host) || isValidDomain(host, {
requireTld: options?.requireTld ?? true,
allowUnderscore: options?.allowUnderscores ?? true,
allowTrailingDot: options?.allowTrailingDot ?? false
} as IsValidDomainOptions);
}
/** 匹配 IPv6 地址的正则表达式 */
const WRAPPED_IPV6_REGEX = /^\[([^\]]+)\](?::([0-9]+))?$/;
/**
* 验证字符串是否为有效的 URL
* @param url - 需要验证的字符串
* @param options - 配置选项
* @returns 是否为有效 URL
*
* @example
* ```typescript
* isURL('https://example.com'); // true
* isURL('user:pass@example.com', { disallowAuth: true }); // false
* ```
*/
export function isURL(url : string | null, options : IsURLOptions | null = null) : boolean {
// assertString(url);
// 1. 基础格式校验
if (url == null || url == '' || url.length == 0 || /[\s<>]/.test(url) || url.startsWith('mailto:')) {
return false;
}
// 合并配置选项
let protocols = options?.protocols ?? ['http', 'https', 'ftp']
// let requireTld = options?.requireTld ?? true
let requireProtocol = options?.requireProtocol ?? false
let requireHost = options?.requireHost ?? true
let requirePort = options?.requirePort ?? false
let requireValidProtocol = options?.requireValidProtocol ?? true
// let allowUnderscores = options?.allowUnderscores ?? false
// let allowTrailingDot = options?.allowTrailingDot ?? false
let allowProtocolRelativeUrls = options?.allowProtocolRelativeUrls ?? false
let allowFragments = options?.allowFragments ?? true
let allowQueryComponents = options?.allowQueryComponents ?? true
let validateLength = options?.validateLength ?? true
let maxAllowedLength = options?.maxAllowedLength ?? 2084
let hostWhitelist = options?.hostWhitelist
let hostBlacklist = options?.hostBlacklist
let disallowAuth = options?.disallowAuth ?? false
// 2. 长度校验
if (validateLength && url!.length > maxAllowedLength) {
return false;
}
// 3. 片段和查询参数校验
if (!allowFragments && url.includes('#')) return false;
if (!allowQueryComponents && (url.includes('?') || url.includes('&'))) return false;
// 处理 URL 组成部分
const [urlWithoutFragment] = url.split('#');
const [baseUrl] = urlWithoutFragment.split('?');
// 4. 协议处理
const protocolParts = baseUrl.split('://');
let protocol:string;
let remainingUrl = baseUrl;
if (protocolParts.length > 1) {
protocol = protocolParts.shift()!.toLowerCase();
if (requireValidProtocol && !protocols!.includes(protocol)) {
return false;
}
remainingUrl = protocolParts.join('://');
} else if (requireProtocol) {
return false;
} else if (baseUrl.startsWith('//')) {
if (!allowProtocolRelativeUrls) return false;
remainingUrl = baseUrl.slice(2);
}
if (remainingUrl == '') return false;
// 5. 处理主机部分
const [hostPart] = remainingUrl.split('/', 1);
const authParts = hostPart.split('@');
// 认证信息校验
if (authParts.length > 1) {
if (disallowAuth || authParts[0] == '') return false;
const auth = authParts.shift()!;
if (auth.split(':').length > 2) return false;
const [user, password] = auth.split(':');
if (user == '' && password == '') return false;
}
const hostname = authParts.join('@');
// 6. 解析主机和端口
type HostInfo = {
host ?: string;
ipv6 ?: string;
port ?: number;
};
const hostInfo : HostInfo = {};
const ipv6Match = hostname.match(WRAPPED_IPV6_REGEX);
if (ipv6Match != null) {
hostInfo.ipv6 = ipv6Match.length > 1 ? ipv6Match[1] : null;
const portStr = ipv6Match.length > 2 ? ipv6Match[2] : null;
if (portStr != null) {
hostInfo.port = parseInt(portStr);
if (!isValidPort(hostInfo.port)) return false;
}
} else {
const [host, ...portParts] = hostname.split(':');
hostInfo.host = host;
if (portParts.length > 0) {
const portStr = portParts.join(':');
hostInfo.port = parseInt(portStr);
if (!isValidPort(hostInfo.port)) return false;
}
}
// 7. 端口校验
if (requirePort && hostInfo.port == null) return false;
// 8. 主机验证逻辑
const finalHost = hostInfo.host ?? hostInfo.ipv6;
if (finalHost == null) return requireHost ? false : true;
// 白名单/黑名单检查
if (hostWhitelist != null && !checkHost(finalHost!, hostWhitelist!)) return false;
if (hostBlacklist != null && checkHost(finalHost!, hostBlacklist!)) return false;
// 9. 综合校验
return validateHost(
finalHost,
options,
!(hostInfo.ipv6 == null || hostInfo.ipv6 == '')
);
}

View File

@@ -0,0 +1,90 @@
// @ts-nocheck
/**
* 域名验证配置选项
*/
export type IsValidDomainOptions = {
/**
* 是否要求必须包含顶级域名(TLD)
* @default true
* @example
* true -> "example.com" 有效
* false -> "localhost" 有效
*/
requireTld : boolean;
/**
* 是否允许域名部分包含下划线(_)
* @default false
* @example
* true -> "my_site.com" 有效
* false -> "my_site.com" 无效
*/
allowUnderscore : boolean;
/**
* 是否允许域名以点号(.)结尾
* @default false
* @example
* true -> "example.com." 有效
* false -> "example.com." 无效
*/
allowTrailingDot : boolean;
}
/**
* 验证字符串是否为合法域名
* @param str 要验证的字符串
* @param options 配置选项,默认 {
* requireTld: true, // 需要顶级域名
* allowUnderscore: false,// 允许下划线
* allowTrailingDot: false// 允许结尾点号
* }
* @returns 验证结果
*
* 示例:
* isValidDomain("example.com") // true
* isValidDomain("my_site.com", { allowUnderscore: true }) // true
*/
export function isValidDomain(
str : string,
options : IsValidDomainOptions = {
requireTld: true,
allowUnderscore: false,
allowTrailingDot: false
}
) : boolean {
// 预处理字符串
let domain = str;
// 处理结尾点号
if (options.allowTrailingDot && domain.endsWith('.')) {
domain = domain.slice(0, -1);
}
// 分割域名部分
const parts = domain.split('.');
if (parts.length == 1 && options.requireTld) return false;
// 验证顶级域名
const tld = parts[parts.length - 1];
if (options.requireTld) {
if (!/^[a-z\u00A1-\uFFFF]{2,}$/i.test(tld) || /\s/.test(tld)) {
return false;
}
}
// 验证每个部分
const domainRegex = options.allowUnderscore
? /^[a-z0-9\u00A1-\uFFFF](?:[a-z0-9-\u00A1-\uFFFF_]*[a-z0-9\u00A1-\uFFFF])?$/i
: /^[a-z0-9\u00A1-\uFFFF](?:[a-z0-9-\u00A1-\uFFFF]*[a-z0-9\u00A1-\uFFFF])?$/i;
return parts.every(part => {
// 长度校验
if (part.length > 63) return false;
// 格式校验
if (!domainRegex.test(part)) return false;
// 禁止开头/结尾连字符
return !(part.startsWith('-') || part.endsWith('-'));
});
}

View File

@@ -0,0 +1,24 @@
// @ts-nocheck
// export function toLowercaseSeparator(key: string) {
// return key.replace(/([A-Z])/g, '-$1').toLowerCase();
// }
/**
* 将字符串转换为指定连接符的命名约定
* @param str 要转换的字符串
* @param separator 指定的连接符,默认为 "-"
* @returns 转换后的字符串
*/
export function kebabCase(str : string, separator : string = "-") : string {
return str
// #ifdef APP-ANDROID
.replace(/[A-Z]/g, (match : string, _ : number, _ : string) : string => `${separator}${match.toLowerCase()}`) // 将大写字母替换为连接符加小写字母
// #endif
// #ifndef APP-ANDROID
.replace(/[A-Z]/g, (match : string) : string => `${separator}${match.toLowerCase()}`) // 将大写字母替换为连接符加小写字母
// #endif
.replace(/[\s_-]+/g, separator) // 将空格、下划线和短横线替换为指定连接符
.replace(new RegExp(`^${separator}|${separator}$`, "g"), "") // 删除开头和结尾的连接符
.toLowerCase(); // 将结果转换为全小写
}

View File

@@ -0,0 +1,33 @@
// @ts-nocheck
/**
* 深度合并两个对象,用默认值填充目标对象中未定义的属性
*
* @template T - 合并对象的泛型类型
* @param {T} obj - 目标对象(将被修改)
* @param {T} defaults - 包含默认值的对象
* @returns {T} 合并后的对象即修改后的obj参数
*/
export function merge<T>(obj : T, defaults : T) : T {
// #ifdef APP-ANDROID
try {
if(obj instanceof UTSJSONObject && defaults instanceof UTSJSONObject) {
return UTSJSONObject.assign<T>(obj, defaults)!// as T
}
const obj1 = { ...toRaw(obj) }
const obj2 = { ...toRaw(defaults) }
return UTSJSONObject.assign<T>(obj1, obj2)!
} catch (error) {
return defaults
}
// #endif
// #ifndef APP-ANDROID
for (const key in defaults) {
if (obj[key] === undefined || obj[key] === null) {
obj[key] = defaults[key];
}
}
return obj;
// #endif
}

View File

@@ -0,0 +1,61 @@
// @ts-nocheck
// #ifndef UNI-APP-X
type UTSJSONObject = Record<string, any>
// #endif
/**
* 将对象转换为URL查询字符串
* @param data - 需要转换的键值对对象
* @param isPrefix - 是否添加问号前缀默认为false
* @returns 格式化后的URL查询参数字符串
*
* @example
* // 基础用法
* obj2url({ name: '张三', age: 25 });
* // => "name=%E5%BC%A0%E4%B8%89&age=25"
*
* @example
* // 数组参数处理
* obj2url({ tags: ['js', 'ts'] });
* // => "tags[]=js&tags[]=ts"
*
* @example
* // 包含空值的过滤
* obj2url({ name: '', age: null, city: undefined });
* // => ""
*
* @description
* 1. 自动过滤空值空字符串、null、undefined
* 2. 支持数组参数转换(自动添加[]后缀)
* 3. 自动进行URI编码
* 4. 支持自定义是否添加问号前缀
*/
export function obj2url(data : UTSJSONObject, isPrefix : boolean = false) : string {
const prefix = isPrefix ? '?' : '';
const _result:string[] = [];
const empty:(any|null)[] = ['', null]
// #ifndef APP-ANDROID
empty.push(undefined)
// #endif
for (const key in data) {
const value = data[key];
// 去掉为空的参数
if (empty.includes(value)) {
continue;
}
if (Array.isArray(value)) {
(value as any[]).forEach((_value) => {
_result.push(
encodeURIComponent(key) + '[]=' + encodeURIComponent(`${_value}`),
);
});
} else {
_result.push(encodeURIComponent(key) + '=' + encodeURIComponent(`${value}`));
}
}
return _result.length > 0 ? prefix + _result.join('&') : '';
}

View File

@@ -0,0 +1,49 @@
// @ts-nocheck
// #ifndef UNI-APP-X
interface UTSJSONObject {
[key : string] : string | number | null
}
// #endif
/**
* 将字符串转换为带有连字符分隔的小写形式
* @param key - 要转换的字符串
* @returns 转换后的字符串
*/
export function toLowercaseSeparator(key : string):string {
return key.replace(/([A-Z])/g, '-$1').toLowerCase();
}
/**
* 获取样式对象对应的样式字符串
* @param style - CSS样式对象
* @returns 由非空有效样式属性键值对组成的字符串
*/
export function objToCss(style : UTSJSONObject) : string {
// #ifdef APP-ANDROID
let styleStr = '';
style.toMap().forEach((value, key) => {
if(value != null && value != '') {
styleStr += `${toLowercaseSeparator(key as string)}: ${value};`
}
})
return styleStr
// #endif
// #ifndef APP-ANDROID
return Object.keys(style)
.filter(
(key) =>
style[key] !== undefined &&
style[key] !== null &&
style[key] !== '')
.map((key : string) => `${toLowercaseSeparator(key)}: ${style[key]};`)
.join(' ');
// #endif
}
// 示例
// const style = { color: 'red', fontSize: '16px', backgroundColor: '', border: null };
// const styleStr = objToCss(style);
// console.log(styleStr);
// 输出: "color: red; font-size: 16px;"

View File

@@ -0,0 +1,110 @@
{
"id": "lime-shared",
"displayName": "lime-shared",
"version": "0.4.2",
"description": "本人插件的几个公共函数获取当前页图片的base64转临时路径图片的exif信息等",
"keywords": [
"lime-shared",
"exif"
],
"repository": "",
"engines": {
"HBuilderX": "^3.1.0",
"uni-app": "^4.55",
"uni-app-x": "^4.55"
},
"dcloudext": {
"type": "sdk-js",
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "无",
"data": "无",
"permissions": "无"
},
"npmurl": "",
"darkmode": "x",
"i18n": "x",
"widescreen": "x"
},
"uni_modules": {
"dependencies": [],
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "√",
"aliyun": "√",
"alipay": "√"
},
"client": {
"uni-app": {
"vue": {
"vue2": "√",
"vue3": "√"
},
"web": {
"safari": "√",
"chrome": "√"
},
"app": {
"vue": "√",
"nvue": "-",
"android": {
"extVersion": "",
"minVersion": "21"
},
"ios": "√",
"harmony": "√"
},
"mp": {
"weixin": "√",
"alipay": "√",
"toutiao": "√",
"baidu": "√",
"kuaishou": "-",
"jd": "-",
"harmony": "-",
"qq": "-",
"lark": "-"
},
"quickapp": {
"huawei": "-",
"union": "-"
}
},
"uni-app-x": {
"web": {
"safari": "√",
"chrome": "√"
},
"app": {
"android": {
"extVersion": "",
"minVersion": "21"
},
"ios": "√",
"harmony": "√"
},
"mp": {
"weixin": "√"
}
},
"App": {
"app-harmony": "u",
"app-nvue": "u",
"app-uvue": "y",
"app-vue": "u"
}
}
}
}
}

View File

@@ -0,0 +1,11 @@
// @ts-nocheck
// #ifndef UNI-APP-X && APP
// export * from './vue.ts'
export { pathToBase64 } from './vue.ts'
// #endif
// #ifdef UNI-APP-X && APP
// export * from './uvue.uts'
export { pathToBase64 } from './uvue.uts'
// #endif

View File

@@ -0,0 +1,17 @@
// @ts-nocheck
// import { processFile, ProcessFileOptions } from '@/uni_modules/lime-file-utils'
export function pathToBase64(path : string) : Promise<string> {
console.error('pathToBase64: 当前环境不支持,请使用 【lime-file-utils】')
// return new Promise((resolve, reject) => {
// processFile({
// type: 'toDataURL',
// path,
// success(res : string) {
// resolve(res)
// },
// fail(err: any){
// reject(err)
// }
// } as ProcessFileOptions)
// })
}

View File

@@ -0,0 +1,121 @@
// @ts-nocheck
// #ifdef APP-PLUS
import { getLocalFilePath } from '../getLocalFilePath'
// #endif
function isImage(extension : string) {
const imageExtensions = ["jpg", "jpeg", "png", "gif", "bmp", "svg"];
return imageExtensions.includes(extension.toLowerCase());
}
// #ifdef H5
function getSVGFromURL(url: string) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.responseType = 'text';
xhr.onload = function () {
if (xhr.status === 200) {
const svg = xhr.responseText;
resolve(svg);
} else {
reject(new Error(xhr.statusText));
}
};
xhr.onerror = function () {
reject(new Error('Network error'));
};
xhr.send();
});
}
// #endif
/**
* 路径转base64
* @param {Object} string
*/
export function pathToBase64(path : string) : Promise<string> {
if (/^data:/.test(path)) return path
let extension = path.substring(path.lastIndexOf('.') + 1);
const isImageFile = isImage(extension)
let prefix = ''
if (isImageFile) {
prefix = 'image/';
if(extension == 'svg') {
extension += '+xml'
}
} else if (extension === 'pdf') {
prefix = 'application/pdf';
} else if (extension === 'txt') {
prefix = 'text/plain';
} else {
// 添加更多文件类型的判断
// 如果不是图片、PDF、文本等类型可以设定默认的前缀或采取其他处理
prefix = 'application/octet-stream';
}
return new Promise((resolve, reject) => {
// #ifdef H5
if (isImageFile) {
if(extension == 'svg') {
getSVGFromURL(path).then(svg => {
const base64 = btoa(svg);
resolve(`data:image/svg+xml;base64,${base64}`);
})
} else {
let image = new Image();
image.setAttribute("crossOrigin", 'Anonymous');
image.onload = function () {
let canvas = document.createElement('canvas');
canvas.width = this.naturalWidth;
canvas.height = this.naturalHeight;
canvas.getContext('2d').drawImage(image, 0, 0);
let result = canvas.toDataURL(`${prefix}${extension}`)
resolve(result);
canvas.height = canvas.width = 0
}
image.src = path + '?v=' + Math.random()
image.onerror = (error) => {
reject(error);
};
}
} else {
reject('not image');
}
// #endif
// #ifdef MP
if (uni.canIUse('getFileSystemManager')) {
uni.getFileSystemManager().readFile({
filePath: path,
encoding: 'base64',
success: (res) => {
resolve(`data:${prefix}${extension};base64,${res.data}`)
},
fail: (error) => {
console.error({ error, path })
reject(error)
}
})
}
// #endif
// #ifdef APP-PLUS
plus.io.resolveLocalFileSystemURL(getLocalFilePath(path), (entry) => {
entry.file((file : any) => {
const fileReader = new plus.io.FileReader()
fileReader.onload = (data) => {
resolve(data.target.result)
}
fileReader.onerror = (error) => {
console.error({ error, path })
reject(error)
}
fileReader.readAsDataURL(file)
}, reject)
}, reject)
// #endif
})
}

View File

@@ -0,0 +1,34 @@
// @ts-nocheck
export function getPlatform():Uni {
// #ifdef MP-WEIXIN
return wx
// #endif
// #ifdef MP-BAIDU
return swan
// #endif
// #ifdef MP-ALIPAY
return my
// #endif
// #ifdef MP-JD
return jd
// #endif
// #ifdef MP-QQ
return qq
// #endif
// #ifdef MP-360
return qh
// #endif
// #ifdef MP-KUAISHOU
return ks
// #endif
// #ifdef MP-LARK||MP-TOUTIAO
return tt
// #endif
// #ifdef MP-DINGTALK
return dd
// #endif
// #ifdef QUICKAPP-WEBVIEW || QUICKAPP-WEBVIEW-UNION || QUICKAPP-WEBVIEW-HUAWEI
return qa
// #endif
return uni
}

View File

@@ -0,0 +1,12 @@
// @ts-nocheck
// #ifdef UNI-APP-X && APP
// export * from './uvue'
export { raf, doubleRaf, cancelRaf } from './uvue'
// #endif
// #ifndef UNI-APP-X && APP
// export * from './vue'
export { raf, doubleRaf, cancelRaf } from './vue'
// #endif

View File

@@ -0,0 +1,48 @@
// @ts-nocheck
// 是否支持被动事件监听
export const supportsPassive = true;
// #ifdef uniVersion < 4.25
// 请求动画帧
export function raf(fn: TimerCallback): number {
return setTimeout(fn, 1000 / 60);
}
// 取消动画帧
export function cancelRaf(id: number) {
clearTimeout(id);
}
// 双倍动画帧
export function doubleRaf(fn: TimerCallback): void {
raf(():number => raf(fn)); // 在下一帧回调中再次请求动画帧,实现双倍动画帧效果
}
// #endif
// #ifdef uniVersion >= 4.25
// 请求动画帧
export function raf(fn: UniAnimationFrameCallback): number
export function raf(fn: UniAnimationFrameCallbackWithNoArgument): number
export function raf(fn: any): number {
if(typeof fn == 'UniAnimationFrameCallback') {
return requestAnimationFrame(fn as UniAnimationFrameCallback);
} else {
return requestAnimationFrame(fn as UniAnimationFrameCallbackWithNoArgument);
}
}
// 取消动画帧
export function cancelRaf(id: number) {
cancelAnimationFrame(id);
}
// 双倍动画帧
export function doubleRaf(fn: UniAnimationFrameCallback): void
export function doubleRaf(fn: UniAnimationFrameCallbackWithNoArgument): void
export function doubleRaf(fn: any): void {
raf(():number => raf(fn)); // 在下一帧回调中再次请求动画帧,实现双倍动画帧效果
}
// #endif

View File

@@ -0,0 +1,32 @@
// @ts-nocheck
type Callback = () => void//Function
// 是否支持被动事件监听
export const supportsPassive = true;
// 请求动画帧
export function raf(fn : Callback) : number {
// #ifndef WEB
return setTimeout(fn, 1000 / 60); // 请求动画帧
// #endif
// #ifdef WEB
return requestAnimationFrame(fn); // 请求动画帧
// #endif
}
// 取消动画帧
export function cancelRaf(id : number) {
// 如果是在浏览器环境下,使用 cancelAnimationFrame 方法
// #ifdef WEB
cancelAnimationFrame(id); // 取消动画帧
// #endif
// #ifndef WEB
clearTimeout(id); // 取消动画帧
// #endif
}
// 双倍动画帧
export function doubleRaf(fn : Callback) : void {
raf(() => {
raf(fn)
}); // 在下一帧回调中再次请求动画帧,实现双倍动画帧效果
}

View File

@@ -0,0 +1,24 @@
// @ts-nocheck
/**
* 生成一个指定范围内的随机数
* @param min 随机数的最小值
* @param max 随机数的最大值
* @param fixed 随机数的小数位数,默认为 0
* @returns 生成的随机数
*/
export function random(min: number, max: number, fixed: number = 0):number {
// 将 min 和 max 转换为数字类型
// min = +min || 0;
// max = +max || 0;
// 计算随机数范围内的一个随机数
const num = Math.random() * (max - min) + min;
// 如果 fixed 参数为 0则返回四舍五入的整数随机数否则保留固定小数位数
// Number
return fixed == 0 ? Math.round(num) : parseFloat(num.toFixed(fixed));
}
// 示例
// console.log(random(0, 10)); // 输出:在 0 和 10 之间的一个整数随机数
// console.log(random(0, 1, 2)); // 输出:在 0 和 1 之间的一个保留两位小数的随机数
// console.log(random(1, 100, 3)); // 输出:在 1 和 100 之间的一个保留三位小数的随机数

View File

@@ -0,0 +1,36 @@
// @ts-nocheck
/**
* 生成一个数字范围的数组
* @param start 范围的起始值
* @param end 范围的结束值
* @param step 步长,默认为 1
* @param fromRight 是否从右侧开始生成,默认为 false
* @returns 生成的数字范围数组
*/
export function range(start : number, end : number, step : number = 1, fromRight : boolean = false) : number[] {
let index = -1;
// 计算范围的长度
let length = Math.max(Math.ceil((end - start) / step), 0);
// 创建一个长度为 length 的数组
// #ifdef APP-ANDROID
const result = Array.fromNative(new IntArray(length.toInt()));
// #endif
// #ifndef APP-ANDROID
const result = new Array(length);
// #endif
// 使用循环生成数字范围数组
let _start = start
while (length-- > 0) {
// 根据 fromRight 参数决定从左侧还是右侧开始填充数组
result[fromRight ? length : ++index] = _start;
_start += step;
}
return result;
}
// 示例
// console.log(range(0, 5)); // 输出: [0, 1, 2, 3, 4]
// console.log(range(1, 10, 2, true)); // 输出: [9, 7, 5, 3, 1]
// console.log(range(5, 0, -1)); // 输出: [5, 4, 3, 2, 1]

View File

@@ -0,0 +1,516 @@
# lime-shared 工具库
- 本人插件的几个公共函数
- 按需引入
## 文档
🚀 [shared【站点1】](https://limex.qcoon.cn/shared/overview.html)
🌍 [shared【站点2】](https://limeui.netlify.app/shared/overview.html)
🔥 [shared【站点3】](https://limeui.familyzone.top/shared/overview.html)
## 安装
在插件市场导入即可
## 使用
按需引入只会引入相关的方法,不要看着 插件函数列表多 而占空间,只要不引用不会被打包
```js
import {getRect} from '@/uni_modules/lime-shared/getRect'
```
## 目录
+ [getRect](#api_getRect): 获取节点尺寸信息
+ [addUnit](#api_addUnit): 将未带单位的数值添加px如果有单位则返回原值
+ [unitConvert](#api_unitConvert): 将带有rpx|px的字符转成number,若本身是number则直接返回
+ [canIUseCanvas2d](#api_canIUseCanvas2d): 环境是否支持使用 canvas 2d
+ [getCurrentPage](#api_getCurrentPage): 获取当前页
+ [base64ToPath](#api_base64ToPath): 把base64的图片转成临时路径
+ [pathToBase64](#api_pathToBase64): 把图片的临时路径转成base64
+ [sleep](#api_sleep): async 内部程序等待一定时间后再执行
+ [throttle](#api_throttle): 节流
+ [debounce](#api_debounce): 防抖
+ [random](#api_random): 返回指定范围的随机数
+ [range](#api_range): 生成区间数组
+ [clamp](#api_clamp): 夹在min和max之间的数值
+ [floatAdd](#api_floatAdd): 返回两个浮点数相加的结果
+ [fillZero](#api_fillZero): 补零如果传入的是个位数则在前面补0
+ [exif](#api_exif): 获取图片exif
+ [selectComponent](#api_selectComponent): 获取页面或当前实例的指定组件
+ [createAnimation](#api_createAnimation): uni.createAnimation
+ [animation](#api_animation): 数值从一个值到另一个值的过渡
+ [camelCase](#api_camelCase): 字符串转换为 camelCase 或 PascalCase 风格的命名约定
+ [kebabCase](#api_kebabCase): 将字符串转换为指定连接符的命名约定
+ [closest](#api_closest): 在给定数组中找到最接近目标数字的元素
+ [shuffle](#api_shuffle): 将给定的数组打乱
+ [merge](#api_merge): 深度合并两个对象
+ [isBase64](#api_isBase64): 判断字符串是否为base64
+ [isNumber](#api_isNumber): 检查一个值是否为数字类型
+ [isNumeric](#api_isNumeric): 检查一个值是否为数字类型或表示数字的字符串
+ [isString](#api_isString): 检查一个值是否为字符串类型
+ [isIP](#api_isIP): 检查一个值是否为IP地址格式
+ [composition-api](#api_composition-api): 为兼容vue2
## Utils
### getRect <a id="api_getRect"></a>
- 返回节点尺寸信息
```js
// 组件内需要传入上下文
// 如果是nvue 则需要在节点上加与id或class同名的ref
getRect('#id',this).then(res => {})
```
##### 兼容性
| uni-app | uni-app x |
|------------|----------------------------------|
| √ | √ |
### addUnit <a id="api_addUnit"></a>
- 将未带单位的数值添加px如果有单位则返回原值
```js
addUnit(10)
// 10px
```
##### 兼容性
| uni-app | uni-app x |
|------------|----------------------------------|
| √ | √ |
### unitConvert <a id="api_unitConvert"></a>
- 将带有rpx|px的字符转成number,若本身是number则直接返回
```js
unitConvert('10rpx')
// 5 设备不同 返回的值也不同
unitConvert('10px')
// 10
unitConvert(10)
// 10
```
##### 兼容性
| uni-app | uni-app x |
|------------|----------------------------------|
| √ | √ |
### canIUseCanvas2d <a id="api_canIUseCanvas2d"></a>
- 环境是否支持使用 canvas 2d
```js
canIUseCanvas2d()
// 若支持返回 true 否则 false
```
##### 兼容性
| uni-app | uni-app x |
|------------|----------------------------------|
| √ | √ |
### getCurrentPage <a id="api_getCurrentPage"></a>
- 获取当前页
```js
const page = getCurrentPage()
```
##### 兼容性
| uni-app | uni-app x |
|------------|----------------------------------|
| √ | √ |
### base64ToPath <a id="api_base64ToPath"></a>
- 把base64的图片转成临时路径
```js
base64ToPath(`xxxxx`).then(res => {})
```
##### 兼容性
| uni-app | uni-app x |
|------------|----------------------------------|
| √ | √ |
### pathToBase64 <a id="api_pathToBase64"></a>
- 把图片的临时路径转成base64
```js
pathToBase64(`xxxxx/xxx.png`).then(res => {})
```
##### 兼容性
| uni-app | uni-app x |
|------------|----------------------------------|
| √ | √ |
### sleep <a id="api_sleep"></a>
- 睡眠,让 async 内部程序等待一定时间后再执行
```js
async next () => {
await sleep(300)
console.log('limeui');
}
```
##### 兼容性
| uni-app | uni-app x |
|------------|----------------------------------|
| √ | √ |
### throttle <a id="api_throttle"></a>
- 节流
```js
throttle((nama) => {console.log(nama)}, 200)('limeui');
```
##### 兼容性
| uni-app | uni-app x |
|------------|----------------------------------|
| √ | √ |
### debounce <a id="api_debounce"></a>
- 防抖
```js
debounce((nama) => {console.log(nama)}, 200)('limeui');
```
##### 兼容性
| uni-app | uni-app x |
|------------|----------------------------------|
| √ | √ |
### random <a id="api_random"></a>
- 返回指定范围的随机数
```js
random(1, 5);
```
##### 兼容性
| uni-app | uni-app x |
|------------|----------------------------------|
| √ | √ |
### range <a id="api_range"></a>
- 生成区间数组
```js
range(0, 5)
// [0,1,2,3,4,5]
```
##### 兼容性
| uni-app | uni-app x |
|------------|----------------------------------|
| √ | √ |
### clamp <a id="api_clamp"></a>
- 夹在min和max之间的数值如小于min返回min, 如大于max返回max否侧原值返回
```js
clamp(0, 10, -1)
// 0
clamp(0, 10, 11)
// 10
clamp(0, 10, 9)
// 9
```
##### 兼容性
| uni-app | uni-app x |
|------------|----------------------------------|
| √ | √ |
### floatAdd <a id="api_floatAdd"></a>
- 返回两个浮点数相加的结果
```js
floatAdd(0.1, 0.2) // 0.3
```
##### 兼容性
| uni-app | uni-app x |
|------------|----------------------------------|
| √ | √ |
### fillZero <a id="api_fillZero"></a>
- 补零,如果传入的是`个位数`则在前面补0
```js
fillZero(9);
// 09
```
##### 兼容性
| uni-app | uni-app x |
|------------|----------------------------------|
| √ | √ |
### exif <a id="api_exif"></a>
- 获取图片exif
- 支持临时路径、base64
```js
uni.chooseImage({
count: 1, //最多可以选择的图片张数
sizeType: "original",
success: (res) => {
exif.getData(res.tempFiles[0], function() {
let tagj = exif.getTag(this, "GPSLongitude");
let Orientation = exif.getTag(this, 'Orientation');
console.log(tagj, Orientation)
})
}
})
```
##### 兼容性
| uni-app | uni-app x |
|------------|----------------------------------|
| √ | x |
### selectComponent <a id="api_selectComponent"></a>
- 获取页面或当前实例的指定组件,会在页面或实例向所有的节点查找(包括子组件或子子组件)
- 仅vue3vue2没有测试过
```js
// 当前页面
const page = getCurrentPage()
selectComponent('.custom', {context: page}).then(res => {
})
```
##### 兼容性
| uni-app | uni-app x |
|------------|----------------------------------|
| √ | x |
### createAnimation <a id="api_createAnimation"></a>
- 创建动画与uni.createAnimation使用方法一致只为了抹平nvue
```html
<view ref="ball" :animation="animationData"></view>
```
```js
const ball = ref(null)
const animation = createAnimation({
transformOrigin: "50% 50%",
duration: 1000,
timingFunction: "ease",
delay: 0
})
animation.scale(2,2).rotate(45).step()
// nvue 无导出数据,这样写只为了平台一致,
// nvue 需要把 ref 传入,其它平台不需要
const animationData = animation.export(ball.value)
```
##### 兼容性
| uni-app | uni-app x |
|------------|----------------------------------|
| √ | √ |
##### 兼容性
| uni-app | uni-app x |
|------------|----------------------------------|
| √ | √ |
### camelCase <a id="api_camelCase"></a>
- 将字符串转换为 camelCase 或 PascalCase 风格的命名约定
```js
camelCase("hello world") // helloWorld
camelCase("hello world", true) // HelloWorld
```
##### 兼容性
| uni-app | uni-app x |
|------------|----------------------------------|
| √ | √ |
### kebabCase <a id="api_kebabCase"></a>
- 将字符串转换为指定连接符的命名约定
```js
kebabCase("helloWorld") // hello-world
kebabCase("hello world_example") // hello-world-example
kebabCase("helloWorld", "_") // hello_world
```
##### 兼容性
| uni-app | uni-app x |
|------------|----------------------------------|
| √ | √ |
### closest <a id="api_closest"></a>
- 在给定数组中找到最接近目标数字的元素
```js
closest([1, 3, 5, 7, 9], 6) // 5
```
##### 兼容性
| uni-app | uni-app x |
|------------|----------------------------------|
| √ | √ |
### shuffle <a id="api_shuffle"></a>
- 在给定数组中找到最接近目标数字的元素
```js
shuffle([1, 3, 5, 7, 9])
```
##### 兼容性
| uni-app | uni-app x |
|------------|----------------------------------|
| √ | √ |
### merge <a id="api_merge"></a>
- 深度合并两个对象
```js
const original = { color: 'red' };
const merged = merge({ ...original }, { color: 'blue', size: 'M' });
console.log('original', original); // 输出: { color: 'red' } (保持不变)
console.log('merged', merged); // 输出: { color: 'red', size: 'M' }
type ColorType = {
color?: string,
size?: string,
}
const merged2 = merge({ color: 'red' } as ColorType, { color: 'blue', size: 'M' } as ColorType);
console.log('merged2', merged2)
```
##### 兼容性
| uni-app | uni-app x |
|------------|----------------------------------|
| √ | √ |
### isBase64 <a id="api_isBase64"></a>
- 判断字符串是否为base64
```js
isBase64('xxxxx')
```
##### 兼容性
| uni-app | uni-app x |
|------------|----------------------------------|
| √ | √ |
### isNumber <a id="api_isNumber"></a>
- 检查一个值是否为数字类型
```js
isNumber('0') // false
isNumber(0) // true
```
##### 兼容性
| uni-app | uni-app x |
|------------|----------------------------------|
| √ | √ |
### isNumeric <a id="api_isNumeric"></a>
- 检查一个值是否为数字类型或表示数字的字符串
```js
isNumeric('0') // true
isNumeric(0) // true
```
##### 兼容性
| uni-app | uni-app x |
|------------|----------------------------------|
| √ | √ |
### isString <a id="api_isString"></a>
- 检查一个值是否为数字类型或表示数字的字符串
```js
isString('0') // true
isString(0) // false
```
##### 兼容性
| uni-app | uni-app x |
|------------|----------------------------------|
| √ | √ |
### isIP <a id="api_isIP"></a>
- 检查一个值是否为IP地址格式可以检测ipv4,ipv6
```js
console.log(isIP('192.168.1.1')); // true
console.log(isIP('2001:0db8:85a3:0000:0000:8a2e:0370:7334')); // true
console.log(isIP('192.168.1.1', 4)); // true
console.log(isIP('255.255.255.255', { version: 4 })); // true
// 标准IPv6格式
console.log(isIP('2001:0db8:85a3:0000:0000:8a2e:0370:7334', 6)); // true
console.log(isIP('fe80::1%eth0', { version: 6 })); // true带区域标识
```
##### 兼容性
| uni-app | uni-app x |
|------------|----------------------------------|
| √ | √ |
## composition-api <a id="api_composition-api"></a>
- 因本人插件需要兼容vue2/vue3故增加一个vue文件,代替条件编译
- vue2需要在main.js加上这一段
```js
// vue2
import Vue from 'vue'
import VueCompositionAPI from '@vue/composition-api'
Vue.use(VueCompositionAPI)
```
```js
//使用
import {computed, onMounted, watch, reactive} from '@/uni_modules/lime-shared/vue'
```
##### 兼容性
| uni-app | uni-app x |
|------------|----------------------------------|
| √ | x |

View File

@@ -0,0 +1,10 @@
// @ts-nocheck
// #ifdef UNI-APP-X
// export * from './uvue.uts'
export { selectAllComponent } from './uvue.uts'
// #endif
// #ifndef UNI-APP-X
// export * from './vue.ts'
export { selectAllComponent } from './vue.ts'
// #endif

View File

@@ -0,0 +1,39 @@
// @ts-nocheck
import { type ComponentPublicInstance } from 'vue';
type SelectOptions = {
context : ComponentPublicInstance,
needAll : boolean | null,
}
export function selectAllComponent(selector : string, options : UTSJSONObject) : ComponentPublicInstance[]|null {
const context = options.get('context')! as ComponentPublicInstance;
let needAll = options.get('needAll') as boolean;
let result:ComponentPublicInstance[] = []
if(needAll == null) { needAll = true };
if(context.$children.length > 0) {
const queue:ComponentPublicInstance[] = [...context.$children];
while(queue.length > 0) {
const child = queue.shift();
const name = child?.$options?.name;
if(name == selector) {
result.push(child as ComponentPublicInstance)
} else {
const children = child?.$children
if(children !== null) {
queue.push(...children)
}
}
if(result.length > 0 && !needAll) {
break;
}
}
}
if(result.length > 0) {
return result
}
return null
}

View File

@@ -0,0 +1,151 @@
// @ts-nocheck
interface SelectOptions {
context?: any
needAll?: boolean
node?: boolean
}
// #ifdef MP
function selectMPComponent(key: string, name: string, context: any, needAll: boolean) {
const {proxy, $vm} = context
context = $vm || proxy
if(!['ref','component'].includes(key)) {
const queue = [context]
let result = null
const selector = (key == 'id' ? '#': '.') + name;
while(queue.length > 0) {
const child = queue.shift();
const flag = child?.selectComponent(selector)
if(flag) {
if(!needAll) {return result = flag.$vm}
return result = child.selectAllComponents(selector).map(item => item.$vm)
} else {
child.$children && (queue.push(...child.$children));
}
}
return result
} else {
const {$templateRefs} = context.$
const nameMap = {}
for (var i = 0; i < $templateRefs.length; i++) {
const item = $templateRefs[i]
nameMap[item.i] = item.r
}
let result = []
if(context.$children.length) {
const queue = [...context.$children]
while(queue.length > 0) {
const child = queue.shift();
if(key == 'component' && (child.type?.name === name || child.$?.type?.name === name)) {
result.push(child)
} else if(child.$refs && child.$refs[name]) {
result = child.$refs[name]
} else if(nameMap[child.id] === name){
result.push(child)
} else {
child.$children && (queue.push(...child.$children));
}
if(result.length && !needAll) {
return;
}
}
}
return needAll ? result : result[0]
}
}
// #endif
// #ifdef H5
function selectH5Component(key: string, name: string, context: any, needAll: boolean) {
const {_, component } = context
const child = {component: _ || component || context, children: null , subTree: null, props: null}
let result = []
let queue = [child]
while(queue.length > 0 ) {
const child = queue.shift()
const {component, children , props, subTree} = child
if(key === 'component' && component?.type?.name == name) {
result.push(component)
} else if(key === 'ref' && component && (props?.ref == name || component[key][name])) {
if(props?.ref == name) {
//exposed
result.push(component)
} else if(component[key][name]) {
result.push(component[key][name])
}
} else if(key !== 'ref' && component?.exposed && new RegExp(`\\b${name}\\b`).test(component.attrs[key])) {
// exposed
result.push(component)
} else if(children && Array.isArray(children)) {
queue.push(...children)
} else if(!component && subTree) {
queue.push(subTree)
} else if(component?.subTree) {
queue.push(component.subTree)
}
if(result.length && !needAll) {
break
}
}
return needAll ? result : result[0]
}
// #endif
// #ifdef APP
function selectAPPComponent(key: string, name: string, context: any, needAll: boolean, node: boolean) {
let result = []
// const {_, component} = context
// const child = {component: _ || component || context, children: null, props: null, subTree: null}
const queue = [context]
while(queue.length > 0) {
const child = queue.shift()
const {component, children, props, subTree} = child
const isComp = component && props && component.exposed && !node
if(key == 'component' && child.type && child.type.name === name) {
result.push(component)
} else if(props?.[key] === name && node) {
result.push(child)
} else if(key === 'ref' && isComp && (props.ref === name || props.ref_key === name)) {
// exposed
result.push(component)
} else if(key !== 'ref' && isComp && new RegExp(`\\b${name}\\b`).test(props[key])) {
// exposed
result.push(component)
}
// else if(component && component.subTree && Array.isArray(component.subTree.children)){
// queue.push(...component.subTree.children)
// }
else if(subTree) {
queue.push(subTree)
} else if(component && component.subTree){
queue.push(component.subTree)
}
else if(children && Array.isArray(children)) {
queue.push(...children)
}
if(result.length && !needAll) {
break;
}
}
return needAll ? result : result[0]
}
// #endif
export function selectAllComponent(selector: string, options: SelectOptions = {}) {
// . class
// # id
// $ ref
// @ component name
const reg = /^(\.|#|@|\$)([a-zA-Z_0-9\-]+)$/;
if(!reg.test(selector)) return null
let { context, needAll = true, node} = options
const [,prefix, name] = selector.match(reg)
const symbolMappings = {'.': 'class', '#': 'id', '$':'ref', '@':'component'}
const key = symbolMappings [prefix] //prefix === '.' ? 'class' : prefix === '#' ? 'id' : 'ref';
// #ifdef MP
return selectMPComponent(key, name, context, needAll)
// #endif
// #ifdef H5
return selectH5Component(key, name, context, needAll)
// #endif
// #ifdef APP
return selectAPPComponent(key, name, context, needAll, node)
// #endif
}

View File

@@ -0,0 +1,9 @@
// @ts-nocheck
// #ifndef UNI-APP-X
// export * from './vue.ts'
export { selectComponent } from './vue.ts'
// #endif
// #ifdef UNI-APP-X
// export * from './uvue.uts'
export { selectComponent } from './uvue.uts'
// #endif

View File

@@ -0,0 +1,75 @@
// @ts-nocheck
import { type ComponentPublicInstance } from 'vue';
// #ifdef APP
function findChildren(selector: string, context: ComponentPublicInstance, needAll: boolean): ComponentPublicInstance [] | null{
let result:ComponentPublicInstance[] = []
if(context !== null && context.$children.length > 0) {
const queue:ComponentPublicInstance[] = [...context.$children];
while(queue.length > 0) {
const child = queue.shift();
const name = child?.$options?.name;
if(name == selector) {
result.push(child as ComponentPublicInstance)
} else {
const children = child?.$children
if(children !== null) {
queue.push(...children)
}
}
if(result.length > 0 && !needAll) {
break;
}
}
}
if(result.length > 0) {
return result
}
return null
}
class Query {
context : ComponentPublicInstance | null = null
selector : string = ''
// components : ComponentPublicInstance[] = []
constructor(selector : string, context : ComponentPublicInstance | null) {
this.selector = selector
this.context = context
}
in(context : ComponentPublicInstance) : Query {
return new Query(this.selector, context)
}
find(): ComponentPublicInstance | null {
const selector = this.selector
if(selector == '') return null
const component = findChildren(selector, this.context!, false)
return component != null ? component[0]: null
}
findAll():ComponentPublicInstance[] | null {
const selector = this.selector
if(selector == '') return null
return findChildren(selector, this.context!, true)
}
closest(): ComponentPublicInstance | null {
const selector = this.selector
if(selector == '') return null
let parent = this.context!.$parent
let name = parent?.$options?.name;
while (parent != null && (name == null || selector != name)) {
parent = parent.$parent
if (parent != null) {
name = parent.$options.name
}
}
return parent
}
}
export function selectComponent(selector: string): Query{
return new Query(selector, null)
}
// #endif
// selectComponent('selector').in(this).find()
// selectComponent('selector').in(this).findAll()
// selectComponent('selector').in(this).closest()

View File

@@ -0,0 +1,149 @@
// @ts-nocheck
// #ifdef MP
function findChildren(selector : string, context : ComponentPublicInstance, needAll : boolean) {
const { proxy, $vm } = context
context = $vm || proxy
if ((selector.startsWith('.') || selector.startsWith('#'))) {
const queue = [context]
let result = null
while (queue.length > 0) {
const child = queue.shift();
const flag = child?.selectComponent(selector)
if (flag) {
if (!needAll) { return result = flag.$vm }
return result = child.selectAllComponents(selector).map(item => item.$vm)
} else {
child.$children && (queue.push(...child.$children));
}
}
return result
} else {
const { $templateRefs } = context.$
const selectorValue = /#|\.|@|$/.test(selector) ? selector.substring(1) : selector
const nameMap = {}
for (var i = 0; i < $templateRefs.length; i++) {
const item = $templateRefs[i]
nameMap[item.i] = item.r
}
let result = []
if (context.$children.length) {
const queue = [...context.$children]
while (queue.length > 0) {
const child = queue.shift();
if (child.type?.name === selectorValue || child.$?.type?.name === selectorValue) {
result.push(child)
} else if (child.$refs && child.$refs[selectorValue]) {
result = child.$refs[selectorValue]
} else if (nameMap[child.id] === selectorValue) {
result.push(child)
} else {
child.$children && (queue.push(...child.$children));
}
if (result.length && !needAll) {
return;
}
}
}
return needAll ? result : result[0]
}
}
// #endif
// #ifdef H5
function findChildren(selector : string, context : ComponentPublicInstance, needAll : boolean){
const {_, component } = context
const child = {component: _ || component || context, children: null , subTree: null, props: null}
let result = []
let queue = [child]
const selectorValue = /#|\.|@|$/.test(selector) ? selector.substring(1) : selector
while(queue.length > 0 ) {
const child = queue.shift()
const {component, children , props, subTree} = child
if(component?.type?.name == selectorValue) {
result.push(component)
} else if(selector.startsWith('$') && component && (props?.ref == selectorValue || component[key][selectorValue])) {
if(props?.ref == selectorValue) {
//exposed
result.push(component)
} else if(component[key][selectorValue]) {
result.push(component[key][selectorValue])
}
} else if(!selector.startsWith('$') && component?.exposed && new RegExp(`\\b${selectorValue}\\b`).test(component.attrs[key])) {
// exposed
result.push(component)
} else if(children && Array.isArray(children)) {
queue.push(...children)
} else if(!component && subTree) {
queue.push(subTree)
} else if(component?.subTree) {
queue.push(component.subTree)
}
if(result.length && !needAll) {
break
}
}
return needAll ? result : result[0]
}
// #endif
// #ifdef APP
function findChildren(selector : string, context : ComponentPublicInstance, needAll : boolean){
let result = []
const selectorValue = /#|\.|@|$/.test(selector) ? selector.substring(1) : selector
const queue = [context]
while(queue.length > 0) {
const child = queue.shift()
const {component, children, props, subTree} = child
const isComp = component && props && component.exposed && !node
if(child.type && child.type.name === selectorValue) {
result.push(component)
} else if(props?.[key] === selectorValue && node) {
result.push(child)
} else if(selector.startsWith('$') && isComp && (props.ref === selectorValue || props.ref_key === selectorValue)) {
// exposed
result.push(component)
} else if(!selector.startsWith('$') && isComp && new RegExp(`\\b${selectorValue}\\b`).test(props[key])) {
// exposed
result.push(component)
}
else if(subTree) {
queue.push(subTree)
} else if(component && component.subTree){
queue.push(component.subTree)
}
else if(children && Array.isArray(children)) {
queue.push(...children)
}
if(result.length && !needAll) {
break;
}
}
return needAll ? result : result[0]
}
// #endif
class Query {
context : ComponentPublicInstance | null = null
selector : string = ''
// components : ComponentPublicInstance[] = []
constructor(selector : string, context : ComponentPublicInstance | null) {
this.selector = selector
this.context = context
}
in(context : ComponentPublicInstance) : Query {
return new Query(this.selector, context)
}
find() : ComponentPublicInstance | null {
return findChildren(this.selector, this.context, false)
}
findAll() : ComponentPublicInstance[] | null {
return findChildren(this.selector, this.context, true)
}
closest() : ComponentPublicInstance | null {
return null
}
}
export function selectComponent(selector: string) {
return new Query(selector)
}

Some files were not shown because too many files have changed in this diff Show More