diff --git a/App.vue b/App.vue index f4d9214..b00c955 100644 --- a/App.vue +++ b/App.vue @@ -2,17 +2,21 @@ const WXAUTH = require('@/common/wxauth.js') const TTAUTH = require('@/common/ttauth.js') const QQAUTH = require('@/common/qqauth.js') + const ALIAUTH = require('@/common/aliauth.js') export default { globalData: { - h5Domain: 'https://flpt.jxsupplier.com', + h5Domain: 'http://tstz.s2m.cc', + wxh5autologin: false, // 微信内浏览器打开是否启用微信自动登录 goLogin: false, - subDomain: 'tz', // jdjf0115 - merchantId: 951, // 42151 - version: '2.0.0', + subDomain: 'tz', + merchantId: 951, + version: '25.4.25', sysconfigkeys: 'mallName,shopMod,share_profile,recharge_amount_min,open_growth,shopping_cart_vop_open,needIdCheck', wxpayOpenAppId: 'wx9b04553fd8c7b9c3', // 微信开放平台的移动端应用appID openAlipayProvider: false, // 是否开通支付宝支付 addressLevel: 3, // 省市区到3级还是4级,可选 3 或者 4 + curLong: undefined, // 当前用户经纬度 + curLat: undefined, // 当前用户经纬度 }, onLaunch: function() { // https://www.yuque.com/apifm/nu0f75/cdqz1n @@ -66,6 +70,12 @@ sysconfigMap[config.key] = config.value }) this.$u.vuex('sysconfigMap', sysconfigMap) + uni.$emit('sysconfigOK', sysconfigMap) + /** + * uni.$on('sysconfigOK',data => { + console.log('监听到事件来自 update ,携带参数 msg 为:' + data.msg); + }) + */ } }, checkForUpdate() { @@ -98,6 +108,16 @@ }, async autoLogin(force) { // 自动登陆 + // #ifdef MP-ALIPAY + const isLogined = await ALIAUTH.checkHasLogined() + if (!isLogined) { + await ALIAUTH.authorize() + // await ALIAUTH.bindSeller() + } + setTimeout(() => { + uni.$emit('loginOK', {}) + }, 500) + // #endif // #ifdef MP-WEIXIN const isLogined = await WXAUTH.checkHasLogined() if (!isLogined) { @@ -136,7 +156,7 @@ if (!isLogined) { // 判断是普通浏览器还是微信浏览器 const ua = window.navigator.userAgent.toLowerCase(); - if (ua.match(/MicroMessenger/i) == 'micromessenger') { + if (ua.match(/MicroMessenger/i) == 'micromessenger' && this.globalData.wxh5autologin) { // 微信内置浏览器打开的 // https://www.yuque.com/apifm/nu0f75/fpvc3m const res = await this.$wxapi.siteStatistics() @@ -185,7 +205,27 @@ uni.$emit('loginOK', {}) }, 500) } - } + }, + async getLocation() { + // 统一获取经纬度 + if (this.globalData.curLong && this.globalData.curLat) { + return { + long: this.globalData.curLong, + lat: this.globalData.curLat + } + } + const res = await this.$wxapi.getLocation() + if (res) { + this.globalData.curLong = res.long + this.globalData.curLat = res.lat + return res + } else { + return { + long: 0, + lat: 0 + } + } + }, } } diff --git a/README.md b/README.md index fb642c9..80da029 100644 --- a/README.md +++ b/README.md @@ -6,26 +6,43 @@ 我希望可以持续的将本项目一直开源维护下去,但是毕竟个人精力有限,所以非常希望大家可以一同加入到开发工作中来,群策群力,一起把这个项目真正的长期做下去 - - -# 天使童装小程序原生项目 - -想了解微信小程序原生项目的可以点击下面的链接: - -- [ GitHub地址 https://github.com/EastWorld/wechat-app-mall ](https://github.com/EastWorld/wechat-app-mall) -- [ 码云地址 https://gitee.com/javazj/wechat-app-mall ](https://gitee.com/javazj/wechat-app-mall) + + +## 近期更新 + +| 抽奖功能 +| :------: | +| | + + +## 其他优秀开源模板推荐 +- [天使童装](https://github.com/EastWorld/wechat-app-mall) / [码云镜像](https://gitee.com/javazj/wechat-app-mall) / [GitCode镜像](https://gitcode.com/gooking2/wechat-app-mall) +- [天使童装(uni-app版本)](https://github.com/gooking/uni-app-mall) / [码云镜像](https://gitee.com/javazj/uni-app-mall) / [GitCode镜像](https://gitcode.com/gooking2/uni-app-mall) +- [简约精品商城(uni-app版本)](https://github.com/gooking/uni-app--mini-mall) / [码云镜像](https://gitee.com/javazj/uni-app--mini-mall) / [GitCode镜像](https://gitcode.com/gooking2/uni-app--mini-mall) +- [舔果果小铺(升级版)](https://github.com/gooking/TianguoguoXiaopu) +- [面馆风格小程序](https://gitee.com/javazj/noodle_shop_procedures) +- [AI名片](https://github.com/gooking/visitingCard) / [码云镜像](https://gitee.com/javazj/visitingCard) / [GitCode镜像](https://gitcode.com/gooking2/visitingCard) +- [仿海底捞订座排队 (uni-app)](https://github.com/gooking/dingzuopaidui) / [码云镜像](https://gitee.com/javazj/dingzuopaidui) / [GitCode镜像](https://gitcode.com/gooking2/dingzuopaidui) +- [H5版本商城/餐饮](https://github.com/gooking/vueMinishop) / [码云镜像](https://gitee.com/javazj/vueMinishop) / [GitCode镜像](https://gitcode.com/gooking2/vueMinishop) +- [餐饮点餐](https://github.com/woniudiancang/bee) / [码云镜像](https://gitee.com/woniudiancang/bee) / [GitCode镜像](https://gitcode.com/gooking2/bee) +- [企业微展](https://github.com/gooking/qiyeweizan) / [码云镜像](https://gitee.com/javazj/qiyeweizan) / [GitCode镜像](https://gitcode.com/gooking2/qiyeweizan) +- [无人棋牌室](https://github.com/gooking/wurenqipai) / [码云镜像](https://gitee.com/javazj/wurenqipai) / [GitCode镜像](https://gitcode.com/gooking2/wurenqipai) +- [酒店客房服务小程序](https://github.com/gooking/hotelRoomService) / [码云镜像](https://gitee.com/javazj/hotelRoomService) / [GitCode镜像](https://gitcode.com/gooking2/hotelRoomService) +- [面包店风格小程序](https://github.com/gooking/bread) / [码云镜像](https://gitee.com/javazj/bread) / [GitCode镜像](https://gitcode.com/gooking2/bread) +- [朋友圈发圈素材小程序](https://github.com/gooking/moments) / [码云镜像](https://gitee.com/javazj/moments) / [GitCode镜像](https://gitcode.com/gooking2/moments) +- [小红书企业微展](https://github.com/gooking/xhs-qiyeweizan) / [码云镜像](https://gitee.com/javazj/xhs-qiyeweizan) / [GitCode镜像](https://gitcode.com/gooking2/xhs-qiyeweizan) ## 扫码体验 如果觉得本项目对你有帮助,欢迎打赏~ -| 微信小程序 | h5 -| :------: | :------: | -| | | +| 微信小程序 | h5 | 抽奖演示 +| :------: | :------: | :------: | +| | | | | Android | ios | :------: | :------: | -| | | +| | | # 特别感谢 @@ -42,7 +59,7 @@ # QQ交流群 - + # 诚邀请一起来开发 @@ -51,10 +68,3 @@ 3. git clone 你自己的git仓库地址,完成开发及测试 4. 通过 “Pull Request” 将你修改的代码提交合并请求 -## 打赏 - -如果觉得本项目对你有帮助,欢迎打赏~ - -| 微信 | 支付宝 | -| :------: | :------: | -| | | diff --git a/common/aliauth.js b/common/aliauth.js new file mode 100644 index 0000000..25a9c01 --- /dev/null +++ b/common/aliauth.js @@ -0,0 +1,224 @@ +const WXAPI = require('apifm-uniapp') +import store from '@/store'; + +async function bindSeller() { + const token = store.state.token + const referrer = store.state.referrer + if (!token) { + return + } + if (!referrer) { + return + } + const res = await WXAPI.bindSeller({ + token, + uid: referrer + }) +} + +// 检测登录状态,返回 true / false +async function checkHasLogined() { + const token = store.state.token + if (!token) { + return false + } + const checkTokenRes = await WXAPI.checkToken(token) + if (checkTokenRes.code != 0) { + store.commit('$uStore', { + name: 'token', + value: '' + }) + return false + } + return true +} + +async function wxaCode() { + return new Promise((resolve, reject) => { + wx.login({ + success(res) { + return resolve(res.code) + }, + fail() { + wx.showToast({ + title: '获取code失败', + icon: 'none' + }) + return resolve('获取code失败') + } + }) + }) +} + +async function login(page) { + const _this = this + wx.login({ + success: function(res) { + const extConfigSync = wx.getExtConfigSync() + if (extConfigSync.subDomain) { + WXAPI.wxappServiceLogin({ + code: res.code + }).then(function(res) { + if (res.code == 10000) { + // 去注册 + return; + } + if (res.code != 0) { + // 登录错误 + wx.showModal({ + title: '无法登录', + content: res.msg, + showCancel: false + }) + return; + } + store.commit('$uStore', { + name: 'token', + value: res.data.token + }) + store.commit('$uStore', { + name: 'uid', + value: res.data.uid + }) + _this.bindSeller() + if (page) { + page.onShow() + } + }) + } else { + WXAPI.login_wx(res.code).then(function(res) { + if (res.code == 10000) { + // 去注册 + return; + } + if (res.code != 0) { + // 登录错误 + wx.showModal({ + title: '无法登录', + content: res.msg, + showCancel: false + }) + return; + } + store.commit('$uStore', { + name: 'token', + value: res.data.token + }) + store.commit('$uStore', { + name: 'uid', + value: res.data.uid + }) + _this.bindSeller() + if (page) { + page.onShow() + } + }) + } + } + }) +} + +async function authorize() { + return new Promise((resolve, reject) => { + my.getAuthCode({ + scopes: 'auth_base', + success: (res) => { + const code = res.authCode + let referrer = '' // 推荐人 + let referrer_storge = store.state.referrer + if (referrer_storge) { + referrer = referrer_storge; + } + WXAPI.aliappUserAuthorize({ + code: code, + referrer: referrer + }).then(function(res) { + if (res.code == 0) { + store.commit('$uStore', { + name: 'token', + value: res.data.token + }) + store.commit('$uStore', { + name: 'uid', + value: res.data.uid + }) + resolve(res) + } else { + wx.showToast({ + title: res.msg, + icon: 'none' + }) + reject(res.msg) + } + }) + }, + fail: (err) => { + my.showToast({ + content: err.errorMessage + }) + reject(err) + } + }) + }) +} + +function loginOut() { + store.commit('$uStore', { + name: 'token', + value: '' + }) + store.commit('$uStore', { + name: 'uid', + value: '' + }) +} + +async function checkAndAuthorize(scope) { + return new Promise((resolve, reject) => { + uni.getSetting({ + success(res) { + if (!res.authSetting[scope]) { + uni.authorize({ + scope: scope, + success() { + resolve() // 无返回参数 + }, + fail(e) { + console.error(e) + uni.showModal({ + title: '无权操作', + content: '需要获得您的授权', + showCancel: false, + confirmText: '立即授权', + confirmColor: '#e64340', + success(res) { + uni.openSetting(); + }, + fail(e) { + console.error(e) + reject(e) + }, + }) + } + }) + } else { + resolve() // 无返回参数 + } + }, + fail(e) { + console.error(e) + reject(e) + } + }) + }) +} + +module.exports = { + checkHasLogined: checkHasLogined, + wxaCode: wxaCode, + login: login, + loginOut: loginOut, + checkAndAuthorize: checkAndAuthorize, + authorize: authorize, + bindSeller: bindSeller +} diff --git a/components/goods-pop/goods-pop.vue b/components/goods-pop/goods-pop.vue index 57f215f..e674ede 100644 --- a/components/goods-pop/goods-pop.vue +++ b/components/goods-pop/goods-pop.vue @@ -117,6 +117,7 @@ goodsAddition: undefined, faved: false, properties: undefined, + lock: false } }, watch: { @@ -136,7 +137,7 @@ }, methods: { _initData() { - if (!this.goodsDetail) { + if (this.lock || !this.goodsDetail) { return } this.pic = this.goodsDetail.basicInfo.pic @@ -157,10 +158,12 @@ this.goodsFavCheck() }, close() { + this.lock = false this.$emit('close') }, // sku 选择事件 async skuSelect(index, index2) { + this.lock = true this.buyNumber = 1 const properties = this.goodsDetail.properties const p = properties[index] diff --git a/manifest.json b/manifest.json index 0509139..8719eee 100644 --- a/manifest.json +++ b/manifest.json @@ -141,11 +141,13 @@ "scope.userLocation" : { "desc" : "为你推荐附近的门店" } - } + }, + "slotMultipleInstance" : true }, "mp-alipay" : { "usingComponents" : true, - "component2" : true + "component2" : true, + "appid" : "2021003199688515" }, "mp-qq" : { "optimization" : { diff --git a/pages.json b/pages.json index 2177925..76cc8df 100644 --- a/pages.json +++ b/pages.json @@ -348,6 +348,12 @@ "style": { "navigationBarTitleText": "会员卡消费记录" } + }, + { + "path" : "pages/raffle/index", + "style": { + "navigationBarTitleText": "抽奖" + } } ], "subPackages": [ diff --git a/pages/goods/detail.vue b/pages/goods/detail.vue index 5100cd0..f4c8046 100644 --- a/pages/goods/detail.vue +++ b/pages/goods/detail.vue @@ -27,6 +27,9 @@ {{goodsDetail.basicInfo.minScore}} + + ¥{{goodsDetail.basicInfo.originalPrice}} + @@ -752,14 +755,20 @@ this.kanjiaSet() }, async kanjiaHelpDetail() { - console.log(this.curGoodsKanjia); - console.log(this.token); const res = await this.$wxapi.kanjiaHelpDetail(this.token, this.curGoodsKanjia.id, this .curKanjiaprogress.kanjiaInfo.uid) if (res.code == 0) { this.myHelpDetail = res.data } }, + async _pingtuanSet() { + const res = await this.$wxapi.pingtuanSet(this.goodsDetail.basicInfo.id) + if (res.code == 0) { + this.pingtuanSet = res.data + // 如果是拼团商品, 默认显示拼团价格 + this.goodsDetail.basicInfo.minPrice = this.goodsDetail.basicInfo.pingtuanPrice + } + }, } } diff --git a/pages/index/index.vue b/pages/index/index.vue index 4ff8b1f..059bd14 100644 --- a/pages/index/index.vue +++ b/pages/index/index.vue @@ -135,7 +135,7 @@ - + {{item.name}} diff --git a/pages/notagree.vue b/pages/notagree.vue index 32be943..b3f79bc 100644 --- a/pages/notagree.vue +++ b/pages/notagree.vue @@ -4,7 +4,7 @@ 很遗憾,若您不同意《用户协议》和《隐私协议》,将无法继续使用我们的服务。 - + diff --git a/pages/order/index.vue b/pages/order/index.vue index 59b8cba..6596c00 100644 --- a/pages/order/index.vue +++ b/pages/order/index.vue @@ -47,6 +47,9 @@ 取消订单 立即支付 + + 删除 + { + if (res.confirm) { + this._orderDelete(orderId) + } + } + }); + }, + async _orderDelete(orderId) { + // https://www.yuque.com/apifm/nu0f75/wh4rrs + const res = await this.$wxapi.orderDelete(this.token, orderId) + if (res.code != 0) { + uni.showToast({ + title: res.msg, + icon: 'none' + }) + } else { + uni.showToast({ + title: '删除成功' + }) + this.change({ + index: this.current + }) } }, async pay(index) { @@ -217,7 +250,9 @@ uni.showToast({ title: '支付成功' }) - this.change(1) + this.change({ + index: 1 + }) } } else { // 发起在线支付 @@ -227,7 +262,9 @@ type: 0, id: orderInfo.id }, () => { - this.change(1) + this.change({ + index: 1 + }) }, (err) => { uni.showToast({ title: '支付失败', diff --git a/pages/raffle/index.vue b/pages/raffle/index.vue new file mode 100644 index 0000000..be038ca --- /dev/null +++ b/pages/raffle/index.vue @@ -0,0 +1,149 @@ + + + + \ No newline at end of file diff --git a/uni_modules/lime-dialer/changelog.md b/uni_modules/lime-dialer/changelog.md new file mode 100644 index 0000000..56df740 --- /dev/null +++ b/uni_modules/lime-dialer/changelog.md @@ -0,0 +1,7 @@ +## 0.2.1(2023-05-08) +- chore: 增加示例 +## 0.2.0(2021-07-09) +- chore: 统一命名规范,无须主动引入组件 +## 0.1.0(2021-06-16) +- 首次上传 +- 纯CSS实现的抽奖转盘 diff --git a/uni_modules/lime-dialer/components/l-dialer/l-dialer.vue b/uni_modules/lime-dialer/components/l-dialer/l-dialer.vue new file mode 100644 index 0000000..8f63e8c --- /dev/null +++ b/uni_modules/lime-dialer/components/l-dialer/l-dialer.vue @@ -0,0 +1,253 @@ + + + diff --git a/uni_modules/lime-dialer/components/lime-dialer/lime-dialer.vue b/uni_modules/lime-dialer/components/lime-dialer/lime-dialer.vue new file mode 100644 index 0000000..4212e7d --- /dev/null +++ b/uni_modules/lime-dialer/components/lime-dialer/lime-dialer.vue @@ -0,0 +1,90 @@ + + + + + \ No newline at end of file diff --git a/uni_modules/lime-dialer/package.json b/uni_modules/lime-dialer/package.json new file mode 100644 index 0000000..61ba733 --- /dev/null +++ b/uni_modules/lime-dialer/package.json @@ -0,0 +1,76 @@ +{ + "id": "lime-dialer", + "displayName": "幸运转盘", + "version": "0.2.1", + "description": "幸运转盘 抽奖 ", + "keywords": [ + "转盘", + "抽奖" +], + "repository": "", +"engines": { + "HBuilderX": "^3.4.12" + }, +"dcloudext": { + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "", + "type": "component-vue" + }, + "uni_modules": { + "dependencies": [ + "lime-shared" + ], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "App": { + "app-vue": "y", + "app-nvue": "n" + }, + "H5-mobile": { + "Safari": "y", + "Android Browser": "y", + "微信浏览器(Android)": "y", + "QQ浏览器(Android)": "y" + }, + "H5-pc": { + "Chrome": "y", + "IE": "y", + "Edge": "y", + "Firefox": "y", + "Safari": "y" + }, + "小程序": { + "微信": "y", + "阿里": "y", + "百度": "y", + "字节跳动": "y", + "QQ": "y" + }, + "快应用": { + "华为": "y", + "联盟": "y" + } + } + } + } +} \ No newline at end of file diff --git a/uni_modules/lime-dialer/readme.md b/uni_modules/lime-dialer/readme.md new file mode 100644 index 0000000..74d63bf --- /dev/null +++ b/uni_modules/lime-dialer/readme.md @@ -0,0 +1,178 @@ +# Dialer 转盘抽奖 +> 营销活动类组件 +> [查看更多 站点1](https://limeui.qcoon.cn/#/dialer)
+> [查看更多 站点2](http://liangei.gitee.io/limeui/#/dialer)
+> Q群:806744170 + + + +### 平台兼容 +| H5 | 微信小程序 | 支付宝小程序 | 百度小程序 | 头条小程序 | QQ小程序 | App | +|-----------|-----------|-----------|-----------|-----------|-----------|-----------| +| √ | √ | √ | √ | √ | √ | √ | + + +### 代码演示 +#### 基础用法 + +```html + +``` +```js +export default { + data() { + return { + // 奖品列表, + prizeList: [ + { + id: 'coupon88', + name: '8.8折', + img: 'https://img11.360buyimg.com/pop/jfs/t1/175718/35/12595/5477/60b660c6Eb850717b/a1cfe750dcdb5b78.png', + }, + { + id: 'coupon900', + Color: 'rgb(251, 219, 216)', + name: '900', + img: 'https://img11.360buyimg.com/pop/jfs/t1/190845/9/6092/4489/60b65fe8Ebb8f8284/955da889f6d1c13e.png', + }, + { + id: 'coupon1', + name: '1元', + img: 'https://img11.360buyimg.com/pop/jfs/t1/189927/14/6092/4174/60b66173E23c472ea/44af15a151defca1.png', + }, + { + id: 'apple', + Color: 'rgba(246, 142, 46, 0.5)', + name: '苹果手机', + img: 'https://img11.360buyimg.com/pop/jfs/t1/177670/26/4591/2514/60a25874Ee0e5332a/99c7bdfede732ae4.png' + }, + { + id: 'coupon210', + name: '210元', + img: 'https://img11.360buyimg.com/pop/jfs/t1/124578/12/20170/4429/60b635d8E7089ebb0/7a47d76a2a260cc0.png' + }, + { + id: 'jd100', + name: '100京豆', + img: 'https://img11.360buyimg.com/pop/jfs/t1/162790/37/15087/28046/6062a49aE8f2c10f2/5591ff0ff38a45e2.png', + }, + { + id: 'coupon400', + name: '400元', + img: 'https://img11.360buyimg.com/pop/jfs/t1/177090/2/7001/4535/60b6607aEe9c1db2a/76c67675f547db3f.png' + }, + { + id: 'thanks', + name: '谢谢参与', + img: 'https://storage.jd.com/cdn-upload/dialTemplateHeart.png', + } + ] + }; + }, + methods: { + onDone(index) { + const prize = this.prizeList[index] + uni.showModal({ + title: prize.id == 'thanks' ? '很遗憾': '恭喜您', + content: (prize.id !== 'thanks' ? `获得`:'') + prize.name + }) + }, + onClick() { + // 奖品的索引 + this.$refs.dialer.run(5) + } + } +} +``` + +#### 表框 +- 1、通过设置`dial-style`设置背景的方式设置,必须是网络图片 +- 2、通过插槽`border`设置 + +```html +// 方式1 + + +// 方式2 + + + +``` + +#### 指针 +- 1、可通过`pointer-style`设置背景的方式设置,但必须为网络图片 +- 2、可通过插槽`pointer`设置 +- 3、因为插件是能过获取内部方法实现抽奖,只要获取方法,任何元素都是指针按钮 + +```html +// 方式1 + +// 方式2 + + + +``` + +#### 奖品插槽 +- 默认会按奖品列表渲染,但想更个性可通过插槽`prize`设置 +- 微信小程序最好使用HX3.7.12+并且在`manifest.json`设置`slotMultipleInstance` +```json +"mp-weixin" : { + "slotMultipleInstance" : true +} +``` + +```html + + + +``` + + +### 查看示例 +- 导入后直接使用这个标签查看演示效果 + +```html + + +``` + + +### 插件标签 +- 默认 l-dialer 为 component +- 默认 lime-dialer 为 demo + + + +### API +#### Props + +| 参数 | 说明 | 类型 | 默认值 | 版本 | +| --- | --- | --- | --- | --- | +| size | 转盘直径,默认单位为 `rpx` | Number | `300` | - | +| prizeList | 奖品列表 | Array | [] | - | +| turns | 旋转圈数 | Number | `10` | - | +| duration | 旋转过程时间,单位为 `s` | Number | `3` | - | +| styleOpt | 转盘中的样式,包括每个扇区的背景颜色(在每条数据中页可单独设置prizeColor),扇区的边框颜色 | Object | {prizeBgColors: [],borderColor: ''} | - | +| customStyle | 外容器的自定义样式 | String | | - | +| dialStyle | 转盘自定义样式 | String | | - | +| pointerStyle | 指针自定义样式 | String | `width:30%` | - | + +#### Event + +| 名称 | 说明 | +| ---- | ---------------------------------------------------------- | +| run(index) | 旋转到指定索引,该事件是通过`ref`获取插件实例的内部方法 | +| done | 旋转结束,该事件是通过标签接收的方法 | +| click | 点击指针,该事件是通过标签接收的方法 | + + +#### Slots + +| 名称 | 说明 | +| ---- | ---------------------------------------------------------- | +| border | 边框插槽 | +| prize | 奖品插槽 | +| pointer | 指针插槽 | diff --git a/uni_modules/lime-dialer/static/turnable_btn.png b/uni_modules/lime-dialer/static/turnable_btn.png new file mode 100644 index 0000000..15999ff Binary files /dev/null and b/uni_modules/lime-dialer/static/turnable_btn.png differ diff --git a/uni_modules/lime-shared/addUnit/index.ts b/uni_modules/lime-shared/addUnit/index.ts new file mode 100644 index 0000000..2468a3a --- /dev/null +++ b/uni_modules/lime-shared/addUnit/index.ts @@ -0,0 +1,25 @@ +// @ts-nocheck +import {isNumeric} from '../isNumeric' +import {isDef} from '../isDef' +/** + * 给一个值添加单位(像素 px) + * @param value 要添加单位的值,可以是字符串或数字 + * @returns 添加了单位的值,如果值为 undefined 则返回 undefined + */ +export function addUnit(value?: string | number): string | undefined { + if (!isDef(value)) { + return undefined; + } + + value = String(value); // 将值转换为字符串 + + // 如果值是数字,则在后面添加单位 "px",否则保持原始值 + return isNumeric(value) ? `${value}px` : value; +} + + +// 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/arrayBufferToFile/index.ts b/uni_modules/lime-shared/arrayBufferToFile/index.ts new file mode 100644 index 0000000..9760b20 --- /dev/null +++ b/uni_modules/lime-shared/arrayBufferToFile/index.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..1f14d0a --- /dev/null +++ b/uni_modules/lime-shared/base64ToPath/index.ts @@ -0,0 +1,76 @@ +// @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) || []; + console.log('format', format) + 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..78a81c8 --- /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) => { + 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..22e4416 --- /dev/null +++ b/uni_modules/lime-shared/canIUseCanvas2d/index.ts @@ -0,0 +1,58 @@ +// @ts-nocheck +// #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; +} + +/** 环境是否支持canvas 2d */ +export function canIUseCanvas2d() { + // #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 +} \ No newline at end of file diff --git a/uni_modules/lime-shared/changelog.md b/uni_modules/lime-shared/changelog.md new file mode 100644 index 0000000..62c3f5b --- /dev/null +++ b/uni_modules/lime-shared/changelog.md @@ -0,0 +1,30 @@ +## 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..b62565c --- /dev/null +++ b/uni_modules/lime-shared/clamp/index.ts @@ -0,0 +1,16 @@ +// @ts-nocheck +/** + * 将一个值限制在指定的范围内 + * @param min 最小值 + * @param max 最大值 + * @param val 要限制的值 + * @returns 限制后的值 + */ +export function clamp(min: number, max: number, val: number): number { + return Math.max(min, Math.min(max, val)); +} + + +// console.log(clamp(0, 10, 5)); // 输出: 5(在范围内,不做更改) +// console.log(clamp(0, 10, -5)); // 输出: 0(小于最小值,被限制为最小值) +// console.log(clamp(0, 10, 15)); // 输出: 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..ded334d --- /dev/null +++ b/uni_modules/lime-shared/cloneDeep/index.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..95804cd --- /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) { + return arr.reduce((pre, cur) => + 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/createAnimation/index.ts b/uni_modules/lime-shared/createAnimation/index.ts new file mode 100644 index 0000000..5b6e2da --- /dev/null +++ b/uni_modules/lime-shared/createAnimation/index.ts @@ -0,0 +1,149 @@ +// @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动画太慢~~~无语 + + + + +// #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 +} +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 + // 在iOS10+QQ小程序平台下,传给原生的对象一定是个普通对象而不是Proxy对象,否则会报parameter should be Object instead of ProxyObject的错误 + return uni.createAnimation({ ...options }) + // #endif + // #ifdef APP-NVUE + return new LimeAnimation(options) + // #endif +} \ No newline at end of file diff --git a/uni_modules/lime-shared/createImage/index.ts b/uni_modules/lime-shared/createImage/index.ts new file mode 100644 index 0000000..674b6f5 --- /dev/null +++ b/uni_modules/lime-shared/createImage/index.ts @@ -0,0 +1,61 @@ +// @ts-nocheck +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.tagName == 'canvas' && !('toBlob' in this) || canvas && !('toBlob' in canvas)){ + return new Image() + } else if(isBrowser) { + return new window.Image() + } + return new Image() +} \ No newline at end of file diff --git a/uni_modules/lime-shared/debounce/index.ts b/uni_modules/lime-shared/debounce/index.ts new file mode 100644 index 0000000..5875145 --- /dev/null +++ b/uni_modules/lime-shared/debounce/index.ts @@ -0,0 +1,38 @@ +// @ts-nocheck +type Timeout = ReturnType | null; +/** + * 防抖函数,通过延迟一定时间来限制函数的执行频率。 + * @param fn 要防抖的函数。 + * @param wait 触发防抖的等待时间,单位为毫秒。 + * @returns 防抖函数。 + */ +export function debounce(fn: (...args: any[]) => void, wait = 300) { + let timer: Timeout = null; // 用于存储 setTimeout 的标识符的变量 + + return function (this: any, ...args: any[]) { + 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..b67e233 --- /dev/null +++ b/uni_modules/lime-shared/exif/index.ts @@ -0,0 +1,1056 @@ +// @ts-nocheck +import { base64ToArrayBuffer } from '../base64ToArrayBuffer'; +import { pathToBase64 } from '../pathToBase64'; +import { isBase64 } 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..c3c62ac --- /dev/null +++ b/uni_modules/lime-shared/getClassStr/index.ts @@ -0,0 +1,27 @@ +// @ts-nocheck +/** + * 获取对象的类名字符串 + * @param obj - 需要处理的对象 + * @returns 由对象属性作为类名组成的字符串 + */ +export function getClassStr(obj: T): string { + let classNames: string[] = []; + + // 遍历对象的属性 + for (let key in obj) { + // 检查属性确实属于对象自身且其值为true + if ((obj as any).hasOwnProperty(key) && obj[key]) { + // 将属性名添加到类名数组中 + classNames.push(key); + } + } + + // 将类名数组用空格连接成字符串并返回 + 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..79ecac8 --- /dev/null +++ b/uni_modules/lime-shared/getCurrentPage/index.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..8d6a68c --- /dev/null +++ b/uni_modules/lime-shared/getLocalFilePath/index.ts @@ -0,0 +1,14 @@ +// @ts-nocheck +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 +} diff --git a/uni_modules/lime-shared/getRect/index.ts b/uni_modules/lime-shared/getRect/index.ts new file mode 100644 index 0000000..9763ee2 --- /dev/null +++ b/uni_modules/lime-shared/getRect/index.ts @@ -0,0 +1,86 @@ +// @ts-nocheck + +// #ifdef APP-NVUE +// 当编译环境是 APP-NVUE 时,引入 uni.requireNativePlugin('dom'),具体插件用途未知 +const dom = uni.requireNativePlugin('dom') +// #endif + +interface RectOptions { + /** + * 上下文 + */ + context ?: any // ComponentInternalInstance 类型,用于指定上下文 + + /** + * 是否需要获取所有节点,nvue 环境下不支持 + */ + needAll ?: boolean, + + /** + * 节点引用对象,类型为 UniNamespace.NodesRef + */ + nodes ?: UniNamespace.NodesRef + + /** + * 节点引用对象的键,类型为 UniNamespace.NodesRef 中的某个键 + */ + type ?: keyof UniNamespace.NodesRef +} + +/** + * 获取节点信息 + * @param selector 选择器字符串 + * @param options RectOptions 对象,用于配置选项 + * @returns 包含节点信息的 Promise 对象 + */ +export function getRect(selector : string, options : RectOptions = {}) { + // #ifndef APP-NVUE + const typeDefault = 'boundingClientRect' + let { context, needAll, type = typeDefault } = options + // #endif + + // #ifdef MP || VUE2 + if (context.proxy) context = context.proxy + // #endif + + return new Promise((resolve, reject) => { + // #ifndef APP-NVUE + const dom = uni.createSelectorQuery().in(context)[needAll ? 'selectAll' : 'select'](selector); + const result = (rect: UniNamespace.NodeInfo) => { + if (rect) { + resolve(rect) + } else { + reject('no rect') + } + } + if (type == typeDefault) { + dom[type](result).exec() + } else { + dom[type]({ + 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/getStyleStr/index.ts b/uni_modules/lime-shared/getStyleStr/index.ts new file mode 100644 index 0000000..33decd5 --- /dev/null +++ b/uni_modules/lime-shared/getStyleStr/index.ts @@ -0,0 +1,30 @@ +// @ts-nocheck +interface CSSProperties { + [key: string]: string | number +} +/** + * 将字符串转换为带有连字符分隔的小写形式 + * @param key - 要转换的字符串 + * @returns 转换后的字符串 + */ +export function toLowercaseSeparator(key: string) { + return key.replace(/([A-Z])/g, '-$1').toLowerCase(); +} + +/** + * 获取样式对象对应的样式字符串 + * @param style - CSS样式对象 + * @returns 由非空有效样式属性键值对组成的字符串 + */ +export function getStyleStr(style: CSSProperties): string { + return Object.keys(style) + .filter(key => style[key] !== undefined && style[key] !== null && style[key] !== '') + .map((key: string) => `${toLowercaseSeparator(key)}: ${style[key]};`) + .join(' '); +} + +// 示例 +// const style = { color: 'red', fontSize: '16px', backgroundColor: '', border: null }; +// const styleStr = getStyleStr(style); +// console.log(styleStr); +// 输出: "color: red; font-size: 16px;" \ No newline at end of file diff --git a/uni_modules/lime-shared/hasOwn/index.ts b/uni_modules/lime-shared/hasOwn/index.ts new file mode 100644 index 0000000..7317879 --- /dev/null +++ b/uni_modules/lime-shared/hasOwn/index.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/index.ts b/uni_modules/lime-shared/index.ts new file mode 100644 index 0000000..8ffb34d --- /dev/null +++ b/uni_modules/lime-shared/index.ts @@ -0,0 +1,43 @@ +// @ts-nocheck +// validator +export * from './isString' +export * from './isNumber' +export * from './isNumeric' +export * from './isDef' +export * from './isFunction' +export * from './isObject' +export * from './isPromise' +export * from './isBase64' + +export * from './hasOwn' + +// 单位转换 +export * from './addUnit' +export * from './unitConvert' +export * from './toNumber' + +export * from './random' +export * from './range' +export * from './fillZero' + +// image +export * from './base64ToPath' +export * from './pathToBase64' +export * from './exif' + +// canvas +export * from './canIUseCanvas2d' + +// page +export * from './getCurrentPage' + +// dom +export * from './getRect' +export * from './selectComponent' +export * from './createAnimation' + +// delay +export * from './sleep' +export * from './debounce' +export * from './throttle' + diff --git a/uni_modules/lime-shared/isBase64/index.ts b/uni_modules/lime-shared/isBase64/index.ts new file mode 100644 index 0000000..44d0f6d --- /dev/null +++ b/uni_modules/lime-shared/isBase64/index.ts @@ -0,0 +1,9 @@ +// @ts-nocheck +/** + * 判断给定的路径是否为Base64编码的图像路径 + * @param path 图像路径 + * @returns 如果路径是Base64编码,则返回true;否则返回false + */ +export const isBase64 = (path: string): boolean => { + return /^data:image\/(\w+);base64/.test(path); +}; \ No newline at end of file diff --git a/uni_modules/lime-shared/isBrowser/index.ts b/uni_modules/lime-shared/isBrowser/index.ts new file mode 100644 index 0000000..241c7f7 --- /dev/null +++ b/uni_modules/lime-shared/isBrowser/index.ts @@ -0,0 +1,2 @@ +// @ts-nocheck +export const isBrowser = typeof window !== 'undefined'; \ No newline at end of file diff --git a/uni_modules/lime-shared/isDef/index.ts b/uni_modules/lime-shared/isDef/index.ts new file mode 100644 index 0000000..029407e --- /dev/null +++ b/uni_modules/lime-shared/isDef/index.ts @@ -0,0 +1,9 @@ +// @ts-nocheck +/** + * 检查一个值是否已定义(不为 undefined)且不为 null + * @param value 要检查的值 + * @returns 如果值已定义且不为 null,则返回 true;否则返回 false + */ +export function isDef(value: unknown): boolean { + return value !== undefined && value !== null; +} \ No newline at end of file diff --git a/uni_modules/lime-shared/isFunction/index.ts b/uni_modules/lime-shared/isFunction/index.ts new file mode 100644 index 0000000..981442d --- /dev/null +++ b/uni_modules/lime-shared/isFunction/index.ts @@ -0,0 +1,8 @@ +// @ts-nocheck +/** + * 检查一个值是否为函数类型 + * @param val 要检查的值 + * @returns 如果值的类型是函数类型,则返回 true;否则返回 false + */ +export const isFunction = (val: unknown): val is Function => + typeof val === 'function'; \ No newline at end of file diff --git a/uni_modules/lime-shared/isNumber/index.ts b/uni_modules/lime-shared/isNumber/index.ts new file mode 100644 index 0000000..2feff2f --- /dev/null +++ b/uni_modules/lime-shared/isNumber/index.ts @@ -0,0 +1,9 @@ +// @ts-nocheck +/** + * 检查一个值是否为数字类型 + * @param value 要检查的值,可以是 number 类型或 string 类型的数字 + * @returns 如果值是数字类型且不是 NaN,则返回 true;否则返回 false + */ +export function isNumber(value: number | string): boolean { + return typeof value === 'number' && !isNaN(value); +} \ No newline at end of file diff --git a/uni_modules/lime-shared/isNumeric/index.ts b/uni_modules/lime-shared/isNumeric/index.ts new file mode 100644 index 0000000..5ee60ba --- /dev/null +++ b/uni_modules/lime-shared/isNumeric/index.ts @@ -0,0 +1,9 @@ +// @ts-nocheck +/** + * 检查一个值是否为数字类型或表示数字的字符串 + * @param value 要检查的值,可以是 string 类型或 number 类型 + * @returns 如果值是数字类型或表示数字的字符串,则返回 true;否则返回 false + */ +export function isNumeric(value: string | number): boolean { + return /^(-)?\d+(\.\d+)?$/.test(value); +} \ No newline at end of file diff --git a/uni_modules/lime-shared/isObject/index.ts b/uni_modules/lime-shared/isObject/index.ts new file mode 100644 index 0000000..17234c9 --- /dev/null +++ b/uni_modules/lime-shared/isObject/index.ts @@ -0,0 +1,8 @@ +// @ts-nocheck +/** + * 检查一个值是否为对象类型 + * @param val 要检查的值 + * @returns 如果值的类型是对象类型,则返回 true;否则返回 false + */ +export const isObject = (val : unknown) : val is Record => + val !== null && typeof val === 'object'; \ No newline at end of file diff --git a/uni_modules/lime-shared/isPromise/index.ts b/uni_modules/lime-shared/isPromise/index.ts new file mode 100644 index 0000000..63847cd --- /dev/null +++ b/uni_modules/lime-shared/isPromise/index.ts @@ -0,0 +1,13 @@ +// @ts-nocheck +import {isFunction} from '../isFunction' +import {isObject} from '../isObject' +/** + * 检查一个值是否为 Promise 类型 + * @param val 要检查的值 + * @returns 如果值的类型是 Promise 类型,则返回 true;否则返回 false + */ +export const isPromise = (val: unknown): val is Promise => { + // 使用 isObject 函数判断值是否为对象类型 + // 使用 isFunction 函数判断值是否具有 then 方法和 catch 方法 + return isObject(val) && isFunction(val.then) && isFunction(val.catch); +}; \ No newline at end of file diff --git a/uni_modules/lime-shared/isString/index.ts b/uni_modules/lime-shared/isString/index.ts new file mode 100644 index 0000000..a130eb2 --- /dev/null +++ b/uni_modules/lime-shared/isString/index.ts @@ -0,0 +1,7 @@ +// @ts-nocheck +/** + * 检查一个值是否为字符串类型 + * @param str 要检查的值 + * @returns 如果值的类型是字符串类型,则返回 true;否则返回 false + */ +export const isString = (str: unknown): str is string => typeof str === 'string'; \ No newline at end of file diff --git a/uni_modules/lime-shared/kebabCase/index.ts b/uni_modules/lime-shared/kebabCase/index.ts new file mode 100644 index 0000000..fa0e4b2 --- /dev/null +++ b/uni_modules/lime-shared/kebabCase/index.ts @@ -0,0 +1,17 @@ +// export function toLowercaseSeparator(key: string) { +// return key.replace(/([A-Z])/g, '-$1').toLowerCase(); +// } + +/** + * 将字符串转换为指定连接符的命名约定 + * @param str 要转换的字符串 + * @param separator 指定的连接符,默认为 "-" + * @returns 转换后的字符串 + */ +export function kebabCase(str: string, separator: string = "-"): string { + return str + .replace(/[A-Z]/g, match => `${separator}${match.toLowerCase()}`) // 将大写字母替换为连接符加小写字母 + .replace(/[\s_-]+/g, separator) // 将空格、下划线和短横线替换为指定连接符 + .replace(new RegExp(`^${separator}|${separator}$`, "g"), "") // 删除开头和结尾的连接符 + .toLowerCase(); // 将结果转换为全小写 +} \ No newline at end of file diff --git a/uni_modules/lime-shared/package.json b/uni_modules/lime-shared/package.json new file mode 100644 index 0000000..2e000c9 --- /dev/null +++ b/uni_modules/lime-shared/package.json @@ -0,0 +1,83 @@ +{ + "id": "lime-shared", + "displayName": "lime-shared", + "version": "0.1.4", + "description": "本人插件的几个公共函数,获取当前页,图片的base64转临时路径,图片的exif信息等", + "keywords": [ + "lime-shared", + "exif", + "selectComponent" +], + "repository": "", + "engines": { + "HBuilderX": "^3.1.0" + }, + "dcloudext": { + "type": "sdk-js", + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "" + }, + "uni_modules": { + "dependencies": [], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "Vue": { + "vue2": "y", + "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", + "阿里": "y", + "百度": "y", + "字节跳动": "y", + "QQ": "y", + "钉钉": "y", + "快手": "y", + "飞书": "y", + "京东": "u" + }, + "快应用": { + "华为": "u", + "联盟": "u" + } + } + } + } +} \ No newline at end of file diff --git a/uni_modules/lime-shared/pathToBase64/index.ts b/uni_modules/lime-shared/pathToBase64/index.ts new file mode 100644 index 0000000..8167f88 --- /dev/null +++ b/uni_modules/lime-shared/pathToBase64/index.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/piexif/index.ts b/uni_modules/lime-shared/piexif/index.ts new file mode 100644 index 0000000..6a52b10 --- /dev/null +++ b/uni_modules/lime-shared/piexif/index.ts @@ -0,0 +1,2320 @@ +// @ts-nocheck +// 源于piexifjs +import { cloneDeep } from '../cloneDeep' +import { isString } from '../isString' +const TAGS = { + 'Image': { + 11: { + 'name': 'ProcessingSoftware', + 'type': 'Ascii' + }, + 254: { + 'name': 'NewSubfileType', + 'type': 'Long' + }, + 255: { + 'name': 'SubfileType', + 'type': 'Short' + }, + 256: { + 'name': 'ImageWidth', + 'type': 'Long' + }, + 257: { + 'name': 'ImageLength', + 'type': 'Long' + }, + 258: { + 'name': 'BitsPerSample', + 'type': 'Short' + }, + 259: { + 'name': 'Compression', + 'type': 'Short' + }, + 262: { + 'name': 'PhotometricInterpretation', + 'type': 'Short' + }, + 263: { + 'name': 'Threshholding', + 'type': 'Short' + }, + 264: { + 'name': 'CellWidth', + 'type': 'Short' + }, + 265: { + 'name': 'CellLength', + 'type': 'Short' + }, + 266: { + 'name': 'FillOrder', + 'type': 'Short' + }, + 269: { + 'name': 'DocumentName', + 'type': 'Ascii' + }, + 270: { + 'name': 'ImageDescription', + 'type': 'Ascii' + }, + 271: { + 'name': 'Make', + 'type': 'Ascii' + }, + 272: { + 'name': 'Model', + 'type': 'Ascii' + }, + 273: { + 'name': 'StripOffsets', + 'type': 'Long' + }, + 274: { + 'name': 'Orientation', + 'type': 'Short' + }, + 277: { + 'name': 'SamplesPerPixel', + 'type': 'Short' + }, + 278: { + 'name': 'RowsPerStrip', + 'type': 'Long' + }, + 279: { + 'name': 'StripByteCounts', + 'type': 'Long' + }, + 282: { + 'name': 'XResolution', + 'type': 'Rational' + }, + 283: { + 'name': 'YResolution', + 'type': 'Rational' + }, + 284: { + 'name': 'PlanarConfiguration', + 'type': 'Short' + }, + 290: { + 'name': 'GrayResponseUnit', + 'type': 'Short' + }, + 291: { + 'name': 'GrayResponseCurve', + 'type': 'Short' + }, + 292: { + 'name': 'T4Options', + 'type': 'Long' + }, + 293: { + 'name': 'T6Options', + 'type': 'Long' + }, + 296: { + 'name': 'ResolutionUnit', + 'type': 'Short' + }, + 301: { + 'name': 'TransferFunction', + 'type': 'Short' + }, + 305: { + 'name': 'Software', + 'type': 'Ascii' + }, + 306: { + 'name': 'DateTime', + 'type': 'Ascii' + }, + 315: { + 'name': 'Artist', + 'type': 'Ascii' + }, + 316: { + 'name': 'HostComputer', + 'type': 'Ascii' + }, + 317: { + 'name': 'Predictor', + 'type': 'Short' + }, + 318: { + 'name': 'WhitePoint', + 'type': 'Rational' + }, + 319: { + 'name': 'PrimaryChromaticities', + 'type': 'Rational' + }, + 320: { + 'name': 'ColorMap', + 'type': 'Short' + }, + 321: { + 'name': 'HalftoneHints', + 'type': 'Short' + }, + 322: { + 'name': 'TileWidth', + 'type': 'Short' + }, + 323: { + 'name': 'TileLength', + 'type': 'Short' + }, + 324: { + 'name': 'TileOffsets', + 'type': 'Short' + }, + 325: { + 'name': 'TileByteCounts', + 'type': 'Short' + }, + 330: { + 'name': 'SubIFDs', + 'type': 'Long' + }, + 332: { + 'name': 'InkSet', + 'type': 'Short' + }, + 333: { + 'name': 'InkNames', + 'type': 'Ascii' + }, + 334: { + 'name': 'NumberOfInks', + 'type': 'Short' + }, + 336: { + 'name': 'DotRange', + 'type': 'Byte' + }, + 337: { + 'name': 'TargetPrinter', + 'type': 'Ascii' + }, + 338: { + 'name': 'ExtraSamples', + 'type': 'Short' + }, + 339: { + 'name': 'SampleFormat', + 'type': 'Short' + }, + 340: { + 'name': 'SMinSampleValue', + 'type': 'Short' + }, + 341: { + 'name': 'SMaxSampleValue', + 'type': 'Short' + }, + 342: { + 'name': 'TransferRange', + 'type': 'Short' + }, + 343: { + 'name': 'ClipPath', + 'type': 'Byte' + }, + 344: { + 'name': 'XClipPathUnits', + 'type': 'Long' + }, + 345: { + 'name': 'YClipPathUnits', + 'type': 'Long' + }, + 346: { + 'name': 'Indexed', + 'type': 'Short' + }, + 347: { + 'name': 'JPEGTables', + 'type': 'Undefined' + }, + 351: { + 'name': 'OPIProxy', + 'type': 'Short' + }, + 512: { + 'name': 'JPEGProc', + 'type': 'Long' + }, + 513: { + 'name': 'JPEGInterchangeFormat', + 'type': 'Long' + }, + 514: { + 'name': 'JPEGInterchangeFormatLength', + 'type': 'Long' + }, + 515: { + 'name': 'JPEGRestartInterval', + 'type': 'Short' + }, + 517: { + 'name': 'JPEGLosslessPredictors', + 'type': 'Short' + }, + 518: { + 'name': 'JPEGPointTransforms', + 'type': 'Short' + }, + 519: { + 'name': 'JPEGQTables', + 'type': 'Long' + }, + 520: { + 'name': 'JPEGDCTables', + 'type': 'Long' + }, + 521: { + 'name': 'JPEGACTables', + 'type': 'Long' + }, + 529: { + 'name': 'YCbCrCoefficients', + 'type': 'Rational' + }, + 530: { + 'name': 'YCbCrSubSampling', + 'type': 'Short' + }, + 531: { + 'name': 'YCbCrPositioning', + 'type': 'Short' + }, + 532: { + 'name': 'ReferenceBlackWhite', + 'type': 'Rational' + }, + 700: { + 'name': 'XMLPacket', + 'type': 'Byte' + }, + 18246: { + 'name': 'Rating', + 'type': 'Short' + }, + 18249: { + 'name': 'RatingPercent', + 'type': 'Short' + }, + 32781: { + 'name': 'ImageID', + 'type': 'Ascii' + }, + 33421: { + 'name': 'CFARepeatPatternDim', + 'type': 'Short' + }, + 33422: { + 'name': 'CFAPattern', + 'type': 'Byte' + }, + 33423: { + 'name': 'BatteryLevel', + 'type': 'Rational' + }, + 33432: { + 'name': 'Copyright', + 'type': 'Ascii' + }, + 33434: { + 'name': 'ExposureTime', + 'type': 'Rational' + }, + 34377: { + 'name': 'ImageResources', + 'type': 'Byte' + }, + 34665: { + 'name': 'ExifTag', + 'type': 'Long' + }, + 34675: { + 'name': 'InterColorProfile', + 'type': 'Undefined' + }, + 34853: { + 'name': 'GPSTag', + 'type': 'Long' + }, + 34857: { + 'name': 'Interlace', + 'type': 'Short' + }, + 34858: { + 'name': 'TimeZoneOffset', + 'type': 'Long' + }, + 34859: { + 'name': 'SelfTimerMode', + 'type': 'Short' + }, + 37387: { + 'name': 'FlashEnergy', + 'type': 'Rational' + }, + 37388: { + 'name': 'SpatialFrequencyResponse', + 'type': 'Undefined' + }, + 37389: { + 'name': 'Noise', + 'type': 'Undefined' + }, + 37390: { + 'name': 'FocalPlaneXResolution', + 'type': 'Rational' + }, + 37391: { + 'name': 'FocalPlaneYResolution', + 'type': 'Rational' + }, + 37392: { + 'name': 'FocalPlaneResolutionUnit', + 'type': 'Short' + }, + 37393: { + 'name': 'ImageNumber', + 'type': 'Long' + }, + 37394: { + 'name': 'SecurityClassification', + 'type': 'Ascii' + }, + 37395: { + 'name': 'ImageHistory', + 'type': 'Ascii' + }, + 37397: { + 'name': 'ExposureIndex', + 'type': 'Rational' + }, + 37398: { + 'name': 'TIFFEPStandardID', + 'type': 'Byte' + }, + 37399: { + 'name': 'SensingMethod', + 'type': 'Short' + }, + 40091: { + 'name': 'XPTitle', + 'type': 'Byte' + }, + 40092: { + 'name': 'XPComment', + 'type': 'Byte' + }, + 40093: { + 'name': 'XPAuthor', + 'type': 'Byte' + }, + 40094: { + 'name': 'XPKeywords', + 'type': 'Byte' + }, + 40095: { + 'name': 'XPSubject', + 'type': 'Byte' + }, + 50341: { + 'name': 'PrintImageMatching', + 'type': 'Undefined' + }, + 50706: { + 'name': 'DNGVersion', + 'type': 'Byte' + }, + 50707: { + 'name': 'DNGBackwardVersion', + 'type': 'Byte' + }, + 50708: { + 'name': 'UniqueCameraModel', + 'type': 'Ascii' + }, + 50709: { + 'name': 'LocalizedCameraModel', + 'type': 'Byte' + }, + 50710: { + 'name': 'CFAPlaneColor', + 'type': 'Byte' + }, + 50711: { + 'name': 'CFALayout', + 'type': 'Short' + }, + 50712: { + 'name': 'LinearizationTable', + 'type': 'Short' + }, + 50713: { + 'name': 'BlackLevelRepeatDim', + 'type': 'Short' + }, + 50714: { + 'name': 'BlackLevel', + 'type': 'Rational' + }, + 50715: { + 'name': 'BlackLevelDeltaH', + 'type': 'SRational' + }, + 50716: { + 'name': 'BlackLevelDeltaV', + 'type': 'SRational' + }, + 50717: { + 'name': 'WhiteLevel', + 'type': 'Short' + }, + 50718: { + 'name': 'DefaultScale', + 'type': 'Rational' + }, + 50719: { + 'name': 'DefaultCropOrigin', + 'type': 'Short' + }, + 50720: { + 'name': 'DefaultCropSize', + 'type': 'Short' + }, + 50721: { + 'name': 'ColorMatrix1', + 'type': 'SRational' + }, + 50722: { + 'name': 'ColorMatrix2', + 'type': 'SRational' + }, + 50723: { + 'name': 'CameraCalibration1', + 'type': 'SRational' + }, + 50724: { + 'name': 'CameraCalibration2', + 'type': 'SRational' + }, + 50725: { + 'name': 'ReductionMatrix1', + 'type': 'SRational' + }, + 50726: { + 'name': 'ReductionMatrix2', + 'type': 'SRational' + }, + 50727: { + 'name': 'AnalogBalance', + 'type': 'Rational' + }, + 50728: { + 'name': 'AsShotNeutral', + 'type': 'Short' + }, + 50729: { + 'name': 'AsShotWhiteXY', + 'type': 'Rational' + }, + 50730: { + 'name': 'BaselineExposure', + 'type': 'SRational' + }, + 50731: { + 'name': 'BaselineNoise', + 'type': 'Rational' + }, + 50732: { + 'name': 'BaselineSharpness', + 'type': 'Rational' + }, + 50733: { + 'name': 'BayerGreenSplit', + 'type': 'Long' + }, + 50734: { + 'name': 'LinearResponseLimit', + 'type': 'Rational' + }, + 50735: { + 'name': 'CameraSerialNumber', + 'type': 'Ascii' + }, + 50736: { + 'name': 'LensInfo', + 'type': 'Rational' + }, + 50737: { + 'name': 'ChromaBlurRadius', + 'type': 'Rational' + }, + 50738: { + 'name': 'AntiAliasStrength', + 'type': 'Rational' + }, + 50739: { + 'name': 'ShadowScale', + 'type': 'SRational' + }, + 50740: { + 'name': 'DNGPrivateData', + 'type': 'Byte' + }, + 50741: { + 'name': 'MakerNoteSafety', + 'type': 'Short' + }, + 50778: { + 'name': 'CalibrationIlluminant1', + 'type': 'Short' + }, + 50779: { + 'name': 'CalibrationIlluminant2', + 'type': 'Short' + }, + 50780: { + 'name': 'BestQualityScale', + 'type': 'Rational' + }, + 50781: { + 'name': 'RawDataUniqueID', + 'type': 'Byte' + }, + 50827: { + 'name': 'OriginalRawFileName', + 'type': 'Byte' + }, + 50828: { + 'name': 'OriginalRawFileData', + 'type': 'Undefined' + }, + 50829: { + 'name': 'ActiveArea', + 'type': 'Short' + }, + 50830: { + 'name': 'MaskedAreas', + 'type': 'Short' + }, + 50831: { + 'name': 'AsShotICCProfile', + 'type': 'Undefined' + }, + 50832: { + 'name': 'AsShotPreProfileMatrix', + 'type': 'SRational' + }, + 50833: { + 'name': 'CurrentICCProfile', + 'type': 'Undefined' + }, + 50834: { + 'name': 'CurrentPreProfileMatrix', + 'type': 'SRational' + }, + 50879: { + 'name': 'ColorimetricReference', + 'type': 'Short' + }, + 50931: { + 'name': 'CameraCalibrationSignature', + 'type': 'Byte' + }, + 50932: { + 'name': 'ProfileCalibrationSignature', + 'type': 'Byte' + }, + 50934: { + 'name': 'AsShotProfileName', + 'type': 'Byte' + }, + 50935: { + 'name': 'NoiseReductionApplied', + 'type': 'Rational' + }, + 50936: { + 'name': 'ProfileName', + 'type': 'Byte' + }, + 50937: { + 'name': 'ProfileHueSatMapDims', + 'type': 'Long' + }, + 50938: { + 'name': 'ProfileHueSatMapData1', + 'type': 'Float' + }, + 50939: { + 'name': 'ProfileHueSatMapData2', + 'type': 'Float' + }, + 50940: { + 'name': 'ProfileToneCurve', + 'type': 'Float' + }, + 50941: { + 'name': 'ProfileEmbedPolicy', + 'type': 'Long' + }, + 50942: { + 'name': 'ProfileCopyright', + 'type': 'Byte' + }, + 50964: { + 'name': 'ForwardMatrix1', + 'type': 'SRational' + }, + 50965: { + 'name': 'ForwardMatrix2', + 'type': 'SRational' + }, + 50966: { + 'name': 'PreviewApplicationName', + 'type': 'Byte' + }, + 50967: { + 'name': 'PreviewApplicationVersion', + 'type': 'Byte' + }, + 50968: { + 'name': 'PreviewSettingsName', + 'type': 'Byte' + }, + 50969: { + 'name': 'PreviewSettingsDigest', + 'type': 'Byte' + }, + 50970: { + 'name': 'PreviewColorSpace', + 'type': 'Long' + }, + 50971: { + 'name': 'PreviewDateTime', + 'type': 'Ascii' + }, + 50972: { + 'name': 'RawImageDigest', + 'type': 'Undefined' + }, + 50973: { + 'name': 'OriginalRawFileDigest', + 'type': 'Undefined' + }, + 50974: { + 'name': 'SubTileBlockSize', + 'type': 'Long' + }, + 50975: { + 'name': 'RowInterleaveFactor', + 'type': 'Long' + }, + 50981: { + 'name': 'ProfileLookTableDims', + 'type': 'Long' + }, + 50982: { + 'name': 'ProfileLookTableData', + 'type': 'Float' + }, + 51008: { + 'name': 'OpcodeList1', + 'type': 'Undefined' + }, + 51009: { + 'name': 'OpcodeList2', + 'type': 'Undefined' + }, + 51022: { + 'name': 'OpcodeList3', + 'type': 'Undefined' + } + }, + 'Exif': { + 33434: { + 'name': 'ExposureTime', + 'type': 'Rational' + }, + 33437: { + 'name': 'FNumber', + 'type': 'Rational' + }, + 34850: { + 'name': 'ExposureProgram', + 'type': 'Short' + }, + 34852: { + 'name': 'SpectralSensitivity', + 'type': 'Ascii' + }, + 34855: { + 'name': 'ISOSpeedRatings', + 'type': 'Short' + }, + 34856: { + 'name': 'OECF', + 'type': 'Undefined' + }, + 34864: { + 'name': 'SensitivityType', + 'type': 'Short' + }, + 34865: { + 'name': 'StandardOutputSensitivity', + 'type': 'Long' + }, + 34866: { + 'name': 'RecommendedExposureIndex', + 'type': 'Long' + }, + 34867: { + 'name': 'ISOSpeed', + 'type': 'Long' + }, + 34868: { + 'name': 'ISOSpeedLatitudeyyy', + 'type': 'Long' + }, + 34869: { + 'name': 'ISOSpeedLatitudezzz', + 'type': 'Long' + }, + 36864: { + 'name': 'ExifVersion', + 'type': 'Undefined' + }, + 36867: { + 'name': 'DateTimeOriginal', + 'type': 'Ascii' + }, + 36868: { + 'name': 'DateTimeDigitized', + 'type': 'Ascii' + }, + 37121: { + 'name': 'ComponentsConfiguration', + 'type': 'Undefined' + }, + 37122: { + 'name': 'CompressedBitsPerPixel', + 'type': 'Rational' + }, + 37377: { + 'name': 'ShutterSpeedValue', + 'type': 'SRational' + }, + 37378: { + 'name': 'ApertureValue', + 'type': 'Rational' + }, + 37379: { + 'name': 'BrightnessValue', + 'type': 'SRational' + }, + 37380: { + 'name': 'ExposureBiasValue', + 'type': 'SRational' + }, + 37381: { + 'name': 'MaxApertureValue', + 'type': 'Rational' + }, + 37382: { + 'name': 'SubjectDistance', + 'type': 'Rational' + }, + 37383: { + 'name': 'MeteringMode', + 'type': 'Short' + }, + 37384: { + 'name': 'LightSource', + 'type': 'Short' + }, + 37385: { + 'name': 'Flash', + 'type': 'Short' + }, + 37386: { + 'name': 'FocalLength', + 'type': 'Rational' + }, + 37396: { + 'name': 'SubjectArea', + 'type': 'Short' + }, + 37500: { + 'name': 'MakerNote', + 'type': 'Undefined' + }, + 37510: { + 'name': 'UserComment', + 'type': 'Ascii' + }, + 37520: { + 'name': 'SubSecTime', + 'type': 'Ascii' + }, + 37521: { + 'name': 'SubSecTimeOriginal', + 'type': 'Ascii' + }, + 37522: { + 'name': 'SubSecTimeDigitized', + 'type': 'Ascii' + }, + 40960: { + 'name': 'FlashpixVersion', + 'type': 'Undefined' + }, + 40961: { + 'name': 'ColorSpace', + 'type': 'Short' + }, + 40962: { + 'name': 'PixelXDimension', + 'type': 'Long' + }, + 40963: { + 'name': 'PixelYDimension', + 'type': 'Long' + }, + 40964: { + 'name': 'RelatedSoundFile', + 'type': 'Ascii' + }, + 40965: { + 'name': 'InteroperabilityTag', + 'type': 'Long' + }, + 41483: { + 'name': 'FlashEnergy', + 'type': 'Rational' + }, + 41484: { + 'name': 'SpatialFrequencyResponse', + 'type': 'Undefined' + }, + 41486: { + 'name': 'FocalPlaneXResolution', + 'type': 'Rational' + }, + 41487: { + 'name': 'FocalPlaneYResolution', + 'type': 'Rational' + }, + 41488: { + 'name': 'FocalPlaneResolutionUnit', + 'type': 'Short' + }, + 41492: { + 'name': 'SubjectLocation', + 'type': 'Short' + }, + 41493: { + 'name': 'ExposureIndex', + 'type': 'Rational' + }, + 41495: { + 'name': 'SensingMethod', + 'type': 'Short' + }, + 41728: { + 'name': 'FileSource', + 'type': 'Undefined' + }, + 41729: { + 'name': 'SceneType', + 'type': 'Undefined' + }, + 41730: { + 'name': 'CFAPattern', + 'type': 'Undefined' + }, + 41985: { + 'name': 'CustomRendered', + 'type': 'Short' + }, + 41986: { + 'name': 'ExposureMode', + 'type': 'Short' + }, + 41987: { + 'name': 'WhiteBalance', + 'type': 'Short' + }, + 41988: { + 'name': 'DigitalZoomRatio', + 'type': 'Rational' + }, + 41989: { + 'name': 'FocalLengthIn35mmFilm', + 'type': 'Short' + }, + 41990: { + 'name': 'SceneCaptureType', + 'type': 'Short' + }, + 41991: { + 'name': 'GainControl', + 'type': 'Short' + }, + 41992: { + 'name': 'Contrast', + 'type': 'Short' + }, + 41993: { + 'name': 'Saturation', + 'type': 'Short' + }, + 41994: { + 'name': 'Sharpness', + 'type': 'Short' + }, + 41995: { + 'name': 'DeviceSettingDescription', + 'type': 'Undefined' + }, + 41996: { + 'name': 'SubjectDistanceRange', + 'type': 'Short' + }, + 42016: { + 'name': 'ImageUniqueID', + 'type': 'Ascii' + }, + 42032: { + 'name': 'CameraOwnerName', + 'type': 'Ascii' + }, + 42033: { + 'name': 'BodySerialNumber', + 'type': 'Ascii' + }, + 42034: { + 'name': 'LensSpecification', + 'type': 'Rational' + }, + 42035: { + 'name': 'LensMake', + 'type': 'Ascii' + }, + 42036: { + 'name': 'LensModel', + 'type': 'Ascii' + }, + 42037: { + 'name': 'LensSerialNumber', + 'type': 'Ascii' + }, + 42240: { + 'name': 'Gamma', + 'type': 'Rational' + } + }, + 'GPS': { + 0: { + 'name': 'GPSVersionID', + 'type': 'Byte' + }, + 1: { + 'name': 'GPSLatitudeRef', + 'type': 'Ascii' + }, + 2: { + 'name': 'GPSLatitude', + 'type': 'Rational' + }, + 3: { + 'name': 'GPSLongitudeRef', + 'type': 'Ascii' + }, + 4: { + 'name': 'GPSLongitude', + 'type': 'Rational' + }, + 5: { + 'name': 'GPSAltitudeRef', + 'type': 'Byte' + }, + 6: { + 'name': 'GPSAltitude', + 'type': 'Rational' + }, + 7: { + 'name': 'GPSTimeStamp', + 'type': 'Rational' + }, + 8: { + 'name': 'GPSSatellites', + 'type': 'Ascii' + }, + 9: { + 'name': 'GPSStatus', + 'type': 'Ascii' + }, + 10: { + 'name': 'GPSMeasureMode', + 'type': 'Ascii' + }, + 11: { + 'name': 'GPSDOP', + 'type': 'Rational' + }, + 12: { + 'name': 'GPSSpeedRef', + 'type': 'Ascii' + }, + 13: { + 'name': 'GPSSpeed', + 'type': 'Rational' + }, + 14: { + 'name': 'GPSTrackRef', + 'type': 'Ascii' + }, + 15: { + 'name': 'GPSTrack', + 'type': 'Rational' + }, + 16: { + 'name': 'GPSImgDirectionRef', + 'type': 'Ascii' + }, + 17: { + 'name': 'GPSImgDirection', + 'type': 'Rational' + }, + 18: { + 'name': 'GPSMapDatum', + 'type': 'Ascii' + }, + 19: { + 'name': 'GPSDestLatitudeRef', + 'type': 'Ascii' + }, + 20: { + 'name': 'GPSDestLatitude', + 'type': 'Rational' + }, + 21: { + 'name': 'GPSDestLongitudeRef', + 'type': 'Ascii' + }, + 22: { + 'name': 'GPSDestLongitude', + 'type': 'Rational' + }, + 23: { + 'name': 'GPSDestBearingRef', + 'type': 'Ascii' + }, + 24: { + 'name': 'GPSDestBearing', + 'type': 'Rational' + }, + 25: { + 'name': 'GPSDestDistanceRef', + 'type': 'Ascii' + }, + 26: { + 'name': 'GPSDestDistance', + 'type': 'Rational' + }, + 27: { + 'name': 'GPSProcessingMethod', + 'type': 'Undefined' + }, + 28: { + 'name': 'GPSAreaInformation', + 'type': 'Undefined' + }, + 29: { + 'name': 'GPSDateStamp', + 'type': 'Ascii' + }, + 30: { + 'name': 'GPSDifferential', + 'type': 'Short' + }, + 31: { + 'name': 'GPSHPositioningError', + 'type': 'Rational' + } + }, + 'Interop': { + 1: { + 'name': 'InteroperabilityIndex', + 'type': 'Ascii' + } + }, +}; +const TYPES = { + "Byte": 1, + "Ascii": 2, + "Short": 3, + "Long": 4, + "Rational": 5, + "Undefined": 7, + "SLong": 9, + "SRational": 10 +}; +TAGS["0th"] = TAGS["Image"]; +TAGS["1st"] = TAGS["Image"]; +class Piexif { + version = "1.0.4" + TAGS = TAGS + ImageIFD = { + ProcessingSoftware: 11, + NewSubfileType: 254, + SubfileType: 255, + ImageWidth: 256, + ImageLength: 257, + BitsPerSample: 258, + Compression: 259, + PhotometricInterpretation: 262, + Threshholding: 263, + CellWidth: 264, + CellLength: 265, + FillOrder: 266, + DocumentName: 269, + ImageDescription: 270, + Make: 271, + Model: 272, + StripOffsets: 273, + Orientation: 274, + SamplesPerPixel: 277, + RowsPerStrip: 278, + StripByteCounts: 279, + XResolution: 282, + YResolution: 283, + PlanarConfiguration: 284, + GrayResponseUnit: 290, + GrayResponseCurve: 291, + T4Options: 292, + T6Options: 293, + ResolutionUnit: 296, + TransferFunction: 301, + Software: 305, + DateTime: 306, + Artist: 315, + HostComputer: 316, + Predictor: 317, + WhitePoint: 318, + PrimaryChromaticities: 319, + ColorMap: 320, + HalftoneHints: 321, + TileWidth: 322, + TileLength: 323, + TileOffsets: 324, + TileByteCounts: 325, + SubIFDs: 330, + InkSet: 332, + InkNames: 333, + NumberOfInks: 334, + DotRange: 336, + TargetPrinter: 337, + ExtraSamples: 338, + SampleFormat: 339, + SMinSampleValue: 340, + SMaxSampleValue: 341, + TransferRange: 342, + ClipPath: 343, + XClipPathUnits: 344, + YClipPathUnits: 345, + Indexed: 346, + JPEGTables: 347, + OPIProxy: 351, + JPEGProc: 512, + JPEGInterchangeFormat: 513, + JPEGInterchangeFormatLength: 514, + JPEGRestartInterval: 515, + JPEGLosslessPredictors: 517, + JPEGPointTransforms: 518, + JPEGQTables: 519, + JPEGDCTables: 520, + JPEGACTables: 521, + YCbCrCoefficients: 529, + YCbCrSubSampling: 530, + YCbCrPositioning: 531, + ReferenceBlackWhite: 532, + XMLPacket: 700, + Rating: 18246, + RatingPercent: 18249, + ImageID: 32781, + CFARepeatPatternDim: 33421, + CFAPattern: 33422, + BatteryLevel: 33423, + Copyright: 33432, + ExposureTime: 33434, + ImageResources: 34377, + ExifTag: 34665, + InterColorProfile: 34675, + GPSTag: 34853, + Interlace: 34857, + TimeZoneOffset: 34858, + SelfTimerMode: 34859, + FlashEnergy: 37387, + SpatialFrequencyResponse: 37388, + Noise: 37389, + FocalPlaneXResolution: 37390, + FocalPlaneYResolution: 37391, + FocalPlaneResolutionUnit: 37392, + ImageNumber: 37393, + SecurityClassification: 37394, + ImageHistory: 37395, + ExposureIndex: 37397, + TIFFEPStandardID: 37398, + SensingMethod: 37399, + XPTitle: 40091, + XPComment: 40092, + XPAuthor: 40093, + XPKeywords: 40094, + XPSubject: 40095, + PrintImageMatching: 50341, + DNGVersion: 50706, + DNGBackwardVersion: 50707, + UniqueCameraModel: 50708, + LocalizedCameraModel: 50709, + CFAPlaneColor: 50710, + CFALayout: 50711, + LinearizationTable: 50712, + BlackLevelRepeatDim: 50713, + BlackLevel: 50714, + BlackLevelDeltaH: 50715, + BlackLevelDeltaV: 50716, + WhiteLevel: 50717, + DefaultScale: 50718, + DefaultCropOrigin: 50719, + DefaultCropSize: 50720, + ColorMatrix1: 50721, + ColorMatrix2: 50722, + CameraCalibration1: 50723, + CameraCalibration2: 50724, + ReductionMatrix1: 50725, + ReductionMatrix2: 50726, + AnalogBalance: 50727, + AsShotNeutral: 50728, + AsShotWhiteXY: 50729, + BaselineExposure: 50730, + BaselineNoise: 50731, + BaselineSharpness: 50732, + BayerGreenSplit: 50733, + LinearResponseLimit: 50734, + CameraSerialNumber: 50735, + LensInfo: 50736, + ChromaBlurRadius: 50737, + AntiAliasStrength: 50738, + ShadowScale: 50739, + DNGPrivateData: 50740, + MakerNoteSafety: 50741, + CalibrationIlluminant1: 50778, + CalibrationIlluminant2: 50779, + BestQualityScale: 50780, + RawDataUniqueID: 50781, + OriginalRawFileName: 50827, + OriginalRawFileData: 50828, + ActiveArea: 50829, + MaskedAreas: 50830, + AsShotICCProfile: 50831, + AsShotPreProfileMatrix: 50832, + CurrentICCProfile: 50833, + CurrentPreProfileMatrix: 50834, + ColorimetricReference: 50879, + CameraCalibrationSignature: 50931, + ProfileCalibrationSignature: 50932, + AsShotProfileName: 50934, + NoiseReductionApplied: 50935, + ProfileName: 50936, + ProfileHueSatMapDims: 50937, + ProfileHueSatMapData1: 50938, + ProfileHueSatMapData2: 50939, + ProfileToneCurve: 50940, + ProfileEmbedPolicy: 50941, + ProfileCopyright: 50942, + ForwardMatrix1: 50964, + ForwardMatrix2: 50965, + PreviewApplicationName: 50966, + PreviewApplicationVersion: 50967, + PreviewSettingsName: 50968, + PreviewSettingsDigest: 50969, + PreviewColorSpace: 50970, + PreviewDateTime: 50971, + RawImageDigest: 50972, + OriginalRawFileDigest: 50973, + SubTileBlockSize: 50974, + RowInterleaveFactor: 50975, + ProfileLookTableDims: 50981, + ProfileLookTableData: 50982, + OpcodeList1: 51008, + OpcodeList2: 51009, + OpcodeList3: 51022, + NoiseProfile: 51041, + } + ExifIFD = { + ExposureTime: 33434, + FNumber: 33437, + ExposureProgram: 34850, + SpectralSensitivity: 34852, + ISOSpeedRatings: 34855, + OECF: 34856, + SensitivityType: 34864, + StandardOutputSensitivity: 34865, + RecommendedExposureIndex: 34866, + ISOSpeed: 34867, + ISOSpeedLatitudeyyy: 34868, + ISOSpeedLatitudezzz: 34869, + ExifVersion: 36864, + DateTimeOriginal: 36867, + DateTimeDigitized: 36868, + ComponentsConfiguration: 37121, + CompressedBitsPerPixel: 37122, + ShutterSpeedValue: 37377, + ApertureValue: 37378, + BrightnessValue: 37379, + ExposureBiasValue: 37380, + MaxApertureValue: 37381, + SubjectDistance: 37382, + MeteringMode: 37383, + LightSource: 37384, + Flash: 37385, + FocalLength: 37386, + SubjectArea: 37396, + MakerNote: 37500, + UserComment: 37510, + SubSecTime: 37520, + SubSecTimeOriginal: 37521, + SubSecTimeDigitized: 37522, + FlashpixVersion: 40960, + ColorSpace: 40961, + PixelXDimension: 40962, + PixelYDimension: 40963, + RelatedSoundFile: 40964, + InteroperabilityTag: 40965, + FlashEnergy: 41483, + SpatialFrequencyResponse: 41484, + FocalPlaneXResolution: 41486, + FocalPlaneYResolution: 41487, + FocalPlaneResolutionUnit: 41488, + SubjectLocation: 41492, + ExposureIndex: 41493, + SensingMethod: 41495, + FileSource: 41728, + SceneType: 41729, + CFAPattern: 41730, + CustomRendered: 41985, + ExposureMode: 41986, + WhiteBalance: 41987, + DigitalZoomRatio: 41988, + FocalLengthIn35mmFilm: 41989, + SceneCaptureType: 41990, + GainControl: 41991, + Contrast: 41992, + Saturation: 41993, + Sharpness: 41994, + DeviceSettingDescription: 41995, + SubjectDistanceRange: 41996, + ImageUniqueID: 42016, + CameraOwnerName: 42032, + BodySerialNumber: 42033, + LensSpecification: 42034, + LensMake: 42035, + LensModel: 42036, + LensSerialNumber: 42037, + Gamma: 42240, + } + GPSIFD = { + GPSVersionID: 0, + GPSLatitudeRef: 1, + GPSLatitude: 2, + GPSLongitudeRef: 3, + GPSLongitude: 4, + GPSAltitudeRef: 5, + GPSAltitude: 6, + GPSTimeStamp: 7, + GPSSatellites: 8, + GPSStatus: 9, + GPSMeasureMode: 10, + GPSDOP: 11, + GPSSpeedRef: 12, + GPSSpeed: 13, + GPSTrackRef: 14, + GPSTrack: 15, + GPSImgDirectionRef: 16, + GPSImgDirection: 17, + GPSMapDatum: 18, + GPSDestLatitudeRef: 19, + GPSDestLatitude: 20, + GPSDestLongitudeRef: 21, + GPSDestLongitude: 22, + GPSDestBearingRef: 23, + GPSDestBearing: 24, + GPSDestDistanceRef: 25, + GPSDestDistance: 26, + GPSProcessingMethod: 27, + GPSAreaInformation: 28, + GPSDateStamp: 29, + GPSDifferential: 30, + GPSHPositioningError: 31, + } + InteropIFD = { + InteroperabilityIndex: 1, + } + GPSHelper = { + degToDmsRational(degFloat : number) { + const degAbs = Math.abs(degFloat); + const minFloat = degAbs % 1 * 60; + const secFloat = minFloat % 1 * 60; + const deg = Math.floor(degAbs); + const min = Math.floor(minFloat); + const sec = Math.round(secFloat * 100); + + return [ + [deg, 1], + [min, 1], + [sec, 100] + ]; + }, + dmsRationalToDeg(dmsArray : number[][], ref : string) { + const sign = (ref === 'S' || ref === 'W') ? -1.0 : 1.0; + const deg = dmsArray[0][0] / dmsArray[0][1] + + dmsArray[1][0] / dmsArray[1][1] / 60.0 + + dmsArray[2][0] / dmsArray[2][1] / 3600.0; + + return deg * sign; + }, + } + remove(jpeg) { + let b64 = false; + if (jpeg.slice(0, 2) == "\xff\xd8") { } else if (jpeg.slice(0, 23) == "data:image/jpeg;base64," || jpeg + .slice(0, 22) == "data:image/jpg;base64,") { + jpeg = atob(jpeg.split(",")[1]); + b64 = true; + } else { + throw new Error("Given data is not jpeg."); + } + + const segments = splitIntoSegments(jpeg); + const newSegments = segments.filter(function (seg) { + return !(seg.slice(0, 2) == "\xff\xe1" && + seg.slice(4, 10) == "Exif\x00\x00"); + }); + + let new_data = newSegments.join(""); + if (b64) { + new_data = "data:image/jpeg;base64," + btoa(new_data); + } + + return new_data; + } + insert(exif, jpeg) { + let b64 = false; + if (exif.slice(0, 6) != "\x45\x78\x69\x66\x00\x00") { + throw new Error("Given data is not exif."); + } + if (jpeg.slice(0, 2) == "\xff\xd8") { } else if (jpeg.slice(0, 23) == "data:image/jpeg;base64," || jpeg + .slice(0, 22) == "data:image/jpg;base64,") { + jpeg = atob(jpeg.split(",")[1]); + b64 = true; + } else { + throw new Error("Given data is not jpeg."); + } + + const exifStr = "\xff\xe1" + pack(">H", [exif.length + 2]) + exif; + const segments = splitIntoSegments(jpeg); + let new_data = mergeSegments(segments, exifStr); + if (b64) { + new_data = "data:image/jpeg;base64," + btoa(new_data); + } + + return new_data; + } + load(data) { + let input_data; + if (isString(data)) { + if (data.slice(0, 2) == "\xff\xd8") { + input_data = data; + } else if (data.slice(0, 23) == "data:image/jpeg;base64," || data.slice(0, 22) == "data:image/jpg;base64,") { + input_data = atob(data.split(",")[1]); + } else if (data.slice(0, 4) == "Exif") { + input_data = data.slice(6); + } else { + throw new Error("'load' gots invalid file data."); + } + } else { + throw new Error("'load' gots invalid type argument."); + } + + let exifDict = {}; + let exif_dict = { + "0th": {}, + "Exif": {}, + "GPS": {}, + "Interop": {}, + "1st": {}, + "thumbnail": null + }; + const exifReader = new ExifReader(input_data); + if (exifReader.tiftag === null) { + return exif_dict; + } + + if (exifReader.tiftag.slice(0, 2) == "\x49\x49") { + exifReader.endian_mark = "<"; + } else { + exifReader.endian_mark = ">"; + } + + let pointer = unpack(exifReader.endian_mark + "L", + exifReader.tiftag.slice(4, 8))[0]; + exif_dict["0th"] = exifReader.get_ifd(pointer, "0th"); + + const first_ifd_pointer = exif_dict["0th"]["first_ifd_pointer"]; + delete exif_dict["0th"]["first_ifd_pointer"]; + + if (34665 in exif_dict["0th"]) { + pointer = exif_dict["0th"][34665]; + exif_dict["Exif"] = exifReader.get_ifd(pointer, "Exif"); + } + if (34853 in exif_dict["0th"]) { + pointer = exif_dict["0th"][34853]; + exif_dict["GPS"] = exifReader.get_ifd(pointer, "GPS"); + } + if (40965 in exif_dict["Exif"]) { + pointer = exif_dict["Exif"][40965]; + exif_dict["Interop"] = exifReader.get_ifd(pointer, "Interop"); + } + if (first_ifd_pointer != "\x00\x00\x00\x00") { + pointer = unpack(exifReader.endian_mark + "L", + first_ifd_pointer)[0]; + exif_dict["1st"] = exifReader.get_ifd(pointer, "1st"); + if ((513 in exif_dict["1st"]) && (514 in exif_dict["1st"])) { + var end = exif_dict["1st"][513] + exif_dict["1st"][514]; + var thumb = exifReader.tiftag.slice(exif_dict["1st"][513], end); + exif_dict["thumbnail"] = thumb; + } + } + + return exif_dict; + } + dump(exif_dict_original) { + const TIFF_HEADER_LENGTH = 8; + + const exif_dict : any = cloneDeep(exif_dict_original); + const header = "Exif\x00\x00\x4d\x4d\x00\x2a\x00\x00\x00\x08"; + let exif_is = 0//false; + let gps_is = 0 //false; + let interop_is = 0//false; + let first_is = false; + + let zeroth_ifd, + exif_ifd, + interop_ifd, + gps_ifd, + first_ifd; + + if ("0th" in exif_dict) { + zeroth_ifd = exif_dict["0th"]; + } else { + zeroth_ifd = {}; + } + + if ((("Exif" in exif_dict) && (Object.keys(exif_dict["Exif"]).length)) || + (("Interop" in exif_dict) && (Object.keys(exif_dict["Interop"]).length))) { + zeroth_ifd[34665] = 1; + exif_is = 1//true; + exif_ifd = exif_dict["Exif"]; + if (("Interop" in exif_dict) && Object.keys(exif_dict["Interop"]).length) { + exif_ifd[40965] = 1; + interop_is = 1//true; + interop_ifd = exif_dict["Interop"]; + } else if (Object.keys(exif_ifd).indexOf(this.ExifIFD.InteroperabilityTag.toString()) > -1) { + delete exif_ifd[40965]; + } + } else if (Object.keys(zeroth_ifd).indexOf(this.ImageIFD.ExifTag.toString()) > -1) { + delete zeroth_ifd[34665]; + } + + if (("GPS" in exif_dict) && (Object.keys(exif_dict["GPS"]).length)) { + zeroth_ifd[this.ImageIFD.GPSTag] = 1; + gps_is = 1 //true; + gps_ifd = exif_dict["GPS"]; + } else if (Object.keys(zeroth_ifd).indexOf(this.ImageIFD.GPSTag.toString()) > -1) { + delete zeroth_ifd[this.ImageIFD.GPSTag]; + } + + if (("1st" in exif_dict) && + ("thumbnail" in exif_dict) && + (exif_dict["thumbnail"] != null)) { + first_is = true; + exif_dict["1st"][513] = 1; + exif_dict["1st"][514] = 1; + first_ifd = exif_dict["1st"]; + } + + const zeroth_set = _dict_to_bytes(zeroth_ifd, "0th", 0); + const zeroth_length = (zeroth_set[0].length + exif_is * 12 + gps_is * 12 + 4 + + zeroth_set[1].length); + + let exif_set, + exif_bytes = "", + exif_length = 0, + gps_set, + gps_bytes = "", + gps_length = 0, + interop_set, + interop_bytes = "", + interop_length = 0, + first_set, + first_bytes = "", + thumbnail; + if (exif_is) { + exif_set = _dict_to_bytes(exif_ifd, "Exif", zeroth_length); + exif_length = exif_set[0].length + interop_is * 12 + exif_set[1].length; + } + if (gps_is) { + gps_set = _dict_to_bytes(gps_ifd, "GPS", zeroth_length + exif_length); + gps_bytes = gps_set.join(""); + gps_length = gps_bytes.length; + } + if (interop_is) { + const offset = zeroth_length + exif_length + gps_length; + interop_set = _dict_to_bytes(interop_ifd, "Interop", offset); + interop_bytes = interop_set.join(""); + interop_length = interop_bytes.length; + } + if (first_is) { + const offset = zeroth_length + exif_length + gps_length + interop_length; + first_set = _dict_to_bytes(first_ifd, "1st", offset); + thumbnail = _get_thumbnail(exif_dict["thumbnail"]); + if (thumbnail.length > 64000) { + throw new Error("Given thumbnail is too large. max 64kB"); + } + } + + let exif_pointer = "", + gps_pointer = "", + interop_pointer = "", + first_ifd_pointer = "\x00\x00\x00\x00"; + if (exif_is) { + const pointer_value = TIFF_HEADER_LENGTH + zeroth_length; + const pointer_str = pack(">L", [pointer_value]); + const key = 34665; + const key_str = pack(">H", [key]); + const type_str = pack(">H", [TYPES["Long"]]); + const length_str = pack(">L", [1]); + exif_pointer = key_str + type_str + length_str + pointer_str; + } + if (gps_is) { + const pointer_value = TIFF_HEADER_LENGTH + zeroth_length + exif_length; + const pointer_str = pack(">L", [pointer_value]); + const key = 34853; + const key_str = pack(">H", [key]); + const type_str = pack(">H", [TYPES["Long"]]); + const length_str = pack(">L", [1]); + gps_pointer = key_str + type_str + length_str + pointer_str; + } + if (interop_is) { + const pointer_value = (TIFF_HEADER_LENGTH + + zeroth_length + exif_length + gps_length); + const pointer_str = pack(">L", [pointer_value]); + const key = 40965; + const key_str = pack(">H", [key]); + const type_str = pack(">H", [TYPES["Long"]]); + const length_str = pack(">L", [1]); + interop_pointer = key_str + type_str + length_str + pointer_str; + } + if (first_is) { + const pointer_value = (TIFF_HEADER_LENGTH + zeroth_length + + exif_length + gps_length + interop_length); + first_ifd_pointer = pack(">L", [pointer_value]); + const thumbnail_pointer = (pointer_value + first_set[0].length + 24 + + 4 + first_set[1].length); + const thumbnail_p_bytes = ("\x02\x01\x00\x04\x00\x00\x00\x01" + + pack(">L", [thumbnail_pointer])); + const thumbnail_length_bytes = ("\x02\x02\x00\x04\x00\x00\x00\x01" + + pack(">L", [thumbnail.length])); + first_bytes = (first_set[0] + thumbnail_p_bytes + + thumbnail_length_bytes + "\x00\x00\x00\x00" + + first_set[1] + thumbnail); + } + + const zeroth_bytes = (zeroth_set[0] + exif_pointer + gps_pointer + + first_ifd_pointer + zeroth_set[1]); + if (exif_is) { + exif_bytes = exif_set[0] + interop_pointer + exif_set[1]; + } + + return (header + zeroth_bytes + exif_bytes + gps_bytes + + interop_bytes + first_bytes); + } +} + + + +function _get_thumbnail(jpeg) { + let segments = splitIntoSegments(jpeg); + while (("\xff\xe0" <= segments[1].slice(0, 2)) && (segments[1].slice(0, 2) <= "\xff\xef")) { + segments = [segments[0]].concat(segments.slice(2)); + } + return segments.join(""); +} +function _pack_byte(array) { + return pack(">" + nStr("B", array.length), array); +} + +function _pack_short(array) { + return pack(">" + nStr("H", array.length), array); +} + +function _pack_long(array) { + return pack(">" + nStr("L", array.length), array); +} + +function _value_to_bytes(raw_value, value_type, offset) { + let four_bytes_over = ""; + let value_str = ""; + let length, + new_value, + num, + den; + + if (value_type == "Byte") { + length = raw_value.length; + if (length <= 4) { + value_str = (_pack_byte(raw_value) + + nStr("\x00", 4 - length)); + } else { + value_str = pack(">L", [offset]); + four_bytes_over = _pack_byte(raw_value); + } + } else if (value_type == "Short") { + length = raw_value.length; + if (length <= 2) { + value_str = (_pack_short(raw_value) + + nStr("\x00\x00", 2 - length)); + } else { + value_str = pack(">L", [offset]); + four_bytes_over = _pack_short(raw_value); + } + } else if (value_type == "Long") { + length = raw_value.length; + if (length <= 1) { + value_str = _pack_long(raw_value); + } else { + value_str = pack(">L", [offset]); + four_bytes_over = _pack_long(raw_value); + } + } else if (value_type == "Ascii") { + new_value = raw_value + "\x00"; + length = new_value.length; + if (length > 4) { + value_str = pack(">L", [offset]); + four_bytes_over = new_value; + } else { + value_str = new_value + nStr("\x00", 4 - length); + } + } else if (value_type == "Rational") { + if (typeof (raw_value[0]) == "number") { + length = 1; + num = raw_value[0]; + den = raw_value[1]; + new_value = pack(">L", [num]) + pack(">L", [den]); + } else { + length = raw_value.length; + new_value = ""; + for (var n = 0; n < length; n++) { + num = raw_value[n][0]; + den = raw_value[n][1]; + new_value += (pack(">L", [num]) + + pack(">L", [den])); + } + } + value_str = pack(">L", [offset]); + four_bytes_over = new_value; + } else if (value_type == "SRational") { + if (typeof (raw_value[0]) == "number") { + length = 1; + num = raw_value[0]; + den = raw_value[1]; + new_value = pack(">l", [num]) + pack(">l", [den]); + } else { + length = raw_value.length; + new_value = ""; + for (var n = 0; n < length; n++) { + num = raw_value[n][0]; + den = raw_value[n][1]; + new_value += (pack(">l", [num]) + + pack(">l", [den])); + } + } + value_str = pack(">L", [offset]); + four_bytes_over = new_value; + } else if (value_type == "Undefined") { + length = raw_value.length; + if (length > 4) { + value_str = pack(">L", [offset]); + four_bytes_over = raw_value; + } else { + value_str = raw_value + nStr("\x00", 4 - length); + } + } + + var length_str = pack(">L", [length]); + + return [length_str, value_str, four_bytes_over]; +} + +function _dict_to_bytes(ifd_dict, ifd, ifd_offset) { + const TIFF_HEADER_LENGTH = 8; + const tag_count = Object.keys(ifd_dict).length; + const entry_header = pack(">H", [tag_count]); + let entries_length; + if (["0th", "1st"].indexOf(ifd) > -1) { + entries_length = 2 + tag_count * 12 + 4; + } else { + entries_length = 2 + tag_count * 12; + } + let entries = ""; + let values = ""; + let key; + + for (key in ifd_dict) { + if (typeof (key) == "string") { + key = parseInt(key); + } + if ((ifd == "0th") && ([34665, 34853].indexOf(key) > -1)) { + continue; + } else if ((ifd == "Exif") && (key == 40965)) { + continue; + } else if ((ifd == "1st") && ([513, 514].indexOf(key) > -1)) { + continue; + } + + var raw_value = ifd_dict[key]; + var key_str = pack(">H", [key]); + var value_type = TAGS[ifd][key]["type"]; + var type_str = pack(">H", [TYPES[value_type]]); + + if (typeof (raw_value) == "number") { + raw_value = [raw_value]; + } + var offset = TIFF_HEADER_LENGTH + entries_length + ifd_offset + values.length; + var b = _value_to_bytes(raw_value, value_type, offset); + var length_str = b[0]; + var value_str = b[1]; + var four_bytes_over = b[2]; + + entries += key_str + type_str + length_str + value_str; + values += four_bytes_over; + } + + return [entry_header + entries, values]; +} + + + +class ExifReader { + tiftag = null + endian_mark = '' + constructor(data) { + let segments, + app1; + if (data.slice(0, 2) == "\xff\xd8") { // JPEG + segments = splitIntoSegments(data); + app1 = getExifSeg(segments); + if (app1) { + this.tiftag = app1.slice(10); + } + } else if (["\x49\x49", "\x4d\x4d"].indexOf(data.slice(0, 2)) > -1) { // TIFF + this.tiftag = data; + } else if (data.slice(0, 4) == "Exif") { // Exif + this.tiftag = data.slice(6); + } else { + throw new Error("Given file is neither JPEG nor TIFF."); + } + } + get_ifd(pointer, ifd_name) { + let ifd_dict = {}; + let tag_count = unpack(this.endian_mark + "H", + this.tiftag.slice(pointer, pointer + 2))[0]; + const offset = pointer + 2; + let t; + if (["0th", "1st"].indexOf(ifd_name) > -1) { + t = "Image"; + } else { + t = ifd_name; + } + + for (let x = 0; x < tag_count; x++) { + pointer = offset + 12 * x; + const tag = unpack(this.endian_mark + "H", + this.tiftag.slice(pointer, pointer + 2))[0]; + const value_type = unpack(this.endian_mark + "H", + this.tiftag.slice(pointer + 2, pointer + 4))[0]; + const value_num = unpack(this.endian_mark + "L", + this.tiftag.slice(pointer + 4, pointer + 8))[0]; + const value = this.tiftag.slice(pointer + 8, pointer + 12); + + const v_set = [value_type, value_num, value]; + if (tag in TAGS[t]) { + ifd_dict[tag] = this.convert_value(v_set); + } + } + + if (ifd_name == "0th") { + pointer = offset + 12 * tag_count; + ifd_dict["first_ifd_pointer"] = this.tiftag.slice(pointer, pointer + 4); + } + + return ifd_dict; + } + + convert_value(val) { + let data = null; + const t = val[0]; + const length = val[1]; + const value = val[2]; + let pointer; + + if (t == 1) { // BYTE + if (length > 4) { + pointer = unpack(this.endian_mark + "L", value)[0]; + data = unpack(this.endian_mark + nStr("B", length), + this.tiftag.slice(pointer, pointer + length)); + } else { + data = unpack(this.endian_mark + nStr("B", length), value.slice(0, length)); + } + } else if (t == 2) { // ASCII + if (length > 4) { + pointer = unpack(this.endian_mark + "L", value)[0]; + data = this.tiftag.slice(pointer, pointer + length - 1); + } else { + data = value.slice(0, length - 1); + } + } else if (t == 3) { // SHORT + if (length > 2) { + pointer = unpack(this.endian_mark + "L", value)[0]; + data = unpack(this.endian_mark + nStr("H", length), + this.tiftag.slice(pointer, pointer + length * 2)); + } else { + data = unpack(this.endian_mark + nStr("H", length), + value.slice(0, length * 2)); + } + } else if (t == 4) { // LONG + if (length > 1) { + pointer = unpack(this.endian_mark + "L", value)[0]; + data = unpack(this.endian_mark + nStr("L", length), + this.tiftag.slice(pointer, pointer + length * 4)); + } else { + data = unpack(this.endian_mark + nStr("L", length), + value); + } + } else if (t == 5) { // RATIONAL + pointer = unpack(this.endian_mark + "L", value)[0]; + if (length > 1) { + data = []; + for (let x = 0; x < length; x++) { + data.push([unpack(this.endian_mark + "L", + this.tiftag.slice(pointer + x * 8, pointer + 4 + x * 8))[0], + unpack(this.endian_mark + "L", + this.tiftag.slice(pointer + 4 + x * 8, pointer + 8 + x * 8))[0] + ]); + } + } else { + data = [unpack(this.endian_mark + "L", + this.tiftag.slice(pointer, pointer + 4))[0], + unpack(this.endian_mark + "L", + this.tiftag.slice(pointer + 4, pointer + 8))[0] + ]; + } + } else if (t == 7) { // UNDEFINED BYTES + if (length > 4) { + pointer = unpack(this.endian_mark + "L", value)[0]; + data = this.tiftag.slice(pointer, pointer + length); + } else { + data = value.slice(0, length); + } + } else if (t == 9) { // SLONG + if (length > 1) { + pointer = unpack(this.endian_mark + "L", value)[0]; + data = unpack(this.endian_mark + nStr("l", length), + this.tiftag.slice(pointer, pointer + length * 4)); + } else { + data = unpack(this.endian_mark + nStr("l", length), + value); + } + } else if (t == 10) { // SRATIONAL + pointer = unpack(this.endian_mark + "L", value)[0]; + if (length > 1) { + data = []; + for (let x = 0; x < length; x++) { + data.push([unpack(this.endian_mark + "l", + this.tiftag.slice(pointer + x * 8, pointer + 4 + x * 8))[0], + unpack(this.endian_mark + "l", + this.tiftag.slice(pointer + 4 + x * 8, pointer + 8 + x * 8))[0] + ]); + } + } else { + data = [unpack(this.endian_mark + "l", + this.tiftag.slice(pointer, pointer + 4))[0], + unpack(this.endian_mark + "l", + this.tiftag.slice(pointer + 4, pointer + 8))[0] + ]; + } + } else { + throw new Error("Exif might be wrong. Got incorrect value " + + "type to decode. type:" + t); + } + + if ((data instanceof Array) && (data.length == 1)) { + return data[0]; + } else { + return data; + } + } + +} + + +function pack(mark, array) { + if (!(array instanceof Array)) { + throw new Error("'pack' error. Got invalid type argument."); + } + if ((mark.length - 1) != array.length) { + throw new Error("'pack' error. " + (mark.length - 1) + " marks, " + array.length + " elements."); + } + + let littleEndian; + if (mark[0] == "<") { + littleEndian = true; + } else if (mark[0] == ">") { + littleEndian = false; + } else { + throw new Error(""); + } + let packed = ""; + let p = 1; + let val = null; + let c = null; + let valStr = null; + + while (c = mark[p]) { + if (c.toLowerCase() == "b") { + val = array[p - 1]; + if ((c == "b") && (val < 0)) { + val += 0x100; + } + if ((val > 0xff) || (val < 0)) { + throw new Error("'pack' error."); + } else { + valStr = String.fromCharCode(val); + } + } else if (c == "H") { + val = array[p - 1]; + if ((val > 0xffff) || (val < 0)) { + throw new Error("'pack' error."); + } else { + valStr = String.fromCharCode(Math.floor((val % 0x10000) / 0x100)) + + String.fromCharCode(val % 0x100); + if (littleEndian) { + valStr = valStr.split("").reverse().join(""); + } + } + } else if (c.toLowerCase() == "l") { + val = array[p - 1]; + if ((c == "l") && (val < 0)) { + val += 0x100000000; + } + if ((val > 0xffffffff) || (val < 0)) { + throw new Error("'pack' error."); + } else { + valStr = String.fromCharCode(Math.floor(val / 0x1000000)) + + String.fromCharCode(Math.floor((val % 0x1000000) / 0x10000)) + + String.fromCharCode(Math.floor((val % 0x10000) / 0x100)) + + String.fromCharCode(val % 0x100); + if (littleEndian) { + valStr = valStr.split("").reverse().join(""); + } + } + } else { + throw new Error("'pack' error."); + } + + packed += valStr; + p += 1; + } + + return packed; +} + + +function unpack(mark, str) { + if (typeof (str) != "string") { + throw new Error("'unpack' error. Got invalid type argument."); + } + let l = 0; + for (let markPointer = 1; markPointer < mark.length; markPointer++) { + if (mark[markPointer].toLowerCase() == "b") { + l += 1; + } else if (mark[markPointer].toLowerCase() == "h") { + l += 2; + } else if (mark[markPointer].toLowerCase() == "l") { + l += 4; + } else { + throw new Error("'unpack' error. Got invalid mark."); + } + } + + if (l != str.length) { + throw new Error("'unpack' error. Mismatch between symbol and string length. " + l + ":" + str.length); + } + + let littleEndian; + if (mark[0] == "<") { + littleEndian = true; + } else if (mark[0] == ">") { + littleEndian = false; + } else { + throw new Error("'unpack' error."); + } + let unpacked = []; + let strPointer = 0; + let p = 1; + let val = null; + let c = null; + let length = null; + let sliced = ""; + + while (c = mark[p]) { + if (c.toLowerCase() == "b") { + length = 1; + sliced = str.slice(strPointer, strPointer + length); + val = sliced.charCodeAt(0); + if ((c == "b") && (val >= 0x80)) { + val -= 0x100; + } + } else if (c == "H") { + length = 2; + sliced = str.slice(strPointer, strPointer + length); + if (littleEndian) { + sliced = sliced.split("").reverse().join(""); + } + val = sliced.charCodeAt(0) * 0x100 + + sliced.charCodeAt(1); + } else if (c.toLowerCase() == "l") { + length = 4; + sliced = str.slice(strPointer, strPointer + length); + if (littleEndian) { + sliced = sliced.split("").reverse().join(""); + } + val = sliced.charCodeAt(0) * 0x1000000 + + sliced.charCodeAt(1) * 0x10000 + + sliced.charCodeAt(2) * 0x100 + + sliced.charCodeAt(3); + if ((c == "l") && (val >= 0x80000000)) { + val -= 0x100000000; + } + } else { + throw new Error("'unpack' error. " + c); + } + + unpacked.push(val); + strPointer += length; + p += 1; + } + + return unpacked; +} + + +function nStr(ch, num) { + let str = ""; + for (let i = 0; i < num; i++) { + str += ch; + } + return str; +} + + +function splitIntoSegments(data) { + if (data.slice(0, 2) != "\xff\xd8") { + throw new Error("Given data isn't JPEG."); + } + + let head = 2; + const segments = ["\xff\xd8"]; + while (true) { + if (data.slice(head, head + 2) == "\xff\xda") { + segments.push(data.slice(head)); + break; + } else { + var length = unpack(">H", data.slice(head + 2, head + 4))[0]; + var endPoint = head + length + 2; + segments.push(data.slice(head, endPoint)); + head = endPoint; + } + + if (head >= data.length) { + throw new Error("Wrong JPEG data."); + } + } + return segments; +} + + +function getExifSeg(segments) { + let seg; + for (let i = 0; i < segments.length; i++) { + seg = segments[i]; + if (seg.slice(0, 2) == "\xff\xe1" && + seg.slice(4, 10) == "Exif\x00\x00") { + return seg; + } + } + return null; +} + + +function mergeSegments(segments, exif) { + let hasExifSegment = false; + let additionalAPP1ExifSegments = []; + + segments.forEach(function (segment, i) { + // Replace first occurence of APP1:Exif segment + if (segment.slice(0, 2) == "\xff\xe1" && + segment.slice(4, 10) == "Exif\x00\x00" + ) { + if (!hasExifSegment) { + segments[i] = exif; + hasExifSegment = true; + } else { + additionalAPP1ExifSegments.unshift(i); + } + } + }); + + // Remove additional occurences of APP1:Exif segment + additionalAPP1ExifSegments.forEach(function (segmentIndex) { + segments.splice(segmentIndex, 1); + }); + + if (!hasExifSegment && exif) { + segments = [segments[0], exif].concat(segments.slice(1)); + } + + return segments.join(""); +} + + diff --git a/uni_modules/lime-shared/platform/index.ts b/uni_modules/lime-shared/platform/index.ts new file mode 100644 index 0000000..af07091 --- /dev/null +++ b/uni_modules/lime-shared/platform/index.ts @@ -0,0 +1,27 @@ +// @ts-nocheck +declare var tt: Uni +declare var swan: Uni +declare var my: Uni +declare var dd: Uni +declare var ks: Uni +declare var jd: Uni +declare var qa: Uni +declare var qq: Uni +declare var qh: Uni +declare var qq: Uni + +export function platform(): Uni | WechatMiniprogram.Wx { + const UNDEFINED = 'undefined' + if(typeof wx !== UNDEFINED) return wx // 微信 + if(typeof tt !== UNDEFINED) return tt // 字节 飞书 + if(typeof swan !== UNDEFINED) return swan // 百度 + if(typeof my !== UNDEFINED) return my // 支付宝 + if(typeof dd !== UNDEFINED) return dd // 钉钉 + if(typeof ks !== UNDEFINED) return ks // 快手 + if(typeof jd !== UNDEFINED) return jd // 京东 + if(typeof qa !== UNDEFINED) return qa // 快应用 + if(typeof qq !== UNDEFINED) return qq // qq + if(typeof qh !== UNDEFINED) return qh // 360 + if(typeof uni !== UNDEFINED) return uni + return null +} \ No newline at end of file diff --git a/uni_modules/lime-shared/raf/index.ts b/uni_modules/lime-shared/raf/index.ts new file mode 100644 index 0000000..ec7650d --- /dev/null +++ b/uni_modules/lime-shared/raf/index.ts @@ -0,0 +1,30 @@ +// @ts-nocheck +import {isBrowser} from '../isBrowser' + +// 是否支持被动事件监听 +export const supportsPassive = true; + +// 请求动画帧 +export function raf(fn: FrameRequestCallback): number { + // 如果是在浏览器环境下,使用 requestAnimationFrame 方法 + if (isBrowser) { + return requestAnimationFrame(fn); // 请求动画帧 + } else { // 在非浏览器环境下,使用 setTimeout 模拟 + return setTimeout(fn, 1000 / 30); // 使用 setTimeout 模拟动画帧,每秒钟执行 30 次 + } +} + +// 取消动画帧 +export function cancelRaf(id: number) { + // 如果是在浏览器环境下,使用 cancelAnimationFrame 方法 + if (isBrowser) { + cancelAnimationFrame(id); // 取消动画帧 + } else { // 在非浏览器环境下,使用 clearTimeout 模拟 + clearTimeout(id); // 使用 clearTimeout 模拟取消动画帧 + } +} + +// 双倍动画帧 +export function doubleRaf(fn: FrameRequestCallback): void { + raf(() => raf(fn)); // 在下一帧回调中再次请求动画帧,实现双倍动画帧效果 +} \ No newline at end of file diff --git a/uni_modules/lime-shared/random/index.ts b/uni_modules/lime-shared/random/index.ts new file mode 100644 index 0000000..dbf2681 --- /dev/null +++ b/uni_modules/lime-shared/random/index.ts @@ -0,0 +1,22 @@ +// @ts-nocheck +/** + * 生成一个指定范围内的随机数 + * @param min 随机数的最小值 + * @param max 随机数的最大值 + * @param fixed 随机数的小数位数,默认为 0 + * @returns 生成的随机数 + */ +export function random(min: number, max: number, fixed: number = 0) { + // 将 min 和 max 转换为数字类型 + min = +min || 0; + max = +max || 0; + // 计算随机数范围内的一个随机数 + const num = Math.random() * (max - min) + min; + // 如果 fixed 参数为 0,则返回四舍五入的整数随机数;否则保留固定小数位数 + return fixed == 0 ? Math.round(num) : Number(num.toFixed(fixed)); +} + +// 示例 +// console.log(random(0, 10)); // 输出:在 0 和 10 之间的一个整数随机数 +// console.log(random(0, 1, 2)); // 输出:在 0 和 1 之间的一个保留两位小数的随机数 +// console.log(random(1, 100, 3)); // 输出:在 1 和 100 之间的一个保留三位小数的随机数 \ No newline at end of file diff --git a/uni_modules/lime-shared/range/index.ts b/uni_modules/lime-shared/range/index.ts new file mode 100644 index 0000000..80acfbf --- /dev/null +++ b/uni_modules/lime-shared/range/index.ts @@ -0,0 +1,31 @@ +// @ts-nocheck +/** + * 生成一个数字范围的数组 + * @param start 范围的起始值 + * @param end 范围的结束值 + * @param step 步长,默认为 1 + * @param fromRight 是否从右侧开始生成,默认为 false + * @returns 生成的数字范围数组 + */ +export function range(start: number, end: number, step: number = 1, fromRight: boolean = false): number[] { + let index = -1; + // 计算范围的长度 + let length = Math.max(Math.ceil((end - start) / (step || 1)), 0); + // 创建一个长度为 length 的数组 + const result = new Array(length); + + // 使用循环生成数字范围数组 + while (length--) { + // 根据 fromRight 参数决定从左侧还是右侧开始填充数组 + result[fromRight ? length : ++index] = start; + start += step; + } + + return result; +} + + +// 示例 +// console.log(range(0, 5)); // 输出: [0, 1, 2, 3, 4] +// console.log(range(1, 10, 2, true)); // 输出: [9, 7, 5, 3, 1] +// console.log(range(5, 0, -1)); // 输出: [5, 4, 3, 2, 1] \ No newline at end of file diff --git a/uni_modules/lime-shared/readme.md b/uni_modules/lime-shared/readme.md new file mode 100644 index 0000000..09ec7e9 --- /dev/null +++ b/uni_modules/lime-shared/readme.md @@ -0,0 +1,251 @@ +# lime-shared 工具库 +- 本人插件的几个公共函数 + +## 引入 +```js +// 按需引入 +// 这种只会引入相关的方法 +import {getRect} from '@/uni_modules/lime-shared/getRect' + +// 全量引入 +// 这种引入方式,会全量打包 +import {getRect} from '@/uni_modules/lime-shared' +``` + +## Utils + +#### getRect +- 返回节点尺寸信息 + +```js +// 组件内需要传入上下文 +// 如果是nvue 则需要在节点上加与id或class同名的ref +getRect('#id',{context: this}).then(res => {}) +``` + +#### addUnit +- 将未带单位的数值添加px,如果有单位则返回原值 + +```js +addUnit(10) +// 10px +``` + +#### unitConvert +- 将带有rpx|px的字符转成number,若本身是number则直接返回 + +```js +unitConvert('10rpx') +// 5 设备不同 返回的值也不同 +unitConvert('10px') +// 10 +unitConvert(10) +// 10 +``` + +#### canIUseCanvas2d +- 环境是否支持使用 canvas 2d + +```js +canIUseCanvas2d() +// 若支持返回 true 否则 false +``` + + +#### getCurrentPage +- 获取当前页 + +```js +const page = getCurrentPage() +``` + + +#### base64ToPath +- 把base64的图片转成临时路径 + +```js +base64ToPath(`xxxxx`).then(res => {}) +``` + +#### pathToBase64 +- 把图片的临时路径转成base64 + +```js +pathToBase64(`xxxxx/xxx.png`).then(res => {}) +``` + +#### sleep +- 睡眠,让 async 内部程序等待一定时间后再执行 + +```js +async next () => { + await sleep(300) + console.log('limeui'); +} +``` + +#### isBase64 +- 判断字符串是否为base64 + +```js +isBase64('xxxxx') +``` + +#### throttle +- 节流 + +```js +throttle((nama) => {console.log(nama)}, 200)('limeui'); +``` + +#### debounce +- 防抖 + +```js +debounce((nama) => {console.log(nama)}, 200)('limeui'); +``` + +#### random +- 返回指定范围的随机数 + +```js +random(1, 5); +``` + +#### range +- 生成区间数组 + +```js +range(0, 5) +// [0,1,2,3,4,5] +``` + +#### clamp +- 夹在min和max之间的数值,如小于min,返回min, 如大于max,返回max,否侧原值返回 + +```js +clamp(0, 10, -1) +// 0 +clamp(0, 10, 11) +// 10 +clamp(0, 10, 9) +// 9 +``` + +#### floatAdd +- 返回两个浮点数相加的结果 + +```js +floatAdd(0.1, 0.2) // 0.3 +``` + + +#### fillZero +- 补零,如果传入的是`个位数`则在前面补0 + +```js +fillZero(9); +// 09 +``` + +#### exif +- 获取图片exif +- 支持临时路径、base64 + +```js +uni.chooseImage({ + count: 1, //最多可以选择的图片张数 + sizeType: "original", + success: (res) => { + exif.getData(res.tempFiles[0], function() { + let tagj = exif.getTag(this, "GPSLongitude"); + let Orientation = exif.getTag(this, 'Orientation'); + console.log(tagj, Orientation) + }) + } +}) +``` + +#### selectComponent +- 获取页面或当前实例的指定组件,会在页面或实例向所有的节点查找(包括子组件或子子组件) +- 仅vue3,vue2没有测试过 + +```js +// 当前页面 +const page = getCurrentPage() +selectComponent('.custom', {context: page}).then(res => { +}) +``` + + +#### createAnimation +- 创建动画,与uni.createAnimation使用方法一致,只为了抹平nvue + +```html + +``` +```js +const ball = ref(null) +const animation = createAnimation({ + transformOrigin: "50% 50%", + duration: 1000, + timingFunction: "ease", + delay: 0 +}) + +animation.scale(2,2).rotate(45).step() +// nvue 无导出数据,这样写只为了平台一致, +// nvue 需要把 ref 传入,其它平台不需要 +const animationData = animation.export(ball.value) +``` + + +## composition-api +- 因本人插件需要兼容vue2/vue3,故增加一个vue文件,代替条件编译 +- vue2需要在main.js加上这一段 +```js +// vue2 +import Vue from 'vue' +import VueCompositionAPI from '@vue/composition-api' +Vue.use(VueCompositionAPI) +``` + +```js +//使用 +import {computed, onMounted, watch, reactive} from '@/uni_modules/lime-shared/vue' +``` + + +## Hooks +#### useIntersectionObserver +- 使用 Intersection Observer 观察元素可见性的钩子函数 + +```html +
+

Hello world

+
+``` + +```js +// options 接口可传的参数,若在插件里context为必传 +interface UseIntersectionObserverOptions { + root ?: string; // 观察器的根元素选择器字符串 + rootMargin ?: { + top ?: number; // 根元素顶部边距 + bottom ?: number; // 根元素底部边距 + left ?: number; // 根元素左侧边距 + right ?: number; // 根元素右侧边距 + }; // 根元素的边距 + thresholds ?: any[]; // 交叉比例数组,用于指定何时触发回调函数 + context ?: any; // 上下文对象,用于指定观察器的上下文 + initialRatio ?: number; // 初始的交叉比例 + observeAll ?: boolean; // 是否同时观察所有交叉对象 +} +const options: UseIntersectionObserverOptions = { + rootMargin: {top: 44}, + context: this +} +const {stop} = useIntersectionObserver('.target', (result) => { + +}, options) +``` \ No newline at end of file diff --git a/uni_modules/lime-shared/selectComponent/index.ts b/uni_modules/lime-shared/selectComponent/index.ts new file mode 100644 index 0000000..39db2fe --- /dev/null +++ b/uni_modules/lime-shared/selectComponent/index.ts @@ -0,0 +1,152 @@ +// @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 selectComponent(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, node} = options + const [,prefix, name] = selector.match(reg) + const symbolMappings = {'.': 'class', '#': 'id', '$':'ref', '@':'component'} + + const key = symbolMappings [prefix] //prefix === '.' ? 'class' : prefix === '#' ? 'id' : 'ref'; + console.log('key', key) + // #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/sleep/index.ts b/uni_modules/lime-shared/sleep/index.ts new file mode 100644 index 0000000..3ae6926 --- /dev/null +++ b/uni_modules/lime-shared/sleep/index.ts @@ -0,0 +1,30 @@ +// @ts-nocheck +/** + * 延迟指定时间后解析的 Promise + * @param delay 延迟的时间(以毫秒为单位),默认为 300 毫秒 + * @returns 一个 Promise,在延迟结束后解析 + */ +export const sleep = (delay: number = 300) => + new Promise(resolve => setTimeout(resolve, delay)); + + +// 示例 +// async function example() { +// console.log("Start"); + +// // 延迟 1 秒后执行 +// await sleep(1000); +// console.log("1 second later"); + +// // 延迟 500 毫秒后执行 +// await sleep(500); +// console.log("500 milliseconds later"); + +// // 延迟 2 秒后执行 +// await sleep(2000); +// console.log("2 seconds later"); + +// console.log("End"); +// } + +// example(); \ No newline at end of file diff --git a/uni_modules/lime-shared/throttle/index.ts b/uni_modules/lime-shared/throttle/index.ts new file mode 100644 index 0000000..740f788 --- /dev/null +++ b/uni_modules/lime-shared/throttle/index.ts @@ -0,0 +1,41 @@ +// @ts-nocheck +/** + * 节流函数,用于限制函数的调用频率 + * @param fn 要进行节流的函数 + * @param delay 两次调用之间的最小间隔时间 + * @returns 节流后的函数 + */ +export function throttle(fn: (...args: any[]) => void, delay: number) { + let flag = true; // 标记是否可以执行函数 + + return (...args: any[]) => { + if (flag) { + flag = false; // 设置为不可执行状态 + fn(...args); // 执行传入的函数 + + setTimeout(() => { + flag = true; // 经过指定时间后,设置为可执行状态 + }, delay); + } + }; +} + + +// // 示例 +// // 定义一个被节流的函数 +// 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); \ No newline at end of file diff --git a/uni_modules/lime-shared/toArray/index.ts b/uni_modules/lime-shared/toArray/index.ts new file mode 100644 index 0000000..b59b0af --- /dev/null +++ b/uni_modules/lime-shared/toArray/index.ts @@ -0,0 +1,13 @@ +// @ts-nocheck +/** + * 将一个或多个元素转换为数组 + * @param item 要转换为数组的元素 + * @returns 转换后的数组 + */ +export const toArray = (item: T | T[]): T[] => Array.isArray(item) ? item : [item]; + +// 示例 +// console.log(toArray(5)); // 输出: [5] +// console.log(toArray("hello")); // 输出: ["hello"] +// console.log(toArray([1, 2, 3])); // 输出: [1, 2, 3] +// console.log(toArray(["apple", "banana"])); // 输出: ["apple", "banana"] \ No newline at end of file diff --git a/uni_modules/lime-shared/toNumber/index.ts b/uni_modules/lime-shared/toNumber/index.ts new file mode 100644 index 0000000..a0745bb --- /dev/null +++ b/uni_modules/lime-shared/toNumber/index.ts @@ -0,0 +1,15 @@ +// @ts-nocheck +/** + * 将字符串转换为数字 + * @param val 要转换的字符串 + * @returns 转换后的数字或原始字符串 + */ +export function toNumber(val: string): number | string { + const n = parseFloat(val); // 使用 parseFloat 函数将字符串转换为浮点数 + return isNaN(n) ? val : n; // 使用 isNaN 函数判断是否为非数字,返回转换后的数字或原始字符串 +} + +// 示例 +// console.log(toNumber("123")); // 输出: 123 +// console.log(toNumber("3.14")); // 输出: 3.14 +// console.log(toNumber("hello")); // 输出: "hello" \ No newline at end of file diff --git a/uni_modules/lime-shared/unitConvert/index.ts b/uni_modules/lime-shared/unitConvert/index.ts new file mode 100644 index 0000000..7cedccf --- /dev/null +++ b/uni_modules/lime-shared/unitConvert/index.ts @@ -0,0 +1,39 @@ +// @ts-nocheck +import {isString} from '../isString' +import {isNumeric} from '../isNumeric' + +/** + * 单位转换函数,将字符串数字或带有单位的字符串转换为数字 + * @param value 要转换的值,可以是字符串数字或带有单位的字符串 + * @returns 转换后的数字,如果无法转换则返回0 + */ +export function unitConvert(value: string | number): number { + // 如果是字符串数字 + if (isNumeric(value)) { + return Number(value); + } + // 如果有单位 + if (isString(value)) { + const reg = /^-?([0-9]+)?([.]{1}[0-9]+){0,1}(em|rpx|px|%)$/g; + const results = reg.exec(value); + if (!value || !results) { + return 0; + } + const unit = results[3]; + value = parseFloat(value); + if (unit === 'rpx') { + return uni.upx2px(value); + } + if (unit === 'px') { + return value * 1; + } + // 如果是其他单位,可以继续添加对应的转换逻辑 + } + return 0; +} + +// 示例 +// console.log(unitConvert("123")); // 输出: 123 (字符串数字转换为数字) +// console.log(unitConvert("3.14em")); // 输出: 0 (无法识别的单位) +// console.log(unitConvert("20rpx")); // 输出: 根据具体情况而定 (根据单位进行转换) +// console.log(unitConvert(10)); // 输出: 10 (数字不需要转换) \ No newline at end of file diff --git a/uni_modules/lime-shared/useIntersectionObserver/index.ts b/uni_modules/lime-shared/useIntersectionObserver/index.ts new file mode 100644 index 0000000..b5862fe --- /dev/null +++ b/uni_modules/lime-shared/useIntersectionObserver/index.ts @@ -0,0 +1,81 @@ +import { watch, unref, Ref } from "../vue" +// #ifdef APP-NVUE +// const dom = weex.requireModule('dom') +// const dom = uni.requireNativePlugin('dom') +// #endif + +interface UseIntersectionObserverOptions { + root ?: string; // 观察器的根元素选择器字符串 + rootMargin ?: { + top ?: number; // 根元素顶部边距 + bottom ?: number; // 根元素底部边距 + left ?: number; // 根元素左侧边距 + right ?: number; // 根元素右侧边距 + }; // 根元素的边距 + thresholds ?: any[]; // 交叉比例数组,用于指定何时触发回调函数 + context ?: any; // 上下文对象,用于指定观察器的上下文 + initialRatio ?: number; // 初始的交叉比例 + observeAll ?: boolean; // 是否同时观察所有交叉对象 +} + +/** + * 使用 Intersection Observer 观察元素可见性的自定义钩子函数 + * @param {Ref | string} target - 目标元素,可以是一个字符串或 ref 对象 + * @param {(result: UniNamespace.ObserveResult) => void} callback - 回调函数,当目标元素的可见性发生变化时调用 + * @param {UseIntersectionObserverOptions} options - 可选的配置参数 + * @returns {Object} - 包含 stop 方法的对象,用于停止观察 + */ +export function useIntersectionObserver( + target : Ref | string, + callback : (result : UniNamespace.ObserveResult) => void, + options : UseIntersectionObserverOptions = {}) { + const { + root, // 观察器的根元素选择器 + rootMargin = { top: 0, bottom: 0 }, // 根元素的边距,默认为顶部和底部都为0 + thresholds = [0], // 交叉比例数组,默认为[0] + initialRatio = 0, // 初始交叉比例,默认为0 + observeAll = false, // 是否同时观察所有交叉对象,默认为false + context // 上下文对象,用于指定观察器的上下文 + } = options + const noop = () => { }; // 空函数,用于初始化 cleanup + let cleanup = noop; // 清理函数,用于停止 Intersection Observer 的观察 + + const stopWatch = watch(() => ({ el: unref(target), root: unref(root) }), ({ el, root }) => { + if (!el) { + return + } + // #ifndef APP-NVUE + // 创建 Intersection Observer 实例 + const observer = uni.createIntersectionObserver(context, { thresholds, initialRatio, observeAll }) + if (root) { + // 相对于根元素设置边界 + observer.relativeTo(root, rootMargin) + } else { + // 相对于视口设置边界 + observer.relativeToViewport(rootMargin) + } + // 观察目标元素的可见性变化 + observer.observe(el, callback) + cleanup = () => { + // 停止观察 + observer.disconnect() + // 将 cleanup 函数重置为空函数 + cleanup = noop + } + // #endif + // #ifdef APP-NVUE + // dom.getComponentRect(el, (res) => { + // console.log('res', res) + // }) + // #endif + }, { immediate: true, flush: 'post' }) + + const stop = () => { + // 调用 cleanup 函数停止观察 + cleanup && cleanup() + // 停止 watch + stopWatch && stopWatch() + } + + return { stop } +} \ No newline at end of file diff --git a/uni_modules/lime-shared/vue/index.ts b/uni_modules/lime-shared/vue/index.ts new file mode 100644 index 0000000..91a4a7f --- /dev/null +++ b/uni_modules/lime-shared/vue/index.ts @@ -0,0 +1,8 @@ +// @ts-nocheck + +// #ifdef VUE3 +export * from 'vue'; +// #endif +// #ifndef VUE3 +export * from '@vue/composition-api'; +// #endif diff --git a/uni_modules/uview-ui/LICENSE b/uni_modules/uview-ui/LICENSE index 8e39ead..4db40ef 100644 --- a/uni_modules/uview-ui/LICENSE +++ b/uni_modules/uview-ui/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2020 www.uviewui.com +Copyright (c) 2023 www.uviewui.com Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file +SOFTWARE. diff --git a/uni_modules/uview-ui/changelog.md b/uni_modules/uview-ui/changelog.md index 817d403..f2bae72 100644 --- a/uni_modules/uview-ui/changelog.md +++ b/uni_modules/uview-ui/changelog.md @@ -1,3 +1,21 @@ +## 2.0.36(2023-03-27) +# uView2.0重磅发布,利剑出鞘,一统江湖 + +1. 重构`deepClone` & `deepMerge`方法 +2. 其他优化 +## 2.0.34(2022-09-24) +# uView2.0重磅发布,利剑出鞘,一统江湖 + +1. `u-input`、`u-textarea`增加`ignoreCompositionEvent`属性 +2. 修复`route`方法调用可能报错的问题 +3. 修复`u-no-network`组件`z-index`无效的问题 +4. 修复`textarea`组件在h5上confirmType=""报错的问题 +5. `u-rate`适配`nvue` +6. 优化验证手机号码的正则表达式(根据工信部发布的《电信网编号计划(2017年版)》进行修改。) +7. `form-item`添加`labelPosition`属性 +8. `u-calendar`修复`maxDate`设置为当前日期,并且当前时间大于08:00时无法显示日期列表的问题 (#724) +9. `u-radio`增加一个默认插槽用于自定义修改label内容 (#680) +10. 修复`timeFormat`函数在safari重的兼容性问题 (#664) ## 2.0.33(2022-06-17) # uView2.0重磅发布,利剑出鞘,一统江湖 diff --git a/uni_modules/uview-ui/components/u-calendar/u-calendar.vue b/uni_modules/uview-ui/components/u-calendar/u-calendar.vue index ad892ff..511f993 100644 --- a/uni_modules/uview-ui/components/u-calendar/u-calendar.vue +++ b/uni_modules/uview-ui/components/u-calendar/u-calendar.vue @@ -209,12 +209,13 @@ export default { } }, init() { - // 校验maxDate,不能小于当前时间 + // 校验maxDate,不能小于minDate if ( this.innerMaxDate && - new Date(this.innerMaxDate).getTime() <= Date.now() + this.innerMinDate && + new Date(this.innerMaxDate).getTime() < new Date(this.innerMinDate).getTime() ) { - return uni.$u.error('maxDate不能小于当前时间') + return uni.$u.error('maxDate不能小于minDate') } // 滚动区域的高度 this.listHeight = this.rowHeight * 5 + 30 diff --git a/uni_modules/uview-ui/components/u-code/u-code.vue b/uni_modules/uview-ui/components/u-code/u-code.vue index cdf9f82..f79a09a 100644 --- a/uni_modules/uview-ui/components/u-code/u-code.vue +++ b/uni_modules/uview-ui/components/u-code/u-code.vue @@ -16,11 +16,11 @@ * @property {String} endText 倒计结束的提示语,见官网说明(默认 '重新获取' ) * @property {Boolean} keepRunning 是否在H5刷新或各端返回再进入时继续倒计时( 默认false ) * @property {String} uniqueKey 为了区分多个页面,或者一个页面多个倒计时组件本地存储的继续倒计时变了 - * + * * @event {Function} change 倒计时期间,每秒触发一次 * @event {Function} start 开始倒计时触发 * @event {Function} end 结束倒计时触发 - * @example + * @example */ export default { name: "u-code", @@ -74,7 +74,6 @@ this.canGetCode = false // 这里放这句,是为了一开始时就提示,否则要等setInterval的1秒后才会有提示 this.changeEvent(this.changeText.replace(/x|X/, this.secNum)) - this.setTimeToStorage() this.timer = setInterval(() => { if (--this.secNum) { // 用当前倒计时的秒数替换提示字符串中的"x"字母 @@ -88,7 +87,8 @@ this.canGetCode = true } }, 1000) - }, + this.setTimeToStorage() + }, // 重置,可以让用户再次获取验证码 reset() { this.canGetCode = true diff --git a/uni_modules/uview-ui/components/u-dropdown-item/u-dropdown-item.vue b/uni_modules/uview-ui/components/u-dropdown-item/u-dropdown-item.vue index 07de583..f830291 100644 --- a/uni_modules/uview-ui/components/u-dropdown-item/u-dropdown-item.vue +++ b/uni_modules/uview-ui/components/u-dropdown-item/u-dropdown-item.vue @@ -1,146 +1,127 @@ - diff --git a/uni_modules/uview-ui/components/u-dropdown/u-dropdown.vue b/uni_modules/uview-ui/components/u-dropdown/u-dropdown.vue index 9c50bfe..f830291 100644 --- a/uni_modules/uview-ui/components/u-dropdown/u-dropdown.vue +++ b/uni_modules/uview-ui/components/u-dropdown/u-dropdown.vue @@ -1,127 +1,127 @@ diff --git a/uni_modules/uview-ui/components/u-form-item/props.js b/uni_modules/uview-ui/components/u-form-item/props.js index 53a0191..7b16655 100644 --- a/uni_modules/uview-ui/components/u-form-item/props.js +++ b/uni_modules/uview-ui/components/u-form-item/props.js @@ -15,6 +15,11 @@ export default { type: [String, Boolean], default: uni.$u.props.formItem.borderBottom }, + // label的位置,left-左边,top-上边 + labelPosition: { + type: String, + default: uni.$u.props.formItem.labelPosition + }, // label的宽度,单位px labelWidth: { type: [String, Number], diff --git a/uni_modules/uview-ui/components/u-form-item/u-form-item.vue b/uni_modules/uview-ui/components/u-form-item/u-form-item.vue index 701d7cc..6aa8d69 100644 --- a/uni_modules/uview-ui/components/u-form-item/u-form-item.vue +++ b/uni_modules/uview-ui/components/u-form-item/u-form-item.vue @@ -4,7 +4,7 @@ class="u-form-item__body" @tap="clickHandler" :style="[$u.addStyle(customStyle), { - flexDirection: parentData.labelPosition === 'left' ? 'row' : 'column' + flexDirection: (labelPosition || parentData.labelPosition) === 'left' ? 'row' : 'column' }]" > diff --git a/uni_modules/uview-ui/components/u-modal/u-modal.vue b/uni_modules/uview-ui/components/u-modal/u-modal.vue index 4c37ae2..2cbc737 100644 --- a/uni_modules/uview-ui/components/u-modal/u-modal.vue +++ b/uni_modules/uview-ui/components/u-modal/u-modal.vue @@ -114,7 +114,7 @@ * @event {Function} confirm 点击确认按钮时触发 * @event {Function} cancel 点击取消按钮时触发 * @event {Function} close 点击遮罩关闭出发,closeOnClickOverlay为true有效 - * @example + * @example */ export default { name: 'u-modal', @@ -146,7 +146,7 @@ }, // 点击遮罩 // 从原理上来说,modal的遮罩点击,并不是真的点击到了遮罩 - // 因为modal依赖于popup的中部弹窗类型,中部弹窗比较特殊,虽有然遮罩,但是为了让弹窗内容能flex居中 + // 因为modal依赖于popup的中部弹窗类型,中部弹窗比较特殊,虽然有遮罩,但是为了让弹窗内容能flex居中 // 多了一个透明的遮罩,此透明的遮罩会覆盖在灰色的遮罩上,所以实际上是点击不到灰色遮罩的,popup内部在 // 透明遮罩的子元素做了.stop处理,所以点击内容区,也不会导致误触发 clickHandler() { diff --git a/uni_modules/uview-ui/components/u-radio/u-radio.vue b/uni_modules/uview-ui/components/u-radio/u-radio.vue index 7133542..c0caab6 100644 --- a/uni_modules/uview-ui/components/u-radio/u-radio.vue +++ b/uni_modules/uview-ui/components/u-radio/u-radio.vue @@ -20,15 +20,17 @@ />
- {{label}} + + {{label}} +
diff --git a/uni_modules/uview-ui/components/u-rate/u-rate.vue b/uni_modules/uview-ui/components/u-rate/u-rate.vue index 5992038..1aa5dd0 100644 --- a/uni_modules/uview-ui/components/u-rate/u-rate.vue +++ b/uni_modules/uview-ui/components/u-rate/u-rate.vue @@ -35,7 +35,8 @@ : inactiveColor " :custom-style="{ - padding: `0 ${$u.addUnit(gutter / 2)}`, + 'padding-left': $u.addUnit(gutter / 2), + 'padding-right': $u.addUnit(gutter / 2) }" :size="size" > @@ -63,7 +64,8 @@ : inactiveColor " :custom-style="{ - padding: `0 ${$u.addUnit(gutter / 2)}` + 'padding-left': $u.addUnit(gutter / 2), + 'padding-right': $u.addUnit(gutter / 2) }" :size="size" > diff --git a/uni_modules/uview-ui/components/u-scroll-list/u-scroll-list.vue b/uni_modules/uview-ui/components/u-scroll-list/u-scroll-list.vue index 2f3df2a..4fe885a 100644 --- a/uni_modules/uview-ui/components/u-scroll-list/u-scroll-list.vue +++ b/uni_modules/uview-ui/components/u-scroll-list/u-scroll-list.vue @@ -55,7 +55,7 @@ [key, deepClone(value, cache)])); + } else if (obj instanceof Set) { + clone = new Set(Array.from(obj, value => deepClone(value, cache))); + } else if (Array.isArray(obj)) { + clone = obj.map(value => deepClone(value, cache)); + } else if (Object.prototype.toString.call(obj) === '[object Object]') { + clone = Object.create(Object.getPrototypeOf(obj)); + cache.set(obj, clone); + for (const [key, value] of Object.entries(obj)) { + clone[key] = deepClone(value, cache); } + } else { + clone = Object.assign({}, obj); } - return o + cache.set(obj, clone); + return clone; } /** @@ -205,24 +217,27 @@ function deepClone(obj) { */ function deepMerge(target = {}, source = {}) { target = deepClone(target) - if (typeof target !== 'object' || typeof source !== 'object') return false + if (typeof target !== 'object' || target === null || typeof source !== 'object' || source === null) return target; + const merged = Array.isArray(target) ? target.slice() : Object.assign({}, target); for (const prop in source) { - if (!source.hasOwnProperty(prop)) continue - if (prop in target) { - if (typeof target[prop] !== 'object') { - target[prop] = source[prop] - } else if (typeof source[prop] !== 'object') { - target[prop] = source[prop] - } else if (target[prop].concat && source[prop].concat) { - target[prop] = target[prop].concat(source[prop]) - } else { - target[prop] = deepMerge(target[prop], source[prop]) - } + if (!source.hasOwnProperty(prop)) continue; + const sourceValue = source[prop]; + const targetValue = merged[prop]; + if (sourceValue instanceof Date) { + merged[prop] = new Date(sourceValue); + } else if (sourceValue instanceof RegExp) { + merged[prop] = new RegExp(sourceValue); + } else if (sourceValue instanceof Map) { + merged[prop] = new Map(sourceValue); + } else if (sourceValue instanceof Set) { + merged[prop] = new Set(sourceValue); + } else if (typeof sourceValue === 'object' && sourceValue !== null) { + merged[prop] = deepMerge(targetValue, sourceValue); } else { - target[prop] = source[prop] + merged[prop] = sourceValue; } } - return target + return merged; } /** @@ -292,15 +307,15 @@ if (!String.prototype.padStart) { else if (typeof dateTime === 'string' && /^\d+$/.test(dateTime.trim())) { date = new Date(Number(dateTime)) } - // 其他都认为符合 RFC 2822 规范 - else { - // 处理平台性差异,在Safari/Webkit中,new Date仅支持/作为分割符的字符串时间 - date = new Date( - typeof dateTime === 'string' - ? dateTime.replace(/-/g, '/') - : dateTime - ) - } + // 处理平台性差异,在Safari/Webkit中,new Date仅支持/作为分割符的字符串时间 + // 处理 '2022-07-10 01:02:03',跳过 '2022-07-10T01:02:03' + else if (typeof dateTime === 'string' && dateTime.includes('-') && !dateTime.includes('T')) { + date = new Date(dateTime.replace(/-/g, '/')) + } + // 其他都认为符合 RFC 2822 规范 + else { + date = new Date(dateTime) + } const timeSource = { 'y': date.getFullYear().toString(), // 年 @@ -327,7 +342,7 @@ if (!String.prototype.padStart) { /** * @description 时间戳转为多久之前 * @param {String|Number} timestamp 时间戳 - * @param {String|Boolean} format + * @param {String|Boolean} format * 格式化规则如果为时间格式字符串,超出一定时间范围,返回固定的时间格式; * 如果为布尔值false,无论什么时间,都返回多久以前的格式 * @returns {string} 转化后的内容 @@ -517,7 +532,7 @@ function priceFormat(number, decimals = 0, decimalPoint = '.', thousandsSeparato while (re.test(s[0])) { s[0] = s[0].replace(re, `$1${sep}$2`) } - + if ((s[1] || '').length < prec) { s[1] = s[1] || '' s[1] += new Array(prec - s[1].length + 1).join('0') @@ -531,7 +546,7 @@ function priceFormat(number, decimals = 0, decimalPoint = '.', thousandsSeparato * 比如以30位阈值,那么300大于30,可以理解为用户想要的是300ms,而不是想花300s去执行一个动画 * @param {String|number} value 比如: "1s"|"100ms"|1|100 * @param {boolean} unit 提示: 如果是false 默认返回number - * @return {string|number} + * @return {string|number} */ function getDuration(value, unit = true) { const valueNum = parseInt(value) @@ -650,6 +665,16 @@ function pages() { return pages } +/** + * 获取页面历史栈指定层实例 + * @param back {number} [0] - 0或者负数,表示获取历史栈的哪一层,0表示获取当前页面实例,-1 表示获取上一个页面实例。默认0。 + */ +function getHistoryPage(back = 0) { + const pages = getCurrentPages() + const len = pages.length + return pages[len - 1 + back] +} + /** * @description 修改uView内置属性值 * @param {object} props 修改内置props属性 @@ -701,5 +726,6 @@ export default { setProperty, page, pages, + getHistoryPage, setConfig } diff --git a/uni_modules/uview-ui/libs/function/test.js b/uni_modules/uview-ui/libs/function/test.js index ae4a1b3..c776437 100644 --- a/uni_modules/uview-ui/libs/function/test.js +++ b/uni_modules/uview-ui/libs/function/test.js @@ -9,7 +9,7 @@ function email(value) { * 验证手机格式 */ function mobile(value) { - return /^1[23456789]\d{9}$/.test(value) + return /^1([3589]\d|4[5-9]|6[1-2,4-7]|7[0-8])\d{8}$/.test(value) } /** diff --git a/uni_modules/uview-ui/libs/util/route.js b/uni_modules/uview-ui/libs/util/route.js index 70b8ca4..2afeea5 100644 --- a/uni_modules/uview-ui/libs/util/route.js +++ b/uni_modules/uview-ui/libs/util/route.js @@ -4,121 +4,121 @@ */ class Router { - constructor() { - // 原始属性定义 - this.config = { - type: 'navigateTo', - url: '', - delta: 1, // navigateBack页面后退时,回退的层数 - params: {}, // 传递的参数 - animationType: 'pop-in', // 窗口动画,只在APP有效 - animationDuration: 300, // 窗口动画持续时间,单位毫秒,只在APP有效 - intercept: false // 是否需要拦截 - } - // 因为route方法是需要对外赋值给另外的对象使用,同时route内部有使用this,会导致route失去上下文 - // 这里在构造函数中进行this绑定 - this.route = this.route.bind(this) - } + constructor() { + // 原始属性定义 + this.config = { + type: 'navigateTo', + url: '', + delta: 1, // navigateBack页面后退时,回退的层数 + params: {}, // 传递的参数 + animationType: 'pop-in', // 窗口动画,只在APP有效 + animationDuration: 300, // 窗口动画持续时间,单位毫秒,只在APP有效 + intercept: false // 是否需要拦截 + } + // 因为route方法是需要对外赋值给另外的对象使用,同时route内部有使用this,会导致route失去上下文 + // 这里在构造函数中进行this绑定 + this.route = this.route.bind(this) + } - // 判断url前面是否有"/",如果没有则加上,否则无法跳转 - addRootPath(url) { - return url[0] === '/' ? url : `/${url}` - } + // 判断url前面是否有"/",如果没有则加上,否则无法跳转 + addRootPath(url) { + return url[0] === '/' ? url : `/${url}` + } - // 整合路由参数 - mixinParam(url, params) { - url = url && this.addRootPath(url) + // 整合路由参数 + mixinParam(url, params) { + url = url && this.addRootPath(url) - // 使用正则匹配,主要依据是判断是否有"/","?","="等,如“/page/index/index?name=mary" - // 如果有url中有get参数,转换后无需带上"?" - let query = '' - if (/.*\/.*\?.*=.*/.test(url)) { - // object对象转为get类型的参数 - query = uni.$u.queryParams(params, false) - // 因为已有get参数,所以后面拼接的参数需要带上"&"隔开 - return url += `&${query}` - } - // 直接拼接参数,因为此处url中没有后面的query参数,也就没有"?/&"之类的符号 - query = uni.$u.queryParams(params) - return url += query - } + // 使用正则匹配,主要依据是判断是否有"/","?","="等,如“/page/index/index?name=mary" + // 如果有url中有get参数,转换后无需带上"?" + let query = '' + if (/.*\/.*\?.*=.*/.test(url)) { + // object对象转为get类型的参数 + query = uni.$u.queryParams(params, false) + // 因为已有get参数,所以后面拼接的参数需要带上"&"隔开 + return url += `&${query}` + } + // 直接拼接参数,因为此处url中没有后面的query参数,也就没有"?/&"之类的符号 + query = uni.$u.queryParams(params) + return url += query + } - // 对外的方法名称 - async route(options = {}, params = {}) { - // 合并用户的配置和内部的默认配置 - let mergeConfig = {} + // 对外的方法名称 + async route(options = {}, params = {}) { + // 合并用户的配置和内部的默认配置 + let mergeConfig = {} - if (typeof options === 'string') { - // 如果options为字符串,则为route(url, params)的形式 - mergeConfig.url = this.mixinParam(options, params) - mergeConfig.type = 'navigateTo' - } else { - mergeConfig = uni.$u.deepMerge(options, this.config) - // 否则正常使用mergeConfig中的url和params进行拼接 - mergeConfig.url = this.mixinParam(options.url, options.params) - } + if (typeof options === 'string') { + // 如果options为字符串,则为route(url, params)的形式 + mergeConfig.url = this.mixinParam(options, params) + mergeConfig.type = 'navigateTo' + } else { + mergeConfig = uni.$u.deepMerge(this.config, options) + // 否则正常使用mergeConfig中的url和params进行拼接 + mergeConfig.url = this.mixinParam(options.url, options.params) + } - // 如果本次跳转的路径和本页面路径一致,不执行跳转,防止用户快速点击跳转按钮,造成多次跳转同一个页面的问题 - if (mergeConfig.url === uni.$u.page()) return + // 如果本次跳转的路径和本页面路径一致,不执行跳转,防止用户快速点击跳转按钮,造成多次跳转同一个页面的问题 + if (mergeConfig.url === uni.$u.page()) return - if (params.intercept) { - this.config.intercept = params.intercept - } - // params参数也带给拦截器 - mergeConfig.params = params - // 合并内外部参数 - mergeConfig = uni.$u.deepMerge(this.config, mergeConfig) - // 判断用户是否定义了拦截器 - if (typeof uni.$u.routeIntercept === 'function') { - // 定一个promise,根据用户执行resolve(true)或者resolve(false)来决定是否进行路由跳转 - const isNext = await new Promise((resolve, reject) => { - uni.$u.routeIntercept(mergeConfig, resolve) - }) - // 如果isNext为true,则执行路由跳转 - isNext && this.openPage(mergeConfig) - } else { - this.openPage(mergeConfig) - } - } + if (params.intercept) { + this.config.intercept = params.intercept + } + // params参数也带给拦截器 + mergeConfig.params = params + // 合并内外部参数 + mergeConfig = uni.$u.deepMerge(this.config, mergeConfig) + // 判断用户是否定义了拦截器 + if (typeof uni.$u.routeIntercept === 'function') { + // 定一个promise,根据用户执行resolve(true)或者resolve(false)来决定是否进行路由跳转 + const isNext = await new Promise((resolve, reject) => { + uni.$u.routeIntercept(mergeConfig, resolve) + }) + // 如果isNext为true,则执行路由跳转 + isNext && this.openPage(mergeConfig) + } else { + this.openPage(mergeConfig) + } + } - // 执行路由跳转 - openPage(config) { - // 解构参数 - const { - url, - type, - delta, - animationType, - animationDuration - } = config - if (config.type == 'navigateTo' || config.type == 'to') { - uni.navigateTo({ - url, - animationType, - animationDuration - }) - } - if (config.type == 'redirectTo' || config.type == 'redirect') { - uni.redirectTo({ - url - }) - } - if (config.type == 'switchTab' || config.type == 'tab') { - uni.switchTab({ - url - }) - } - if (config.type == 'reLaunch' || config.type == 'launch') { - uni.reLaunch({ - url - }) - } - if (config.type == 'navigateBack' || config.type == 'back') { - uni.navigateBack({ - delta - }) - } - } + // 执行路由跳转 + openPage(config) { + // 解构参数 + const { + url, + type, + delta, + animationType, + animationDuration + } = config + if (config.type == 'navigateTo' || config.type == 'to') { + uni.navigateTo({ + url, + animationType, + animationDuration + }) + } + if (config.type == 'redirectTo' || config.type == 'redirect') { + uni.redirectTo({ + url + }) + } + if (config.type == 'switchTab' || config.type == 'tab') { + uni.switchTab({ + url + }) + } + if (config.type == 'reLaunch' || config.type == 'launch') { + uni.reLaunch({ + url + }) + } + if (config.type == 'navigateBack' || config.type == 'back') { + uni.navigateBack({ + delta + }) + } + } } export default (new Router()).route diff --git a/uni_modules/uview-ui/package.json b/uni_modules/uview-ui/package.json index fee4372..e1169e5 100644 --- a/uni_modules/uview-ui/package.json +++ b/uni_modules/uview-ui/package.json @@ -2,7 +2,7 @@ "id": "uview-ui", "name": "uview-ui", "displayName": "uView2.0重磅发布,利剑出鞘,一统江湖", - "version": "2.0.33", + "version": "2.0.36", "description": "uView UI已完美兼容nvue,全面的组件和便捷的工具会让您信手拈来,如鱼得水", "keywords": [ "uview", @@ -17,12 +17,8 @@ "engines": { "HBuilderX": "^3.1.0" }, - "dcloudext": { - "category": [ - "前端组件", - "通用组件" - ], - "sale": { + "dcloudext": { + "sale": { "regular": { "price": "0.00" }, @@ -38,7 +34,8 @@ "data": "无", "permissions": "无" }, - "npmurl": "https://www.npmjs.com/package/uview-ui" + "npmurl": "https://www.npmjs.com/package/uview-ui", + "type": "component-vue" }, "uni_modules": { "dependencies": [],