diff --git a/pages/zyb/fillVolunteer/index.vue b/pages/zyb/fillVolunteer/index.vue index f852ef4..3ba3ddc 100644 --- a/pages/zyb/fillVolunteer/index.vue +++ b/pages/zyb/fillVolunteer/index.vue @@ -1,7 +1,5 @@ \ No newline at end of file diff --git a/uni_modules/lime-shared/createAnimation/type.ts b/uni_modules/lime-shared/createAnimation/type.ts new file mode 100644 index 0000000..0f8ad34 --- /dev/null +++ b/uni_modules/lime-shared/createAnimation/type.ts @@ -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; +} \ No newline at end of file diff --git a/uni_modules/lime-shared/createAnimation/uvue.uts b/uni_modules/lime-shared/createAnimation/uvue.uts new file mode 100644 index 0000000..96d8263 --- /dev/null +++ b/uni_modules/lime-shared/createAnimation/uvue.uts @@ -0,0 +1,5 @@ +// @ts-nocheck +// export * from '@/uni_modules/lime-animateIt' +export function createAnimation() { + console.error('当前环境不支持,请使用:lime-animateIt') +} \ No newline at end of file diff --git a/uni_modules/lime-shared/createAnimation/vue.ts b/uni_modules/lime-shared/createAnimation/vue.ts new file mode 100644 index 0000000..6934f27 --- /dev/null +++ b/uni_modules/lime-shared/createAnimation/vue.ts @@ -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 +} diff --git a/uni_modules/lime-shared/createCanvas/index.ts b/uni_modules/lime-shared/createCanvas/index.ts new file mode 100644 index 0000000..987be92 --- /dev/null +++ b/uni_modules/lime-shared/createCanvas/index.ts @@ -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 +} \ No newline at end of file diff --git a/uni_modules/lime-shared/cssToObj/index.ts b/uni_modules/lime-shared/cssToObj/index.ts new file mode 100644 index 0000000..ec09623 --- /dev/null +++ b/uni_modules/lime-shared/cssToObj/index.ts @@ -0,0 +1,45 @@ +// @ts-nocheck +// #ifndef UNI-APP-X +type UTSJSONObject = Record +// #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; +} \ No newline at end of file diff --git a/uni_modules/lime-shared/debounce/uvue.uts b/uni_modules/lime-shared/debounce/uvue.uts new file mode 100644 index 0000000..f1fc29d --- /dev/null +++ b/uni_modules/lime-shared/debounce/uvue.uts @@ -0,0 +1,36 @@ +// @ts-nocheck +/** + * 防抖函数,通过延迟一定时间来限制函数的执行频率。 + * @param fn 要防抖的函数。 + * @param wait 触发防抖的等待时间,单位为毫秒。 + * @returns 防抖函数。 + */ +export function debounce(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" \ No newline at end of file diff --git a/uni_modules/lime-shared/debounce/vue.ts b/uni_modules/lime-shared/debounce/vue.ts new file mode 100644 index 0000000..694b44d --- /dev/null +++ b/uni_modules/lime-shared/debounce/vue.ts @@ -0,0 +1,40 @@ +// @ts-nocheck +type Timeout = ReturnType | null; +/** + * 防抖函数,通过延迟一定时间来限制函数的执行频率。 + * @param fn 要防抖的函数。 + * @param wait 触发防抖的等待时间,单位为毫秒。 + * @returns 防抖函数。 + */ +export function debounce( + 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" \ No newline at end of file diff --git a/uni_modules/lime-shared/dom/index.ts b/uni_modules/lime-shared/dom/index.ts new file mode 100644 index 0000000..9c82e09 --- /dev/null +++ b/uni_modules/lime-shared/dom/index.ts @@ -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; +}; \ No newline at end of file diff --git a/uni_modules/lime-shared/exif/uvue.uts b/uni_modules/lime-shared/exif/uvue.uts new file mode 100644 index 0000000..01d21a2 --- /dev/null +++ b/uni_modules/lime-shared/exif/uvue.uts @@ -0,0 +1,7 @@ +class EXIF { + constructor(){ + console.error('当前环境不支持') + } +} + +export const exif = new EXIF() \ No newline at end of file diff --git a/uni_modules/lime-shared/exif/vue.ts b/uni_modules/lime-shared/exif/vue.ts new file mode 100644 index 0000000..6862263 --- /dev/null +++ b/uni_modules/lime-shared/exif/vue.ts @@ -0,0 +1,1057 @@ +// @ts-nocheck +import { base64ToArrayBuffer } from '../base64ToArrayBuffer'; +import { pathToBase64 } from '../pathToBase64'; +// import { isBase64 } from '../isBase64'; +import { isDataURI } from '../isBase64'; +import { isString } from '../isString'; + +interface File { + exifdata : any + iptcdata : any + xmpdata : any + src : string +} +class EXIF { + isXmpEnabled = false + debug = false + Tags = { + // version tags + 0x9000: "ExifVersion", // EXIF version + 0xA000: "FlashpixVersion", // Flashpix format version + + // colorspace tags + 0xA001: "ColorSpace", // Color space information tag + + // image configuration + 0xA002: "PixelXDimension", // Valid width of meaningful image + 0xA003: "PixelYDimension", // Valid height of meaningful image + 0x9101: "ComponentsConfiguration", // Information about channels + 0x9102: "CompressedBitsPerPixel", // Compressed bits per pixel + + // user information + 0x927C: "MakerNote", // Any desired information written by the manufacturer + 0x9286: "UserComment", // Comments by user + + // related file + 0xA004: "RelatedSoundFile", // Name of related sound file + + // date and time + 0x9003: "DateTimeOriginal", // Date and time when the original image was generated + 0x9004: "DateTimeDigitized", // Date and time when the image was stored digitally + 0x9290: "SubsecTime", // Fractions of seconds for DateTime + 0x9291: "SubsecTimeOriginal", // Fractions of seconds for DateTimeOriginal + 0x9292: "SubsecTimeDigitized", // Fractions of seconds for DateTimeDigitized + + // picture-taking conditions + 0x829A: "ExposureTime", // Exposure time (in seconds) + 0x829D: "FNumber", // F number + 0x8822: "ExposureProgram", // Exposure program + 0x8824: "SpectralSensitivity", // Spectral sensitivity + 0x8827: "ISOSpeedRatings", // ISO speed rating + 0x8828: "OECF", // Optoelectric conversion factor + 0x9201: "ShutterSpeedValue", // Shutter speed + 0x9202: "ApertureValue", // Lens aperture + 0x9203: "BrightnessValue", // Value of brightness + 0x9204: "ExposureBias", // Exposure bias + 0x9205: "MaxApertureValue", // Smallest F number of lens + 0x9206: "SubjectDistance", // Distance to subject in meters + 0x9207: "MeteringMode", // Metering mode + 0x9208: "LightSource", // Kind of light source + 0x9209: "Flash", // Flash status + 0x9214: "SubjectArea", // Location and area of main subject + 0x920A: "FocalLength", // Focal length of the lens in mm + 0xA20B: "FlashEnergy", // Strobe energy in BCPS + 0xA20C: "SpatialFrequencyResponse", // + 0xA20E: "FocalPlaneXResolution", // Number of pixels in width direction per FocalPlaneResolutionUnit + 0xA20F: "FocalPlaneYResolution", // Number of pixels in height direction per FocalPlaneResolutionUnit + 0xA210: "FocalPlaneResolutionUnit", // Unit for measuring FocalPlaneXResolution and FocalPlaneYResolution + 0xA214: "SubjectLocation", // Location of subject in image + 0xA215: "ExposureIndex", // Exposure index selected on camera + 0xA217: "SensingMethod", // Image sensor type + 0xA300: "FileSource", // Image source (3 == DSC) + 0xA301: "SceneType", // Scene type (1 == directly photographed) + 0xA302: "CFAPattern", // Color filter array geometric pattern + 0xA401: "CustomRendered", // Special processing + 0xA402: "ExposureMode", // Exposure mode + 0xA403: "WhiteBalance", // 1 = auto white balance, 2 = manual + 0xA404: "DigitalZoomRation", // Digital zoom ratio + 0xA405: "FocalLengthIn35mmFilm", // Equivalent foacl length assuming 35mm film camera (in mm) + 0xA406: "SceneCaptureType", // Type of scene + 0xA407: "GainControl", // Degree of overall image gain adjustment + 0xA408: "Contrast", // Direction of contrast processing applied by camera + 0xA409: "Saturation", // Direction of saturation processing applied by camera + 0xA40A: "Sharpness", // Direction of sharpness processing applied by camera + 0xA40B: "DeviceSettingDescription", // + 0xA40C: "SubjectDistanceRange", // Distance to subject + + // other tags + 0xA005: "InteroperabilityIFDPointer", + 0xA420: "ImageUniqueID" // Identifier assigned uniquely to each image + } + TiffTags = { + 0x0100: "ImageWidth", + 0x0101: "ImageHeight", + 0x8769: "ExifIFDPointer", + 0x8825: "GPSInfoIFDPointer", + 0xA005: "InteroperabilityIFDPointer", + 0x0102: "BitsPerSample", + 0x0103: "Compression", + 0x0106: "PhotometricInterpretation", + 0x0112: "Orientation", + 0x0115: "SamplesPerPixel", + 0x011C: "PlanarConfiguration", + 0x0212: "YCbCrSubSampling", + 0x0213: "YCbCrPositioning", + 0x011A: "XResolution", + 0x011B: "YResolution", + 0x0128: "ResolutionUnit", + 0x0111: "StripOffsets", + 0x0116: "RowsPerStrip", + 0x0117: "StripByteCounts", + 0x0201: "JPEGInterchangeFormat", + 0x0202: "JPEGInterchangeFormatLength", + 0x012D: "TransferFunction", + 0x013E: "WhitePoint", + 0x013F: "PrimaryChromaticities", + 0x0211: "YCbCrCoefficients", + 0x0214: "ReferenceBlackWhite", + 0x0132: "DateTime", + 0x010E: "ImageDescription", + 0x010F: "Make", + 0x0110: "Model", + 0x0131: "Software", + 0x013B: "Artist", + 0x8298: "Copyright" + } + GPSTags = { + 0x0000: "GPSVersionID", + 0x0001: "GPSLatitudeRef", + 0x0002: "GPSLatitude", + 0x0003: "GPSLongitudeRef", + 0x0004: "GPSLongitude", + 0x0005: "GPSAltitudeRef", + 0x0006: "GPSAltitude", + 0x0007: "GPSTimeStamp", + 0x0008: "GPSSatellites", + 0x0009: "GPSStatus", + 0x000A: "GPSMeasureMode", + 0x000B: "GPSDOP", + 0x000C: "GPSSpeedRef", + 0x000D: "GPSSpeed", + 0x000E: "GPSTrackRef", + 0x000F: "GPSTrack", + 0x0010: "GPSImgDirectionRef", + 0x0011: "GPSImgDirection", + 0x0012: "GPSMapDatum", + 0x0013: "GPSDestLatitudeRef", + 0x0014: "GPSDestLatitude", + 0x0015: "GPSDestLongitudeRef", + 0x0016: "GPSDestLongitude", + 0x0017: "GPSDestBearingRef", + 0x0018: "GPSDestBearing", + 0x0019: "GPSDestDistanceRef", + 0x001A: "GPSDestDistance", + 0x001B: "GPSProcessingMethod", + 0x001C: "GPSAreaInformation", + 0x001D: "GPSDateStamp", + 0x001E: "GPSDifferential" + } + // EXIF 2.3 Spec + IFD1Tags = { + 0x0100: "ImageWidth", + 0x0101: "ImageHeight", + 0x0102: "BitsPerSample", + 0x0103: "Compression", + 0x0106: "PhotometricInterpretation", + 0x0111: "StripOffsets", + 0x0112: "Orientation", + 0x0115: "SamplesPerPixel", + 0x0116: "RowsPerStrip", + 0x0117: "StripByteCounts", + 0x011A: "XResolution", + 0x011B: "YResolution", + 0x011C: "PlanarConfiguration", + 0x0128: "ResolutionUnit", + 0x0201: "JpegIFOffset", // When image format is JPEG, this value show offset to JPEG data stored.(aka "ThumbnailOffset" or "JPEGInterchangeFormat") + 0x0202: "JpegIFByteCount", // When image format is JPEG, this value shows data size of JPEG image (aka "ThumbnailLength" or "JPEGInterchangeFormatLength") + 0x0211: "YCbCrCoefficients", + 0x0212: "YCbCrSubSampling", + 0x0213: "YCbCrPositioning", + 0x0214: "ReferenceBlackWhite" + } + StringValues = { + ExposureProgram: { + 0: "Not defined", + 1: "Manual", + 2: "Normal program", + 3: "Aperture priority", + 4: "Shutter priority", + 5: "Creative program", + 6: "Action program", + 7: "Portrait mode", + 8: "Landscape mode" + }, + MeteringMode: { + 0: "Unknown", + 1: "Average", + 2: "CenterWeightedAverage", + 3: "Spot", + 4: "MultiSpot", + 5: "Pattern", + 6: "Partial", + 255: "Other" + }, + LightSource: { + 0: "Unknown", + 1: "Daylight", + 2: "Fluorescent", + 3: "Tungsten (incandescent light)", + 4: "Flash", + 9: "Fine weather", + 10: "Cloudy weather", + 11: "Shade", + 12: "Daylight fluorescent (D 5700 - 7100K)", + 13: "Day white fluorescent (N 4600 - 5400K)", + 14: "Cool white fluorescent (W 3900 - 4500K)", + 15: "White fluorescent (WW 3200 - 3700K)", + 17: "Standard light A", + 18: "Standard light B", + 19: "Standard light C", + 20: "D55", + 21: "D65", + 22: "D75", + 23: "D50", + 24: "ISO studio tungsten", + 255: "Other" + }, + Flash: { + 0x0000: "Flash did not fire", + 0x0001: "Flash fired", + 0x0005: "Strobe return light not detected", + 0x0007: "Strobe return light detected", + 0x0009: "Flash fired, compulsory flash mode", + 0x000D: "Flash fired, compulsory flash mode, return light not detected", + 0x000F: "Flash fired, compulsory flash mode, return light detected", + 0x0010: "Flash did not fire, compulsory flash mode", + 0x0018: "Flash did not fire, auto mode", + 0x0019: "Flash fired, auto mode", + 0x001D: "Flash fired, auto mode, return light not detected", + 0x001F: "Flash fired, auto mode, return light detected", + 0x0020: "No flash function", + 0x0041: "Flash fired, red-eye reduction mode", + 0x0045: "Flash fired, red-eye reduction mode, return light not detected", + 0x0047: "Flash fired, red-eye reduction mode, return light detected", + 0x0049: "Flash fired, compulsory flash mode, red-eye reduction mode", + 0x004D: "Flash fired, compulsory flash mode, red-eye reduction mode, return light not detected", + 0x004F: "Flash fired, compulsory flash mode, red-eye reduction mode, return light detected", + 0x0059: "Flash fired, auto mode, red-eye reduction mode", + 0x005D: "Flash fired, auto mode, return light not detected, red-eye reduction mode", + 0x005F: "Flash fired, auto mode, return light detected, red-eye reduction mode" + }, + SensingMethod: { + 1: "Not defined", + 2: "One-chip color area sensor", + 3: "Two-chip color area sensor", + 4: "Three-chip color area sensor", + 5: "Color sequential area sensor", + 7: "Trilinear sensor", + 8: "Color sequential linear sensor" + }, + SceneCaptureType: { + 0: "Standard", + 1: "Landscape", + 2: "Portrait", + 3: "Night scene" + }, + SceneType: { + 1: "Directly photographed" + }, + CustomRendered: { + 0: "Normal process", + 1: "Custom process" + }, + WhiteBalance: { + 0: "Auto white balance", + 1: "Manual white balance" + }, + GainControl: { + 0: "None", + 1: "Low gain up", + 2: "High gain up", + 3: "Low gain down", + 4: "High gain down" + }, + Contrast: { + 0: "Normal", + 1: "Soft", + 2: "Hard" + }, + Saturation: { + 0: "Normal", + 1: "Low saturation", + 2: "High saturation" + }, + Sharpness: { + 0: "Normal", + 1: "Soft", + 2: "Hard" + }, + SubjectDistanceRange: { + 0: "Unknown", + 1: "Macro", + 2: "Close view", + 3: "Distant view" + }, + FileSource: { + 3: "DSC" + }, + + Components: { + 0: "", + 1: "Y", + 2: "Cb", + 3: "Cr", + 4: "R", + 5: "G", + 6: "B" + } + } + enableXmp() { + this.isXmpEnabled = true + } + disableXmp() { + this.isXmpEnabled = false; + } + /** + * 获取图片数据 + * @param img 图片地址 + * @param callback 回调 返回图片数据 + * */ + getData(img : any, callback : Function) { + // if (((self.Image && img instanceof self.Image) || (self.HTMLImageElement && img instanceof self.HTMLImageElement)) && !img.complete) + // return false; + let file : File = { + src: '', + exifdata: null, + iptcdata: null, + xmpdata: null, + } + if (isDataURI(img)) { + file.src = img + } else if (img.path) { + file.src = img.path + } else if (isString(img)) { + file.src = img + } else { + return false; + } + + + if (!imageHasData(file)) { + getImageData(file, callback); + } else { + if (callback) { + callback.call(file); + } + } + return true; + } + /** + * 获取图片tag + * @param img 图片数据 + * @param tag tag 类型 + * */ + getTag(img : File, tag : string) { + if (!imageHasData(img)) return; + return img.exifdata[tag]; + } + getIptcTag(img : File, tag : string) { + if (!imageHasData(img)) return; + return img.iptcdata[tag]; + } + getAllTags(img : File) { + if (!imageHasData(img)) return {}; + let a, + data = img.exifdata, + tags = {}; + for (a in data) { + if (data.hasOwnProperty(a)) { + tags[a] = data[a]; + } + } + return tags; + } + getAllIptcTags(img : File) { + if (!imageHasData(img)) return {}; + let a, + data = img.iptcdata, + tags = {}; + for (a in data) { + if (data.hasOwnProperty(a)) { + tags[a] = data[a]; + } + } + return tags; + } + pretty(img : File) { + if (!imageHasData(img)) return ""; + let a, + data = img.exifdata, + strPretty = ""; + for (a in data) { + if (data.hasOwnProperty(a)) { + if (typeof data[a] == "object") { + if (data[a] instanceof Number) { + strPretty += a + " : " + data[a] + " [" + data[a].numerator + "/" + data[a] + .denominator + "]\r\n"; + } else { + strPretty += a + " : [" + data[a].length + " values]\r\n"; + } + } else { + strPretty += a + " : " + data[a] + "\r\n"; + } + } + } + return strPretty; + } + readFromBinaryFile(file: ArrayBuffer) { + return findEXIFinJPEG(file); + } +} + +export const exif = new EXIF() +// export function getData(img, callback) { +// const exif = new EXIF() +// exif.getData(img, callback) +// } + +// export default {getData} +const ExifTags = exif.Tags +const TiffTags = exif.TiffTags +const IFD1Tags = exif.IFD1Tags +const GPSTags = exif.GPSTags +const StringValues = exif.StringValues + + +function imageHasData(img : File) : boolean { + return !!(img.exifdata); +} + +function objectURLToBlob(url : string, callback : Function) { + try { + const http = new XMLHttpRequest(); + http.open("GET", url, true); + http.responseType = "blob"; + http.onload = function (e) { + if (this.status == 200 || this.status === 0) { + callback(this.response); + } + }; + http.send(); + } catch (e) { + console.warn(e) + } +} + + +function getImageData(img : File, callback : Function) { + function handleBinaryFile(binFile: ArrayBuffer) { + const data = findEXIFinJPEG(binFile); + img.exifdata = data ?? {}; + const iptcdata = findIPTCinJPEG(binFile); + img.iptcdata = iptcdata ?? {}; + if (exif.isXmpEnabled) { + const xmpdata = findXMPinJPEG(binFile); + img.xmpdata = xmpdata ?? {}; + } + if (callback) { + callback.call(img); + } + } + + if (img.src) { + if (/^data\:/i.test(img.src)) { // Data URI + // var arrayBuffer = base64ToArrayBuffer(img.src); + handleBinaryFile(base64ToArrayBuffer(img.src)); + + } else if (/^blob\:/i.test(img.src) && typeof FileReader !== 'undefined') { // Object URL + var fileReader = new FileReader(); + fileReader.onload = function (e) { + handleBinaryFile(e.target.result); + }; + objectURLToBlob(img.src, function (blob : Blob) { + fileReader.readAsArrayBuffer(blob); + }); + } else if (typeof XMLHttpRequest !== 'undefined') { + var http = new XMLHttpRequest(); + http.onload = function () { + if (this.status == 200 || this.status === 0) { + handleBinaryFile(http.response); + } else { + throw "Could not load image"; + } + http = null; + }; + http.open("GET", img.src, true); + http.responseType = "arraybuffer"; + http.send(null); + } else { + pathToBase64(img.src).then(res => { + handleBinaryFile(base64ToArrayBuffer(res)); + }) + } + } else if (typeof FileReader !== 'undefined' && self.FileReader && (img instanceof self.Blob || img instanceof self.File)) { + var fileReader = new FileReader(); + fileReader.onload = function (e : any) { + if (exif.debug) console.log("Got file of length " + e.target.result.byteLength); + handleBinaryFile(e.target.result); + }; + + fileReader.readAsArrayBuffer(img); + } +} + +function findEXIFinJPEG(file: ArrayBuffer) { + const dataView = new DataView(file); + + if (exif.debug) console.log("Got file of length " + file.byteLength); + if ((dataView.getUint8(0) != 0xFF) || (dataView.getUint8(1) != 0xD8)) { + if (exif.debug) console.log("Not a valid JPEG"); + return false; // not a valid jpeg + } + + let offset = 2, + length = file.byteLength, + marker; + + while (offset < length) { + if (dataView.getUint8(offset) != 0xFF) { + if (exif.debug) console.log("Not a valid marker at offset " + offset + ", found: " + dataView.getUint8( + offset)); + return false; // not a valid marker, something is wrong + } + + marker = dataView.getUint8(offset + 1); + if (exif.debug) console.log(marker); + + // we could implement handling for other markers here, + // but we're only looking for 0xFFE1 for EXIF data + + if (marker == 225) { + if (exif.debug) console.log("Found 0xFFE1 marker"); + + return readEXIFData(dataView, offset + 4, dataView.getUint16(offset + 2) - 2); + + // offset += 2 + file.getShortAt(offset+2, true); + + } else { + offset += 2 + dataView.getUint16(offset + 2); + } + + } + +} + +function findIPTCinJPEG(file: ArrayBuffer) { + const dataView = new DataView(file); + + if (exif.debug) console.log("Got file of length " + file.byteLength); + if ((dataView.getUint8(0) != 0xFF) || (dataView.getUint8(1) != 0xD8)) { + if (exif.debug) console.log("Not a valid JPEG"); + return false; // not a valid jpeg + } + + let offset = 2, + length = file.byteLength; + + + const isFieldSegmentStart = function (dataView, offset: number) { + return ( + dataView.getUint8(offset) === 0x38 && + dataView.getUint8(offset + 1) === 0x42 && + dataView.getUint8(offset + 2) === 0x49 && + dataView.getUint8(offset + 3) === 0x4D && + dataView.getUint8(offset + 4) === 0x04 && + dataView.getUint8(offset + 5) === 0x04 + ); + }; + + while (offset < length) { + + if (isFieldSegmentStart(dataView, offset)) { + + // Get the length of the name header (which is padded to an even number of bytes) + var nameHeaderLength = dataView.getUint8(offset + 7); + if (nameHeaderLength % 2 !== 0) nameHeaderLength += 1; + // Check for pre photoshop 6 format + if (nameHeaderLength === 0) { + // Always 4 + nameHeaderLength = 4; + } + + var startOffset = offset + 8 + nameHeaderLength; + var sectionLength = dataView.getUint16(offset + 6 + nameHeaderLength); + + return readIPTCData(file, startOffset, sectionLength); + + break; + + } + + + // Not the marker, continue searching + offset++; + + } + +} + +const IptcFieldMap = { + 0x78: 'caption', + 0x6E: 'credit', + 0x19: 'keywords', + 0x37: 'dateCreated', + 0x50: 'byline', + 0x55: 'bylineTitle', + 0x7A: 'captionWriter', + 0x69: 'headline', + 0x74: 'copyright', + 0x0F: 'category' +}; + +function readIPTCData(file: ArrayBuffer, startOffset: number, sectionLength: number) { + const dataView = new DataView(file); + let data = {}; + let fieldValue, fieldName, dataSize, segmentType, segmentSize; + let segmentStartPos = startOffset; + while (segmentStartPos < startOffset + sectionLength) { + if (dataView.getUint8(segmentStartPos) === 0x1C && dataView.getUint8(segmentStartPos + 1) === 0x02) { + segmentType = dataView.getUint8(segmentStartPos + 2); + if (segmentType in IptcFieldMap) { + dataSize = dataView.getInt16(segmentStartPos + 3); + segmentSize = dataSize + 5; + fieldName = IptcFieldMap[segmentType]; + fieldValue = getStringFromDB(dataView, segmentStartPos + 5, dataSize); + // Check if we already stored a value with this name + if (data.hasOwnProperty(fieldName)) { + // Value already stored with this name, create multivalue field + if (data[fieldName] instanceof Array) { + data[fieldName].push(fieldValue); + } else { + data[fieldName] = [data[fieldName], fieldValue]; + } + } else { + data[fieldName] = fieldValue; + } + } + + } + segmentStartPos++; + } + return data; +} + +function readTags(file: DataView, tiffStart: number, dirStart: number, strings: any[], bigEnd: number) { + let entries = file.getUint16(dirStart, !bigEnd), + tags = {}, + entryOffset, tag; + + for (let i = 0; i < entries; i++) { + entryOffset = dirStart + i * 12 + 2; + tag = strings[file.getUint16(entryOffset, !bigEnd)]; + if (!tag && exif.debug) console.log("Unknown tag: " + file.getUint16(entryOffset, !bigEnd)); + tags[tag] = readTagValue(file, entryOffset, tiffStart, dirStart, bigEnd); + } + return tags; +} + +function readTagValue(file: DataView, entryOffset: number, tiffStart: number, dirStart: number, bigEnd: number) { + let type = file.getUint16(entryOffset + 2, !bigEnd), + numValues = file.getUint32(entryOffset + 4, !bigEnd), + valueOffset = file.getUint32(entryOffset + 8, !bigEnd) + tiffStart, + offset, + vals, val, n, + numerator, denominator; + + switch (type) { + case 1: // byte, 8-bit unsigned int + case 7: // undefined, 8-bit byte, value depending on field + if (numValues == 1) { + return file.getUint8(entryOffset + 8, !bigEnd); + } else { + offset = numValues > 4 ? valueOffset : (entryOffset + 8); + vals = []; + for (n = 0; n < numValues; n++) { + vals[n] = file.getUint8(offset + n); + } + return vals; + } + + case 2: // ascii, 8-bit byte + offset = numValues > 4 ? valueOffset : (entryOffset + 8); + return getStringFromDB(file, offset, numValues - 1); + + case 3: // short, 16 bit int + if (numValues == 1) { + return file.getUint16(entryOffset + 8, !bigEnd); + } else { + offset = numValues > 2 ? valueOffset : (entryOffset + 8); + vals = []; + for (n = 0; n < numValues; n++) { + vals[n] = file.getUint16(offset + 2 * n, !bigEnd); + } + return vals; + } + + case 4: // long, 32 bit int + if (numValues == 1) { + return file.getUint32(entryOffset + 8, !bigEnd); + } else { + vals = []; + for (n = 0; n < numValues; n++) { + vals[n] = file.getUint32(valueOffset + 4 * n, !bigEnd); + } + return vals; + } + + case 5: // rational = two long values, first is numerator, second is denominator + if (numValues == 1) { + numerator = file.getUint32(valueOffset, !bigEnd); + denominator = file.getUint32(valueOffset + 4, !bigEnd); + val = new Number(numerator / denominator); + val.numerator = numerator; + val.denominator = denominator; + return val; + } else { + vals = []; + for (n = 0; n < numValues; n++) { + numerator = file.getUint32(valueOffset + 8 * n, !bigEnd); + denominator = file.getUint32(valueOffset + 4 + 8 * n, !bigEnd); + vals[n] = new Number(numerator / denominator); + vals[n].numerator = numerator; + vals[n].denominator = denominator; + } + return vals; + } + + case 9: // slong, 32 bit signed int + if (numValues == 1) { + return file.getInt32(entryOffset + 8, !bigEnd); + } else { + vals = []; + for (n = 0; n < numValues; n++) { + vals[n] = file.getInt32(valueOffset + 4 * n, !bigEnd); + } + return vals; + } + + case 10: // signed rational, two slongs, first is numerator, second is denominator + if (numValues == 1) { + return file.getInt32(valueOffset, !bigEnd) / file.getInt32(valueOffset + 4, !bigEnd); + } else { + vals = []; + for (n = 0; n < numValues; n++) { + vals[n] = file.getInt32(valueOffset + 8 * n, !bigEnd) / file.getInt32(valueOffset + 4 + 8 * + n, !bigEnd); + } + return vals; + } + } +} +/** + * Given an IFD (Image File Directory) start offset + * returns an offset to next IFD or 0 if it's the last IFD. + */ +function getNextIFDOffset(dataView: DataView, dirStart: number, bigEnd: number) { + //the first 2bytes means the number of directory entries contains in this IFD + var entries = dataView.getUint16(dirStart, !bigEnd); + + // After last directory entry, there is a 4bytes of data, + // it means an offset to next IFD. + // If its value is '0x00000000', it means this is the last IFD and there is no linked IFD. + + return dataView.getUint32(dirStart + 2 + entries * 12, !bigEnd); // each entry is 12 bytes long +} + +function readThumbnailImage(dataView: DataView, tiffStart: number, firstIFDOffset: number, bigEnd: number) { + // get the IFD1 offset + const IFD1OffsetPointer = getNextIFDOffset(dataView, tiffStart + firstIFDOffset, bigEnd); + + if (!IFD1OffsetPointer) { + // console.log('******** IFD1Offset is empty, image thumb not found ********'); + return {}; + } else if (IFD1OffsetPointer > dataView.byteLength) { // this should not happen + // console.log('******** IFD1Offset is outside the bounds of the DataView ********'); + return {}; + } + // console.log('******* thumbnail IFD offset (IFD1) is: %s', IFD1OffsetPointer); + + let thumbTags : any = readTags(dataView, tiffStart, tiffStart + IFD1OffsetPointer, IFD1Tags, bigEnd) + + // EXIF 2.3 specification for JPEG format thumbnail + + // If the value of Compression(0x0103) Tag in IFD1 is '6', thumbnail image format is JPEG. + // Most of Exif image uses JPEG format for thumbnail. In that case, you can get offset of thumbnail + // by JpegIFOffset(0x0201) Tag in IFD1, size of thumbnail by JpegIFByteCount(0x0202) Tag. + // Data format is ordinary JPEG format, starts from 0xFFD8 and ends by 0xFFD9. It seems that + // JPEG format and 160x120pixels of size are recommended thumbnail format for Exif2.1 or later. + + if (thumbTags['Compression'] && typeof Blob !== 'undefined') { + // console.log('Thumbnail image found!'); + + switch (thumbTags['Compression']) { + case 6: + // console.log('Thumbnail image format is JPEG'); + if (thumbTags.JpegIFOffset && thumbTags.JpegIFByteCount) { + // extract the thumbnail + var tOffset = tiffStart + thumbTags.JpegIFOffset; + var tLength = thumbTags.JpegIFByteCount; + thumbTags['blob'] = new Blob([new Uint8Array(dataView.buffer, tOffset, tLength)], { + type: 'image/jpeg' + }); + } + break; + + case 1: + console.log("Thumbnail image format is TIFF, which is not implemented."); + break; + default: + console.log("Unknown thumbnail image format '%s'", thumbTags['Compression']); + } + } else if (thumbTags['PhotometricInterpretation'] == 2) { + console.log("Thumbnail image format is RGB, which is not implemented."); + } + return thumbTags; +} + +function getStringFromDB(buffer: DataView, start: number, length: number) { + let outstr = ""; + for (let n = start; n < start + length; n++) { + outstr += String.fromCharCode(buffer.getUint8(n)); + } + return outstr; +} + +function readEXIFData(file: DataView, start: number) { + if (getStringFromDB(file, start, 4) != "Exif") { + if (exif.debug) console.log("Not valid EXIF data! " + getStringFromDB(file, start, 4)); + return false; + } + + let bigEnd, + tags, tag, + exifData, gpsData, + tiffOffset = start + 6; + + // test for TIFF validity and endianness + if (file.getUint16(tiffOffset) == 0x4949) { + bigEnd = false; + } else if (file.getUint16(tiffOffset) == 0x4D4D) { + bigEnd = true; + } else { + if (exif.debug) console.log("Not valid TIFF data! (no 0x4949 or 0x4D4D)"); + return false; + } + + if (file.getUint16(tiffOffset + 2, !bigEnd) != 0x002A) { + if (exif.debug) console.log("Not valid TIFF data! (no 0x002A)"); + return false; + } + + const firstIFDOffset = file.getUint32(tiffOffset + 4, !bigEnd); + + if (firstIFDOffset < 0x00000008) { + if (exif.debug) console.log("Not valid TIFF data! (First offset less than 8)", file.getUint32(tiffOffset + 4, + !bigEnd)); + return false; + } + + tags = readTags(file, tiffOffset, tiffOffset + firstIFDOffset, TiffTags, bigEnd); + + if (tags.ExifIFDPointer) { + exifData = readTags(file, tiffOffset, tiffOffset + tags.ExifIFDPointer, ExifTags, bigEnd); + for (tag in exifData) { + switch (tag) { + case "LightSource": + case "Flash": + case "MeteringMode": + case "ExposureProgram": + case "SensingMethod": + case "SceneCaptureType": + case "SceneType": + case "CustomRendered": + case "WhiteBalance": + case "GainControl": + case "Contrast": + case "Saturation": + case "Sharpness": + case "SubjectDistanceRange": + case "FileSource": + exifData[tag] = StringValues[tag][exifData[tag]]; + break; + + case "ExifVersion": + case "FlashpixVersion": + exifData[tag] = String.fromCharCode(exifData[tag][0], exifData[tag][1], exifData[tag][2], + exifData[tag][3]); + break; + + case "ComponentsConfiguration": + exifData[tag] = + StringValues.Components[exifData[tag][0]] + + StringValues.Components[exifData[tag][1]] + + StringValues.Components[exifData[tag][2]] + + StringValues.Components[exifData[tag][3]]; + break; + } + tags[tag] = exifData[tag]; + } + } + + if (tags.GPSInfoIFDPointer) { + gpsData = readTags(file, tiffOffset, tiffOffset + tags.GPSInfoIFDPointer, GPSTags, bigEnd); + for (tag in gpsData) { + switch (tag) { + case "GPSVersionID": + gpsData[tag] = gpsData[tag][0] + + "." + gpsData[tag][1] + + "." + gpsData[tag][2] + + "." + gpsData[tag][3]; + break; + } + tags[tag] = gpsData[tag]; + } + } + + // extract thumbnail + tags['thumbnail'] = readThumbnailImage(file, tiffOffset, firstIFDOffset, bigEnd); + + return tags; +} + +function findXMPinJPEG(file: ArrayBuffer) { + + if (!('DOMParser' in self)) { + // console.warn('XML parsing not supported without DOMParser'); + return; + } + const dataView = new DataView(file); + + if (exif.debug) console.log("Got file of length " + file.byteLength); + if ((dataView.getUint8(0) != 0xFF) || (dataView.getUint8(1) != 0xD8)) { + if (exif.debug) console.log("Not a valid JPEG"); + return false; // not a valid jpeg + } + + let offset = 2, + length = file.byteLength, + dom = new DOMParser(); + + while (offset < (length - 4)) { + if (getStringFromDB(dataView, offset, 4) == "http") { + const startOffset = offset - 1; + const sectionLength = dataView.getUint16(offset - 2) - 1; + let xmpString = getStringFromDB(dataView, startOffset, sectionLength) + const xmpEndIndex = xmpString.indexOf('xmpmeta>') + 8; + xmpString = xmpString.substring(xmpString.indexOf(' 0) { + json['@attributes'] = {}; + for (var j = 0; j < xml.attributes.length; j++) { + var attribute = xml.attributes.item(j); + json['@attributes'][attribute.nodeName] = attribute.nodeValue; + } + } + } else if (xml.nodeType == 3) { // text node + return xml.nodeValue; + } + + // deal with children + if (xml.hasChildNodes()) { + for (var i = 0; i < xml.childNodes.length; i++) { + var child = xml.childNodes.item(i); + var nodeName = child.nodeName; + if (json[nodeName] == null) { + json[nodeName] = xml2json(child); + } else { + if (json[nodeName].push == null) { + var old = json[nodeName]; + json[nodeName] = []; + json[nodeName].push(old); + } + json[nodeName].push(xml2json(child)); + } + } + } + + return json; +} + +function xml2Object(xml: any) { + try { + var obj = {}; + if (xml.children.length > 0) { + for (var i = 0; i < xml.children.length; i++) { + var item = xml.children.item(i); + var attributes = item.attributes; + for (var idx in attributes) { + var itemAtt = attributes[idx]; + var dataKey = itemAtt.nodeName; + var dataValue = itemAtt.nodeValue; + + if (dataKey !== undefined) { + obj[dataKey] = dataValue; + } + } + var nodeName = item.nodeName; + + if (typeof (obj[nodeName]) == "undefined") { + obj[nodeName] = xml2json(item); + } else { + if (typeof (obj[nodeName].push) == "undefined") { + var old = obj[nodeName]; + + obj[nodeName] = []; + obj[nodeName].push(old); + } + obj[nodeName].push(xml2json(item)); + } + } + } else { + obj = xml.textContent; + } + return obj; + } catch (e) { + console.log(e.message); + } +} \ No newline at end of file diff --git a/uni_modules/lime-shared/floatDiv/index.ts b/uni_modules/lime-shared/floatDiv/index.ts new file mode 100644 index 0000000..195d4ab --- /dev/null +++ b/uni_modules/lime-shared/floatDiv/index.ts @@ -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 +} diff --git a/uni_modules/lime-shared/floatMul/index.ts b/uni_modules/lime-shared/floatMul/index.ts new file mode 100644 index 0000000..51a867e --- /dev/null +++ b/uni_modules/lime-shared/floatMul/index.ts @@ -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 +} diff --git a/uni_modules/lime-shared/floatSub/index.ts b/uni_modules/lime-shared/floatSub/index.ts new file mode 100644 index 0000000..9bc25cb --- /dev/null +++ b/uni_modules/lime-shared/floatSub/index.ts @@ -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 +} \ No newline at end of file diff --git a/uni_modules/lime-shared/getCurrentPage/uvue.uts b/uni_modules/lime-shared/getCurrentPage/uvue.uts new file mode 100644 index 0000000..9e96d2b --- /dev/null +++ b/uni_modules/lime-shared/getCurrentPage/uvue.uts @@ -0,0 +1,5 @@ +// @ts-nocheck +export const getCurrentPage = ():Page => { + const pages = getCurrentPages(); + return pages[pages.length - 1] +}; \ No newline at end of file diff --git a/uni_modules/lime-shared/getCurrentPage/vue.ts b/uni_modules/lime-shared/getCurrentPage/vue.ts new file mode 100644 index 0000000..79ecac8 --- /dev/null +++ b/uni_modules/lime-shared/getCurrentPage/vue.ts @@ -0,0 +1,6 @@ +// @ts-nocheck +/** 获取当前页 */ +export const getCurrentPage = () => { + const pages = getCurrentPages(); + return pages[pages.length - 1] //as T & WechatMiniprogram.Page.TrivialInstance; +}; \ No newline at end of file diff --git a/uni_modules/lime-shared/getRect/uvue.uts b/uni_modules/lime-shared/getRect/uvue.uts new file mode 100644 index 0000000..05f16ee --- /dev/null +++ b/uni_modules/lime-shared/getRect/uvue.uts @@ -0,0 +1,17 @@ +// @ts-nocheck +export function getRect(selector : string, context: ComponentPublicInstance):Promise { + 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 { + return new Promise((resolve)=>{ + uni.createSelectorQuery().in(context).selectAll(selector).boundingClientRect(res =>{ + resolve(res as NodeInfo[]) + }).exec(); + }) +} + diff --git a/uni_modules/lime-shared/getRect/vue.ts b/uni_modules/lime-shared/getRect/vue.ts new file mode 100644 index 0000000..e44b60e --- /dev/null +++ b/uni_modules/lime-shared/getRect/vue.ts @@ -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((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((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 + }); +}; \ No newline at end of file diff --git a/uni_modules/lime-shared/guid/index.ts b/uni_modules/lime-shared/guid/index.ts new file mode 100644 index 0000000..675b250 --- /dev/null +++ b/uni_modules/lime-shared/guid/index.ts @@ -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); +} \ No newline at end of file diff --git a/uni_modules/lime-shared/hasOwn/uvue.uts b/uni_modules/lime-shared/hasOwn/uvue.uts new file mode 100644 index 0000000..553ccdd --- /dev/null +++ b/uni_modules/lime-shared/hasOwn/uvue.uts @@ -0,0 +1,43 @@ +// @ts-nocheck +/** + * 检查对象或数组是否具有指定的属性或键 + * @param obj 要检查的对象或数组 + * @param key 指定的属性或键 + * @returns 如果对象或数组具有指定的属性或键,则返回true;否则返回false + */ +function hasOwn(obj: UTSJSONObject, key: string): boolean +function hasOwn(obj: Map, key: string): boolean +function hasOwn(obj: any, key: string): boolean { + if(obj instanceof UTSJSONObject){ + return obj[key] != null + } + if(obj instanceof Map){ + return (obj as Map).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' 属性 \ No newline at end of file diff --git a/uni_modules/lime-shared/hasOwn/vue.ts b/uni_modules/lime-shared/hasOwn/vue.ts new file mode 100644 index 0000000..7317879 --- /dev/null +++ b/uni_modules/lime-shared/hasOwn/vue.ts @@ -0,0 +1,30 @@ +// @ts-nocheck +const hasOwnProperty = Object.prototype.hasOwnProperty +/** + * 检查对象或数组是否具有指定的属性或键 + * @param obj 要检查的对象或数组 + * @param key 指定的属性或键 + * @returns 如果对象或数组具有指定的属性或键,则返回true;否则返回false + */ +export function hasOwn(obj: Object | Array, 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' 属性 \ No newline at end of file diff --git a/uni_modules/lime-shared/isBoolean/index.ts b/uni_modules/lime-shared/isBoolean/index.ts new file mode 100644 index 0000000..8816788 --- /dev/null +++ b/uni_modules/lime-shared/isBoolean/index.ts @@ -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 +} \ No newline at end of file diff --git a/uni_modules/lime-shared/isByteLength/index.ts b/uni_modules/lime-shared/isByteLength/index.ts new file mode 100644 index 0000000..73774d8 --- /dev/null +++ b/uni_modules/lime-shared/isByteLength/index.ts @@ -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 }); // true(unicode 国旗符号占 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 +} \ No newline at end of file diff --git a/uni_modules/lime-shared/isDate/index.ts b/uni_modules/lime-shared/isDate/index.ts new file mode 100644 index 0000000..899b942 --- /dev/null +++ b/uni_modules/lime-shared/isDate/index.ts @@ -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 }); // false(2023年不是闰年) + */ +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(); + + + // 解析日期组成部分 + 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; + } + +} \ No newline at end of file diff --git a/uni_modules/lime-shared/isEmail/index.ts b/uni_modules/lime-shared/isEmail/index.ts new file mode 100644 index 0000000..01f78d4 --- /dev/null +++ b/uni_modules/lime-shared/isEmail/index.ts @@ -0,0 +1,11 @@ +// @ts-nocheck + +/** + * 验证电子邮件地址格式 + * @param email - 要验证的字符串 + * @returns 是否通过验证 + */ +export function isEmail(email : string) : boolean { + const emailRegex = /\S+@\S+\.\S+/; + return emailRegex.test(email); +} \ No newline at end of file diff --git a/uni_modules/lime-shared/isEmpty/index.ts b/uni_modules/lime-shared/isEmpty/index.ts new file mode 100644 index 0000000..2e89acb --- /dev/null +++ b/uni_modules/lime-shared/isEmpty/index.ts @@ -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).length == 0 + } + // Map + if(value instanceof Map) { + return value.size == 0 + } + // Set + if(value instanceof Set) { + 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 \ No newline at end of file diff --git a/uni_modules/lime-shared/isIP/index.ts b/uni_modules/lime-shared/isIP/index.ts new file mode 100644 index 0000000..36f4906 --- /dev/null +++ b/uni_modules/lime-shared/isIP/index.ts @@ -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; +} + diff --git a/uni_modules/lime-shared/isRegExp/index.ts b/uni_modules/lime-shared/isRegExp/index.ts new file mode 100644 index 0000000..8aaefdf --- /dev/null +++ b/uni_modules/lime-shared/isRegExp/index.ts @@ -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 +} \ No newline at end of file diff --git a/uni_modules/lime-shared/isURL/index.ts b/uni_modules/lime-shared/isURL/index.ts new file mode 100644 index 0000000..4121143 --- /dev/null +++ b/uni_modules/lime-shared/isURL/index.ts @@ -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; + /** 黑名单主机列表 */ + hostBlacklist ?: Array; +} + +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 == '') + ); +} \ No newline at end of file diff --git a/uni_modules/lime-shared/isValidDomain/index.ts b/uni_modules/lime-shared/isValidDomain/index.ts new file mode 100644 index 0000000..40c717f --- /dev/null +++ b/uni_modules/lime-shared/isValidDomain/index.ts @@ -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('-')); + }); +} \ No newline at end of file diff --git a/uni_modules/lime-shared/merge/index.ts b/uni_modules/lime-shared/merge/index.ts new file mode 100644 index 0000000..b4dcf14 --- /dev/null +++ b/uni_modules/lime-shared/merge/index.ts @@ -0,0 +1,33 @@ +// @ts-nocheck +/** + * 深度合并两个对象,用默认值填充目标对象中未定义的属性 + * + * @template T - 合并对象的泛型类型 + * @param {T} obj - 目标对象(将被修改) + * @param {T} defaults - 包含默认值的对象 + * @returns {T} 合并后的对象(即修改后的obj参数) + */ + +export function merge(obj : T, defaults : T) : T { + // #ifdef APP-ANDROID + try { + if(obj instanceof UTSJSONObject && defaults instanceof UTSJSONObject) { + return UTSJSONObject.assign(obj, defaults)!// as T + } + const obj1 = { ...toRaw(obj) } + const obj2 = { ...toRaw(defaults) } + return UTSJSONObject.assign(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 +} \ No newline at end of file diff --git a/uni_modules/lime-shared/obj2url/index.ts b/uni_modules/lime-shared/obj2url/index.ts new file mode 100644 index 0000000..d0125cf --- /dev/null +++ b/uni_modules/lime-shared/obj2url/index.ts @@ -0,0 +1,61 @@ +// @ts-nocheck +// #ifndef UNI-APP-X +type UTSJSONObject = Record +// #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('&') : ''; +} \ No newline at end of file diff --git a/uni_modules/lime-shared/objToCss/index.ts b/uni_modules/lime-shared/objToCss/index.ts new file mode 100644 index 0000000..2175269 --- /dev/null +++ b/uni_modules/lime-shared/objToCss/index.ts @@ -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;" \ No newline at end of file diff --git a/uni_modules/lime-shared/pathToBase64/uvue.uts b/uni_modules/lime-shared/pathToBase64/uvue.uts new file mode 100644 index 0000000..d5bbdb1 --- /dev/null +++ b/uni_modules/lime-shared/pathToBase64/uvue.uts @@ -0,0 +1,17 @@ +// @ts-nocheck +// import { processFile, ProcessFileOptions } from '@/uni_modules/lime-file-utils' +export function pathToBase64(path : string) : Promise { + 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) + // }) +} \ No newline at end of file diff --git a/uni_modules/lime-shared/pathToBase64/vue.ts b/uni_modules/lime-shared/pathToBase64/vue.ts new file mode 100644 index 0000000..8167f88 --- /dev/null +++ b/uni_modules/lime-shared/pathToBase64/vue.ts @@ -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 { + 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 + }) +} \ No newline at end of file diff --git a/uni_modules/lime-shared/raf/uvue.uts b/uni_modules/lime-shared/raf/uvue.uts new file mode 100644 index 0000000..1b0d9f2 --- /dev/null +++ b/uni_modules/lime-shared/raf/uvue.uts @@ -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 + diff --git a/uni_modules/lime-shared/raf/vue.ts b/uni_modules/lime-shared/raf/vue.ts new file mode 100644 index 0000000..98a364e --- /dev/null +++ b/uni_modules/lime-shared/raf/vue.ts @@ -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) + }); // 在下一帧回调中再次请求动画帧,实现双倍动画帧效果 +} \ No newline at end of file diff --git a/uni_modules/lime-shared/selectAllComponent/index.ts b/uni_modules/lime-shared/selectAllComponent/index.ts new file mode 100644 index 0000000..e30a2c8 --- /dev/null +++ b/uni_modules/lime-shared/selectAllComponent/index.ts @@ -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 \ No newline at end of file diff --git a/uni_modules/lime-shared/selectAllComponent/uvue.uts b/uni_modules/lime-shared/selectAllComponent/uvue.uts new file mode 100644 index 0000000..07c9fcd --- /dev/null +++ b/uni_modules/lime-shared/selectAllComponent/uvue.uts @@ -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 +} \ No newline at end of file diff --git a/uni_modules/lime-shared/selectAllComponent/vue.ts b/uni_modules/lime-shared/selectAllComponent/vue.ts new file mode 100644 index 0000000..380bd7a --- /dev/null +++ b/uni_modules/lime-shared/selectAllComponent/vue.ts @@ -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 +} \ No newline at end of file diff --git a/uni_modules/lime-shared/selectComponent/uvue.uts b/uni_modules/lime-shared/selectComponent/uvue.uts new file mode 100644 index 0000000..c2aa2bc --- /dev/null +++ b/uni_modules/lime-shared/selectComponent/uvue.uts @@ -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() diff --git a/uni_modules/lime-shared/selectComponent/vue.ts b/uni_modules/lime-shared/selectComponent/vue.ts new file mode 100644 index 0000000..9fca0cd --- /dev/null +++ b/uni_modules/lime-shared/selectComponent/vue.ts @@ -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) +} \ No newline at end of file diff --git a/uni_modules/lime-shared/selectElement/index.ts b/uni_modules/lime-shared/selectElement/index.ts new file mode 100644 index 0000000..fbe5fb4 --- /dev/null +++ b/uni_modules/lime-shared/selectElement/index.ts @@ -0,0 +1,277 @@ +// @ts-nocheck +import {isDef} from '../isDef' +// #ifdef UNI-APP-X +import {type ComponentPublicInstance} from '../vue' +// #endif + +type HasSelectorFunc = (selector : string, element : UniElement) => boolean + +const hasSelectorClassName : HasSelectorFunc = (selector : string, element : UniElement) : boolean => { + return element.classList.includes(selector) +} +const hasSelectorId : HasSelectorFunc = (selector : string, element : UniElement) : boolean => { + return element.getAttribute("id") == selector +} +const hasSelectorTagName : HasSelectorFunc = (selector : string, element : UniElement) : boolean => { + return element.tagName!.toLowerCase() == selector.toLowerCase() +} + +type ProcessSelectorResult = { + selectorValue : string + hasSelector : HasSelectorFunc +} +const processSelector = (selector : string) : ProcessSelectorResult => { + + const selectorValue = /#|\./.test(selector) ? selector.substring(1) : selector + let hasSelector : HasSelectorFunc + + if (selector.startsWith('.')) { + hasSelector = hasSelectorClassName + } else if (selector.startsWith('#')) { + hasSelector = hasSelectorId + } else { + hasSelector = hasSelectorTagName + } + + return { + selectorValue, + hasSelector + } as ProcessSelectorResult +} + + +function isNotEmptyString(str:string): boolean { + return str.length > 0; +} + +function isElement(element:UniElement|null):boolean { + return isDef(element) && element?.tagName != 'COMMENT'; +} + +type ElementArray = Array +class Query { + context : ComponentPublicInstance | null = null + selector : string = '' + elements : ElementArray = [] + constructor(selector : string | null, context : ComponentPublicInstance | null) { + this.context = context + if(selector != null){ + this.selector = selector + } + this.find(this.selector) + } + in(context : ComponentPublicInstance) : Query { + return new Query(this.selector, context) + } + findAll(selector : string): Query { + if (isDef(this.context)) { + const root = this.context?.$el //as Element | null; + if (isDef(root)) { + this.elements = [root!] //as ElementArray + } + const { selectorValue, hasSelector } = processSelector(selector) + const foundElements : ElementArray = []; + + function findChildren(element : UniElement) { + element.children.forEach((child : UniElement) => { + if (hasSelector(selectorValue, child)) { + foundElements.push(child) + } + }) + } + this.elements.forEach(el => { + findChildren(el!); + }); + this.elements = foundElements + } else if (selector.startsWith('#')) { + const element = uni.getElementById(selector) + if (isElement(element!)) { + this.elements = [element] + } + } + return this; + } + /** + * 在当前元素集合中查找匹配的元素 + */ + find(selector : string) : Query { + if (isDef(this.context)) { + const root = this.context?.$el //as Element | null; + if (isElement(root)) { + this.elements = [root] //as ElementArray + } + if(isNotEmptyString(selector) && this.elements.length > 0){ + const { selectorValue, hasSelector } = processSelector(selector) + const foundElements : ElementArray = []; + function findChildren(element : UniElement) { + element.children.forEach((child : UniElement) => { + if (hasSelector(selectorValue, child) && foundElements.length < 1) { + foundElements.push(child) + } + if (foundElements.length < 1) { + findChildren(child); + } + }) + } + this.elements.forEach(el => { + findChildren(el!); + }); + this.elements = foundElements + } + + } else if (selector.startsWith('#')) { + const element = uni.getElementById(selector) + if (isElement(element!)) { + this.elements = [element] + } + } + return this; + } + /** + * 获取当前元素集合的直接子元素 + */ + children() : Query { + // if (this.elements.length > 0) { + // const children = this.elements.reduce((acc, el) => [...acc, ...Array.from(el.children)], []); + // this.elements = children; + // } + return this; + } + /** + * 获取当前元素集合的父元素 + */ + parent() : Query { + // if (this.elements.length > 0) { + // const parents = this.elements.map(el => el.parentElement).filter(parent => parent !== null) as ElementArray; + // this.elements = parents + // // this.elements = Array.from(new Set(parents)); + // } + return this; + } + /** + * 获取当前元素集合的兄弟元素 + */ + siblings() : Query { + // if (this.elements.length > 0) { + // const siblings = this.elements.reduce((acc, el) => [...acc, ...Array.from(el.parentElement?.children || [])], []); + // this.elements = siblings.filter(sibling => sibling !== null && !this.elements?.includes(sibling)); + // } + return this; + } + /** + * 获取当前元素集合的下一个兄弟元素 + */ + next() : Query { + // if (this.elements.length > 0) { + // const nextElements = this.elements.map(el => el.nextElementSibling).filter(next => next !== null) as ElementArray; + // this.elements = nextElements; + // } + return this; + } + /** + * 获取当前元素集合的上一个兄弟元素 + */ + prev() : Query { + // if (this.elements.length > 0) { + // const prevElements = this.elements.map(el => el.previousElementSibling).filter(prev => prev !== null) as ElementArray; + // this.elements = prevElements; + // } + return this; + } + /** + * 从当前元素开始向上查找匹配的元素 + */ + closest(selector : string) : Query { + if (isDef(this.context)) { + // && this.context.$parent != null && this.context.$parent.$el !== null + if(this.elements.length == 0 && isDef(this.context?.$parent) && isElement(this.context!.$parent?.$el)){ + this.elements = [this.context!.$parent?.$el!] + } + + const selectorsArray = selector.split(',') + // const { selectorValue, hasSelector } = processSelector(selector) + const processedSelectors = selectorsArray.map((selector: string):ProcessSelectorResult => processSelector(selector)) + const closestElements = this.elements.map((el) : UniElement | null => { + let closestElement : UniElement | null = el + while (closestElement !== null) { + // if (hasSelector(selectorValue, closestElement)) { + // break; + // } + const isMatchingSelector = processedSelectors.some(({selectorValue, hasSelector}):boolean => { + return hasSelector(selectorValue, closestElement!) + }) + if(isMatchingSelector){ + break; + } + closestElement = closestElement.parentElement; + } + return closestElement + }) + this.elements = closestElements.filter((closest : UniElement | null) : boolean => isDef(closest))// as ElementArray + + } + return this; + } + + /** + * 从当前元素集合中过滤出匹配的元素 + */ + filter() : Query { + + return this; + } + /** + * 从当前元素集合中排除匹配的元素 + */ + not() { } + /** + * 从当前元素集合中查找包含匹配元素的元素 + */ + has() { } + /** + * 获取当前元素集合的第一个 + */ + first() : Query { + if (this.elements.length > 0) { + // this.elements = [this.elements[0]]; + } + return this; + } + /** + * 最后一个元素 + */ + last() : Query { + if (this.elements.length > 0) { + // this.elements = [this.elements[this.elements.length - 1]]; + } + return this; + } + /** + * 获取当前元素在其兄弟元素中的索引 + */ + index() : number | null { + // if (this.elements.length > 0 && this.elements.length > 0 && this.elements[0].parentElement !== null) { + // return Array.from(this.elements[0].parentElement.children).indexOf(this.elements[0]); + // } + return null; + } + get(index : number) : UniElement | null { + if (this.elements.length > index) { + return this.elements[index] //as Element + } + return null + } +} + +export function selectElement(selector : string | null = null) : Query { + // if(typeof selector == 'string' || selector == null){ + // return new Query(selector as string | null, null) + // } + // else if(selector instanceof ComponentPublicInstance){ + // return new Query(null, selector) + // } + return new Query(selector, null) +} + +// $('xxx').in(this).find('xxx') +// $('xxx').in(this).get() \ No newline at end of file diff --git a/uni_modules/lime-shared/shuffle/index.ts b/uni_modules/lime-shared/shuffle/index.ts new file mode 100644 index 0000000..5b3ab51 --- /dev/null +++ b/uni_modules/lime-shared/shuffle/index.ts @@ -0,0 +1,16 @@ +// @ts-nocheck +/** + * 随机化数组中元素的顺序,使用 Fisher-Yates 算法 + * @description 函数接受一个数组作为参数,返回一个新的数组,其中包含原数组随机化顺序后的元素。 + * @param arr 要随机化的数组 + * @returns 一个新的数组,其中包含原数组随机化顺序后的元素。 + */ +export function shuffle(arr : T[]) : T[] { + for (let i = arr.length - 1; i > 0; i--) { + const j = Math.floor(Math.random() * (i + 1)) + const temp = arr[i] + arr[i] = arr[j] + arr[j] = temp + } + return arr +} \ No newline at end of file diff --git a/uni_modules/lime-shared/throttle/index_old.ts b/uni_modules/lime-shared/throttle/index_old.ts new file mode 100644 index 0000000..914be49 --- /dev/null +++ b/uni_modules/lime-shared/throttle/index_old.ts @@ -0,0 +1,112 @@ +// @ts-nocheck +/** + * 节流函数,用于限制函数的调用频率 + * @param fn 要进行节流的函数 + * @param delay 两次调用之间的最小间隔时间 + * @returns 节流后的函数 + */ + +// #ifndef UNI-APP-X && APP +// export function throttle(fn: (...args: any[]) => void, delay: number, options) { +// let flag = true; // 标记是否可以执行函数 + +// return (...args: any[]) => { +// if (flag) { +// flag = false; // 设置为不可执行状态 +// fn(...args); // 执行传入的函数 + +// setTimeout(() => { +// flag = true; // 经过指定时间后,设置为可执行状态 +// }, delay); +// } +// }; +// } + +export function throttle void>( + fn: T, + delay: number, + { leading = true, trailing = true }: { leading?: boolean; trailing?: boolean } = {} +): (...args: Parameters) => void { + let isCoolingDown = false; + let lastArgs: Parameters | null = null; + let timerId: ReturnType | null = null; + + const executeTrailing = () => { + if (trailing && lastArgs) { + fn(...lastArgs); + lastArgs = null; + } + isCoolingDown = false; + timerId = null; + }; + + return function (...args: Parameters) { + // 1. 如果不在冷却期,且 leading=true,立即执行 + if (!isCoolingDown && leading) { + fn(...args); + isCoolingDown = true; + timerId = setTimeout(executeTrailing, delay); + } + // 2. 如果在冷却期,记录最后一次调用的参数(用于 trailing) + else if (trailing) { + lastArgs = args; + } + }; +} +// #endif + + +// #ifdef UNI-APP-X && APP +// type Rfun = (...args: any[]) => void +// type Rfun = (...args: any[]) => void + +export function throttle( + fn: (args : T) => void, + delay: number):(args : T) => void { + let flag = true; // 标记是否可以执行函数 + + return (args : T) =>{ + if(flag){ + flag = false; + fn(args); + + setTimeout(()=>{ + flag = true; + }, delay) + } + } + // return (...args: any[]) => { + // // if (flag) { + // // flag = false; // 设置为不可执行状态 + // // fn(...args); // 执行传入的函数 + + // // setTimeout(() => { + // // flag = true; // 经过指定时间后,设置为可执行状态 + // // }, delay); + // // } + // }; +} + +// #endif + +// // 示例 +// // 定义一个被节流的函数 +// function handleScroll() { +// console.log("Scroll event handled!"); +// } + +// // 使用节流函数对 handleScroll 进行节流,间隔时间为 500 毫秒 +// const throttledScroll = throttle(handleScroll, 500); + +// // 模拟多次调用 handleScroll +// throttledScroll(); // 输出 "Scroll event handled!" +// throttledScroll(); // 不会输出 +// throttledScroll(); // 不会输出 + +// // 经过 500 毫秒后,再次调用 handleScroll +// setTimeout(() => { +// throttledScroll(); // 输出 "Scroll event handled!" +// }, 500); + + + diff --git a/uni_modules/lime-shared/toBoolean/index.ts b/uni_modules/lime-shared/toBoolean/index.ts new file mode 100644 index 0000000..6dda115 --- /dev/null +++ b/uni_modules/lime-shared/toBoolean/index.ts @@ -0,0 +1,40 @@ +// @ts-nocheck +import { isNumber } from '../isNumber' +import { isString } from '../isString' +// 函数重载,定义多个函数签名 +// function toBoolean(value : any) : boolean; +// function toBoolean(value : string) : boolean; +// function toBoolean(value : number) : boolean; +// function toBoolean(value : boolean) : boolean; + +// #ifdef UNI-APP-X && APP +function toBoolean(value : any | null) : boolean { + // 根据输入值的类型,返回相应的布尔值 + // if (isNumber(value)) { + // return (value as number) != 0; + // } + // if (isString(value)) { + // return `${value}`.length > 0; + // } + // if (typeof value == 'boolean') { + // return value as boolean; + // } + // #ifdef APP-IOS || APP-HARMONY + return value != null && value != undefined + // #endif + // #ifdef APP-ANDROID + return value != null + // #endif +} +// #endif + + +// #ifndef UNI-APP-X && APP +function toBoolean(value : any | null) : value is NonNullable { + return !!value//value !== null && value !== undefined; +} +// #endif + +export { + toBoolean +} \ No newline at end of file diff --git a/uni_modules/lime-shared/while/index.ts b/uni_modules/lime-shared/while/index.ts new file mode 100644 index 0000000..1b8a96b --- /dev/null +++ b/uni_modules/lime-shared/while/index.ts @@ -0,0 +1,55 @@ +// @ts-nocheck + +// 来自kux大佬的神之一手 +// 原地址 https://ext.dcloud.net.cn/plugin?id=23291 +export type ControlCommand = 'continue' | 'break' | null; + +export type ControllableWhileReturn = { + start: () => void; + abort: () => void; + execContinue: () => 'continue'; + execBreak: () => 'break'; +}; + +export type Controller = { + abort: () => void; +}; + +export function controllableWhile( + condition : () => boolean, + body: (controller: Controller) => ControlCommand +): ControllableWhileReturn { + let isActive = true; + + const controller: Controller = { + abort: () => { + isActive = false; + } + }; + + const execContinue = () => 'continue'; + const execBreak = () => 'break'; + + return { + start: () => { + // #ifdef APP-ANDROID + UTSAndroid.getDispatcher('io').async((_) => { + // #endif + while (isActive && condition()) { + const result = body(controller); + if (result == 'break') { + controller.abort(); + break; + } else if (result == 'continue') { + continue; + } + } + // #ifdef APP-ANDROID + }, null); + // #endif + }, + abort: controller.abort, + execContinue: execContinue, + execBreak: execBreak + } as ControllableWhileReturn; +} \ No newline at end of file