diff --git a/.gitignore b/.gitignore
index 0a73778..8b13789 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1 @@
-/uni_modules
-/unpackage/dist
+
diff --git a/uni_modules/lime-drag/changelog.md b/uni_modules/lime-drag/changelog.md
new file mode 100644
index 0000000..e78c265
--- /dev/null
+++ b/uni_modules/lime-drag/changelog.md
@@ -0,0 +1,33 @@
+## 0.1.3(2023-08-19)
+- fix: 修复使用remove导致样式错乱
+## 0.1.2(2023-08-09)
+- fix: 修复nvue没有获取节点的问题
+- fix: 修复因延时导致卡在中途
+- fix: 修复change事件有时失效的问题
+## 0.1.1(2023-07-03)
+- chore: 更新文档
+## 0.1.0(2023-07-03)
+- fix: 外面的事件冒泡导致点击调动内部移动方法错乱
+## 0.0.9(2023-05-30)
+- fix: 修复因手机事件为`onLongpress`导致,在手机上无法长按
+- fix: 无法因css导致滚动
+## 0.0.8(2023-04-23)
+- feat: 更新文档
+## 0.0.7(2023-04-23)
+- feat: 由于删除是一个危险的动作,故把方法暴露出来,而不在内部处理。如果之前有使用删除的,需要注意
+- feat: 原来的`add`变更为`push`,增加`unshift`
+## 0.0.6(2023-04-12)
+- fix: 修复`handle`不生效问题
+- feat: 增加 `to`方法
+## 0.0.5(2023-04-11)
+- chore: `grid` 插槽增加 `nindex`、`oindex`
+## 0.0.4(2023-04-04)
+- chore: 去掉 script-setup 语法糖
+- chore: 文档增加 vue2 使用方法
+## 0.0.3(2023-03-30)
+- feat: 重要说明 更新 list 只会再次初始化
+- feat: 更新文档
+## 0.0.2(2023-03-29)
+- 修改文档
+## 0.0.1(2023-03-29)
+- 初次提交
diff --git a/uni_modules/lime-drag/components/l-drag/index.scss b/uni_modules/lime-drag/components/l-drag/index.scss
new file mode 100644
index 0000000..e74ba5a
--- /dev/null
+++ b/uni_modules/lime-drag/components/l-drag/index.scss
@@ -0,0 +1,93 @@
+$drag-handle-size: var(--l-drag-handle-size, 50rpx);
+$drag-delete-size: var(--l-drag-delete-size, 32rpx);
+.l-drag {
+ // min-height: 100rpx;
+ overflow: hidden;
+
+
+ margin: 24rpx 30rpx 0 30rpx;
+ // padding: 30rpx 0;
+
+
+ /* #ifdef APP-NVUE */
+ // flex: 1;
+ /* #endif */
+ /* #ifndef APP-NVUE */
+ // width: 100%;
+ /* #endif */
+}
+.l-drag__inner {
+ /* #ifdef APP-NVUE */
+ flex: 1;
+ /* #endif */
+ /* #ifndef APP-NVUE */
+ width: 100%;
+ /* #endif */
+ min-height: 100rpx;
+}
+.l-drag__view {
+ // touch-action: none;
+ // user-select: none;
+ // -webkit-user-select: auto;
+ z-index: 2;
+ transition: opacity 300ms ease;
+ .mask {
+ position: absolute;
+ inset: 0;
+ background-color: transparent;
+ z-index: 9;
+ }
+ /* #ifndef APP-NVUE */
+ > view {
+ &:last-child {
+ width: 100%;
+ height: 100%;
+ }
+ }
+ box-sizing: border-box;
+ /* #endif */
+
+}
+.l-drag-enter {
+ opacity: 0;
+}
+.l-drag__ghost {
+ /* #ifndef APP-NVUE */
+ > view {
+ &:last-child {
+ width: 100%;
+ height: 100%;
+ }
+ }
+ box-sizing: border-box;
+ /* #endif */
+}
+.l-is-active {
+ z-index: 3;
+}
+.l-is-hidden {
+ opacity: 0;
+}
+.l-drag__delete {
+ position: absolute;
+ z-index: 10;
+ width: $drag-delete-size;
+ height: $drag-delete-size;
+}
+.l-drag__handle {
+ position: absolute;
+ z-index: 10;
+ width: $drag-handle-size;
+ height: $drag-handle-size;
+}
+/* #ifndef APP-NVUE */
+.l-drag__delete::before,.l-drag__handle::before {
+ content: '';
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ left: 0;
+ top: 0;
+ z-index: 10;
+}
+/* #endif */
\ No newline at end of file
diff --git a/uni_modules/lime-drag/components/l-drag/l-drag.vue b/uni_modules/lime-drag/components/l-drag/l-drag.vue
new file mode 100644
index 0000000..dc438a7
--- /dev/null
+++ b/uni_modules/lime-drag/components/l-drag/l-drag.vue
@@ -0,0 +1,532 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/uni_modules/lime-drag/components/l-drag/props.ts b/uni_modules/lime-drag/components/l-drag/props.ts
new file mode 100644
index 0000000..c475fe8
--- /dev/null
+++ b/uni_modules/lime-drag/components/l-drag/props.ts
@@ -0,0 +1,47 @@
+// @ts-nocheck
+export default {
+ list: {
+ type: Array,
+ default: []
+ },
+ column: {
+ type: Number,
+ default: 2
+ },
+ /**宽高比 填写这项, gridHeight 失效*/
+ aspectRatio: Number,
+ gridHeight: {
+ type: [Number, String],
+ default: '120rpx'
+ },
+ // removeStyle: String,
+ // handleStyle: String,
+ damping: {
+ type: Number,
+ default: 40
+ },
+ friction: {
+ type: Number,
+ default: 2
+ },
+ /**
+ * 由于 movable-area 无法动态设置高度,故增加额外的行数。用于增加动态项时,高度不够无法正确显示
+ */
+ extraRow: {
+ type: Number,
+ default: 0
+ },
+ /**
+ * 由于 movable-area 无法动态设置高度,但vif 重染可以,另一种实现动态高度的方式, 这BUG uni官方好像修复了。
+ */
+ // reset: Boolean,
+ // sort: Boolean,
+ // remove: Boolean,
+ ghost: Boolean,
+ handle: Boolean,
+ touchHandle: Boolean,
+ before: Boolean,
+ after: Boolean,
+ disabled: Boolean,
+ longpress: Boolean,
+ }
\ No newline at end of file
diff --git a/uni_modules/lime-drag/components/l-drag/type.ts b/uni_modules/lime-drag/components/l-drag/type.ts
new file mode 100644
index 0000000..7cfaa50
--- /dev/null
+++ b/uni_modules/lime-drag/components/l-drag/type.ts
@@ -0,0 +1,21 @@
+export interface Position {
+ x: number
+ y: number
+}
+export interface GridRect extends Position{
+ row : number
+ // x : number
+ // y : number
+ x1 : number
+ y1 : number
+}
+export interface Grid extends Position{
+ id : string
+ index : number
+ oldindex : number
+ content : any
+ // x : number
+ // y : number
+ class : string
+ show: boolean
+}
\ No newline at end of file
diff --git a/uni_modules/lime-drag/components/l-drag/vue.ts b/uni_modules/lime-drag/components/l-drag/vue.ts
new file mode 100644
index 0000000..de8fe1b
--- /dev/null
+++ b/uni_modules/lime-drag/components/l-drag/vue.ts
@@ -0,0 +1,9 @@
+// @ts-nocheck
+// export * from '@/uni_modules/lime-vue'
+
+// #ifdef VUE3
+export * from 'vue';
+// #endif
+// #ifndef VUE3
+export * from '@vue/composition-api';
+// #endif
diff --git a/uni_modules/lime-drag/components/lime-drag/lime-drag.vue b/uni_modules/lime-drag/components/lime-drag/lime-drag.vue
new file mode 100644
index 0000000..ab315a2
--- /dev/null
+++ b/uni_modules/lime-drag/components/lime-drag/lime-drag.vue
@@ -0,0 +1,268 @@
+
+
+
+
+
+
+ {{index}}
+
+
+
+
+
+
+
+
+
+
+
+ {{content}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 把原始下标为
+
+ 的项移动到
+
+
+
+
+
+
+
+
+ {{content}}
+ 上移
+ 下移
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/uni_modules/lime-drag/package.json b/uni_modules/lime-drag/package.json
new file mode 100644
index 0000000..839f606
--- /dev/null
+++ b/uni_modules/lime-drag/package.json
@@ -0,0 +1,87 @@
+{
+ "id": "lime-drag",
+ "displayName": "拖拽排序-拖动排序-LimeUI",
+ "version": "0.1.3",
+ "description": "uniapp vue3 拖拽排序插件,用于图片或列表的拖动排序,可设置列数、增加删除等功能, vue2只要配置@vue/composition-api",
+ "keywords": [
+ "拖拽",
+ "拖拽排序",
+ "排序",
+ "拖动",
+ "拖动排序"
+],
+ "repository": "",
+ "engines": {
+ "HBuilderX": "^3.7.12"
+ },
+ "dcloudext": {
+ "type": "component-vue",
+ "sale": {
+ "regular": {
+ "price": "0.00"
+ },
+ "sourcecode": {
+ "price": "0.00"
+ }
+ },
+ "contact": {
+ "qq": ""
+ },
+ "declaration": {
+ "ads": "无",
+ "data": "无",
+ "permissions": "无"
+ },
+ "npmurl": ""
+ },
+ "uni_modules": {
+ "dependencies": [
+ "lime-shared"
+ ],
+ "encrypt": [],
+ "platforms": {
+ "cloud": {
+ "tcb": "y",
+ "aliyun": "y"
+ },
+ "client": {
+ "Vue": {
+ "vue2": "n",
+ "vue3": "y"
+ },
+ "App": {
+ "app-vue": "y",
+ "app-nvue": "y"
+ },
+ "H5-mobile": {
+ "Safari": "y",
+ "Android Browser": "y",
+ "微信浏览器(Android)": "y",
+ "QQ浏览器(Android)": "y"
+ },
+ "H5-pc": {
+ "Chrome": "y",
+ "IE": "u",
+ "Edge": "u",
+ "Firefox": "u",
+ "Safari": "u"
+ },
+ "小程序": {
+ "微信": "y",
+ "阿里": "u",
+ "百度": "u",
+ "字节跳动": "u",
+ "QQ": "u",
+ "钉钉": "u",
+ "快手": "u",
+ "飞书": "u",
+ "京东": "u"
+ },
+ "快应用": {
+ "华为": "u",
+ "联盟": "u"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/uni_modules/lime-drag/readme.md b/uni_modules/lime-drag/readme.md
new file mode 100644
index 0000000..c461f95
--- /dev/null
+++ b/uni_modules/lime-drag/readme.md
@@ -0,0 +1,170 @@
+# lime-drag 拖拽排序
+- 当前为初版 可能会有BUG
+- 基于uniapp vue3
+- Q群 1169785031
+
+
+### 安装
+- 在市场导入插件即可在任意页面使用,无须再`import`
+
+
+### 使用
+- 提供简单的使用示例,更多请查看下方的demo
+
+```html
+
+
+
+
+
+ {{grid.content}}
+
+
+
+```
+
+```js
+const list = new Array(7).fill(0).map((v,i) => i);
+// 拖拽后新的数据
+const newList = ref([])
+const change = v => newList.value = v
+```
+#### 增删
+- 不要给list赋值,这样只会重新初始化
+- 增加数据 调用暴露的`push`
+- 删除某条数据调用暴露的`remove`方法,需要传入`oindex`
+
+```html
+
+
+
+
+
+
+
+
+
+ {{content}}
+
+
+
+
+
+
+
+
+```
+```js
+const dragRef = ref(null)
+const list = new Array(7).fill(0).map((v,i) => i);
+const onAdd = () => {
+ dragRef.value.push(Math.round(Math.random() * 1000))
+}
+const onRemove = (oindex) => {
+ if(dragRef.value && oindex >= 0) {
+ // 记得oindex为数组的原始index
+ dragRef.value.remove(oindex)
+ }
+}
+```
+
+
+#### 插槽
+```html
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+
+### 查看示例
+- 导入后直接使用这个标签查看演示效果
+
+```html
+
+
+```
+
+
+### 插件标签
+- 默认 l-drag 为 component
+- 默认 lime-drag 为 demo
+
+### 关于vue2的使用方式
+- 插件使用了`composition-api`, 如果你希望在vue2中使用请按官方的教程[vue-composition-api](https://uniapp.dcloud.net.cn/tutorial/vue-composition-api.html)配置
+- 关键代码是: 在main.js中 在vue2部分加上这一段即可,官方是把它单独成了一个文件.
+```js
+// vue2
+import Vue from 'vue'
+import VueCompositionAPI from '@vue/composition-api'
+Vue.use(VueCompositionAPI)
+```
+
+- 另外插件也用到了TS,vue2可能会遇过官方的TS版本过低的问题,找到HX目录下的`compile-typescript`目录
+```cmd
+// \HBuilderX\plugins\compile-typescript
+yarn add typescript -D
+- or -
+npm install typescript -D
+```
+
+- 小程序需要在`manifest.json`启用`slotMultipleInstance`
+```json
+"mp-weixin" : {
+ "slotMultipleInstance" : true
+}
+```
+
+
+## API
+
+### Props
+
+| 参数 | 说明 | 类型 | 默认值 |
+| --------------------------| ------------------------------------------------------------ | ---------------- | ------------ |
+| list | 列表数组,不可变化,变化后会重新初始化 | array | `[]` |
+| column | 列数 | number | `2` |
+| gridHeight | 行高,宫格高度 | string | `120rpx` |
+| damping | 阻尼系数,用于控制x或y改变时的动画和过界回弹的动画,值越大移动越快 | string | `-` |
+| friction | 摩擦系数,用于控制惯性滑动的动画,值越大摩擦力越大,滑动越快停止;必须大于0,否则会被设置成默认值 | number | `2` |
+| extraRow | 额外行数 | number | `0` |
+| ghost | 开启幽灵插槽 | boolean | `false` |
+| before | 开启列前插槽 | boolean | `false` |
+| after | 开启列后插槽 | boolean | `false` |
+| disabled | 是否禁用 | boolean | `false` |
+| longpress | 是否长按 | boolean | `false` |
+
+### Events
+| 参数 | 说明 | 参数 |
+| --------------------------| ------------------------------------------------------------ | ---------------- |
+| change | 返回新数据 | list |
+
+### Expose
+| 参数 | 说明 | 参数 |
+| --------------------------| ------------------------------------------------------------ | ---------------- |
+| remove | 删除, 传入`oindex`,即数据列表原始的index | |
+| push | 向后增加,可以是数组或单数据 | |
+| unshift | 向前增加,可以是数组或单数据 | |
+| move | 移动, 传入(`oindex`, `toindex`),将数据列表原始的index项移到视图中的目标位置 | |
+
+
+### TODO
+将来实现的功能
+- splice
+
+## 打赏
+
+如果你觉得本插件,解决了你的问题,赠人玫瑰,手留余香。
+
+
\ No newline at end of file
diff --git a/uni_modules/lime-shared/addUnit/index.ts b/uni_modules/lime-shared/addUnit/index.ts
new file mode 100644
index 0000000..25bc2b1
--- /dev/null
+++ b/uni_modules/lime-shared/addUnit/index.ts
@@ -0,0 +1,42 @@
+// @ts-nocheck
+import {isNumeric} from '../isNumeric'
+import {isDef} from '../isDef'
+/**
+ * 给一个值添加单位(像素 px)
+ * @param value 要添加单位的值,可以是字符串或数字
+ * @returns 添加了单位的值,如果值为 null 则返回 null
+ */
+
+// #ifndef APP-IOS || APP-ANDROID
+export function addUnit(value?: string | number): string | null {
+ if (!isDef(value)) {
+ return null;
+ }
+ value = String(value); // 将值转换为字符串
+ // 如果值是数字,则在后面添加单位 "px",否则保持原始值
+ return isNumeric(value) ? `${value}px` : value;
+}
+// #endif
+
+
+// #ifdef APP-IOS || APP-ANDROID
+function addUnit(value: string): string
+function addUnit(value: number): string
+function addUnit(value: any|null): string|null {
+ if (!isDef(value)) {
+ return null;
+ }
+ value = `${value}` //value.toString(); // 将值转换为字符串
+
+ // 如果值是数字,则在后面添加单位 "px",否则保持原始值
+ return isNumeric(value) ? `${value}px` : value;
+}
+export {addUnit}
+// #endif
+
+
+// console.log(addUnit(100)); // 输出: "100px"
+// console.log(addUnit("200")); // 输出: "200px"
+// console.log(addUnit("300px")); // 输出: "300px"(已经包含单位)
+// console.log(addUnit()); // 输出: undefined(值为 undefined)
+// console.log(addUnit(null)); // 输出: undefined(值为 null)
\ No newline at end of file
diff --git a/uni_modules/lime-shared/animation/bezier.ts b/uni_modules/lime-shared/animation/bezier.ts
new file mode 100644
index 0000000..b4239e1
--- /dev/null
+++ b/uni_modules/lime-shared/animation/bezier.ts
@@ -0,0 +1,82 @@
+export function cubicBezier(p1x : number, p1y : number, p2x : number, p2y : number):(x: number)=> number {
+ const ZERO_LIMIT = 1e-6;
+ // Calculate the polynomial coefficients,
+ // implicit first and last control points are (0,0) and (1,1).
+ const ax = 3 * p1x - 3 * p2x + 1;
+ const bx = 3 * p2x - 6 * p1x;
+ const cx = 3 * p1x;
+
+ const ay = 3 * p1y - 3 * p2y + 1;
+ const by = 3 * p2y - 6 * p1y;
+ const cy = 3 * p1y;
+
+ function sampleCurveDerivativeX(t : number) : number {
+ // `ax t^3 + bx t^2 + cx t` expanded using Horner's rule
+ return (3 * ax * t + 2 * bx) * t + cx;
+ }
+
+ function sampleCurveX(t : number) : number {
+ return ((ax * t + bx) * t + cx) * t;
+ }
+
+ function sampleCurveY(t : number) : number {
+ return ((ay * t + by) * t + cy) * t;
+ }
+
+ // Given an x value, find a parametric value it came from.
+ function solveCurveX(x : number) : number {
+ let t2 = x;
+ let derivative : number;
+ let x2 : number;
+
+ // https://trac.webkit.org/browser/trunk/Source/WebCore/platform/animation
+ // first try a few iterations of Newton's method -- normally very fast.
+ // http://en.wikipedia.org/wikiNewton's_method
+ for (let i = 0; i < 8; i++) {
+ // f(t) - x = 0
+ x2 = sampleCurveX(t2) - x;
+ if (Math.abs(x2) < ZERO_LIMIT) {
+ return t2;
+ }
+ derivative = sampleCurveDerivativeX(t2);
+ // == 0, failure
+ /* istanbul ignore if */
+ if (Math.abs(derivative) < ZERO_LIMIT) {
+ break;
+ }
+ t2 -= x2 / derivative;
+ }
+
+ // Fall back to the bisection method for reliability.
+ // bisection
+ // http://en.wikipedia.org/wiki/Bisection_method
+ let t1 = 1;
+ /* istanbul ignore next */
+ let t0 = 0;
+
+ /* istanbul ignore next */
+ t2 = x;
+ /* istanbul ignore next */
+ while (t1 > t0) {
+ x2 = sampleCurveX(t2) - x;
+ if (Math.abs(x2) < ZERO_LIMIT) {
+ return t2;
+ }
+ if (x2 > 0) {
+ t1 = t2;
+ } else {
+ t0 = t2;
+ }
+ t2 = (t1 + t0) / 2;
+ }
+
+ // Failure
+ return t2;
+ }
+
+ return function (x : number) : number {
+ return sampleCurveY(solveCurveX(x));
+ }
+
+ // return solve;
+}
\ No newline at end of file
diff --git a/uni_modules/lime-shared/animation/ease.ts b/uni_modules/lime-shared/animation/ease.ts
new file mode 100644
index 0000000..9358c6d
--- /dev/null
+++ b/uni_modules/lime-shared/animation/ease.ts
@@ -0,0 +1,2 @@
+import {cubicBezier} from './bezier';
+export let ease = cubicBezier(0.25, 0.1, 0.25, 1);
\ No newline at end of file
diff --git a/uni_modules/lime-shared/animation/index.ts b/uni_modules/lime-shared/animation/index.ts
new file mode 100644
index 0000000..5665983
--- /dev/null
+++ b/uni_modules/lime-shared/animation/index.ts
@@ -0,0 +1,10 @@
+// @ts-nocheck
+// #ifdef APP-IOS || APP-ANDROID
+export * from './uvue.uts'
+// #endif
+
+
+
+// #ifndef APP-IOS || APP-ANDROID
+export * from './vue.ts'
+// #endif
diff --git a/uni_modules/lime-shared/animation/useTransition.ts b/uni_modules/lime-shared/animation/useTransition.ts
new file mode 100644
index 0000000..818e591
--- /dev/null
+++ b/uni_modules/lime-shared/animation/useTransition.ts
@@ -0,0 +1,97 @@
+// @ts-nocheck
+import { ComponentPublicInstance } from 'vue'
+import { ease } from './ease';
+import { Timeline, Animation } from './';
+export type UseTransitionOptions = {
+ duration ?: number
+ immediate ?: boolean
+ context ?: ComponentPublicInstance
+}
+// #ifndef APP-IOS || APP-ANDROID
+import { ref, watch, Ref } from '@/uni_modules/lime-shared/vue'
+
+export function useTransition(percent : Ref|(() => number), options : UseTransitionOptions) : Ref {
+ const current = ref(0)
+ const { immediate, duration = 300 } = options
+ let tl:Timeline|null = null;
+ let timer = -1
+ const isFunction = typeof percent === 'function'
+ watch(isFunction ? percent : () => percent.value, (v) => {
+ if(tl == null){
+ tl = new Timeline()
+ }
+ tl.start();
+ tl.add(
+ new Animation(
+ current.value,
+ v,
+ duration,
+ 0,
+ ease,
+ nowValue => {
+ current.value = nowValue
+ clearTimeout(timer)
+ if(current.value == v){
+ timer = setTimeout(()=>{
+ tl?.pause();
+ tl = null
+ }, duration)
+ }
+ }
+ )
+ );
+ }, { immediate })
+
+ return current
+}
+
+// #endif
+
+// #ifdef APP-IOS || APP-ANDROID
+type UseTransitionReturnType = Ref
+export function useTransition(source : any, options : UseTransitionOptions) : UseTransitionReturnType {
+ const outputRef : Ref = ref(0)
+ const immediate = options.immediate ?? false
+ const duration = options.duration ?? 300
+ const context = options.context //as ComponentPublicInstance | null
+ let tl:Timeline|null = null;
+ let timer = -1
+ const watchFunc = (v : number) => {
+ if(tl == null){
+ tl = new Timeline()
+ }
+ tl!.start();
+ tl!.add(
+ new Animation(
+ outputRef.value,
+ v,
+ duration,
+ 0,
+ ease,
+ nowValue => {
+ outputRef.value = nowValue //nowValue < 0.0001 ? 0 : Math.abs(v - nowValue) < 0.00001 ? v : nowValue;
+ clearTimeout(timer)
+ if(outputRef.value == v){
+ timer = setTimeout(()=>{
+ tl?.pause();
+ tl = null
+ }, duration)
+ }
+ }
+ ), null
+ );
+ }
+
+ if (context != null && typeof source == 'string') {
+ context.$watch(source, watchFunc, { immediate } as WatchOptions)
+ } else {
+ watch(source, watchFunc, { immediate } as WatchOptions)
+ }
+
+ const stop = ()=>{
+
+ }
+ return outputRef //as UseTransitionReturnType
+}
+
+// #endif
\ No newline at end of file
diff --git a/uni_modules/lime-shared/animation/uvue.uts b/uni_modules/lime-shared/animation/uvue.uts
new file mode 100644
index 0000000..9a10db0
--- /dev/null
+++ b/uni_modules/lime-shared/animation/uvue.uts
@@ -0,0 +1,112 @@
+// @ts-nocheck
+export class Timeline {
+ state : string
+ animations : Set = new Set()
+ delAnimations : Animation[] = []
+ startTimes : Map = new Map()
+ pauseTime : number = 0
+ pauseStart : number = Date.now()
+ tickHandler : number = 0
+ tickHandlers : number[] = []
+ tick : (() => void) | null = null
+ constructor() {
+ this.state = 'Initiated';
+ }
+ start() {
+ if (!(this.state === 'Initiated')) return;
+ this.state = 'Started';
+
+ let startTime = Date.now();
+ this.pauseTime = 0;
+ this.tick = () => {
+ let now = Date.now();
+ this.animations.forEach((animation : Animation) => {
+ let t:number;
+ const ani = this.startTimes.get(animation)
+ if (ani == null) return
+ if (ani < startTime) {
+ t = now - startTime - animation.delay - this.pauseTime;
+ } else {
+ t = now - ani - animation.delay - this.pauseTime;
+ }
+ if (t > animation.duration) {
+ this.delAnimations.push(animation)
+ // 不能在 foreach 里面 对 集合进行删除操作
+ // this.animations.delete(animation);
+ t = animation.duration;
+ }
+ if (t > 0) animation.run(t);
+ })
+ // 不能在 foreach 里面 对 集合进行删除操作
+ while (this.delAnimations.length > 0) {
+ const animation = this.delAnimations.pop();
+ if (animation == null) return
+ this.animations.delete(animation);
+ }
+ clearTimeout(this.tickHandler);
+ if (this.state != 'Started') return
+ this.tickHandler = setTimeout(() => {
+ this.tick!()
+ }, 1000 / 60)
+ // this.tickHandlers.push(this.tickHandler)
+ }
+ this.tick!()
+ }
+ pause() {
+ if (!(this.state === 'Started')) return;
+ this.state = 'Paused';
+ this.pauseStart = Date.now();
+ clearTimeout(this.tickHandler);
+ }
+ resume() {
+ if (!(this.state === 'Paused')) return;
+ this.state = 'Started';
+ this.pauseTime += Date.now() - this.pauseStart;
+ this.tick!();
+ }
+ reset() {
+ this.pause();
+ this.state = 'Initiated';
+ this.pauseTime = 0;
+ this.pauseStart = 0;
+ this.animations.clear()
+ this.delAnimations.clear()
+ this.startTimes.clear()
+ this.tickHandler = 0;
+ }
+ add(animation : Animation, startTime ?: number | null) {
+ if (startTime == null) startTime = Date.now();
+ this.animations.add(animation);
+ this.startTimes.set(animation, startTime);
+ }
+}
+
+export class Animation {
+ startValue : number
+ endValue : number
+ duration : number
+ timingFunction : (t : number) => number
+ delay : number
+ template : (t : number) => void
+ constructor(
+ startValue : number,
+ endValue : number,
+ duration : number,
+ delay : number,
+ timingFunction : (t : number) => number,
+ template : (v : number) => void) {
+ this.startValue = startValue;
+ this.endValue = endValue;
+ this.duration = duration;
+ this.timingFunction = timingFunction;
+ this.delay = delay;
+ this.template = template;
+ }
+
+ run(time : number) {
+ let range = this.endValue - this.startValue;
+ let progress = time / this.duration
+ if(progress != 1) progress = this.timingFunction(progress)
+ this.template(this.startValue + range * progress)
+ }
+}
\ No newline at end of file
diff --git a/uni_modules/lime-shared/animation/vue.ts b/uni_modules/lime-shared/animation/vue.ts
new file mode 100644
index 0000000..30f89e5
--- /dev/null
+++ b/uni_modules/lime-shared/animation/vue.ts
@@ -0,0 +1,123 @@
+// @ts-nocheck
+const TICK = Symbol('tick');
+const TICK_HANDLER = Symbol('tick-handler');
+const ANIMATIONS = Symbol('animations');
+const START_TIMES = Symbol('start-times');
+const PAUSE_START = Symbol('pause-start');
+const PAUSE_TIME = Symbol('pause-time');
+const _raf = typeof requestAnimationFrame !== 'undefined' ? requestAnimationFrame : function(cb: Function) {return setTimeout(cb, 1000/60)}
+const _caf = typeof cancelAnimationFrame !== 'undefined' ? cancelAnimationFrame: function(id: any) {clearTimeout(id)}
+
+// const TICK = 'tick';
+// const TICK_HANDLER = 'tick-handler';
+// const ANIMATIONS = 'animations';
+// const START_TIMES = 'start-times';
+// const PAUSE_START = 'pause-start';
+// const PAUSE_TIME = 'pause-time';
+// const _raf = function(callback):number|null {return setTimeout(callback, 1000/60)}
+// const _caf = function(id: number):void {clearTimeout(id)}
+
+export class Timeline {
+ state: string
+ constructor() {
+ this.state = 'Initiated';
+ this[ANIMATIONS] = new Set();
+ this[START_TIMES] = new Map();
+ }
+ start() {
+ if (!(this.state === 'Initiated')) return;
+ this.state = 'Started';
+
+ let startTime = Date.now();
+ this[PAUSE_TIME] = 0;
+ this[TICK] = () => {
+ let now = Date.now();
+ this[ANIMATIONS].forEach((animation) => {
+ let t: number;
+ if (this[START_TIMES].get(animation) < startTime) {
+ t = now - startTime - animation.delay - this[PAUSE_TIME];
+ } else {
+ t = now - this[START_TIMES].get(animation) - animation.delay - this[PAUSE_TIME];
+ }
+
+ if (t > animation.duration) {
+ this[ANIMATIONS].delete(animation);
+ t = animation.duration;
+ }
+ if (t > 0) animation.run(t);
+ })
+ // for (let animation of this[ANIMATIONS]) {
+ // let t: number;
+ // console.log('animation', animation)
+ // if (this[START_TIMES].get(animation) < startTime) {
+ // t = now - startTime - animation.delay - this[PAUSE_TIME];
+ // } else {
+ // t = now - this[START_TIMES].get(animation) - animation.delay - this[PAUSE_TIME];
+ // }
+
+ // if (t > animation.duration) {
+ // this[ANIMATIONS].delete(animation);
+ // t = animation.duration;
+ // }
+ // if (t > 0) animation.run(t);
+ // }
+ this[TICK_HANDLER] = _raf(this[TICK]);
+ };
+ this[TICK]();
+ }
+ pause() {
+ if (!(this.state === 'Started')) return;
+ this.state = 'Paused';
+
+ this[PAUSE_START] = Date.now();
+ _caf(this[TICK_HANDLER]);
+ }
+ resume() {
+ if (!(this.state === 'Paused')) return;
+ this.state = 'Started';
+
+ this[PAUSE_TIME] += Date.now() - this[PAUSE_START];
+ this[TICK]();
+ }
+ reset() {
+ this.pause();
+ this.state = 'Initiated';
+ this[PAUSE_TIME] = 0;
+ this[PAUSE_START] = 0;
+ this[ANIMATIONS] = new Set();
+ this[START_TIMES] = new Map();
+ this[TICK_HANDLER] = null;
+ }
+ add(animation: any, startTime?: number) {
+ if (arguments.length < 2) startTime = Date.now();
+ this[ANIMATIONS].add(animation);
+ this[START_TIMES].set(animation, startTime);
+ }
+}
+
+export class Animation {
+ startValue: number
+ endValue: number
+ duration: number
+ timingFunction: (t: number) => number
+ delay: number
+ template: (t: number) => void
+ constructor(startValue: number, endValue: number, duration: number, delay: number, timingFunction: (t: number) => number, template: (v: number) => void) {
+ timingFunction = timingFunction || (v => v);
+ template = template || (v => v);
+
+ this.startValue = startValue;
+ this.endValue = endValue;
+ this.duration = duration;
+ this.timingFunction = timingFunction;
+ this.delay = delay;
+ this.template = template;
+ }
+
+ run(time: number) {
+ let range = this.endValue - this.startValue;
+ let progress = time / this.duration
+ if(progress != 1) progress = this.timingFunction(progress)
+ this.template(this.startValue + range * progress)
+ }
+}
\ No newline at end of file
diff --git a/uni_modules/lime-shared/arrayBufferToFile/index.ts b/uni_modules/lime-shared/arrayBufferToFile/index.ts
new file mode 100644
index 0000000..fd67048
--- /dev/null
+++ b/uni_modules/lime-shared/arrayBufferToFile/index.ts
@@ -0,0 +1,10 @@
+// @ts-nocheck
+// #ifndef APP-IOS || APP-ANDROID
+export * from './vue.ts'
+// #endif
+
+// #ifdef UNI-APP-X
+// #ifdef APP-IOS || APP-ANDROID
+export * from './uvue.uts'
+// #endif
+// #endif
\ No newline at end of file
diff --git a/uni_modules/lime-shared/arrayBufferToFile/uvue.uts b/uni_modules/lime-shared/arrayBufferToFile/uvue.uts
new file mode 100644
index 0000000..65c7b14
--- /dev/null
+++ b/uni_modules/lime-shared/arrayBufferToFile/uvue.uts
@@ -0,0 +1,10 @@
+// @ts-nocheck
+// import {platform} from '../platform'
+/**
+ * buffer转路径
+ * @param {Object} buffer
+ */
+// @ts-nocheck
+export function arrayBufferToFile(buffer: ArrayBuffer, name?: string, format?:string):Promise<(File|string)> {
+ console.error('[arrayBufferToFile] 当前环境不支持')
+}
\ No newline at end of file
diff --git a/uni_modules/lime-shared/arrayBufferToFile/vue.ts b/uni_modules/lime-shared/arrayBufferToFile/vue.ts
new file mode 100644
index 0000000..9760b20
--- /dev/null
+++ b/uni_modules/lime-shared/arrayBufferToFile/vue.ts
@@ -0,0 +1,63 @@
+// @ts-nocheck
+import {platform} from '../platform'
+/**
+ * buffer转路径
+ * @param {Object} buffer
+ */
+// @ts-nocheck
+export function arrayBufferToFile(buffer: ArrayBuffer | Blob, name?: string, format?:string):Promise<(File|string)> {
+ return new Promise((resolve, reject) => {
+ // #ifdef MP
+ const fs = uni.getFileSystemManager()
+ //自定义文件名
+ if (!name && !format) {
+ reject(new Error('ERROR_NAME_PARSE'))
+ }
+ const fileName = `${name || new Date().getTime()}.${format.replace(/(.+)?\//,'')}`;
+ let pre = platform()
+ const filePath = `${pre.env.USER_DATA_PATH}/${fileName}`
+ fs.writeFile({
+ filePath,
+ data: buffer,
+ success() {
+ resolve(filePath)
+ },
+ fail(err) {
+ console.error(err)
+ reject(err)
+ }
+ })
+ // #endif
+
+ // #ifdef H5
+ const file = new File([buffer], name, {
+ type: format,
+ });
+ resolve(file)
+ // #endif
+
+ // #ifdef APP-PLUS
+ const bitmap = new plus.nativeObj.Bitmap('bitmap' + Date.now())
+ const base64 = uni.arrayBufferToBase64(buffer)
+ bitmap.loadBase64Data(base64, () => {
+ if (!name && !format) {
+ reject(new Error('ERROR_NAME_PARSE'))
+ }
+ const fileNmae = `${name || new Date().getTime()}.${format.replace(/(.+)?\//,'')}`;
+ const filePath = `_doc/uniapp_temp/${fileNmae}`
+ bitmap.save(filePath, {},
+ () => {
+ bitmap.clear()
+ resolve(filePath)
+ },
+ (error) => {
+ bitmap.clear()
+ reject(error)
+ })
+ }, (error) => {
+ bitmap.clear()
+ reject(error)
+ })
+ // #endif
+ })
+}
\ No newline at end of file
diff --git a/uni_modules/lime-shared/base64ToArrayBuffer/index.ts b/uni_modules/lime-shared/base64ToArrayBuffer/index.ts
new file mode 100644
index 0000000..f83b640
--- /dev/null
+++ b/uni_modules/lime-shared/base64ToArrayBuffer/index.ts
@@ -0,0 +1,13 @@
+// @ts-nocheck
+// 未完成
+export function base64ToArrayBuffer(base64 : string) {
+ const [, format, bodyData] = /data:image\/(\w+);base64,(.*)/.exec(base64) || [];
+ if (!format) {
+ new Error('ERROR_BASE64SRC_PARSE')
+ }
+ if(uni.base64ToArrayBuffer) {
+ return uni.base64ToArrayBuffer(bodyData)
+ } else {
+
+ }
+}
\ No newline at end of file
diff --git a/uni_modules/lime-shared/base64ToPath/index.ts b/uni_modules/lime-shared/base64ToPath/index.ts
new file mode 100644
index 0000000..28a3bf5
--- /dev/null
+++ b/uni_modules/lime-shared/base64ToPath/index.ts
@@ -0,0 +1,9 @@
+// @ts-nocheck
+// #ifndef APP-IOS || APP-ANDROID
+export * from './vue.ts'
+// #endif
+
+
+// #ifdef APP-IOS || APP-ANDROID
+export * from './uvue.uts'
+// #endif
diff --git a/uni_modules/lime-shared/base64ToPath/uvue.uts b/uni_modules/lime-shared/base64ToPath/uvue.uts
new file mode 100644
index 0000000..7019ecb
--- /dev/null
+++ b/uni_modules/lime-shared/base64ToPath/uvue.uts
@@ -0,0 +1,22 @@
+// @ts-nocheck
+import { processFile, ProcessFileOptions } from '@/uni_modules/lime-file-utils'
+
+/**
+ * base64转路径
+ * @param {Object} base64
+ */
+export function base64ToPath(base64: string, filename: string | null = null):Promise {
+ return new Promise((resolve,reject) => {
+ processFile({
+ type: 'toDataURL',
+ path: base64,
+ filename,
+ success(res: string){
+ resolve(res)
+ },
+ fail(err){
+ reject(err)
+ }
+ } as ProcessFileOptions)
+ })
+}
\ No newline at end of file
diff --git a/uni_modules/lime-shared/base64ToPath/vue.ts b/uni_modules/lime-shared/base64ToPath/vue.ts
new file mode 100644
index 0000000..735000f
--- /dev/null
+++ b/uni_modules/lime-shared/base64ToPath/vue.ts
@@ -0,0 +1,75 @@
+// @ts-nocheck
+import {platform} from '../platform'
+/**
+ * base64转路径
+ * @param {Object} base64
+ */
+export function base64ToPath(base64: string, filename?: string):Promise {
+ const [, format] = /^data:image\/(\w+);base64,/.exec(base64) || [];
+ return new Promise((resolve, reject) => {
+ // #ifdef MP
+ const fs = uni.getFileSystemManager()
+ //自定义文件名
+ if (!filename && !format) {
+ reject(new Error('ERROR_BASE64SRC_PARSE'))
+ }
+ // const time = new Date().getTime();
+ const name = filename || `${new Date().getTime()}.${format}`;
+ let pre = platform()
+ const filePath = `${pre.env.USER_DATA_PATH}/${name}`
+ fs.writeFile({
+ filePath,
+ data: base64.split(',')[1],
+ encoding: 'base64',
+ success() {
+ resolve(filePath)
+ },
+ fail(err) {
+ console.error(err)
+ reject(err)
+ }
+ })
+ // #endif
+
+ // #ifdef H5
+ // mime类型
+ let mimeString = base64.split(',')[0].split(':')[1].split(';')[0];
+ //base64 解码
+ let byteString = atob(base64.split(',')[1]);
+ //创建缓冲数组
+ let arrayBuffer = new ArrayBuffer(byteString.length);
+ //创建视图
+ let intArray = new Uint8Array(arrayBuffer);
+ for (let i = 0; i < byteString.length; i++) {
+ intArray[i] = byteString.charCodeAt(i);
+ }
+ resolve(URL.createObjectURL(new Blob([intArray], {
+ type: mimeString
+ })))
+ // #endif
+
+ // #ifdef APP-PLUS
+ const bitmap = new plus.nativeObj.Bitmap('bitmap' + Date.now())
+ bitmap.loadBase64Data(base64, () => {
+ if (!filename && !format) {
+ reject(new Error('ERROR_BASE64SRC_PARSE'))
+ }
+ // const time = new Date().getTime();
+ const name = filename || `${new Date().getTime()}.${format}`;
+ const filePath = `_doc/uniapp_temp/${name}`
+ bitmap.save(filePath, {},
+ () => {
+ bitmap.clear()
+ resolve(filePath)
+ },
+ (error) => {
+ bitmap.clear()
+ reject(error)
+ })
+ }, (error) => {
+ bitmap.clear()
+ reject(error)
+ })
+ // #endif
+ })
+}
\ No newline at end of file
diff --git a/uni_modules/lime-shared/camelCase/index.ts b/uni_modules/lime-shared/camelCase/index.ts
new file mode 100644
index 0000000..dd470ab
--- /dev/null
+++ b/uni_modules/lime-shared/camelCase/index.ts
@@ -0,0 +1,21 @@
+/**
+ * 将字符串转换为 camelCase 或 PascalCase 风格的命名约定
+ * @param str 要转换的字符串
+ * @param isPascalCase 指示是否转换为 PascalCase 的布尔值,默认为 false
+ * @returns 转换后的字符串
+ */
+export function camelCase(str: string, isPascalCase: boolean = false): string {
+ // 将字符串分割成单词数组
+ let words: string[] = str.split(/[\s_-]+/);
+
+ // 将数组中的每个单词首字母大写(除了第一个单词)
+ let camelCased: string[] = words.map((word, index):string => {
+ if (index == 0 && !isPascalCase) {
+ return word.toLowerCase(); // 第一个单词全小写
+ }
+ return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase();
+ });
+
+ // 将数组中的单词拼接成一个字符串
+ return camelCased.join('');
+};
\ No newline at end of file
diff --git a/uni_modules/lime-shared/canIUseCanvas2d/index.ts b/uni_modules/lime-shared/canIUseCanvas2d/index.ts
new file mode 100644
index 0000000..95211d1
--- /dev/null
+++ b/uni_modules/lime-shared/canIUseCanvas2d/index.ts
@@ -0,0 +1,67 @@
+// @ts-nocheck
+
+// #ifndef APP-IOS || APP-ANDROID
+
+// #ifdef MP-ALIPAY
+interface My {
+ SDKVersion: string
+}
+declare var my: My
+// #endif
+
+function compareVersion(v1:string, v2:string) {
+ let a1 = v1.split('.');
+ let a2 = v2.split('.');
+ const len = Math.max(a1.length, a2.length);
+
+ while (a1.length < len) {
+ a1.push('0');
+ }
+ while (a2.length < len) {
+ a2.push('0');
+ }
+
+ for (let i = 0; i < len; i++) {
+ const num1 = parseInt(a1[i], 10);
+ const num2 = parseInt(a2[i], 10);
+
+ if (num1 > num2) {
+ return 1;
+ }
+ if (num1 < num2) {
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+function gte(version: string) {
+ let {SDKVersion} = uni.getSystemInfoSync();
+ // #ifdef MP-ALIPAY
+ SDKVersion = my.SDKVersion
+ // #endif
+ return compareVersion(SDKVersion, version) >= 0;
+}
+// #endif
+
+
+/** 环境是否支持canvas 2d */
+export function canIUseCanvas2d(): boolean {
+ // #ifdef MP-WEIXIN
+ return gte('2.9.0');
+ // #endif
+ // #ifdef MP-ALIPAY
+ return gte('2.7.0');
+ // #endif
+ // #ifdef MP-TOUTIAO
+ return gte('1.78.0');
+ // #endif
+ // #ifndef MP-WEIXIN || MP-ALIPAY || MP-TOUTIAO
+ return false
+ // #endif
+
+ // #ifdef APP-IOS || APP-ANDROID || APP-NVUE || APP-VUE
+ return false;
+ // #endif
+}
diff --git a/uni_modules/lime-shared/changelog.md b/uni_modules/lime-shared/changelog.md
new file mode 100644
index 0000000..9b9b7dc
--- /dev/null
+++ b/uni_modules/lime-shared/changelog.md
@@ -0,0 +1,36 @@
+## 0.1.6(2024-07-24)
+- fix: vue2 app ts需要明确的后缀,所有补全
+- chore: 减少依赖
+## 0.1.5(2024-07-21)
+- feat: 删除 Hooks
+- feat: 兼容uniappx
+## 0.1.4(2023-09-05)
+- feat: 增加 Hooks `useIntersectionObserver`
+- feat: 增加 `floatAdd`
+- feat: 因为本人插件兼容 vue2 需要使用 `composition-api`,故增加vue文件代码插件的条件编译
+## 0.1.3(2023-08-13)
+- feat: 增加 `camelCase`
+## 0.1.2(2023-07-17)
+- feat: 增加 `getClassStr`
+## 0.1.1(2023-07-06)
+- feat: 增加 `isNumeric`, 区别于 `isNumber`
+## 0.1.0(2023-06-30)
+- fix: `clamp`忘记导出了
+## 0.0.9(2023-06-27)
+- feat: 增加`arrayBufferToFile`
+## 0.0.8(2023-06-19)
+- feat: 增加`createAnimation`、`clamp`
+## 0.0.7(2023-06-08)
+- chore: 更新注释
+## 0.0.6(2023-06-08)
+- chore: 增加`createImage`为`lime-watermark`和`lime-qrcode`提供依赖
+## 0.0.5(2023-06-03)
+- chore: 更新注释
+## 0.0.4(2023-05-22)
+- feat: 增加`range`,`exif`,`selectComponent`
+## 0.0.3(2023-05-08)
+- feat: 增加`fillZero`,`debounce`,`throttle`,`random`
+## 0.0.2(2023-05-05)
+- chore: 更新文档
+## 0.0.1(2023-05-05)
+- 无
diff --git a/uni_modules/lime-shared/clamp/index.ts b/uni_modules/lime-shared/clamp/index.ts
new file mode 100644
index 0000000..0e16358
--- /dev/null
+++ b/uni_modules/lime-shared/clamp/index.ts
@@ -0,0 +1,16 @@
+// @ts-nocheck
+/**
+ * 将一个值限制在指定的范围内
+ * @param val 要限制的值
+ * @param min 最小值
+ * @param max 最大值
+ * @returns 限制后的值
+ */
+export function clamp(val: number, min: number, max: number): number {
+ return Math.max(min, Math.min(max, val));
+}
+
+
+// console.log(clamp(5 ,0, 10)); // 输出: 5(在范围内,不做更改)
+// console.log(clamp(-5 ,0, 10)); // 输出: 0(小于最小值,被限制为最小值)
+// console.log(clamp(15 ,0, 10)); // 输出: 10(大于最大值,被限制为最大值)
\ No newline at end of file
diff --git a/uni_modules/lime-shared/cloneDeep/index.ts b/uni_modules/lime-shared/cloneDeep/index.ts
new file mode 100644
index 0000000..03f85cd
--- /dev/null
+++ b/uni_modules/lime-shared/cloneDeep/index.ts
@@ -0,0 +1,10 @@
+// @ts-nocheck
+
+// #ifdef APP-IOS || APP-ANDROID
+export * from './uvue.ts'
+// #endif
+
+
+// #ifndef APP-IOS || APP-ANDROID
+export * from './vue.ts'
+// #endif
\ No newline at end of file
diff --git a/uni_modules/lime-shared/cloneDeep/uvue.ts b/uni_modules/lime-shared/cloneDeep/uvue.ts
new file mode 100644
index 0000000..7929bc2
--- /dev/null
+++ b/uni_modules/lime-shared/cloneDeep/uvue.ts
@@ -0,0 +1,17 @@
+// @ts-nocheck
+/**
+ * 深度克隆一个对象或数组
+ * @param obj 要克隆的对象或数组
+ * @returns 克隆后的对象或数组
+ */
+export function cloneDeep(obj: any): T {
+ // 如果传入的对象是基本数据类型(如字符串、数字等),则直接返回
+ // if(['number', 'string'].includes(typeof obj) || Array.isArray(obj)){
+ // return obj as T
+ // }
+ if(typeof obj == 'object'){
+ return JSON.parse(JSON.stringify(obj as T)) as T;
+ }
+ return obj as T
+}
+
diff --git a/uni_modules/lime-shared/cloneDeep/vue.ts b/uni_modules/lime-shared/cloneDeep/vue.ts
new file mode 100644
index 0000000..ded334d
--- /dev/null
+++ b/uni_modules/lime-shared/cloneDeep/vue.ts
@@ -0,0 +1,103 @@
+// @ts-nocheck
+/**
+ * 深度克隆一个对象或数组
+ * @param obj 要克隆的对象或数组
+ * @returns 克隆后的对象或数组
+ */
+export function cloneDeep(obj: any): T {
+ // 如果传入的对象为空,返回空
+ if (obj === null) {
+ return null as unknown as T;
+ }
+
+ // 如果传入的对象是 Set 类型,则将其转换为数组,并通过新的 Set 构造函数创建一个新的 Set 对象
+ if (obj instanceof Set) {
+ return new Set([...obj]) as unknown as T;
+ }
+
+ // 如果传入的对象是 Map 类型,则将其转换为数组,并通过新的 Map 构造函数创建一个新的 Map 对象
+ if (obj instanceof Map) {
+ return new Map([...obj]) as unknown as T;
+ }
+
+ // 如果传入的对象是 WeakMap 类型,则直接用传入的 WeakMap 对象进行赋值
+ if (obj instanceof WeakMap) {
+ let weakMap = new WeakMap();
+ weakMap = obj;
+ return weakMap as unknown as T;
+ }
+
+ // 如果传入的对象是 WeakSet 类型,则直接用传入的 WeakSet 对象进行赋值
+ if (obj instanceof WeakSet) {
+ let weakSet = new WeakSet();
+ weakSet = obj;
+ return weakSet as unknown as T;
+ }
+
+ // 如果传入的对象是 RegExp 类型,则通过新的 RegExp 构造函数创建一个新的 RegExp 对象
+ if (obj instanceof RegExp) {
+ return new RegExp(obj) as unknown as T;
+ }
+
+ // 如果传入的对象是 undefined 类型,则返回 undefined
+ if (typeof obj === 'undefined') {
+ return undefined as unknown as T;
+ }
+
+ // 如果传入的对象是数组,则递归调用 cloneDeep 函数对数组中的每个元素进行克隆
+ if (Array.isArray(obj)) {
+ return obj.map(cloneDeep) as unknown as T;
+ }
+
+ // 如果传入的对象是 Date 类型,则通过新的 Date 构造函数创建一个新的 Date 对象
+ if (obj instanceof Date) {
+ return new Date(obj.getTime()) as unknown as T;
+ }
+
+ // 如果传入的对象是普通对象,则使用递归调用 cloneDeep 函数对对象的每个属性进行克隆
+ if (typeof obj === 'object') {
+ const newObj: any = {};
+ for (const [key, value] of Object.entries(obj)) {
+ newObj[key] = cloneDeep(value);
+ }
+ const symbolKeys = Object.getOwnPropertySymbols(obj);
+ for (const key of symbolKeys) {
+ newObj[key] = cloneDeep(obj[key]);
+ }
+ return newObj;
+ }
+
+ // 如果传入的对象是基本数据类型(如字符串、数字等),则直接返回
+ return obj;
+}
+
+// 示例使用
+
+// // 克隆一个对象
+// const obj = { name: 'John', age: 30 };
+// const clonedObj = cloneDeep(obj);
+
+// console.log(clonedObj); // 输出: { name: 'John', age: 30 }
+// console.log(clonedObj === obj); // 输出: false (副本与原对象是独立的)
+
+// // 克隆一个数组
+// const arr = [1, 2, 3];
+// const clonedArr = cloneDeep(arr);
+
+// console.log(clonedArr); // 输出: [1, 2, 3]
+// console.log(clonedArr === arr); // 输出: false (副本与原数组是独立的)
+
+// // 克隆一个包含嵌套对象的对象
+// const person = {
+// name: 'Alice',
+// age: 25,
+// address: {
+// city: 'New York',
+// country: 'USA',
+// },
+// };
+// const clonedPerson = cloneDeep(person);
+
+// console.log(clonedPerson); // 输出: { name: 'Alice', age: 25, address: { city: 'New York', country: 'USA' } }
+// console.log(clonedPerson === person); // 输出: false (副本与原对象是独立的)
+// console.log(clonedPerson.address === person.address); // 输出: false (嵌套对象的副本也是独立的)
\ No newline at end of file
diff --git a/uni_modules/lime-shared/closest/index.ts b/uni_modules/lime-shared/closest/index.ts
new file mode 100644
index 0000000..e6e79c2
--- /dev/null
+++ b/uni_modules/lime-shared/closest/index.ts
@@ -0,0 +1,22 @@
+// @ts-nocheck
+
+/**
+ * 在给定数组中找到最接近目标数字的元素。
+ * @param arr 要搜索的数字数组。
+ * @param target 目标数字。
+ * @returns 最接近目标数字的数组元素。
+ */
+export function closest(arr: number[], target: number):number {
+ return arr.reduce((pre: number, cur: number):number =>
+ Math.abs(pre - target) < Math.abs(cur - target) ? pre : cur
+ );
+}
+
+// 示例
+// // 定义一个数字数组
+// const numbers = [1, 3, 5, 7, 9];
+
+// // 在数组中找到最接近目标数字 6 的元素
+// const closestNumber = closest(numbers, 6);
+
+// console.log(closestNumber); // 输出结果: 5
\ No newline at end of file
diff --git a/uni_modules/lime-shared/components/lime-shared/lime-shared.vue b/uni_modules/lime-shared/components/lime-shared/lime-shared.vue
new file mode 100644
index 0000000..284e8f0
--- /dev/null
+++ b/uni_modules/lime-shared/components/lime-shared/lime-shared.vue
@@ -0,0 +1,139 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/uni_modules/lime-shared/createAnimation/index.ts b/uni_modules/lime-shared/createAnimation/index.ts
new file mode 100644
index 0000000..999ec1c
--- /dev/null
+++ b/uni_modules/lime-shared/createAnimation/index.ts
@@ -0,0 +1,9 @@
+// @ts-nocheck
+// #ifndef UNI-APP-X
+export * from './type.ts'
+export * from './vue.ts'
+// #endif
+
+// #ifdef UNI-APP-X
+export * from './uvue.ts'
+// #endif
\ 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.ts b/uni_modules/lime-shared/createAnimation/uvue.ts
new file mode 100644
index 0000000..96d8263
--- /dev/null
+++ b/uni_modules/lime-shared/createAnimation/uvue.ts
@@ -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..d6223d8
--- /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 { 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/createImage/index.ts b/uni_modules/lime-shared/createImage/index.ts
new file mode 100644
index 0000000..0cf7dc0
--- /dev/null
+++ b/uni_modules/lime-shared/createImage/index.ts
@@ -0,0 +1,70 @@
+// @ts-nocheck
+// #ifndef APP-IOS || APP-ANDROID
+import {isBrowser} from '../isBrowser'
+class Image {
+ currentSrc: string | null = null
+ naturalHeight: number = 0
+ naturalWidth: number = 0
+ width: number = 0
+ height: number = 0
+ tagName: string = 'IMG'
+ path: string = ''
+ crossOrigin: string = ''
+ referrerPolicy: string = ''
+ onload: () => void = () => {}
+ onerror: () => void = () => {}
+ complete: boolean = false
+ constructor() {}
+ set src(src: string) {
+ console.log('src', src)
+ if(!src) {
+ return this.onerror()
+ }
+ src = src.replace(/^@\//,'/')
+ this.currentSrc = src
+ uni.getImageInfo({
+ src,
+ success: (res) => {
+ const localReg = /^\.|^\/(?=[^\/])/;
+ // #ifdef MP-WEIXIN || MP-BAIDU || MP-QQ || MP-TOUTIAO
+ res.path = localReg.test(src) ? `/${res.path}` : res.path;
+ // #endif
+ this.complete = true
+ this.path = res.path
+ this.naturalWidth = this.width = res.width
+ this.naturalHeight = this.height = res.height
+ this.onload()
+ },
+ fail: () => {
+ this.onerror()
+ }
+ })
+ }
+ get src() {
+ return this.currentSrc
+ }
+}
+interface UniImage extends WechatMiniprogram.Image {
+ complete?: boolean
+ naturalHeight?: number
+ naturalWidth?: number
+}
+/** 创建用于 canvas 的 img */
+export function createImage(canvas?: any): HTMLImageElement | UniImage {
+ if(canvas && canvas.createImage) {
+ return (canvas as WechatMiniprogram.Canvas).createImage()
+ } else if(this && this['tagName'] == 'canvas' && !('toBlob' in this) || canvas && !('toBlob' in canvas)){
+ return new Image()
+ } else if(isBrowser) {
+ return new window.Image()
+ }
+ return new Image()
+}
+// #endif
+
+
+// #ifdef APP-IOS || APP-ANDROID
+export function createImage(){
+ console.error('当前环境不支持')
+}
+// #endif
diff --git a/uni_modules/lime-shared/debounce/index.ts b/uni_modules/lime-shared/debounce/index.ts
new file mode 100644
index 0000000..d51b8d0
--- /dev/null
+++ b/uni_modules/lime-shared/debounce/index.ts
@@ -0,0 +1,10 @@
+// @ts-nocheck
+
+// #ifdef APP-IOS || APP-ANDROID
+export * from './uvue.ts'
+
+// #endif
+
+// #ifndef APP-IOS || APP-ANDROID
+export * from './vue.ts'
+// #endif
\ No newline at end of file
diff --git a/uni_modules/lime-shared/debounce/uvue.ts b/uni_modules/lime-shared/debounce/uvue.ts
new file mode 100644
index 0000000..f1fc29d
--- /dev/null
+++ b/uni_modules/lime-shared/debounce/uvue.ts
@@ -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/exif/index.ts b/uni_modules/lime-shared/exif/index.ts
new file mode 100644
index 0000000..31d4658
--- /dev/null
+++ b/uni_modules/lime-shared/exif/index.ts
@@ -0,0 +1,9 @@
+// @ts-nocheck
+// #ifndef APP-IOS || APP-ANDROID
+export * from './vue.ts'
+// #endif
+
+
+// #ifdef APP-IOS || APP-ANDROID
+export * from './uvue.ts'
+// #endif
diff --git a/uni_modules/lime-shared/exif/uvue.ts b/uni_modules/lime-shared/exif/uvue.ts
new file mode 100644
index 0000000..01d21a2
--- /dev/null
+++ b/uni_modules/lime-shared/exif/uvue.ts
@@ -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..86f2454
--- /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 { isBase64DataUri } 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 (isBase64(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/fillZero/index.ts b/uni_modules/lime-shared/fillZero/index.ts
new file mode 100644
index 0000000..9952c45
--- /dev/null
+++ b/uni_modules/lime-shared/fillZero/index.ts
@@ -0,0 +1,11 @@
+// @ts-nocheck
+/**
+ * 在数字前填充零,返回字符串形式的结果
+ * @param number 要填充零的数字
+ * @param length 填充零后的字符串长度,默认为2
+ * @returns 填充零后的字符串
+ */
+export function fillZero(number: number, length: number = 2): string {
+ // 将数字转换为字符串,然后使用 padStart 方法填充零到指定长度
+ return `${number}`.padStart(length, '0');
+}
\ No newline at end of file
diff --git a/uni_modules/lime-shared/floatAdd/index.ts b/uni_modules/lime-shared/floatAdd/index.ts
new file mode 100644
index 0000000..aba4774
--- /dev/null
+++ b/uni_modules/lime-shared/floatAdd/index.ts
@@ -0,0 +1,36 @@
+import {isNumber} from '../isNumber'
+/**
+ * 返回两个浮点数相加的结果
+ * @param num1 第一个浮点数
+ * @param num2 第二个浮点数
+ * @returns 两个浮点数的相加结果
+ */
+export function floatAdd(num1: number, num2: number): number {
+ // 检查 num1 和 num2 是否为数字类型
+ if (!(isNumber(num1) || isNumber(num2))) {
+ console.warn('Please pass in the number type');
+ return NaN;
+ }
+
+ let r1: number, r2: number, m: number;
+
+ try {
+ // 获取 num1 小数点后的位数
+ r1 = num1.toString().split('.')[1].length;
+ } catch (error) {
+ r1 = 0;
+ }
+
+ try {
+ // 获取 num2 小数点后的位数
+ r2 = num2.toString().split('.')[1].length;
+ } catch (error) {
+ r2 = 0;
+ }
+
+ // 计算需要扩大的倍数
+ m = Math.pow(10, Math.max(r1, r2));
+
+ // 返回相加结果
+ return (num1 * m + num2 * m) / m;
+}
diff --git a/uni_modules/lime-shared/getClassStr/index.ts b/uni_modules/lime-shared/getClassStr/index.ts
new file mode 100644
index 0000000..32a0a74
--- /dev/null
+++ b/uni_modules/lime-shared/getClassStr/index.ts
@@ -0,0 +1,53 @@
+// @ts-nocheck
+
+// #ifdef APP-IOS || APP-ANDROID
+import { isNumber } from '../isNumber'
+import { isString } from '../isString'
+import { isDef } from '../isDef'
+// #endif
+
+/**
+ * 获取对象的类名字符串
+ * @param obj - 需要处理的对象
+ * @returns 由对象属性作为类名组成的字符串
+ */
+export function getClassStr(obj : T) : string {
+ let classNames : string[] = [];
+ // #ifdef APP-IOS || APP-ANDROID
+ if (obj instanceof UTSJSONObject) {
+ (obj as UTSJSONObject).toMap().forEach((value, key) => {
+ if (isDef(value)) {
+ if (isNumber(value)) {
+ classNames.push(key);
+ }
+ if (isString(value) && value !== '') {
+ classNames.push(key);
+ }
+ if (typeof value == 'boolean' && (value as boolean)) {
+ classNames.push(key);
+ }
+ }
+ })
+ }
+ // #endif
+ // #ifndef APP-IOS || APP-ANDROID
+ // 遍历对象的属性
+ for (let key in obj) {
+ // 检查属性确实属于对象自身且其值为true
+ if ((obj as any).hasOwnProperty(key) && obj[key]) {
+ // 将属性名添加到类名数组中
+ classNames.push(key);
+ }
+ }
+ // #endif
+
+
+ // 将类名数组用空格连接成字符串并返回
+ return classNames.join(' ');
+}
+
+
+// 示例
+// const obj = { foo: true, bar: false, baz: true };
+// const classNameStr = getClassStr(obj);
+// console.log(classNameStr); // 输出: "foo baz"
\ No newline at end of file
diff --git a/uni_modules/lime-shared/getCurrentPage/index.ts b/uni_modules/lime-shared/getCurrentPage/index.ts
new file mode 100644
index 0000000..28a3bf5
--- /dev/null
+++ b/uni_modules/lime-shared/getCurrentPage/index.ts
@@ -0,0 +1,9 @@
+// @ts-nocheck
+// #ifndef APP-IOS || APP-ANDROID
+export * from './vue.ts'
+// #endif
+
+
+// #ifdef APP-IOS || APP-ANDROID
+export * from './uvue.uts'
+// #endif
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/getLocalFilePath/index.ts b/uni_modules/lime-shared/getLocalFilePath/index.ts
new file mode 100644
index 0000000..42eb80c
--- /dev/null
+++ b/uni_modules/lime-shared/getLocalFilePath/index.ts
@@ -0,0 +1,62 @@
+// @ts-nocheck
+// #ifdef APP-NVUE || APP-VUE
+export const getLocalFilePath = (path : string) => {
+ if (typeof plus == 'undefined') return path
+ if (/^(_www|_doc|_documents|_downloads|file:\/\/|\/storage\/emulated\/0\/)/.test(path)) return path
+ if (/^\//.test(path)) {
+ const localFilePath = plus.io.convertAbsoluteFileSystem(path)
+ if (localFilePath !== path) {
+ return localFilePath
+ } else {
+ path = path.slice(1)
+ }
+ }
+ return '_www/' + path
+}
+// #endif
+
+
+// #ifdef APP-ANDROID || APP-IOS
+export { getResourcePath as getLocalFilePath } from '@/uni_modules/lime-file-utils'
+// export const getLocalFilePath = (path : string) : string => {
+// let uri = path
+// if (uri.startsWith("http") || uri.startsWith("