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