From cc0ad19e2d63718bd89cb0d9b0ead141298c8b58 Mon Sep 17 00:00:00 2001 From: liub Date: Fri, 11 Jul 2025 16:11:40 +0800 Subject: [PATCH] =?UTF-8?q?6155=E9=83=A8=E5=88=86=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- App.vue | 2 +- api/6155/BlueHelper.js | 671 ++++++++++++++++ .../BottomSlideMenuPlus.vue | 258 ++++++ components/MessagePopup/MessagePopup.vue | 381 +++++++++ components/Progress/Progress.vue | 129 +++ manifest.json | 2 +- static/images/6155/DeviceDetail/add.png | Bin 0 -> 145 bytes static/images/6155/DeviceDetail/battry.png | Bin 0 -> 268 bytes static/images/6155/DeviceDetail/equip.png | Bin 0 -> 14402 bytes static/images/6155/DeviceDetail/fan.png | Bin 0 -> 275 bytes static/images/6155/DeviceDetail/fuLamp.png | Bin 0 -> 415 bytes static/images/6155/DeviceDetail/mainLamp.png | Bin 0 -> 413 bytes static/images/6155/DeviceDetail/open.png | Bin 0 -> 357 bytes static/images/6155/DeviceDetail/param.png | Bin 0 -> 289 bytes static/images/6155/DeviceDetail/qiang.png | Bin 0 -> 612 bytes static/images/6155/DeviceDetail/remark.png | Bin 0 -> 269 bytes static/images/6155/DeviceDetail/ruo.png | Bin 0 -> 528 bytes static/images/6155/DeviceDetail/sendSucc.png | Bin 0 -> 500 bytes static/images/6155/DeviceDetail/shan.png | Bin 0 -> 304 bytes .../images/6155/DeviceDetail/slideToggle.png | Bin 0 -> 194 bytes static/images/6155/DeviceDetail/time.png | Bin 0 -> 321 bytes .../6155/DeviceDetail/uploadSuccess.png | Bin 0 -> 282 bytes static/images/6155/DeviceDetail/video.png | Bin 0 -> 212 bytes static/images/BLEAdd/device.png | Bin 0 -> 9891 bytes static/images/BLEAdd/linked.png | Bin 0 -> 793 bytes static/images/BLEAdd/noLink.png | Bin 0 -> 670 bytes static/images/BLEAdd/wifi.png | Bin 0 -> 522 bytes store/BLETools.js | 23 +- uni_modules/qf-image-cropper/changelog.md | 67 ++ .../qf-image-cropper.render.js | 738 +++++++++++++++++ .../qf-image-cropper/qf-image-cropper.vue | 746 ++++++++++++++++++ .../qf-image-cropper/qf-image-cropper.wxs | 604 ++++++++++++++ uni_modules/qf-image-cropper/package.json | 81 ++ uni_modules/qf-image-cropper/readme.md | 95 +++ unpackage/dist/dev/.nvue/app.css.js | 11 + unpackage/dist/dev/.nvue/app.js | 2 + .../dist/dev/app-plus/__uniappautomator.js | 16 + .../dist/dev/app-plus/uni-app-view.umd.js | 7 + utils/request.js | 3 +- 39 files changed, 3823 insertions(+), 13 deletions(-) create mode 100644 api/6155/BlueHelper.js create mode 100644 components/BottomSlideMenuPlus/BottomSlideMenuPlus.vue create mode 100644 components/MessagePopup/MessagePopup.vue create mode 100644 components/Progress/Progress.vue create mode 100644 static/images/6155/DeviceDetail/add.png create mode 100644 static/images/6155/DeviceDetail/battry.png create mode 100644 static/images/6155/DeviceDetail/equip.png create mode 100644 static/images/6155/DeviceDetail/fan.png create mode 100644 static/images/6155/DeviceDetail/fuLamp.png create mode 100644 static/images/6155/DeviceDetail/mainLamp.png create mode 100644 static/images/6155/DeviceDetail/open.png create mode 100644 static/images/6155/DeviceDetail/param.png create mode 100644 static/images/6155/DeviceDetail/qiang.png create mode 100644 static/images/6155/DeviceDetail/remark.png create mode 100644 static/images/6155/DeviceDetail/ruo.png create mode 100644 static/images/6155/DeviceDetail/sendSucc.png create mode 100644 static/images/6155/DeviceDetail/shan.png create mode 100644 static/images/6155/DeviceDetail/slideToggle.png create mode 100644 static/images/6155/DeviceDetail/time.png create mode 100644 static/images/6155/DeviceDetail/uploadSuccess.png create mode 100644 static/images/6155/DeviceDetail/video.png create mode 100644 static/images/BLEAdd/device.png create mode 100644 static/images/BLEAdd/linked.png create mode 100644 static/images/BLEAdd/noLink.png create mode 100644 static/images/BLEAdd/wifi.png create mode 100644 uni_modules/qf-image-cropper/changelog.md create mode 100644 uni_modules/qf-image-cropper/components/qf-image-cropper/qf-image-cropper.render.js create mode 100644 uni_modules/qf-image-cropper/components/qf-image-cropper/qf-image-cropper.vue create mode 100644 uni_modules/qf-image-cropper/components/qf-image-cropper/qf-image-cropper.wxs create mode 100644 uni_modules/qf-image-cropper/package.json create mode 100644 uni_modules/qf-image-cropper/readme.md create mode 100644 unpackage/dist/dev/.nvue/app.css.js create mode 100644 unpackage/dist/dev/.nvue/app.js create mode 100644 unpackage/dist/dev/app-plus/__uniappautomator.js create mode 100644 unpackage/dist/dev/app-plus/uni-app-view.umd.js diff --git a/App.vue b/App.vue index ee65c57..89526a8 100644 --- a/App.vue +++ b/App.vue @@ -2,7 +2,7 @@ export default { onLaunch: function() { - + }, onShow: function() { console.log('App Show') diff --git a/api/6155/BlueHelper.js b/api/6155/BlueHelper.js new file mode 100644 index 0000000..82dd23d --- /dev/null +++ b/api/6155/BlueHelper.js @@ -0,0 +1,671 @@ +export default { + featrueValueCallback: null,//蓝牙特征变化回调 + BleChangeCallback:null,//蓝牙状态变化回调 + //引导用户打开蓝牙 + showBluetoothGuide: function(showTip) { + let platform = process.env.UNI_PLATFORM; + + + var openBlueSetting = function() { + // 判断平台类型 + if (platform === 'mp-weixin') { + uni.openSetting(); + } else if (platform === 'app-plus' || platform === 'app') { + //---------------------------------------------------------------- + const osName = plus.os.name; + + if (osName === 'iOS') { + // iOS 平台打开蓝牙设置 + plus.runtime.openURL('App-Prefs:root=Bluetooth', function() { + console.log('成功打开蓝牙设置'); + }, function(err) { + console.error('打开蓝牙设置失败:' + err.message); + uni.showModal({ + title: '提示', + content: '无法自动打开蓝牙设置,请手动前往设置 > 蓝牙 进行操作。', + showCancel: false + }); + }); + } else if (osName === 'Android') { + // Android 平台打开蓝牙设置 + try { + const Intent = plus.android.importClass('android.content.Intent'); + const Settings = plus.android.importClass('android.provider.Settings'); + const main = plus.android.runtimeMainActivity(); + const intent = new Intent(Settings.ACTION_BLUETOOTH_SETTINGS); + main.startActivity(intent); + } catch (e) { + console.error('打开蓝牙设置失败:' + e.message); + // 尝试使用通用设置页面作为备选方案 + plus.runtime.openURL('settings://', function() { + console.log('打开系统设置成功,请手动找到蓝牙选项'); + }, function() { + uni.showModal({ + title: '提示', + content: '无法自动打开蓝牙设置,请手动前往设置页面开启蓝牙。', + showCancel: false + }); + }); + } + } else { + uni.showModal({ + title: '提示', + content: '当前系统不支持自动打开蓝牙设置,请手动操作。', + showCancel: false + }); + } + + + //-------------------------------------------------------------------- + } else if (platform === 'mp-alipay') { + uni.openSetting(); + } + + } + + if (showTip !== undefined) { + openBlueSetting(); + return; + } + if (platform === 'mp-weixin' || platform === 'app-plus' || platform === 'mp-alipay' || platform === 'app') { + uni.showModal({ + title: '蓝牙未开启', + content: '请在系统设置中打开蓝牙以使用此功能', + success: (res) => { + if (res.confirm) { + openBlueSetting(); + } + } + }); + } else { + console.log("当前平台不支持打开系统设置" + platform); + } + + + }, + //获取蓝牙适配器状态 + CheckBlue: function(callback) { + + uni.getBluetoothAdapterState({ + success(res1) { + + console.log("当前蓝牙适配器状态:" + JSON.stringify(res1)) + if (callback) { + callback(res1); + } + + }, + fail(ex1) { + console.log("检查蓝牙状态异常:" + JSON.stringify(ex1)); + if (callback) { + if (ex1.code == 10000) { + console.log("未初始化蓝牙适配器"); + } + let res1 = { + available: false, + discovering: false + } + callback(res1); + } + }, + complete() { + + } + }); + }, + //初始化蓝牙模块 + OpenBlue: function(isCheckState, callback, availCallback) { + + var these = this; + + var init = function() { + uni.openBluetoothAdapter({ + success: (res) => { + console.log("蓝牙初始化成功:" + JSON.stringify(res)); + if (callback) { + callback(); + } + uni.onBluetoothAdapterStateChange(function(state) { + console.log('蓝牙状态发生变化:' + JSON.stringify(state)); + if(this.BleChangeCallback){ + this.BleChangeCallback() + } + }) + }, + fail: function(ex2) { + console.log("蓝牙初始化失败:" + JSON.stringify(ex2)) + if (ex2.code == '10001') { + console.log("手机蓝牙未打开或设备不支持蓝牙"); + + + if (availCallback) { + availCallback(); + } else { + these.showBluetoothGuide(); + } + } + } + }); + } + if (isCheckState) { + this.CheckBlue(function(res1) { + if (res1.available) { + if (callback) { + callback(); + } + return; + } + init(); + }) + } else { + init(); + } + + + + }, + //关闭蓝牙模块,停止搜索、断开所有连接 + CloseBlue: function(callback) { + + this.StopSearch(); + + this.disconnectDevice(); + + uni.closeBluetoothAdapter({ + success: () => { + console.log("蓝牙模块已关闭"); + if (callback) { + callback(); + } + } + }); + }, + //开始搜索新设备 + StartSearch: function(callback) { + + var these = this; + + //发现新设备 + var onDeviceFound = function() { + uni.onBluetoothDeviceFound(function(res) { + console.log("发现新设备:" + JSON.stringify(res)); + if (callback) { + callback(res); + } + }) + } + //开始搜索 + var Search = function() { + uni.startBluetoothDevicesDiscovery({ + services: ["0xFFE0"], + allowDuplicatesKey: false, + success: (res) => { + console.log('开始搜索蓝牙设备成功'); + onDeviceFound(); + }, + fail: (err) => { + console.log(`搜索蓝牙设备失败: ${err.errMsg}`); + } + }); + + } + //先检查蓝牙状态是可用 + this.CheckBlue(function(res1) { + if (res1.available) { + if (!res1.discovering) { + Search(); + } else { + console.log("当前蓝牙正在搜索设备") + } + + } else { + these.OpenBlue(false, Search, () => { + these.showBluetoothGuide(); + }); + } + }); + + + + }, + //停止搜索 + StopSearch: function() { + uni.stopBluetoothDevicesDiscovery({ + success: (res) => { + console.log("停止搜索蓝牙设备成功") + }, + fail() { + console.log("无法停止蓝牙搜索") + } + }); + }, + //获取已连接的设备 + getLinkBlue: function(callback) { + uni.getConnectedBluetoothDevices({ + services: ["0xFFE0"], + success: (res) => { + if (callback) { + callback(res); + + } + }, + fail: function(ex) { + console.log("获取已连接设备异常"); + if (callback) { + callback({ + devices: [] + }); + } + } + }) + }, + //连接某个设备 + LinkBlue: function(deviceId, callback, error) { + + this.StopSearch(); + var these = this; + let key = "linkedDevices"; + var store = uni.getStorageInfoSync(); + var f = store.keys.find(function(v) { + return v == key; + }); + var linkedDevices = []; + if (f) { + var str = uni.getStorageSync(key); + if (str) { + linkedDevices = JSON.parse(str); + }else{ + linkedDevices=[]; + } + + } + //连接成功的回调 + var lindedCallback = function () { + + let c = linkedDevices.find(function (v) { + return v.deviceId == deviceId; + }); + + if (c) { + console.log("连接成功开始监听特征变化") + //监听设备的特征变化 + uni.notifyBLECharacteristicValueChange({ + deviceId: deviceId, + serviceId: c.notifyServiceid, + characteristicId: c.notifyCharactId, + state: true, + success: function (res) { + console.log("开始监听成功。。。。") + if(res.errCode=='0'){ + //订阅特征值 + uni.onBLECharacteristicValueChange(function(data){ + // data.characteristicId + // data.deviceId + // data.serviceId + // data.value + console.log("监听到特征值:"+JSON.stringify(data)); + + if(these.featrueValueCallback){ + these.featrueValueCallback(data); + } + }); + + } + } + }); + } + + if (callback) { + callback(deviceId); + } + } + + var linkState = function(res) { + console.log("获取已连接的设备回调" + JSON.stringify(res)) + let flag = res.devices.find(function(v) { + if (v.deviceId == deviceId) { + return true; + } + return false; + }); + if (flag) { + console.log("设备状态已连接"); + + lindedCallback(deviceId); + + return; + } else { + console.log("设备未连接"); + linkDevice(deviceId); + } + } + + var linkDevice = function(id) { + console.log("正在连接"+id); + uni.createBLEConnection({ + deviceId: id, + timeout: 30000, + success: function(info) { + + console.log("连接成功"); + + uni.setBLEMTU({ + deviceId: id, + mtu: 512, + success: () => { + console.log("mtu设置成功"); + if(linkedDevices){ + console.log("11111"+JSON.stringify(linkedDevices)); + f = linkedDevices.find(function (v) { + return v.deviceId == id; + }); + }else{ + console.log("22222") + f=null; + } + + + + if (!f) { + console.log("缓存中没有找到该设备") + + these.getLinkBlue(function (res) { + if (res.devices && res.devices.length) { + let f = res.devices.find(function (v) { + return v.deviceId == id; + }); + linkedDevices.push(f); + uni.setStorageSync(key, JSON.stringify(linkedDevices)); + + getService(id); + + } + + }); + + + } else { + console.log("缓存中已连接过"); + if (!f.services) { + getService(id); + } else { + + lindedCallback(id); + + } + } + }, + fail: function() { + console.log("mtu设置失败") + } + }); + + }, + fail: function(ex) { + if (error) { + console.log("蓝牙连接失败" + JSON.stringify(error)); + error(ex); + } + } + }); + } + //获取服务 + var getService = function(id) { + + var repeatCnt = 0; + var startgetService = function() { + uni.getBLEDeviceServices({ + deviceId: id, + success: function(res) { + if (res.services && res.services.length > 0) { + console.log("获取到服务:" + JSON.stringify(res)); + + linkedDevices.find(function(v) { + if (v.deviceId == id) { + v.services = res.services; + } + }); + uni.setStorageSync(key, JSON.stringify(linkedDevices)); + var promises = []; + for (var i = 0; i < res.services.length; i++) { + let service = res.services[i]; + promises.push(getFeatrus(id, service.uuid)); + } + + Promise.all(promises) + .then(results => { + console.log('所有操作成功完成', results); + + lindedCallback(id); + + }) + .catch(error => { + console.error('至少一个操作失败', error); + }); + + + } else { + repeatCnt++; + if (repeatCnt > 5) { + + lindedCallback(id); + + return; + } + setTimeout(function() { + startgetService(id); + }, 500); + } + } + }) + } + + setTimeout(function() { + startgetService(id); + }, 1000); + } + //获取特性 + var getFeatrus = function(id, serviceId) { + var promise = new Promise((resolve, reject) => { + uni.getBLEDeviceCharacteristics({ + deviceId: id, + serviceId: serviceId, + success: (res) => { + console.log("获取到特征:" + JSON.stringify(res)); + + //写特征 + let writeChar = res.characteristics.find(char => + char.uuid.indexOf("FFE1") > -1 + ); + //通知特征 + let notiChar = res.characteristics.find(char => + char.uuid.indexOf("FFE2") > -1 + ); + + linkedDevices.find(function(v) { + if (v.deviceId == id) { + if (!v.Characteristics) { + v.Characteristics = []; + } + v.Characteristics = v.Characteristics.concat(res + .characteristics); + + if (writeChar) { + v.writeServiceId = serviceId; + v.wirteCharactId = writeChar.uuid; + } + + if (notiChar) { + v.notifyServiceid = serviceId; + v.notifyCharactId = notiChar.uuid; + } + } + }); + + uni.setStorageSync(key, JSON.stringify(linkedDevices)); + resolve(res); + }, + fail: (ex) => { + console.log("获取特征出现异常:" + JSON.stringify(ex)); + resolve(ex); + } + }); + }); + return promise; + } + + //监测蓝牙状态变化 + uni.onBLEConnectionStateChange(function(res) { + if (!res.connected) { + console.log("蓝牙断开连接" + res.deviceId + ""); + // lindDevice(res.deviceId); + } + }); + + console.log("正在获取蓝牙适配器状态") + this.CheckBlue((res) => { + console.log("蓝牙状态:" + JSON.stringify(res)); + if (res.available) { + this.getLinkBlue(linkState); + } else { + console.log("蓝牙适配器不可用,正在初始化"); + this.OpenBlue(false, () => { + this.getLinkBlue(linkState); + }, () => { + console.log("请引导用户打开蓝牙"); + these.showBluetoothGuide(); + }) + } + + }); + + + }, + //断开连接 + disconnectDevice: function(deviceId) { + + var disconnect = function(id) { + uni.closeBLEConnection({ + deviceId: id, + success: (res) => { + console.log("蓝牙连接已断开"); + + } + }); + } + if (deviceId) { + disconnect(deviceId); + return; + } + //断开所有已连接的设备 + this.getLinkBlue(function(res) { + if (res.devices && res.devices.length > 0) { + for (var i = 0; i < res.devices.length; i++) { + let item = res.devices[i]; + disconnect(item.deviceId); + } + + } else { + console.log("无连接设备"); + } + }); + + }, + //发送二进制数据 + sendData: function(deviceid, buffer) { + + console.log("准备向设备发送数据,deviceid=" + deviceid); + return new Promise((resolve, reject) => { + if (!deviceid) { + reject(`deviceid为空,请输入要发送的设备`); + return; + } + console.log("准备发送数据包"); + let key = "linkedDevices"; + var store = uni.getStorageInfoSync(); + var f = store.keys.find(function(v) { + return v == key; + }); + console.log("倒计时:5"); + var linkedDevices = []; + if (f) { + var str = uni.getStorageSync(key); + if (str) { + linkedDevices = JSON.parse(str); + } + + } + console.log("倒计时:4"); + if (linkedDevices && linkedDevices.length && linkedDevices.length > 0) { + console.log("倒计时:3"); + f = linkedDevices.find(function(v) { + return v.deviceId == deviceid; + + }); + console.log("f=" + JSON.stringify(f)); + // console.log("deviceid=" + deviceid); + console.log("倒计时:2"); + if (f) { + console.log("倒计时:1"); + uni.writeBLECharacteristicValue({ + deviceId: f.deviceId, + serviceId: f.writeServiceId, + characteristicId: f.wirteCharactId, + value: buffer, + success: () => { + console.log("发送数据成功"); + resolve(); + }, + fail: (err) => { + console.log("发送数据失败" + JSON.stringify(err)); + reject(`发送数据失败: ${err.errMsg}`); + }, + complete: function() { + console.log("发送数据complete"); + } + }); + } else { + reject(`已连接设备中无法找到此设备`); + // console.log("警报:已连接设备中无法找到此设备") + } + + } else { + console.log("检测到未与设备建立连接"); + reject(`检测到未与设备建立连接`); + } + + + }); + }, + sendDataNew: function(deviceid, serviceId, characteristicId, buffer) { + + console.log("准备向设备发送数据,deviceid=" + deviceid); + return new Promise((resolve, reject) => { + uni.writeBLECharacteristicValue({ + deviceId: deviceid, + serviceId: serviceId, + characteristicId: characteristicId, + value: buffer, + success: () => { + console.log("发送数据成功"); + resolve(); + }, + fail: (err) => { + console.log("发送数据失败" + JSON.stringify(err)); + reject(`发送数据失败: ${err.errMsg}`); + }, + complete: function() { + console.log("发送数据complete"); + } + }); + + + + + + }); + } + +} + + diff --git a/components/BottomSlideMenuPlus/BottomSlideMenuPlus.vue b/components/BottomSlideMenuPlus/BottomSlideMenuPlus.vue new file mode 100644 index 0000000..af0dd3b --- /dev/null +++ b/components/BottomSlideMenuPlus/BottomSlideMenuPlus.vue @@ -0,0 +1,258 @@ + + + + + \ No newline at end of file diff --git a/components/MessagePopup/MessagePopup.vue b/components/MessagePopup/MessagePopup.vue new file mode 100644 index 0000000..a5faf71 --- /dev/null +++ b/components/MessagePopup/MessagePopup.vue @@ -0,0 +1,381 @@ + + + + + \ No newline at end of file diff --git a/components/Progress/Progress.vue b/components/Progress/Progress.vue new file mode 100644 index 0000000..697e69a --- /dev/null +++ b/components/Progress/Progress.vue @@ -0,0 +1,129 @@ + + + + + \ No newline at end of file diff --git a/manifest.json b/manifest.json index cfd69b3..86e2a73 100644 --- a/manifest.json +++ b/manifest.json @@ -151,6 +151,6 @@ "uniStatistics" : { "enable" : false }, - "vueVersion" : "3", + "vueVersion" : "2", "locale" : "auto" } diff --git a/static/images/6155/DeviceDetail/add.png b/static/images/6155/DeviceDetail/add.png new file mode 100644 index 0000000000000000000000000000000000000000..3488847502a0c539483044453e1529ab980763d2 GIT binary patch literal 145 zcmeAS@N?(olHy`uVBq!ia0vp^HXzKz3?$tr89oG3jKx9jPK-BC>eK@{oB=)|u0Z-f z7!+45O$Kt9OM?7@8H#6?Z$HWnxvI3=(2Cx9b@b*W_7LM9%& iUB4R3{%g!vvSUb;F>Srodu=UH3xlVtpUXO@geCy&4lEe} literal 0 HcmV?d00001 diff --git a/static/images/6155/DeviceDetail/battry.png b/static/images/6155/DeviceDetail/battry.png new file mode 100644 index 0000000000000000000000000000000000000000..7c812ac2627a9f8c2e01f4ea9bd092353763576c GIT binary patch literal 268 zcmeAS@N?(olHy`uVBq!ia0vp^CLqkh3?$9N*lqwR#^NA%C&rs6b?Si}l>na*S0K&s zpMhaj8pFmUhV^L->rxrEWHM~dU|5sJusR)t)})`Fo!kdh##0jH7tFxOFQu#|C_VZA z$(Y$Ojz@vwb)GJcAr*6y6A}cBIFB-%>*8@Z(5A`A#Obgy=uOZ=3)99eosTAQr34&R z6+M#B<*ha2py>1v4raZX7cLxBxoRoQx<$!z;i)q*Rvt^r-XGJvYFQk@r6}5Ky?{+a z>!XU4^KW@wqr@tM^Vj1JC-ng6pgRovaNAdJ;x-*&?~O;dcq{}T98jXUHx3v IIVCg!0Q5~+X#fBK literal 0 HcmV?d00001 diff --git a/static/images/6155/DeviceDetail/equip.png b/static/images/6155/DeviceDetail/equip.png new file mode 100644 index 0000000000000000000000000000000000000000..c9bab86a274151c838f16cf1bf8cc585a1dfb1e0 GIT binary patch literal 14402 zcmV-IIK9V-P)CY)p_Q3->X-9@0+@%*4El3Hc4z|kywm@7`z4J#94i2NG6Hn z<76^f=UC2UGIQcM#KGglPd@N5fJQs9BlH#{|!NS80&xP0HfW51M4#y@UZ(y%N`P~}iFtHP|-P_9%3RT8yY zRaDAVQKfCOg#u0t4v2xlLDZ^MVOb^!Fku*krL5d-*)VM>i`QLu+ZPO@`uE$nZ!hp= z=ibMA1{etf3pBc zOk!a0I0~}`ShfYBX^0sKB$H_dTy2xKjFl^;u{O_hxw(WEK$Ms))xnMSzZQHPH0z*2D$$*eL!2<{`82p`IrBV@7(-~MK zQ4j!02#kne{6B84d$xVqwB9%&~8gHj#`ckQ`-`uom(@SA+E;=xh ztI;?UtK<^@<4}L@zHeovQRT`yzi}NMIak}H`XZ$xH3~E7NKUa>gr;lIgob!LfyRag z^sQKq8*jWG1OWl0(|!`wY83+m19<46Z{w|l2O~W_OIBy5CLTzodOi&79`G)A(SgCX zZALL)yRDp``PJ`?n~`Z-gYEz5LF0Uub(LF0L2>(q$b3}wHD!iZQYpGmPk^oqU#gbUQd^tY)(GR1ksY&hQfkHf` zR8AdAQk6(1aqIhUCH_g`@BiTeBb98tMat@@=;sIch>I&RfAS}-B~AOoH}Ysbj2N{# zpmPQ2pYSEcEgl+D)lz*m$>LOrH#8zyGU4d%?!g^*d;pD&4Iv$*kD3dN<-zW#B&*jo z*It9{Ocww6;8#*hm#+NpANauCk3I3kqsJ~PFx5o#mV8aS<%cyPbVERQo7ZVs!S#1= z@T3H8t8*oZS87DK%F@ubmX>zheDhXxbap6-aBx(jrg%rzsbx?MQmrjpwqRsrSp4t@ z-*4z!vHF+qx#ylgzi7aaC5l%xY{}Y46sWa{H=XV3Pe8Qrzw=kyrxzyN{A>>7+dz-% zSl+iBeSOR5JetD81D2(UdB!wdX$eV^SJDk>+;a1+`0?XEG)l$duhvX_>7oHsZH%w1 z6)Sf=pEvay@rM$3^^5TS!~g3ywDSW?%j|3(W#Si>j$|@L(y~QaBX4I96rI$?G4(DQ z^$f0xMmu*e?8e9M{iOELL*MFIv0}qVE($PAy5<+Z@T-%y-g!)mBND|NPLZd563%go zD=gYLmCYz=(KLxAifvSv=!%meD@EX+P755uN*qaf~I|DMCpHb=*iA1n!^(y7iDN12b zNG}bN6rFx6m#fYReMt$<9b8B%scCI%!R1$M76%U=+;~xdDP=OPrlGeE<;%#Iy!#Af zMtUTyh!{TSI9@K4B{EG@?ZfJ`YSl^tLs!xfaRK4)6kw|TNC@eZt8<6JZ3_<=-#j_Hu8_`XYBIm0GCHG9r$)~;o#6Bh*-s@4_NTCL$^sY;)+HhTc^P{(aKB{ zD7jqLu|`ypuC4{-*hLYI8A^TF0&yu7E3}V&Mi9b9)^QD#!`Iup_~LlJn$)h$TQG{` z(P>hI?<>%Imli@ap#D&FtgMlfPB73(q#KXNNDU&sL{*4a2zdA4fN1M!W)5PEJ8fdH zmoM)-KVXzl-*;bxno2`ko4}GKpr(^JdX(;}qRx3V7Bg2&P1VGb zLHD@>!?!nm-#v|uJ?(ec0$n0M*@j|ATG;yjQogW+s=8T|((J0$c4i`t-JYudLoBPQ$w-RM*XGCR$7R8a%_r$WlR zVxsg%qNoicHEWtV1GX?l#7M3xj~tF_r5eR3R2erif(M9jeH#B=_-F~2grl*TvN)20 zPT`txmJ|n+cOaG1F$%UyC%{an)8`J%)>V0too!yAvS@k)wOE7GTGNwaX7C7#6C==# z7}5)uqM^61DFVCE8h#1Ik&~#BXJ*x^qHXmCBn*iLX(8%5SkkvTB2cN2n0v8~bYmmG zI;2jF4Xb2*h3oy4)111~!X`SmaPNUmcwQV+EtN#9YElh|`H4YtL&tIS#iuY{s^B$C z#}NwJyASo_?#{iq;>O!Wvau0bDTm7NAZpyo>o3Q2*%5hJC({U-?s^h--W~N}@^yBV zDk~R_2{P3&6sSUG&2srC2qk0)B>A=&d2CM>tyDe_V0KgMbdyym=E);UqMV(?p_iV< zzCsndmu*6~@5ilx~)U!|`2j$QbBP()be z+>*=9Dia-vlKZbmk1=-X+<_rn&}~^2i%u@c zBo4g%3{-4EslE-+2o$ckkw~KZ^7r9KdtXG)>$`E=mDj>(X-9GTC=n>B1DRW! zd=XQ^^=NHnifuPl=~*4yvO`|4RL4|nRR=fvJ~=U^DoV6TQlp`owfC(L}s^DP|A8iTsHpNF|!7Wte<5H{aqAt>)%Xob&-z!0@&S zzg0`+B8tU=;+4tCDN?PP3IkYj6gW*$Iyb46 zcq69sdHQW3aSR1)=|-fRI?yakl;IF4d`HhG9t30{T(i;$PfIu)9?Ix#{QNw#%j&(IL=yyQ}tM3M;ttG{YH`CaMe zbN%$Ln{)A5x88iPnPvx1{o>>zuy^l%@>Z9SnT;!=b%hS8lun#RmXEw*mM#w%M^#BW zgsZ|XP^`vdV`Fqo>D++HWX2aXB$BacSBttUt3p-^9`W~M zFP5|#>_~F|l1A3j)*W?G04Y8bP~B84QN(*c-S;{HdX5erols|)Pf~2c(H)2OQ4aTo z(@L>eoPxK43h=nIOAF;I*>U2EWQlk!GO@O)Dbrf5lZtxXcd!T|Yvtiurkk(|%yA}@ zK{lI3f>6*}lVrYi=Pa~;L>H)5oPd+CAt@ms=-ws0l+?H#dA22pm#R4U)$As#8tVpUfMfli)vH$#XeG3?Bvj(cJ5G-X)W}Ljn9*EmV@H=INYLr>vw1xF z=%Y%F7|e47#tD=S_sp@jE~KLdx-ut`Az)gGWHVD!$mjQ}mgRn*?BobKtqL0A37k0m zCPt}+8e52b$#GWI3kP|^yF}P3A>bf*!D88$E^j|>fEQnQ2|YdCL}866ki*%~7*~~- z!ZG8s1!@${fY*bd66V6`w%zsG9^$DCnwnbGWzPv19Zr9Z?s}Bb#ulWz7UI&eQ5+rV zhe=*96QCMdK%khlGDZ&U#h-7wguv8rV4xqPmYZ-2*MF}2uRcqJy@JvNM?{k!mAmiJ)OnzbkwX0eaNy$px+ z2suU4H7n%h+QG}|NVDB6ob=U)x3hzH1|-!!hYugYV~;(7+i$;>R7xk8A>~U~B*Fw+ zhKxm3ZkWaC;QqsS_@VD9mqB^N@;rbM+*wE$&kdizj~@LdY73U&H$HMNn%8f{#*_W% zou0zC_xuC{aY~7fKRJ97%_MDRnSgOUUe8t6^+-U% z!XaBv^uzpf;>7XO15g*7g9i?h^o--qJ8vfdmY}sIsXPRhceYrZw<(ux?A`M^9(m+Z zWv-2A%&nwz%CiRMzWcT%$H)6GCtyTwbOgsMCSHB(Fm65YI#zGE0?lhKLozpm&o1dx zG!bj$F)1<9X}S?|{*E0xjG`@7MRnpEBbh>?%2F{W^%$yu5FrQMCsJ`t1zsrZ`7?gn zMml3^CdM=P#y7r&wzd|0;u9Z6XJ@P0sa!Jg`s)Yr%+pU(Of;jy7G0P17e5z{iN~wU z%jLpdWGmGz#ECPiW*L9}>~r`_BZ{Scs}XBxL^Rz1HD-m~c2*18w@C5RBzcoX`c9z_ zIvSfWLDs3Jg2uT?Hd!YG(nH8C?D>|+{BstMt&VBdRBxl_F(U*Li6e$jjo`1p_!Xz0 ziT1b1GSy7S7h>t;^PQHO!C3-B!DD34oUjU5t;(mTJEj{E?_0c> z%-mt@Db=uxBIawB^hsUXzL;MMm+2DcmL64eVyK8G^d1PDuxBPC%=H@BZkp5Eye>ZkHRWQ*z(_1OT^ z*tlrT^z;+y#f$s?p04rR%uOwA?fBq=9wc{e$1wRRhmIdX-|`g@q$bBFCs3pdR|$v_ zimEdzvrEKB=Zev%!vjarRO&=|+9LA}x5K^eE|ve`=XrgvS2(IfoW(hr2&n=`*Q)c4 z$y;;WiJIx;eyi28@^7ldNxZ*B+gWPa8)qNO8Sz(^hQ(iiOVofR-7AREkD zwSOizvv+jtlvzpCA{Dy8^37Lc?U6UpT(04&OE)U(LuL#2Ubh8R@^+t}DWNYBRnc&S zge}4G6un+Kbqd$_F2eFOFjjRc?pPn`1K_oy3I>H|xu8-GPVi?TLaNIg!$}C8)av6O zzZc7vEms`U)YPDSsnO9%Jo5hRHo;**xqOLZmazA6kV73g-a}!2sNID zre-34@=|!(FsynNqR}Xl$pqRvT8Rr|DzE$PZ+`1 zR|LAKH7@Pw#+G%Lp+sCTWjP(@2@>fzRj@`5T7K{l5_S|51pyPm_&BfB^#p{HSGk#K ze|)dO<462E1^F&hs&|sd`7#~a#Bs})FGh@jP<`4eD=3sY?bxvchv}Ycq+&e_mngNQ zOVmhkpRQDlhkqt8WQ{~FD}GMXwFM;ValWA%>nQ1DoNi>#@qYX=H6wSXAA0Le%u>a3 z1k4woe;)VW_I@ntTZ_!-DP$|7h?68GBPGO@ZdWly6Rz zclEW3bair~cyxA>%xr?JNZvNFcWfNj(@ifZ3cP9QA?iJ|v*&H()P;|mD>+UUP$<8| z!P8dWEGMIeP7Y(m%4Ke(?F@Z=_~Gv;&zU)^Yr!G{Q60x!>3c&KbSzMLBs##P>+Uv;>fb#4@FQw`$vaDoYZQ2N zb(X~6^AN6~z>v+}Up)Ft z&XTDulRC}vO-chBnw!y0YB234yyte$dih)LJLgS`6{lmjOdxwHi5OX^?gb0T$0;bQ z#HtlFG+c7oDs;3rQ;3km_|z1>{N?+RB|j=o#NFB1qg05FKTIe2okt#dB_P&% z$fcKEja|E5K(&ygC|fF%*FV@#(vrh4IcoPm^9;W9;SXck#v4#%04Js~YgSbY^VnU- zDx}erZo(|JblEOr_hbfZDBfOQ((t2-76K)7={cXPZj`P36>kNSQT)RNIr0VM~J0qIa79NY&+8T3{O}kZ? z;O&0o2=MAFuPN!_FyhBQ{yy!WLrZfj8XB6pUU|#5f6LG-Pi&hrT;*+n*|tq;D_8!g zabhCVbn4Ve?d6xAQF)M_u7!|>fftS)!`+*1zz1%*6L{?Z;q^il`%etwU`IPvb#-Fp znoE%_7S+fH1q^qFk)EfCYue0KBus6opV$j0LA%XCX}{$>c6-_iB*j~;*g$?Dwt zpAi@y-1Ap|_4QP-*!WL|5m{0!<^(6VckOz~8Rpg6gOO|&-+k!?eDsE`*t}{r#9Ie2 zSSw-Yf&FNp25lw3rJeM6B9l>GX@pwM&^e11C3R{m}6)nU$397J)u~-r)(q?d2QoN41Absq7vfV+A3%Yw1t|VO(mWq>$_fik<40;%FfT^rjai;p@k&i2YdJ6)~nu! zrB~mA+3{f@lf`7YfTJTLxN>1PRf6P&h*l$o(#gz6w210f;og_#rWUkkCQ+7Uks?nP^-Cq!C3m+gih@|$ms^sww zkc;uV-Cd1OKm72+*4t^!8G)gC8RhcOUDC4ko3?U_FoT^twyQ5spSkNpSb6nLC~f}{A~k_amMno?%0rUW@OTzYgD%DC- zW0JaQ`wc(%!H%1+yYAY1M}~*DPEL&D z^}W0C%FEATHF6sbivELw!S3p3bPH1WzSFJkF!x1(k0YE+5jGNVJd|G*(! z-q8k;Ag7C@hJ!$M%NS_M4j-L|fkD!QQLUkYK%XEpKgCfw`9KTE{H}{f(JwVM5Wp7B zs0dD5qzQxsq`LWpS;iP*&YU+j;z8$(eUaeULybIY7_qNcD)ASVbjK?{OLg8Bm`{KD zk2`h^9Q!x@gmx8GJVqX4Wc8YLC=fyZ_NV`dROvZ2EQ47+9#0`brhD@xS3tb+I;Qed zIB?(%Y+kp4>ThDti2)oZ-{wjtO*ZvT=P(%vzAvdd>{!j3m{N&TCtY3c_{a#UOcP&X%l|0;ZJc5WyB@oZprkh() z`5kI~9Ze+i23;$~eo=N3t8Nec53(pPM##s zmfqW1JFtL)tvoqXsvnPd061kC3HNmqh?Zzf4tvUv+0p#bv)9Ty=r8qH3_A`q)>?YM2p3SjscBCV~6 z(D5AQ>$aTw{Dnls0|t@UXX6ngCY^!$C=@5v0H7R^D7)DlXRxI3(6N}#6B!I@?V8Hp zK1tE@P2|UDiH%IFCYn6Qf|SwR;i%g;X>{u6vwFmJ?bnEp6n= z7u77b{WS}hwserqnSn;v_<_ z#ot}CE!@OXgtjqB*WyVGXOEG^imK}x^gc+@a05AloU1VizI09GKf}F`Y#piPlpmyo4Wjz++g9z} z0;7`4LJ=?fFG_{{tvP*h!T$FL`HJ$kAhV^LL_d52KYIySCA@r>6 zLpRlWK{LfciL@e0uV^Z1<#ed2S*~t=*Wi3D#aB(jqF74$`ChhwlVk`e?r}QS%0;wC z$zQIP-E)YucJa>{7&ggV7#P!)7U8B2{ahN|L?gvD4Gm2cT~8{eJC)1f`2+iL{q=X? zN^-=Ep<$e`%6NX@6mIHn1+}d*@0vSZ&#_4E@GdvG?MV}Z&Z9pCo%`NZEAhywQCMV_ zc@Sf7?=oc!7$k%Ir8c_?oj$u`YUXW*Uc{Tp!5MT;ZaXoJbji)jXh?V?r@fRT59fGj z*Q>bdvQ1dI`396{9)-x4k)c+7eKLZ}niJ&7){njvZuZ_$1>p|&B55Jh8zXaE9Y2X* zykZM>3=bnq_r+uNhKEn8lxmbD?6=mfK)OZ_qcebz&#d1wU%>E~d}p+QGo)O)pm;ps znAjkD#_J?7c)ZWh!~|ZXAad*4by#)jCYXC)f#3;RcmBK2s?&6>Vj^2-Z;hq z25!Hxmw}fmZ{j6FnP!xU6pooxgMVUCET#zmo6kOt)fCuuFI|hiv6GOehS5(PkS)cO z)$zGkbIms3`!R}p85mLxj<>9-F%;k0jZGW3;%{%g8E;HXs@(9Ey^GNsC-s@0gx=iZ z45qeC3e0W&9yZtW0nBV64{!X4Go8-)!4OWq)ybkk_429shATq*_by$DP2;2FjTZ6B zt2^<*Yp+M^%FD5sq@|jffsAWNm?fXQeW?L_>*Q~t3&oq;A$yjfGE_rp|I29YU4{2^ zj+V#d5zumm%zq*d!4l-&7&Vlf59dw+L%xgK`|FHW@~)!#gTT30M~%2Ak2}6qo`QUe z&Lw@=JT{2#iiIkNxMRcUN+;2=bPW>eVI;}4nj`@#HqvyT%su@PQU=n*VFCFo@ddpq z1zMY#gf%^m3b_w@B83ROjC2Eq0Zsn703VScwR<6fDHdjm#4Aa@W0n{$w%ZlTV{@yn zpTlWRwjSQ}@pwZTmwn{da2d6kUChIp$-vA`K#*liQmfijxnOi*Pz6U~0?zWBNZunt zq^gsH$P>Qol3Ec-&9Bs%Y?En~!|~5KcNph|28KM@9R$d2P88$(WA#xZFX`eXW&|~O zIPOH&A=%Z8m@{UVhfu&7QE8LsDy^D7{L5D*zh6i?(h}-%ls@;+Kb|OIbU^Y5ZQhT0 zN_C3aQY|bcv|yrr?Mg&X^SjfxXh2TGJ^-5pcxGz=a3P zg&WQT!!NF>cY^njFXO0Exc5hmp7IXW95B2knVe(XeciEhnrkKIOJ*X;Wc11GbnY=S z)jtR3Ri_&trV1V%0%nVpAcrgB4pbIi-wjtlCAwXVG|jJQ9T7O2eHDW3Wb}glL;#UL zU`V>1{O)InI?+%I3JMRdc>w0<(WBM&_MShIvV1k&QzziepOZ?TZMWM)R~#c;4wAOp z+HM7XWWuA7y&~#AsMdEo%6y<)36icTK-SAr_!>Sx@kH3OnSFk~!0;6(Cx;LB^z?kZ zSSJdz32O#PE^ zCt{Jrl=Er`nUvv(UI5-0JHCz|cR6R%iqqs@oK>gdd6mxl)+j?A&kAaqjbq_uVB)zum(eBixa@ZjXU*k}}Ym+Mw|mTXN_m4@l1EVDo4x zPhEL12`A!HDaQln!25h<8GhujWMG0Vv_$ zU0xCH@9Kjl-vg=?QU_1v!>t*bk>=TJFGn0c9&Rk=OObDq=Xk}T;{Pgw3Fa=xoc-;e zV>t&^@(2{p{kRi9rSFz|V&Ym8&lUDLAgo%!A+C$(eF^OlNKpV-$f2}tV9x0-WO-K- z3a7N`t7b@OnK}4=#y9ryYCY%2@#ukI%|#HSXWB>c;g%JS@>+74s;=Z*}j-unG43Am_76jD>kP z%(OxTYU`hG{>nLo1j1hz%~W#|gY|WTo%}yR)jiAX4jQTl2_a0iV1MsqVY40l5C9o0 zG;_hrxv7#{aWx|>EXv-yKYJuNKzQD)T5%|pu9S8_Nnx*7ff4jpx+g#VMufYj&_(xr zy;~AIJ}q8nID#Vmu8?|_@xFVLLfy*L6AeOi48QU&x6~)U+-_f?&6_@*&+4qGOF?_S ziuejc@r=hp;=K?O*dbdo*KcuePOV+#3?B>Jc<*>oUvc*% zd>LU-{oZoSh1PctE%<6E#k_SZoeMBpy#-YzSc}*BDxJ*z8KYlMn&z15kd*lAn z1C-yDJb&sPo%43z(A>sGTXs-5zA zHXJ}ok2PHb%wKKK%hl?ogg<(K2I$POTtFaQVW&sV0sX?4cRfzlTkA>PqHo(lt=c?Gau#M0Tb#SkS>ur_i3vXLJ&9wR!4-63qIGE zx~!fX#S7^z&Lw?N=d+w2Fsd)zTZJqXGr6TMDDWi11;dezxjGeMUyp!gc;JV7J$SMs zUC(zOM^v5nQ66)8cV^2_ONY#jr$X*;frA#>&y|SNVy6&&)#2#=CgvmpFJyUlsm<_6 z2OpFA^gta4h)@u5T6o~T%MhqS`aBg5UZr>a_Y(PTrPr8cN4-{4#cNV2!}sqNZ-`-d z=eZ2wtsAb@{~R{kq0G3WZ9(p5KHxd`2R7vMVnIVH=}UvxY37k-h)e3D=Rj=&k2Wa2 zw^z3}Cn zf!Wm1AjgF}?LO?0hO%jnXX+8IyG?}luXE|c;Dz74bB42Yr(3}JD(C)uC3>wEdZ8{8 zBb<3h9*@W{V4fQnmika{rSp#9J)DaP2S#-!-1#egoxHdqfnRiC1tj99GG)k8pGigB z5{_*=t5)ye2I>^f>4rfJz?-)*^XF-W(g4wJ8a=2Oo@IWIqEVMEHBf3K`hUB8k4 zH*9*ilHMR_Z}AH&@PE8lXt>j zKuq9EV%W}yBu$2-O*y}H9eNk8oF_2uC@R%m5;Dtfg4;J0p1Jh_aQE}x!pU;L1zOa( zJJRp##_7ic^XBB8zr^@`L2fN#bGTQS_cx9Jne!%jk%C0mLik_nb&V<&+)4 zb5<04Ufqe$wnfpI8B_~&dDHr&XUdfTd5)k6sSxUDL`CKzX#M&yDQv|8IOIHHb6&ud zEDO(1WbxE9&jW4UIG8KI$j%^cR}oJ)s%gpIv>UJWgWN8ckWQS@t9R3xb>xn~OLkRD zi1)xGi~6FYTr&}m#$Xx-ie?G?I5@jjhGtrba^Jobes8yHhumWwlJBc zPESF&>ZU#jcM9VCl=A|{nF;AuhbzXDOQmz8Fc*Y$oa34hMETE{mW*SIFf`5K<_hWar!E+{?~h0VRsHU4Dw z_XfxC0;NCv{O65D1Ba8vSt%mi@)4cLSfHxuYDp!#R)cLg6%WWXbl}a%KS8WEx)^K8dl_*I?7hS5cnHIEgFW z(+d$i-OtMj3y)aC6%NsKc-V+LPOtt!c&d*(BJA4LXldcOoB#2^;HzJaR0a?KulW95 zzY^QOi(mk{D2|jXSE&_5tOL6-dtwl|bPLL{6ghh1&?2_8-i)$PZgt&7(EY*8VSk2o z!6HA%eK=lS7~o6*hc8Xus4crj*QyqvUr0!ta#p|;v)K;4rRxs*nDjV9@B$Z^VLZ8K zFTQo~1V;2YI;FEdH$Q2MpQjYloptVbQ4H`(BGEXscnW$v36V%aizUD-0eIYOs^#4n zSoP>$_W=>X+(-t_;s;K-t0UE_8&hf#$hzJ z`s!m*xKYUG(a_qe7T_>vXHb}#K`Nb850)94${IbQcJuBZY&rn!zMx}U&kC5z#Au7t ztM24plrK`Ns#Ten^sR%I$)Gx&0WW&VtE3g^78AVeK_WpaMI_A9!jJqRaJ>0U;AhI3 z?&MOQDQA^2J#Ykx_62GVcEr%t0z1dvd;=RcU(1PRn3E@P?BqCBEm}yDlf$8j=}235 z%O9i`CVup6!`A+rwr{^+Gt|!tnAyRDCq%ho`AI3)%qACfBbJ{+GM_<`M0_&UfK0K3 zGAaE!YGq#ikRObzyYq~MNkL{wsP$!R3OZSm_?(heilk#^Y68_)wxf613N?g_S2-IS zA4cc#!)Q$<37AtD9-Bf+*U=C+P$C*TlB+FPwSL97mTuYn+s8lmxqZuSx~cph225^Z z;*dQxI!xl-h&A3 zaD5j>j_wzc!DAa&T)E}Zq)7jD`Pj)vCZZTx)Z1HrH@?l;P0MNMvBs+AL~i87T8n4Q zOQ{~fFn#zCR;*czbWbCG{MIO`MincP23Dk_(5rc9H8!cHv%HEsc||fP{1`%a@f=>I zl$T_wRY{5{Rw|Q&I7lkBu&WypQaxS_Fk39(=*g2?bo)T%d;mI|r{#!K=G5>!;Z-}_ zM-Q?Zs%K#vq+?O_nStWdO})vcme0H~I-WRQuHu-~VQO&CCUZHp9$%qY#PXG^v68JDKQ@HdxN7UrBdLZo^0_}9Gb&$&dX(k`1cu9KX0)X> z-C_()Wdtviy{vBoTCcnTv$-rPnN=~cdsT9%Sj@rsS@@RnFn5?V?LqQJD4u+A&cd`Dn9Hr*l(GS%kb5DJ-5v~IXs zJ-@Yu%qri=;Lw0ty1I2mAJ~DJ7#+d)jvU94s)=^umF0;z7A50Yl8B;*+`}l*icUPD z@dGuPUr3!a_Hir@NGQWxa2@MUbWK!~+>T&!KQfk~@{s+}SSvLOX6eQW1>su(3&fuQDzX# zvk%goqOKwm-rPxlfiN!qJSDi>gK-sp@y7N^_d!|C>%8YPy|Yy+mxyj&7|*B z#2Kwt1;awE4Ck(#%}-9y_$lmKEZu~aix-O(F&1c${bcU1JQprNycv?JF-iYl3)${NzLIK0tpUT4s9 z7k-A9xpEYQQ4!8uWmB4_N!XfX$IG-aM+C}Gt}D?|B??Tlv~NKOJx8^!P#jjH?PY3- zS*e}`p;vTW6somqg=nfQ?OMr{)k1n07*qo IM6N<$f?rl8>;M1& literal 0 HcmV?d00001 diff --git a/static/images/6155/DeviceDetail/fan.png b/static/images/6155/DeviceDetail/fan.png new file mode 100644 index 0000000000000000000000000000000000000000..3481c634d12aa650e849da9641b459c69bd1b973 GIT binary patch literal 275 zcmeAS@N?(olHy`uVBq!ia0vp^Iv~u#3?$#IayJ4}jKx9jPK-BC>eK@{`~f~8u0Z-f z66kljmjM)FEeY}qX0XU#c2C!HUmQ?ip{I*uNX4A9m!gH540u>C1erD5VogY7wA;my z_3q#Glz5fIV}bQ0C&DdM<}OJ%bMH~b{$A71i(hbRo+|Ed-Fm*u-H%zRSfeL9^KlmU zg!z+#>+ILqGzY!g(fad{`@}O3vlgGV;waC(a!!6<^riKMVNustE-afbdc0qz?6BWQ zr5w@K+9AJJ7?y19m+PkfH(YkT=YCPmdH;s~kWi22Bj!NIGI+ZB KxvXdm z7srr_IcKlf@--QVuzxVGWDw?m@ZiBZbpsiPr>b764>{rvDOa$Vg^Q(@E=)}kdatA# zpZhY|fbUQc2m!`@0}4j|Kq|} zPD0`v+j(}K=-c-y_UY3l0sFMnHPTf+&JXsOq>;I(`@jDujZd?mDID&JUz^3JtozjK pV%eJ7UK87_NR`_wHXqbuv}5K~n$q8+>jw-j22WQ%mvv4FO#nL;rEUNK literal 0 HcmV?d00001 diff --git a/static/images/6155/DeviceDetail/mainLamp.png b/static/images/6155/DeviceDetail/mainLamp.png new file mode 100644 index 0000000000000000000000000000000000000000..838b901fa236fc0d0533007004087b5cc5669bf9 GIT binary patch literal 413 zcmeAS@N?(olHy`uVBq!ia0vp^CLqkh3?$9N*lqwR#^NA%C&rs6b?Si}tpJ}8R}ihG zrHl=zsHg-72XET6>G9*o&CSidpOf-|s`*NS{DK+SQa8T&UsAlC{XY=gTb9rcG@ISi z#WAE}&ebcsc@G%~usv8U!O+R5q46S-X$oV-9F{_*RF-K8EN&jI8o`XJz6GaB62+6O z`Hz3x>2)}M&eNJt-d%hQ%d}-ujN7#)D|XD$-gV=phn`bUg4;z-F_E_ky6*8|(@rT) zE9IXjQ>mCWsZ^AIL0IwI$H9WvrhJ$-xp>WaU!Vt%m15v!_xr@qY_a-JCPsJU?A!^wR+=YM;0>4yK}UrH-m0|Ijv z{|;x#O>tX%d-18$UY!KgtYRhuVja(fir`SAY4;uitt?1I;RUtRU=L55~{*S2-v no@P{C+2pY$FuOUH=?}x3Pg*+3rT2t@fyLnI>gTe~DWM4fhJmG> literal 0 HcmV?d00001 diff --git a/static/images/6155/DeviceDetail/open.png b/static/images/6155/DeviceDetail/open.png new file mode 100644 index 0000000000000000000000000000000000000000..b2f321e7ff0713572759c7784cecc3479189d228 GIT binary patch literal 357 zcmeAS@N?(olHy`uVBq!ia0vp^CLqkh3?$9N*lqwR#^NA%C&rs6b?Si}^#Gp`S0JsT zqN0ohw6wG!yyoWS$B!Rx+O#P+IM{b;pb}6iZ%L3}Favw)#y9`jO1A$8f;EC0R{|w2 zd%8G=RLnVhWh3t)0|C|xuU|}*Xkb!P5LRGoDBy6`QmLAjz+!&$z(POg`Fl4!)iB#v z;~nx)T=UP9IkLyyT|V=6=Uh)Qp8Vj`hA%S9jpsaBc31FiX6p+F)rZp38ftFgb~v6`$pHNnN*8M__Kz nTkXqViq=lbOXe-}{K9x$qcF~P;d)M>_ZU1~{an^LB{Ts5L^q2% literal 0 HcmV?d00001 diff --git a/static/images/6155/DeviceDetail/param.png b/static/images/6155/DeviceDetail/param.png new file mode 100644 index 0000000000000000000000000000000000000000..10cbd630b03c783c707ddb9bd52a019f884cbb86 GIT binary patch literal 289 zcmeAS@N?(olHy`uVBq!ia0vp^8bGYW!VDyzIzIORQjEnx?oNz1PwLbIIs5@WA+A9B zKN9G7x|abIVl4^s3udrbb}#?M_T*BazFSTQ4(m9ai9By|Cje!?bq{bK?@s z`ZQByA+X=rfILT)TEo3;RnBJP9nC{#uv6*+pM7f5SJsDr$F~4NU*|5j* z^FPURJLWGlJn%q6O59@6gm2{nmpv?%x3$)Fct85!@^o9{dz;735;5H8g!FCKXfHO{ zT)OJ_{=S<>f1jxQ^k&n~ThD(RYO{3)>u%|~X}kT2#eK8)_D5FKw{$IYef8m*-V&AE bbBDOked$T|SoG-!(CG}Gu6{1-oD!MM}1M)(OLR|tGBBkyvAbQS+aayn@V`F*cA5Ti_=_; zw{6LaZFWmxoIU-L@U%k~GD&J1g)Ri`3}{%Qw=6lG{RS|cm^@t^Ln`LHy_y+($UuPY zLF*2gMFCnG6$@m#Vw%|e1AhIl7oEM(aOvHI*B=@sZ}?ZFSXD^)PZ8qRxO}JdvptXTv5%ZK| z%ncv@8BhJQ@J1!Oiry0e*ZFZt$Dh>fo$=_F$S>7J+hj~@hpR;k?DwZHN2m zOqEH>v-peu?rwVDFxSntqbFWT-G#rEd+(uT6R)RhTzl!XvU#(FyxEV9S8HcIPTM%^ zN8|pxG6xPa&xx91d$AT^vIy=DeMBU8u=G#3gxmSzv(9iW}9ZjJQ Yeo&Efikz~u80cFDPgg&ebxsLQ0C}P>jsO4v literal 0 HcmV?d00001 diff --git a/static/images/6155/DeviceDetail/sendSucc.png b/static/images/6155/DeviceDetail/sendSucc.png new file mode 100644 index 0000000000000000000000000000000000000000..a9cba803f441242fd8ff90fa082d8222912111fa GIT binary patch literal 500 zcmVeK@{`~f~8u0Z-f z66kljmjM)FEeY}qX2`c#c26i_V*yZLpQnpsNX4Aq$**}^40xI^e`9Mn#2{kGu!85n z;os*=WS+8Q$#VYxBjwurxZ}>MmXm!V(yaO-yL%t?H-&e#&wUhja6!d0i5K^hxu?Xj z8XnN7ZHs-{uy(`IBsP!PlczCn)mZ)enU3A5?Emkj9v#*5{r97u<)X^AydsHODf2t} z)o$Qi#TC!A!^MBCD_{Au~;tdBuiF%#)Nl)HX=m@lfG*Sf&uoAF{6HiNykw b5KV?gN4BS1Z5BNPn$6(p>gTe~DWM4fho3_7 literal 0 HcmV?d00001 diff --git a/static/images/6155/DeviceDetail/time.png b/static/images/6155/DeviceDetail/time.png new file mode 100644 index 0000000000000000000000000000000000000000..68a4826c51a3794c4e2468a61285dac913f240b2 GIT binary patch literal 321 zcmeAS@N?(olHy`uVBq!ia0vp^8bGYY!VDyhC0Ig%6k~CayA$KhlREW44u618h%1o( ze>~v-alij3eE*;DlkI#V0Tf~_3GxeOaA?^7|C^}9H=w|IPZ!6KiaC2H-R3>4z~jnW z!VqN0u!zTDG0y~+_x8aXPj7y*Jm~6g*5muOIkNp&d?b16`^ipc)NcsPXE@EIura_j z$5XJxg}+Tsnbku(-s7aA;b|@x-VpU=6=5ugT~*(i3L1Lw@NCWh(9&z{f57jSfXH6! zpQ7d4&G*UL{f^w3Vz9HmHm%u4&EMYH@X&#PI^Or^%r&ywW<7W*b3X0&#I79sn!vh= z`QtH#u4N>LH)`48ngC99FMT|K_}xA@O#~lf8z2 RP6NHh;OXk;vd$@?2>@A*eh2^n literal 0 HcmV?d00001 diff --git a/static/images/6155/DeviceDetail/uploadSuccess.png b/static/images/6155/DeviceDetail/uploadSuccess.png new file mode 100644 index 0000000000000000000000000000000000000000..a0252a581ab6a6e6de094b53c2ea7eb2cfc5a9f6 GIT binary patch literal 282 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0J3?w7mbKU|e#^NA%C&rs6b?Si}{s5m4S0KIn zIm7N}VDyyXTc%_QP>8i8$S;_|VgLVzmk(!t1`4e3ba4!+nDcg0G~Xcw0awS!22N>% z%c3`4GWjh39X~nLyL8@P)3)EPMgDv|6NJ3mUd~%2@JQq<>vb2dDyGIqsvRO<*kX>V zeGt9E;6AbN{85%y>^o#Ra$Nti@vsUuNt&%N~ZnmNBKCswY`v6a8X`^(s>qxo^ZGtf2$Pgg&ebxsLQ02@?Oc>n+a literal 0 HcmV?d00001 diff --git a/static/images/BLEAdd/device.png b/static/images/BLEAdd/device.png new file mode 100644 index 0000000000000000000000000000000000000000..f3b9bc84e71786760db2c5b7c9509276cb899516 GIT binary patch literal 9891 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 Vw=^{8uIB&%002ovPDHLkV1mLm+ll}H literal 0 HcmV?d00001 diff --git a/static/images/BLEAdd/linked.png b/static/images/BLEAdd/linked.png new file mode 100644 index 0000000000000000000000000000000000000000..880160961ffe8499f5b5a2c03746b62eafb9876c GIT binary patch literal 793 zcmV+!1LpjRP)9ELgq8H06B^1R_0=VToNjzAs9$$8iCLD)<*>GWmiz18r~T3r4m|pM0P` z0!`&&E@I~6Gt1@BJ%d0B=8|+)k@^JH3$#QSts$sY-;b6G12lV{RaT%~T@|CvCXIv& zQXO@G<_OJ!61ErLSwq{~nJ1Y}BAM&y%|c(`Y8{#DsR8;TS2HDaf(#5qxo=Y2N)6Bg zts|NF_}HHas*V~^W2H5q+-=;+dCAP(n7Je(;qu!R02tNftg}C$m#w!bcXbsT>XX7~1-slm%M0D1@f9(bHke&q6|rT@3XVQ%t3g7bh9X%4Hx@n? zvZm`kP$Rop}-x#tv;x!vUzA4*$NFu}++nr0ylisLIVlmlu(>D(V(Mkf;R=XZ*_05I4%4T5Z>pwuo0+{*_ X8R_8K11>rN00000NkvXXu0mjf)?8|O literal 0 HcmV?d00001 diff --git a/static/images/BLEAdd/noLink.png b/static/images/BLEAdd/noLink.png new file mode 100644 index 0000000000000000000000000000000000000000..b3af9929ebc72fbeffe38805fd8b242bef14672f GIT binary patch literal 670 zcmV;P0%84$P)^PwY^ZCa7+1o7W)rmV~o|fqBVRe3L13^ab!WV zfru0_j>a9Jdv39yY(n~I+$o|K36f9Tz;IpqkSG=k3l=0_cp$e|d@K+(D`>SP76QYi zI+#f5{mKMJ%p<8$vCN3_3Mx{JWkMA9-c+g>z~;PBMg_@d+U%VHVS?0vfuK!7GYv8J z!w>e5d_k{bPAYo)spFwcOHF~{VvaOw?#qkm?WeDYaxJy$=sY7=igP)|00004b3#c}2nYxW zd7ros zAqr^TQtew@-F=~B zJae{D=-2@88V9-Azf`Ic!b_^g+yDRo M07*qoM6N<$g3deMaR2}S literal 0 HcmV?d00001 diff --git a/store/BLETools.js b/store/BLETools.js index 8e8b4a6..5e291c5 100644 --- a/store/BLETools.js +++ b/store/BLETools.js @@ -20,7 +20,7 @@ let connected = false //测试 let service_uuid = "0000AE30-0000-1000-8000-00805F9B34FB" -let write_characteristic = "0000AE03-0000-1000-8000-00805F9B34FB" +let write_characteristic = "0000AE03-0000-1000-8000-00805F9B34FB" let notify_characteristic = "0000AE03-0000-1000-8000-00805F9B34FB" let notify_characteristic_2 = "0000AE02-0000-1000-8000-00805F9B34FB"//获取设备电量和设备mac地址 @@ -123,15 +123,18 @@ String.prototype.transFloat = function() { return str.indexOf('.') != -1 ? parseFloat(str).toFixed(1) : str } -// //uni -uni.onBluetoothAdapterStateChange(function(res) { - if (!res.available) { - isOpen = false - isBleOn = false - } else { - isBleOn = true - } -}) +// + +if (uni.getSystemInfoSync().platform){ + uni.onBluetoothAdapterStateChange(function(res) { + if (!res.available) { + isOpen = false + isBleOn = false + } else { + isBleOn = true + } + }) +} diff --git a/uni_modules/qf-image-cropper/changelog.md b/uni_modules/qf-image-cropper/changelog.md new file mode 100644 index 0000000..531a5e4 --- /dev/null +++ b/uni_modules/qf-image-cropper/changelog.md @@ -0,0 +1,67 @@ +## 2.2.4(2024-06-21) +* 新增 reverseRotatable 属性,是否支持逆向翻转 +* 修复 `2.1.7` 版本导致旋转后图片没有自动适配裁剪框的问题 +## 2.2.3(2024-06-21) +* 新增 gpu 属性,是否开启硬件加速,图片缩放过程中如果出现元素的“留影”或“重影”效果,可通过该方式解决或减轻这一问题 +* 修复 组件使用 `v-if` 并设置 `src` 属性时可能会出现图片渲染位置存在偏差的问题 + +## 2.2.2(2024-06-21) +* 优化 组件实例 chooseImage 方法支持传参 +* 修复 组件使用 `v-if` 时组件无非正常渲染的问题 + +## 2.2.1(2024-06-15) +* 修复 H5平台不支持手势拖动图片的问题 + +## 2.2.0(2024-05-31) +* 修复 APP平台 `vue2` 项目因 `2.1.9` 版本修复 `vue3` 项目bug而引发的问题 + +## 2.1.9(2024-05-29) +* 修复 APP平台 `vue3` 项目因 uniapp `renderjs` 中未支持条件编译,导致运行了H5平台代码报错的问题 + +## 2.1.8(2024-05-29) +* 新增 zIndex 属性,调整组件层级 +* 新增 组件内容插槽 +* 优化 微信小程序平台动态修改元素style时的多余内容 + +## 2.1.7(2024-05-28) +* 新增 checkRange 属性,当 checkRange=false 时允许图片位置超出裁剪边界 +* 新增 minScale 属性,图片最小缩放倍数,当 minScale<0 时可使图片宽高不再受裁剪区域宽高限制 +* 新增 backgroundColor 属性,生成图片背景色,如果裁剪区域没有完全包含在图片中时,不设置该属性生成图片存在一定的透明块 +* 优化 动态修改图片宽高但没有传入src时,尺寸适应问题 +* 修复 APP平台通过 `this.$ownerInstance` 获取组件实例时机过早,其值为 `undefined` 导致报错界面没有正常渲染的问题 + +## 2.1.6(2023-04-16) +* 修复 组件使用 v-show 指令会导致选择图片后初始位置严重偏位的问题 + +## 2.1.5(2023-04-15) +* 新增 兼容APP平台 + +## 2.1.4(2023-03-13) +* 新增 fileType 属性,用于指定生成文件的类型,只支持 'jpg' 或 'png',默认为 'png' +* 新增 delay 属性,微信小程序平台使用 `Canvas 2D` 绘制时控制图片从绘制到生成所需时间 +* 优化 当生成图片的尺寸宽/高超过 Canvas 2D 最大限制(1365*1365)则将画布尺寸缩放在限制范围内绘制完成后输出目标尺寸 +* 优化 旋转图标指示方向与实际旋转方向不符 + +## 2.1.3(2023-02-06) +* 优化 vue3支持 + +## 2.1.2(2023-02-03) +* 新增 navigation 属性,H5平台当 showAngle 为 true 时,使用插件的页面在 `page.json` 中配置了 "navigationStyle": "custom" 时,必须将此值设为 false ,否则四个可拉伸角的触发位置会有偏差 +* 修复 H5平台部分设备(已知iPhone11以下机型)拍照的图片缩放时会闪动的问题 + +## 2.1.1(2022-12-06) +* 修复 横屏适配问题 + +## 2.1.0(2022-12-06) +* 新增 兼容H5平台,使用 renderjs 响应手势事件 + +## 2.0.0(2022-12-05) +* 重构 插件,使用 WXS 响应手势事件 +* 新增 图片翻转 +* 新增 拉伸裁剪框放大图片 +* 新增 监听PC鼠标滚轮触发缩放 +* 新增 圆形、圆角矩形的图片裁剪 +* 优化 图片缩放,移动端以双指触摸中心点为缩放中心点,PC端以鼠标所在点为缩放中心点 +* 优化 裁剪框样式 +* 优化 图片位置拖动 支持边界回弹效果(滑动时可滑出边界,释放时回弹到边界) +* 优化 生成图片使用新版 Canvas 2D 接口 diff --git a/uni_modules/qf-image-cropper/components/qf-image-cropper/qf-image-cropper.render.js b/uni_modules/qf-image-cropper/components/qf-image-cropper/qf-image-cropper.render.js new file mode 100644 index 0000000..d4e2339 --- /dev/null +++ b/uni_modules/qf-image-cropper/components/qf-image-cropper/qf-image-cropper.render.js @@ -0,0 +1,738 @@ +/** + * 图片编辑器-手势监听 + * 1. 支持编译到app-vue(uni-app 2.5.5及以上版本)、H5上 + */ +/** 图片偏移量 */ +var offset = { x: 0, y: 0 }; +/** 图片缩放比例 */ +var scale = 1; +/** 图片最小缩放比例 */ +var minScale = 1; +/** 图片旋转角度 */ +var rotate = 0; +/** 触摸点 */ +var touches = []; +/** 图片布局信息 */ +var img = {}; +/** 系统信息 */ +var sys = {}; +/** 裁剪区域布局信息 */ +var area = {}; +/** 触摸行为类型 */ +var touchType = ''; +/** 操作角的位置 */ +var activeAngle = 0; +/** 裁剪区域布局信息偏移量 */ +var areaOffset = { left: 0, right: 0, top: 0, bottom: 0 }; +/** 元素ID */ +var elIds = { + 'imageStyles': 'crop-image', + 'maskStylesList': 'crop-mask-block', + 'borderStyles': 'crop-border', + 'circleBoxStyles': 'crop-circle-box', + 'circleStyles': 'crop-circle', + 'gridStylesList': 'crop-grid', + 'angleStylesList': 'crop-angle', +} +/** 记录上次初始化时间戳,排除APP重复更新 */ +var timestamp = 0; +/** vue3 renderjs 条件编译无效,以此方式区别 APP 和 H5 */ +// #ifdef H5 +var platform = 'H5'; +// #endif +// #ifdef APP +var platform = 'APP'; +// #endif +/** + * 样式对象转字符串 + * @param {Object} style 样式对象 + */ +function styleToString(style) { + if(typeof style === 'string') return style; + var str = ''; + for (let k in style) { + str += k + ':' + style[k] + ';'; + } + return str; +} +/** + * + * @param {Object} instance 页面实例对象 + * @param {Object} key 要修改样式的key + * @param {Object|Array} style 样式 + */ +function setStyle(instance, key, style) { + // console.log('setStyle', instance, key, JSON.stringify(style)) + // #ifdef APP-PLUS + if(platform === 'APP') { + if(Object.prototype.toString.call(style) === '[object Array]') { + for (var i = 0, len = style.length; i < len; i++) { + var el = window.document.getElementById(elIds[key] + '-' + (i + 1)); + el && (el.style = styleToString(style[i])); + } + } else { + var el = window.document.getElementById(elIds[key]); + el && (el.style = styleToString(style)); + } + } + // #endif + // #ifdef H5 + if(platform === 'H5') instance[key] = style; + // #endif +} +/** + * 触发页面实例指定方法 + * @param {Object} instance 页面实例对象 + * @param {Object} name 方法名称 + * @param {Object} obj 传递参数 + */ +function callMethod(instance, name, obj) { + // #ifdef APP-PLUS + if(platform === 'APP') instance.callMethod(name, obj); + // #endif + // #ifdef H5 + if(platform === 'H5') instance[name](obj); + // #endif +} +/** + * 计算两点间距 + * @param {Object} touches 触摸点信息 + */ +function getDistanceByTouches(touches) { + // 根据勾股定理求两点间距离 + var a = touches[1].pageX - touches[0].pageX; + var b = touches[1].pageY - touches[0].pageY; + var c = Math.sqrt(Math.pow(a, 2) + Math.pow(b, 2)); + // 求两点间的中点坐标 + // 1. a、b可能为负值 + // 2. 在求a、b时,如用touches[1]减touches[0],则求中点坐标也得用touches[1]减a/2、b/2 + // 3. 同理,在求a、b时,也可用touches[0]减touches[1],则求中点坐标也得用touches[0]减a/2、b/2 + var x = touches[1].pageX - a / 2; + var y = touches[1].pageY - b / 2; + return { c, x, y }; +}; + +/** + * 修正取值 + * @param {Object} a + * @param {Object} b + * @param {Object} c + * @param {Object} reverse 是否反向 + */ +function correctValue(a, b, c, reverse) { + return reverse ? Math.max(Math.min(a, b), c) : Math.min(Math.max(a, b), c); +} + +/** + * 检查边界:限制 x、y 拖动范围,禁止滑出边界 + * @param {Object} e 点坐标 + */ +function checkRange(e) { + var r = rotate / 90 % 2; + if(r === 1) { // 因图片宽高可能不等,翻转 90° 或 270° 后图片宽高需反着计算,且左右和上下边界要根据差值做偏移 + var o = (img.height - img.width) / 2; // 宽高差值一半 + return { + x: correctValue(e.x, -img.height + o + area.width + area.left, area.left + o, img.height < area.height), + y: correctValue(e.y, -img.width - o + area.height + area.top, area.top - o, img.width < area.width) + } + } + return { + x: correctValue(e.x, -img.width + area.width + area.left, area.left, img.width < area.width), + y: correctValue(e.y, -img.height + area.height + area.top, area.top, img.height < area.height) + } +}; +/** + * 变更图片布局信息 + * @param {Object} e 布局信息 + */ +function changeImageRect(e) { + // console.log('changeImageRect', e) + offset.x += e.x || 0; + offset.y += e.y || 0; + if(e.check && area.checkRange) { // 检查边界 + var point = checkRange(offset); + if(offset.x !== point.x || offset.y !== point.y) { + offset = point; + } + } + + // 因频繁修改 width/height 会造成大量的内存消耗,改为scale + // e.instance.imageStyles = { + // width: img.width + 'px', + // height: img.height + 'px', + // transform: 'translate(' + (offset.x + ox) + 'px, ' + (offset.y + ox) + 'px) rotate(' + rotate +'deg)' + // }; + var ox = (img.width - img.oldWidth) / 2; + var oy = (img.height - img.oldHeight) / 2; + // e.instance.imageStyles = { + // width: img.oldWidth + 'px', + // height: img.oldHeight + 'px', + // transform: 'translate(' + (offset.x + ox) + 'px, ' + (offset.y + oy) + 'px) rotate(' + rotate +'deg) scale(' + scale + ')' + // }; + setStyle(e.instance, 'imageStyles', { + width: img.oldWidth + 'px', + height: img.oldHeight + 'px', + transform: (img.gpu ? 'translateZ(0) ' : '') + 'translate(' + (offset.x + ox) + 'px, ' + (offset.y + oy) + 'px' + ') rotate(' + rotate +'deg) scale(' + scale + ')' + }); + callMethod(e.instance, 'dataChange', { + width: img.width, + height: img.height, + x: offset.x, + y: offset.y, + rotate: rotate + }); +}; +/** + * 变更裁剪区域布局信息 + * @param {Object} e 布局信息 + */ +function changeAreaRect(e) { + // console.log('changeAreaRect', e) + // 变更蒙版样式 + setStyle(e.instance, 'maskStylesList', [ + { + left: 0, + width: (area.left + areaOffset.left) + 'px', + top: 0, + bottom: 0, + 'z-index': area.zIndex + 2 + }, + { + left: (area.right + areaOffset.right) + 'px', + right: 0, + top: 0, + bottom: 0, + 'z-index': area.zIndex + 2 + }, + { + left: (area.left + areaOffset.left) + 'px', + width: (area.width + areaOffset.right - areaOffset.left) + 'px', + top: 0, + height: (area.top + areaOffset.top) + 'px', + 'z-index': area.zIndex + 2 + }, + { + left: (area.left + areaOffset.left) + 'px', + width: (area.width + areaOffset.right - areaOffset.left) + 'px', + top: (area.bottom + areaOffset.bottom) + 'px', + // height: (area.top - areaOffset.bottom + sys.offsetBottom) + 'px', + bottom: 0, + 'z-index': area.zIndex + 2 + } + ]); + // 变更边框样式 + if(area.showBorder) { + setStyle(e.instance, 'borderStyles', { + left: (area.left + areaOffset.left) + 'px', + top: (area.top + areaOffset.top) + 'px', + width: (area.width + areaOffset.right - areaOffset.left) + 'px', + height: (area.height + areaOffset.bottom - areaOffset.top) + 'px', + 'z-index': area.zIndex + 3 + }); + } + + // 变更参考线样式 + if(area.showGrid) { + setStyle(e.instance, 'gridStylesList', [ + { + 'border-width': '1px 0 0 0', + left: (area.left + areaOffset.left) + 'px', + right: (area.right + areaOffset.right) + 'px', + top: (area.top + areaOffset.top + (area.height + areaOffset.bottom - areaOffset.top) / 3 - 0.5) + 'px', + width: (area.width + areaOffset.right - areaOffset.left) + 'px', + 'z-index': area.zIndex + 3 + }, + { + 'border-width': '1px 0 0 0', + left: (area.left + areaOffset.left) + 'px', + right: (area.right + areaOffset.right) + 'px', + top: (area.top + areaOffset.top + (area.height + areaOffset.bottom - areaOffset.top) * 2 / 3 - 0.5) + 'px', + width: (area.width + areaOffset.right - areaOffset.left) + 'px', + 'z-index': area.zIndex + 3 + }, + { + 'border-width': '0 1px 0 0', + top: (area.top + areaOffset.top) + 'px', + bottom: (area.bottom + areaOffset.bottom) + 'px', + left: (area.left + areaOffset.left + (area.width + areaOffset.right - areaOffset.left) / 3 - 0.5) + 'px', + height: (area.height + areaOffset.bottom - areaOffset.top) + 'px', + 'z-index': area.zIndex + 3 + }, + { + 'border-width': '0 1px 0 0', + top: (area.top + areaOffset.top) + 'px', + bottom: (area.bottom + areaOffset.bottom) + 'px', + left: (area.left + areaOffset.left + (area.width + areaOffset.right - areaOffset.left) * 2 / 3 - 0.5) + 'px', + height: (area.height + areaOffset.bottom - areaOffset.top) + 'px', + 'z-index': area.zIndex + 3 + } + ]); + } + + // 变更四个伸缩角样式 + if(area.showAngle) { + setStyle(e.instance, 'angleStylesList', [ + { + 'border-width': area.angleBorderWidth + 'px 0 0 ' + area.angleBorderWidth + 'px', + left: (area.left + areaOffset.left - area.angleBorderWidth) + 'px', + top: (area.top + areaOffset.top - area.angleBorderWidth) + 'px', + 'z-index': area.zIndex + 3 + }, + { + 'border-width': area.angleBorderWidth + 'px ' + area.angleBorderWidth + 'px 0 0', + left: (area.right + areaOffset.right - area.angleSize) + 'px', + top: (area.top + areaOffset.top - area.angleBorderWidth) + 'px', + 'z-index': area.zIndex + 3 + }, + { + 'border-width': '0 0 ' + area.angleBorderWidth + 'px ' + area.angleBorderWidth + 'px', + left: (area.left + areaOffset.left - area.angleBorderWidth) + 'px', + top: (area.bottom + areaOffset.bottom - area.angleSize) + 'px', + 'z-index': area.zIndex + 3 + }, + { + 'border-width': '0 ' + area.angleBorderWidth + 'px ' + area.angleBorderWidth + 'px 0', + left: (area.right + areaOffset.right - area.angleSize) + 'px', + top: (area.bottom + areaOffset.bottom - area.angleSize) + 'px', + 'z-index': area.zIndex + 3 + } + ]); + } + + // 变更圆角样式 + if(area.radius > 0) { + var radius = area.radius; + if(area.width === area.height && area.radius >= area.width / 2) { // 圆形 + radius = (area.width / 2); + } else { // 圆角矩形 + if(area.width !== area.height) { // 限制圆角半径不能超过短边的一半 + radius = Math.min(area.width / 2, area.height / 2, radius); + } + } + setStyle(e.instance, 'circleBoxStyles', { + left: (area.left + areaOffset.left) + 'px', + top: (area.top + areaOffset.top) + 'px', + width: (area.width + areaOffset.right - areaOffset.left) + 'px', + height: (area.height + areaOffset.bottom - areaOffset.top) + 'px', + 'z-index': area.zIndex + 2 + }); + setStyle(e.instance, 'circleStyles', { + 'box-shadow': '0 0 0 ' + Math.max(area.width, area.height) + 'px rgba(51, 51, 51, 0.8)', + 'border-radius': radius + 'px' + }); + } +}; +/** + * 缩放图片 + * @param {Object} e 布局信息 + */ +function scaleImage(e) { + // console.log('scaleImage', e) + var last = scale; + scale = Math.min(Math.max(e.scale + scale, minScale), img.maxScale); + if(last !== scale) { + img.width = img.oldWidth * scale; + img.height = img.oldHeight * scale; + // 参考问题:有一个长4000px、宽4000px的四方形ABCD,A点的坐标固定在(-2000,-2000), + // 该四边形上有一个点E,坐标为(-100,-300),将该四方形复制一份并缩小到90%后, + // 新四边形的A点坐标为多少时可使新四边形的E点与原四边形的E点重合? + // 预期效果:从图中选取某点(参照物)为中心点进行缩放,缩放时无论图像怎么变化,该点位置始终固定不变 + // 计算方法:以相同起点先计算缩放前后两点间的距离,再加上原图像偏移量即可 + e.x = (e.x - offset.x) * (1 - scale / last); + e.y = (e.y - offset.y) * (1 - scale / last); + changeImageRect(e); + return true; + } + return false; +}; +/** + * 获取触摸点在哪个角 + * @param {number} x 触摸点x轴坐标 + * @param {number} y 触摸点y轴坐标 + * @return {number} 角的位置:0=无;1=左上;2=右上;3=左下;4=右下; + */ +function getToucheAngle(x, y) { + // console.log('getToucheAngle', x, y, JSON.stringify(area)) + var o = area.angleBorderWidth; // 需扩大触发范围则把 o 值加大即可 + var oy = sys.navigation ? 0 : sys.windowTop; + if(y >= area.top - o + oy && y <= area.top + area.angleSize + o + oy) { + if(x >= area.left - o && x <= area.left + area.angleSize + o) { + return 1; // 左上角 + } else if(x >= area.right - area.angleSize - o && x <= area.right + o) { + return 2; // 右上角 + } + } else if(y >= area.bottom - area.angleSize - o + oy && y <= area.bottom + o + oy) { + if(x >= area.left - o && x <= area.left + area.angleSize + o) { + return 3; // 左下角 + } else if(x >= area.right - area.angleSize - o && x <= area.right + o) { + return 4; // 右下角 + } + } + return 0; // 无触摸到角 +}; +/** + * 重置数据 + */ +function resetData() { + offset = { x: 0, y: 0 }; + scale = 1; + minScale = img.minScale; + rotate = 0; +}; +function getTouchs(touches) { + var result = []; + var len = touches ? touches.length : 0 + for (var i = 0; i < len; i++) { + result[i] = { + pageX: touches[i].pageX, + // h5无标题栏时,窗口顶部距离仍为标题栏高度,且触摸点y轴坐标还是有标题栏的值,即减去标题栏高度的值 + pageY: touches[i].pageY + sys.windowTop + }; + } + return result; +}; +var mouseEvent = false; +export default { + data() { + return { + imageStyles: {}, + maskStylesList: [{}, {}, {}, {}], + borderStyles: {}, + gridStylesList: [{}, {}, {}, {}], + angleStylesList: [{}, {}, {}, {}], + circleBoxStyles: {}, + circleStyles: {} + } + }, + created() { + // 监听 PC 端鼠标滚轮 + // #ifdef H5 + platform === 'H5' && window.addEventListener('mousewheel', async (e) => { + var touchs = getTouchs([e]) + img.src && scaleImage({ + instance: await this.getInstance(), + check: true, + // 鼠标向上滚动时,deltaY 固定 -100,鼠标向下滚动时,deltaY 固定 100 + scale: e.deltaY > 0 ? -0.05 : 0.05, + x: touchs[0].pageX, + y: touchs[0].pageY + }); + }); + // #endif + }, + // #ifdef H5 + mounted() { + platform === 'H5' && this.initH5Events(); + }, + // #endif + setPlatform(p) { + platform = p; + }, + methods: { + // #ifdef H5 + getTouchEvent(e) { + e.touches = [ + { pageX: e.pageX, pageY: e.pageY } + ]; + return e; + }, + initH5Events() { + const preview = document.getElementById('pic-preview'); + preview?.addEventListener('mousedown', (e, ev) => { + mouseEvent = true; + this.touchstart(this.getTouchEvent(e)); + }); + preview?.addEventListener('mousemove', (e) => { + if (!mouseEvent) return; + this.touchmove(this.getTouchEvent(e)); + }); + preview?.addEventListener('mouseup', (e) => { + mouseEvent = false; + this.touchend(this.getTouchEvent(e)) + }); + preview?.addEventListener('mouseleave', (e) => { + mouseEvent = false; + this.touchend(this.getTouchEvent(e)) + }); + }, + // #endif + async getInstance() { + // #ifdef APP-PLUS + if(platform === 'APP') + return this.$ownerInstance + ? Promise.resolve(this.$ownerInstance) + : new Promise((resolve) => { + setTimeout(async () => { + resolve(await this.getInstance()); + }); + }); + // #endif + // #ifdef H5 + if(platform === 'H5') + return Promise.resolve(this); + // #endif + }, + /** + * 初始化:观察数据变更 + * @param {Object} newVal 新数据 + * @param {Object} oldVal 旧数据 + * @param {Object} o 组件实例对象 + */ + initObserver: async function(newVal, oldVal, o, i) { + // console.log('initObserver', newVal, oldVal, o, i) + if(newVal && (!img.src || timestamp !== newVal.timestamp)) { + timestamp = newVal.timestamp; + img = newVal.img; + sys = newVal.sys; + area = newVal.area; + minScale = img.minScale; + resetData(); + const instance = await this.getInstance() + img.src && changeImageRect({ + instance, + x: (sys.windowWidth - img.width) / 2, + y: (sys.windowHeight + sys.windowTop - sys.offsetBottom - img.height) / 2 + }); + changeAreaRect({ + instance + }); + } + }, + /** + * 鼠标滚轮滚动 + * @param {Object} e 事件对象 + * @param {Object} o 组件实例对象 + */ + mousewheel: function(e, o) { + // h5平台 wheel 事件无法判断滚轮滑动方向,需使用 mousewheel + }, + /** + * 触摸开始 + * @param {Object} e 事件对象 + * @param {Object} o 组件实例对象 + */ + touchstart: function(e, o) { + if(!img.src) return; + touches = getTouchs(e.touches); + activeAngle = area.showAngle ? getToucheAngle(touches[0].pageX, touches[0].pageY) : 0; + if(touches.length === 1 && activeAngle !== 0) { + touchType = 'stretch'; // 伸缩裁剪区域 + } else { + touchType = ''; + } + // console.log('touchstart', e, activeAngle) + }, + /** + * 触摸移动 + * @param {Object} e 事件对象 + * @param {Object} o 组件实例对象 + */ + touchmove: async function(e, o) { + if(!img.src) return; + // console.log('touchmove', e, o) + e.touches = getTouchs(e.touches); + if(touchType === 'stretch') { // 触摸四个角进行拉伸 + var point = e.touches[0]; + var start = touches[0]; + var x = point.pageX - start.pageX; + var y = point.pageY - start.pageY; + if(x !== 0 || y !== 0) { + var maxX = area.width * (1 - area.minScale); + var maxY = area.height * (1 - area.minScale); + // console.log(x, y, maxX, maxY, offset, area) + touches[0] = point; + switch(activeAngle) { + case 1: // 左上角 + x += areaOffset.left; + y += areaOffset.top; + // console.log(x, y, offset.left > area.left) + // console.log(maxX, maxY) + if(x >= 0 && y >= 0) { // 有效滑动 + var max = minScale < 1 && area.checkRange && ((offset.x > 0 && offset.x >= area.left) || (offset.y > 0 && offset.y >= area.top)) + ? Math.min(offset.y - area.top, offset.x - area.left) + : false; + if(x > y) { // 以x轴滑动距离为缩放基准 + if(typeof max === 'number') maxX = max; + if(x > maxX) x = maxX; + y = x * area.height / area.width; + } else { // 以y轴滑动距离为缩放基准 + if(typeof max === 'number') maxY = max; + if(y > maxY) y = maxY; + x = y * area.width / area.height; + } + areaOffset.left = x; + areaOffset.top = y; + } + break; + case 2: // 右上角 + x += areaOffset.right; + y += areaOffset.top; + if(x <= 0 && y >= 0) { // 有效滑动 + var max = minScale < 1 && area.checkRange && ((offset.x > 0 && offset.x + img.width <= area.right) || (offset.y > 0 && offset.y >= area.top)) + ? Math.min(offset.y - area.top, area.right - offset.x - img.width) + : false; + if(-x > y) { // 以x轴滑动距离为缩放基准 + if(typeof max === 'number') maxX = max; + if(-x > maxX) x = -maxX; + y = -x * area.height / area.width; + } else { // 以y轴滑动距离为缩放基准 + if(typeof max === 'number') maxY = max; + if(y > maxY) y = maxY; + x = -y * area.width / area.height; + } + areaOffset.right = x; + areaOffset.top = y; + } + break; + case 3: // 左下角 + x += areaOffset.left; + y += areaOffset.bottom; + if(x >= 0 && y <= 0) { // 有效滑动 + var max = minScale < 1 && area.checkRange && ((offset.x > 0 && offset.x >= area.left) || (offset.y > 0 && offset.y + img.height <= area.bottom)) + ? Math.min(area.bottom - offset.y - img.height, offset.x - area.left) + : false; + if(x > -y) { // 以x轴滑动距离为缩放基准 + if(typeof max === 'number') maxX = max; + if(x > maxX) x = maxX; + y = -x * area.height / area.width; + } else { // 以y轴滑动距离为缩放基准 + if(typeof max === 'number') maxY = max; + if(-y > maxY) y = -maxY; + x = -y * area.width / area.height; + } + areaOffset.left = x; + areaOffset.bottom = y; + } + break; + case 4: // 右下角 + x += areaOffset.right; + y += areaOffset.bottom; + if(x <= 0 && y <= 0) { // 有效滑动 + var max = minScale < 1 && area.checkRange && ((offset.x > 0 && offset.x + img.width <= area.right) || (offset.y > 0 && offset.y + img.height <= area.bottom)) + ? Math.min(area.bottom - offset.y - img.height, area.right - offset.x - img.width) + : false; + if(-x > -y) { // 以x轴滑动距离为缩放基准 + if(typeof max === 'number') maxX = max; + if(-x > maxX) x = -maxX; + y = x * area.height / area.width; + } else { // 以y轴滑动距离为缩放基准 + if(typeof max === 'number') maxY = max; + if(-y > maxY) y = -maxY; + x = y * area.width / area.height; + } + areaOffset.right = x; + areaOffset.bottom = y; + } + break; + } + // console.log(x, y, JSON.stringify(areaOffset)) + changeAreaRect({ + instance: await this.getInstance(), + }); + // this.draw(); + } + } else if (e.touches.length == 2) { // 双点触摸缩放 + var start = getDistanceByTouches(touches); + var end = getDistanceByTouches(e.touches); + scaleImage({ + instance: await this.getInstance(), + check: !area.bounce, + scale: (end.c - start.c) / 100, + x: end.x, + y: end.y + }); + touchType = 'scale'; + } else if(touchType === 'scale') {// 从双点触摸变成单点触摸 / 从缩放变成拖动 + touchType = 'move'; + } else { + changeImageRect({ + instance: await this.getInstance(), + check: !area.bounce, + x: e.touches[0].pageX - touches[0].pageX, + y: e.touches[0].pageY - touches[0].pageY + }); + touchType = 'move'; + } + touches = e.touches; + }, + /** + * 触摸结束 + * @param {Object} e 事件对象 + * @param {Object} o 组件实例对象 + */ + touchend: async function(e, o) { + if(!img.src) return; + if(touchType === 'stretch') { // 拉伸裁剪区域的四个角缩放 + // 裁剪区域宽度被缩放到多少 + var left = areaOffset.left; + var right = areaOffset.right; + var top = areaOffset.top; + var bottom = areaOffset.bottom; + var w = area.width + right - left; + var h = area.height + bottom - top; + // 图像放大倍数 + var p = scale * (area.width / w) - scale; + // 复原裁剪区域 + areaOffset = { left: 0, right: 0, top: 0, bottom: 0 }; + changeAreaRect({ + instance: await this.getInstance(), + }); + scaleImage({ + instance: await this.getInstance(), + scale: p, + x: area.left + left + (1 === activeAngle || 3 === activeAngle ? w : 0), + y: area.top + top + (1 === activeAngle || 2 === activeAngle ? h : 0) + }); + } else if (area.bounce) { // 检查边界并矫正,实现拖动到边界时有回弹效果 + changeImageRect({ + instance: await this.getInstance(), + check: true + }); + } + }, + /** + * 顺时针翻转图片90° + * @param {Object} e 事件对象 + * @param {Object} o 组件实例对象 + */ + rotateImage: async function(r) { + rotate = (rotate + (r || 90)) % 360; + + if(img.minScale >= 1) { + // 因图片宽高可能不等,翻转后图片宽高需足够填满裁剪区域 + minScale = 1; + if(img.width < area.height) { + minScale = area.height / img.oldWidth; + } else if(img.height < area.width) { + minScale = (area.width / img.oldHeight) + } + if(minScale !== 1) { + scaleImage({ + instance: await this.getInstance(), + scale: minScale - scale, + x: sys.windowWidth / 2, + y: (sys.windowHeight - sys.offsetBottom) / 2 + }); + } + } + + // 由于拖动画布后会导致图片位置偏移,翻转时的旋转中心点需是图片区域+偏移区域的中心点 + // 翻转x轴中心点 = (超出裁剪区域右侧的图片宽度 - 超出裁剪区域左侧的图片宽度) / 2 + // 翻转y轴中心点 = (超出裁剪区域下方的图片宽度 - 超出裁剪区域上方的图片宽度) / 2 + var ox = ((offset.x + img.width - area.right) - (area.left - offset.x)) / 2; + var oy = ((offset.y + img.height - area.bottom) - (area.top - offset.y)) / 2; + changeImageRect({ + instance: await this.getInstance(), + check: true, + x: -ox - oy, + y: -oy + ox + }); + }, + rotateImage90: function() { + this.rotateImage(90) + }, + rotateImage270: function() { + this.rotateImage(270) + }, + } +} \ No newline at end of file diff --git a/uni_modules/qf-image-cropper/components/qf-image-cropper/qf-image-cropper.vue b/uni_modules/qf-image-cropper/components/qf-image-cropper/qf-image-cropper.vue new file mode 100644 index 0000000..80b1f74 --- /dev/null +++ b/uni_modules/qf-image-cropper/components/qf-image-cropper/qf-image-cropper.vue @@ -0,0 +1,746 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/uni_modules/qf-image-cropper/components/qf-image-cropper/qf-image-cropper.wxs b/uni_modules/qf-image-cropper/components/qf-image-cropper/qf-image-cropper.wxs new file mode 100644 index 0000000..27fbd51 --- /dev/null +++ b/uni_modules/qf-image-cropper/components/qf-image-cropper/qf-image-cropper.wxs @@ -0,0 +1,604 @@ +/** + * 图片编辑器-手势监听 + * 1. wxs 暂不支持 es6 语法 + * 2. 支持编译到微信小程序、QQ小程序、app-vue、H5上(uni-app 2.2.5及以上版本) + */ +/** 图片偏移量 */ +var offset = { x: 0, y: 0 }; +/** 图片缩放比例 */ +var scale = 1; +/** 图片最小缩放比例 */ +var minScale = 1; +/** 图片旋转角度 */ +var rotate = 0; +/** 触摸点 */ +var touches = []; +/** 图片布局信息 */ +var img = {}; +/** 系统信息 */ +var sys = {}; +/** 裁剪区域布局信息 */ +var area = {}; +/** 触摸行为类型 */ +var touchType = ''; +/** 操作角的位置 */ +var activeAngle = 0; +/** 裁剪区域布局信息偏移量 */ +var areaOffset = { left: 0, right: 0, top: 0, bottom: 0 }; +/** + * 计算两点间距 + * @param {Object} touches 触摸点信息 + */ +function getDistanceByTouches(touches) { + // 根据勾股定理求两点间距离 + var a = touches[1].pageX - touches[0].pageX; + var b = touches[1].pageY - touches[0].pageY; + var c = Math.sqrt(Math.pow(a, 2) + Math.pow(b, 2)); + // 求两点间的中点坐标 + // 1. a、b可能为负值 + // 2. 在求a、b时,如用touches[1]减touches[0],则求中点坐标也得用touches[1]减a/2、b/2 + // 3. 同理,在求a、b时,也可用touches[0]减touches[1],则求中点坐标也得用touches[0]减a/2、b/2 + var x = touches[1].pageX - a / 2; + var y = touches[1].pageY - b / 2; + return { c, x, y }; +}; +/** + * 修正取值 + * @param {Object} a + * @param {Object} b + * @param {Object} c + * @param {Object} reverse 是否反向 + */ +function correctValue(a, b, c, reverse) { + return reverse ? Math.max(Math.min(a, b), c) : Math.min(Math.max(a, b), c); +} + +/** + * 检查边界:限制 x、y 拖动范围,禁止滑出边界 + * @param {Object} e 点坐标 + */ +function checkRange(e) { + var r = rotate / 90 % 2; + if(r === 1) { // 因图片宽高可能不等,翻转 90° 或 270° 后图片宽高需反着计算,且左右和上下边界要根据差值做偏移 + var o = (img.height - img.width) / 2; // 宽高差值一半 + return { + x: correctValue(e.x, -img.height + o + area.width + area.left, area.left + o, img.height < area.height), + y: correctValue(e.y, -img.width - o + area.height + area.top, area.top - o, img.width < area.width) + } + } + return { + x: correctValue(e.x, -img.width + area.width + area.left, area.left, img.width < area.width), + y: correctValue(e.y, -img.height + area.height + area.top, area.top, img.height < area.height) + } +}; +/** + * 变更图片布局信息 + * @param {Object} e 布局信息 + */ +function changeImageRect(e) { + offset.x += e.x || 0; + offset.y += e.y || 0; + var image = e.instance.selectComponent('.crop-image'); + if(e.check && area.checkRange) { // 检查边界 + var point = checkRange(offset); + if(offset.x !== point.x || offset.y !== point.y) { + offset = point; + } + } + // image.setStyle({ + // width: img.width + 'px', + // height: img.height + 'px', + // transform: 'translate(' + offset.x + 'px, ' + offset.y + 'px) rotate(' + rotate +'deg)' + // }); + var ox = (img.width - img.oldWidth) / 2; + var oy = (img.height - img.oldHeight) / 2; + image.setStyle({ + width: img.oldWidth + 'px', + height: img.oldHeight + 'px', + transform: (img.gpu ? 'translateZ(0) ' : '') + 'translate(' + (offset.x + ox) + 'px, ' + (offset.y + oy) + 'px) rotate(' + rotate +'deg) scale(' + scale + ')' + }); + + e.instance.callMethod('dataChange', { + width: img.width, + height: img.height, + x: offset.x, + y: offset.y, + rotate: rotate + }); +}; +/** + * 变更裁剪区域布局信息 + * @param {Object} e 布局信息 + */ +function changeAreaRect(e) { + // 变更蒙版样式 + var masks = e.instance.selectAllComponents('.crop-mask-block'); + var maskStyles = [ + { + left: 0, + width: (area.left + areaOffset.left) + 'px', + top: 0, + bottom: 0, + 'z-index': area.zIndex + 2 + }, + { + left: (area.right + areaOffset.right) + 'px', + right: 0, + top: 0, + bottom: 0, + 'z-index': area.zIndex + 2 + }, + { + left: (area.left + areaOffset.left) + 'px', + width: (area.width + areaOffset.right - areaOffset.left) + 'px', + top: 0, + height: (area.top + areaOffset.top) + 'px', + 'z-index': area.zIndex + 2 + }, + { + left: (area.left + areaOffset.left) + 'px', + width: (area.width + areaOffset.right - areaOffset.left) + 'px', + top: (area.bottom + areaOffset.bottom) + 'px', + // height: (area.top - areaOffset.bottom + sys.offsetBottom) + 'px', + bottom: 0, + 'z-index': area.zIndex + 2 + } + ]; + var len = masks.length; + for (var i = 0; i < len; i++) { + masks[i].setStyle(maskStyles[i]); + } + + // 变更边框样式 + if(area.showBorder) { + var border = e.instance.selectComponent('.crop-border'); + border.setStyle({ + left: (area.left + areaOffset.left) + 'px', + top: (area.top + areaOffset.top) + 'px', + width: (area.width + areaOffset.right - areaOffset.left) + 'px', + height: (area.height + areaOffset.bottom - areaOffset.top) + 'px', + 'z-index': area.zIndex + 3 + }); + } + + // 变更参考线样式 + if(area.showGrid) { + var grids = e.instance.selectAllComponents('.crop-grid'); + var gridStyles = [ + { + 'border-width': '1px 0 0 0', + left: (area.left + areaOffset.left) + 'px', + right: (area.right + areaOffset.right) + 'px', + top: (area.top + areaOffset.top + (area.height + areaOffset.bottom - areaOffset.top) / 3 - 0.5) + 'px', + width: (area.width + areaOffset.right - areaOffset.left) + 'px', + 'z-index': area.zIndex + 3 + }, + { + 'border-width': '1px 0 0 0', + left: (area.left + areaOffset.left) + 'px', + right: (area.right + areaOffset.right) + 'px', + top: (area.top + areaOffset.top + (area.height + areaOffset.bottom - areaOffset.top) * 2 / 3 - 0.5) + 'px', + width: (area.width + areaOffset.right - areaOffset.left) + 'px', + 'z-index': area.zIndex + 3 + }, + { + 'border-width': '0 1px 0 0', + top: (area.top + areaOffset.top) + 'px', + bottom: (area.bottom + areaOffset.bottom) + 'px', + left: (area.left + areaOffset.left + (area.width + areaOffset.right - areaOffset.left) / 3 - 0.5) + 'px', + height: (area.height + areaOffset.bottom - areaOffset.top) + 'px', + 'z-index': area.zIndex + 3 + }, + { + 'border-width': '0 1px 0 0', + top: (area.top + areaOffset.top) + 'px', + bottom: (area.bottom + areaOffset.bottom) + 'px', + left: (area.left + areaOffset.left + (area.width + areaOffset.right - areaOffset.left) * 2 / 3 - 0.5) + 'px', + height: (area.height + areaOffset.bottom - areaOffset.top) + 'px', + 'z-index': area.zIndex + 3 + } + ]; + var len = grids.length; + for (var i = 0; i < len; i++) { + grids[i].setStyle(gridStyles[i]); + } + } + + // 变更四个伸缩角样式 + if(area.showAngle) { + var angles = e.instance.selectAllComponents('.crop-angle'); + var angleStyles = [ + { + 'border-width': area.angleBorderWidth + 'px 0 0 ' + area.angleBorderWidth + 'px', + left: (area.left + areaOffset.left - area.angleBorderWidth) + 'px', + top: (area.top + areaOffset.top - area.angleBorderWidth) + 'px', + 'z-index': area.zIndex + 3 + }, + { + 'border-width': area.angleBorderWidth + 'px ' + area.angleBorderWidth + 'px 0 0', + left: (area.right + areaOffset.right - area.angleSize) + 'px', + top: (area.top + areaOffset.top - area.angleBorderWidth) + 'px', + 'z-index': area.zIndex + 3 + }, + { + 'border-width': '0 0 ' + area.angleBorderWidth + 'px ' + area.angleBorderWidth + 'px', + left: (area.left + areaOffset.left - area.angleBorderWidth) + 'px', + top: (area.bottom + areaOffset.bottom - area.angleSize) + 'px', + 'z-index': area.zIndex + 3 + }, + { + 'border-width': '0 ' + area.angleBorderWidth + 'px ' + area.angleBorderWidth + 'px 0', + left: (area.right + areaOffset.right - area.angleSize) + 'px', + top: (area.bottom + areaOffset.bottom - area.angleSize) + 'px', + 'z-index': area.zIndex + 3 + } + ]; + var len = angles.length; + for (var i = 0; i < len; i++) { + angles[i].setStyle(angleStyles[i]); + } + } + + // 变更圆角样式 + if(area.radius > 0) { + var circleBox = e.instance.selectComponent('.crop-circle-box'); + var circle = e.instance.selectComponent('.crop-circle'); + var radius = area.radius; + if(area.width === area.height && area.radius >= area.width / 2) { // 圆形 + radius = (area.width / 2); + } else { // 圆角矩形 + if(area.width !== area.height) { // 限制圆角半径不能超过短边的一半 + radius = Math.min(area.width / 2, area.height / 2, radius); + } + } + circleBox.setStyle({ + left: (area.left + areaOffset.left) + 'px', + top: (area.top + areaOffset.top) + 'px', + width: (area.width + areaOffset.right - areaOffset.left) + 'px', + height: (area.height + areaOffset.bottom - areaOffset.top) + 'px', + 'z-index': area.zIndex + 2 + }); + circle.setStyle({ + 'box-shadow': '0 0 0 ' + Math.max(area.width, area.height) + 'px rgba(51, 51, 51, 0.8)', + 'border-radius': radius + 'px' + }); + } +}; +/** + * 缩放图片 + * @param {Object} e 布局信息 + */ +function scaleImage(e) { + var last = scale; + scale = Math.min(Math.max(e.scale + scale, minScale), img.maxScale); + if(last !== scale) { + img.width = img.oldWidth * scale; + img.height = img.oldHeight * scale; + // 参考问题:有一个长4000px、宽4000px的四方形ABCD,A点的坐标固定在(-2000,-2000), + // 该四边形上有一个点E,坐标为(-100,-300),将该四方形复制一份并缩小到90%后, + // 新四边形的A点坐标为多少时可使新四边形的E点与原四边形的E点重合? + // 预期效果:从图中选取某点(参照物)为中心点进行缩放,缩放时无论图像怎么变化,该点位置始终固定不变 + // 计算方法:以相同起点先计算缩放前后两点间的距离,再加上原图像偏移量即可 + e.x = (e.x - offset.x) * (1 - scale / last); + e.y = (e.y - offset.y) * (1 - scale / last); + changeImageRect(e); + return true; + } + return false; +}; +/** + * 获取触摸点在哪个角 + * @param {number} x 触摸点x轴坐标 + * @param {number} y 触摸点y轴坐标 + * @return {number} 角的位置:0=无;1=左上;2=右上;3=左下;4=右下; + */ +function getToucheAngle(x, y) { + // console.log('getToucheAngle', x, y, JSON.stringify(area)) + var o = area.angleBorderWidth; // 需扩大触发范围则把 o 值加大即可 + if(y >= area.top - o && y <= area.top + area.angleSize + o) { + if(x >= area.left - o && x <= area.left + area.angleSize + o) { + return 1; // 左上角 + } else if(x >= area.right - area.angleSize - o && x <= area.right + o) { + return 2; // 右上角 + } + } else if(y >= area.bottom - area.angleSize - o && y <= area.bottom + o) { + if(x >= area.left - o && x <= area.left + area.angleSize + o) { + return 3; // 左下角 + } else if(x >= area.right - area.angleSize - o && x <= area.right + o) { + return 4; // 右下角 + } + } + return 0; // 无触摸到角 +}; +/** + * 重置数据 + */ +function resetData() { + offset = { x: 0, y: 0 }; + scale = 1; + minScale = img.minScale; + rotate = 0; +}; +/** +* 顺时针翻转图片90° +* @param {Object} e 事件对象 +* @param {Object} o 组件实例对象 +*/ +function rotateImage(e, o, r) { + rotate = (rotate + r) % 360; + + if(img.minScale >= 1) { + // 因图片宽高可能不等,翻转后图片宽高需足够填满裁剪区域 + minScale = 1; + if(img.width < area.height) { + minScale = area.height / img.oldWidth; + } else if(img.height < area.width) { + minScale = (area.width / img.oldHeight) + } + if(minScale !== 1) { + scaleImage({ + instance: o, + scale: minScale - scale, + x: sys.windowWidth / 2, + y: (sys.windowHeight - sys.offsetBottom) / 2 + }); + } + } + + // 由于拖动画布后会导致图片位置偏移,翻转时的旋转中心点需是图片区域+偏移区域的中心点 + // 翻转x轴中心点 = (超出裁剪区域右侧的图片宽度 - 超出裁剪区域左侧的图片宽度) / 2 + // 翻转y轴中心点 = (超出裁剪区域下方的图片宽度 - 超出裁剪区域上方的图片宽度) / 2 + var ox = ((offset.x + img.width - area.right) - (area.left - offset.x)) / 2; + var oy = ((offset.y + img.height - area.bottom) - (area.top - offset.y)) / 2; + changeImageRect({ + instance: o, + check: true, + x: -ox - oy, + y: -oy + ox + }); +}; +module.exports = { + /** + * 初始化:观察数据变更 + * @param {Object} newVal 新数据 + * @param {Object} oldVal 旧数据 + * @param {Object} o 组件实例对象 + */ + initObserver: function(newVal, oldVal, o, i) { + if(newVal) { + img = newVal.img; + sys = newVal.sys; + area = newVal.area; + minScale = img.minScale; + resetData(); + img.src && changeImageRect({ + instance: o, + x: (sys.windowWidth - img.width) / 2, + y: (sys.windowHeight - sys.offsetBottom - img.height) / 2 + }); + changeAreaRect({ + instance: o + }); + // console.log('initRect', JSON.stringify(newVal)) + } + }, + /** + * 鼠标滚轮滚动 + * @param {Object} e 事件对象 + * @param {Object} o 组件实例对象 + */ + mousewheel: function(e, o) { + if(!img.src) return; + scaleImage({ + instance: o, + check: true, + // 鼠标向上滚动时,deltaY 固定 -100,鼠标向下滚动时,deltaY 固定 100 + scale: e.detail.deltaY > 0 ? -0.05 : 0.05, + x: e.touches[0].pageX, + y: e.touches[0].pageY + }); + }, + /** + * 触摸开始 + * @param {Object} e 事件对象 + * @param {Object} o 组件实例对象 + */ + touchstart: function(e, o) { + if(!img.src) return; + touches = e.touches; + activeAngle = area.showAngle ? getToucheAngle(touches[0].pageX, touches[0].pageY) : 0; + if(touches.length === 1 && activeAngle !== 0) { + touchType = 'stretch'; // 伸缩裁剪区域 + } else { + touchType = ''; + } + // console.log('touchstart', JSON.stringify(e), activeAngle) + }, + /** + * 触摸移动 + * @param {Object} e 事件对象 + * @param {Object} o 组件实例对象 + */ + touchmove: function(e, o) { + if(!img.src) return; + // console.log('touchmove', JSON.stringify(e), JSON.stringify(o)) + if(touchType === 'stretch') { // 触摸四个角进行拉伸 + var point = e.touches[0]; + var start = touches[0]; + var x = point.pageX - start.pageX; + var y = point.pageY - start.pageY; + if(x !== 0 || y !== 0) { + var maxX = area.width * (1 - area.minScale); + var maxY = area.height * (1 - area.minScale); + // console.log(x, y, maxX, maxY, offset, area) + touches[0] = point; + switch(activeAngle) { + case 1: // 左上角 + x += areaOffset.left; + y += areaOffset.top; + if(x >= 0 && y >= 0) { // 有效滑动 + var max = minScale < 1 && area.checkRange && ((offset.x > 0 && offset.x >= area.left) || (offset.y > 0 && offset.y >= area.top)) + ? Math.min(offset.y - area.top, offset.x - area.left) + : false; + if(x > y) { // 以x轴滑动距离为缩放基准 + if(typeof max === 'number') maxX = max; + if(x > maxX) x = maxX; + y = x * area.height / area.width; + } else { // 以y轴滑动距离为缩放基准 + if(typeof max === 'number') maxY = max; + if(y > maxY) y = maxY; + x = y * area.width / area.height; + } + areaOffset.left = x; + areaOffset.top = y; + } + break; + case 2: // 右上角 + x += areaOffset.right; + y += areaOffset.top; + if(x <= 0 && y >= 0) { // 有效滑动 + var max = minScale < 1 && area.checkRange && ((offset.x > 0 && offset.x + img.width <= area.right) || (offset.y > 0 && offset.y >= area.top)) + ? Math.min(offset.y - area.top, area.right - offset.x - img.width) + : false; + if(-x > y) { // 以x轴滑动距离为缩放基准 + if(typeof max === 'number') maxX = max; + if(-x > maxX) x = -maxX; + y = -x * area.height / area.width; + } else { // 以y轴滑动距离为缩放基准 + if(typeof max === 'number') maxY = max; + if(y > maxY) y = maxY; + x = -y * area.width / area.height; + } + areaOffset.right = x; + areaOffset.top = y; + } + break; + case 3: // 左下角 + x += areaOffset.left; + y += areaOffset.bottom; + if(x >= 0 && y <= 0) { // 有效滑动 + var max = minScale < 1 && area.checkRange && ((offset.x > 0 && offset.x >= area.left) || (offset.y > 0 && offset.y + img.height <= area.bottom)) + ? Math.min(area.bottom - offset.y - img.height, offset.x - area.left) + : false; + if(x > -y) { // 以x轴滑动距离为缩放基准 + if(typeof max === 'number') maxX = max; + if(x > maxX) x = maxX; + y = -x * area.height / area.width; + } else { // 以y轴滑动距离为缩放基准 + if(typeof max === 'number') maxY = max; + if(-y > maxY) y = -maxY; + x = -y * area.width / area.height; + } + areaOffset.left = x; + areaOffset.bottom = y; + } + break; + case 4: // 右下角 + x += areaOffset.right; + y += areaOffset.bottom; + if(x <= 0 && y <= 0) { // 有效滑动 + var max = minScale < 1 && area.checkRange && ((offset.x > 0 && offset.x + img.width <= area.right) || (offset.y > 0 && offset.y + img.height <= area.bottom)) + ? Math.min(area.bottom - offset.y - img.height, area.right - offset.x - img.width) + : false; + if(-x > -y) { // 以x轴滑动距离为缩放基准 + if(typeof max === 'number') maxX = max; + if(-x > maxX) x = -maxX; + y = x * area.height / area.width; + } else { // 以y轴滑动距离为缩放基准 + if(typeof max === 'number') maxY = max; + if(-y > maxY) y = -maxY; + x = y * area.width / area.height; + } + areaOffset.right = x; + areaOffset.bottom = y; + } + break; + } + // console.log(x, y, JSON.stringify(areaOffset)) + changeAreaRect({ + instance: o, + }); + // this.draw(); + } + } else if (e.touches.length == 2) { // 双点触摸缩放 + var start = getDistanceByTouches(touches); + var end = getDistanceByTouches(e.touches); + scaleImage({ + instance: o, + check: !area.bounce, + scale: (end.c - start.c) / 100, + x: end.x, + y: end.y + }); + touchType = 'scale'; + } else if(touchType === 'scale') {// 从双点触摸变成单点触摸 / 从缩放变成拖动 + touchType = 'move'; + } else { + changeImageRect({ + instance: o, + check: !area.bounce, + x: e.touches[0].pageX - touches[0].pageX, + y: e.touches[0].pageY - touches[0].pageY + }); + touchType = 'move'; + } + touches = e.touches; + }, + /** + * 触摸结束 + * @param {Object} e 事件对象 + * @param {Object} o 组件实例对象 + */ + touchend: function(e, o) { + if(!img.src) return; + if(touchType === 'stretch') { // 拉伸裁剪区域的四个角缩放 + // 裁剪区域宽度被缩放到多少 + var left = areaOffset.left; + var right = areaOffset.right; + var top = areaOffset.top; + var bottom = areaOffset.bottom; + var w = area.width + right - left; + var h = area.height + bottom - top; + // 图像放大倍数 + var p = scale * (area.width / w) - scale; + // 复原裁剪区域 + areaOffset = { left: 0, right: 0, top: 0, bottom: 0 }; + changeAreaRect({ + instance: o, + }); + scaleImage({ + instance: o, + scale: p, + x: area.left + left + (1 === activeAngle || 3 === activeAngle ? w : 0), + y: area.top + top + (1 === activeAngle || 2 === activeAngle ? h : 0) + }); + } else if (area.bounce) { // 检查边界并矫正,实现拖动到边界时有回弹效果 + changeImageRect({ + instance: o, + check: true + }); + } + }, + /** + * 顺时针翻转图片90° + * @param {Object} e 事件对象 + * @param {Object} o 组件实例对象 + */ + rotateImage: function(e, o) { + rotateImage(e, o, 90); + }, + rotateImage90: function(e, o) { + rotateImage(e, o, 90) + }, + rotateImage270: function(e, o) { + rotateImage(e, o, 270) + }, + // 此处只用于对齐其他平台端的样式参数,防止异常,无作用 + imageStyles: '', + maskStylesList: ['', '', '', ''], + borderStyles: '', + gridStylesList: ['', '', '', ''], + angleStylesList: ['', '', '', ''], + circleBoxStyles: '', + circleStyles: '', +} \ No newline at end of file diff --git a/uni_modules/qf-image-cropper/package.json b/uni_modules/qf-image-cropper/package.json new file mode 100644 index 0000000..e945454 --- /dev/null +++ b/uni_modules/qf-image-cropper/package.json @@ -0,0 +1,81 @@ +{ + "id": "qf-image-cropper", + "displayName": "图片裁剪插件", + "version": "2.2.4", + "description": "图片裁剪插件,支持自定义尺寸、定点等比例缩放、拖动、图片翻转、剪切圆形/圆角图片、定制样式,功能多性能高体验好注释全。", + "keywords": [ + "qf-image-cropper", + "图片裁剪", + "图片编辑", + "头像裁剪", + "小程序" +], + "repository": "", + "engines": { + "HBuilderX": "^3.1.0" + }, +"dcloudext": { + "type": "component-vue", + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "插件不采集任何数据", + "permissions": "无" + }, + "npmurl": "" + }, + "uni_modules": { + "dependencies": [], + "encrypt": [], + "platforms": { + "client": { + "Vue": { + "vue2": "y", + "vue3": "y" + }, + "App": { + "app-vue": "y", + "app-nvue": "n" + }, + "H5-mobile": { + "Safari": "y", + "Android Browser": "y", + "微信浏览器(Android)": "y", + "QQ浏览器(Android)": "u" + }, + "H5-pc": { + "Chrome": "u", + "IE": "u", + "Edge": "u", + "Firefox": "u", + "Safari": "u" + }, + "小程序": { + "微信": "y", + "阿里": "n", + "百度": "n", + "字节跳动": "n", + "QQ": "u", + "钉钉": "n", + "快手": "n", + "飞书": "n", + "京东": "n" + }, + "快应用": { + "华为": "n", + "联盟": "n" + } + } + } + } +} \ No newline at end of file diff --git a/uni_modules/qf-image-cropper/readme.md b/uni_modules/qf-image-cropper/readme.md new file mode 100644 index 0000000..0c62aac --- /dev/null +++ b/uni_modules/qf-image-cropper/readme.md @@ -0,0 +1,95 @@ +# qf-image-cropper +## 图片裁剪插件 +uniapp微信小程序图片裁剪插件,支持自定义尺寸、定点等比例缩放、拖动、图片翻转、剪切圆形/圆角图片、定制样式,功能多性能高体验好注释全。 + +### 平台支持: +1. 支持微信小程序:移动端、PC端、开发者工具 +2. 支持H5平台(2.1.0版本起) +3. 支持APP平台(2.1.5版本起):Android、IOS +4. 其他平台暂未测试兼容性未知 + +### 支持功能: +1. 自定义裁剪尺寸 +2. 定点等比例缩放:移动端以双指触摸中心点为缩放中心点,PC端以鼠标所在点为缩放中心点 +3. 自由拖动:支持限制滑出边界,也支持回弹效果(滑动时可滑出边界,释放时回弹到边界) +4. 图片翻转:在裁剪尺寸非 1:1 的情况下,翻转时宽高无法铺满裁剪区域时,图片会自动放大到合适尺寸 +5. 裁剪生成新图片 +6. 本地选择图片 +7. 可定制样式:可自由选择是否渲染裁剪边框、可伸缩裁剪顶角、参考线 +8. 裁剪圆角图片:圆形、圆角矩形 + +### 属性说明 +| 属性名 | 类型 | 默认值 | 说明 | +|:---|:---|:---|:---| +| src | String | | 图片资源地址 | +| width | Number | 300 | 裁剪宽度 | +| height | Number | 300 | 裁剪高度 | +| showBorder | Boolean | true | 是否绘制裁剪区域边框 | +| showGrid | Boolean | true | 是否绘制裁剪区域网格参考线 | +| showAngle | Boolean | true | 是否展示四个支持伸缩的角 | +| areaScale | Number | 0.3 | 裁剪区域最小缩放倍数 | +| minScale | Number | 1 | 图片最小缩放倍数 | +| maxScale | Number | 5 | 图片最大缩放倍数 | +| checkRange | Boolean | true | 检查图片位置是否超出裁剪边界,如果超出则会矫正位置 | +| backgroundColor | String | | 生成图片背景色:如果裁剪区域没有完全包含在图片中时,不设置该属性则生成图片存在一定的透明块 | +| bounce | Boolean | true | 是否有回弹效果:当 checkRange 为 true 时有效,拖动时可以拖出边界,释放时会弹回边界 | +| rotatable | Boolean | true | 是否支持翻转 | +| reverseRotatable | Boolean | false | 是否支持逆向翻转 | +| choosable | Boolean | true | 是否支持从本地选择素材 | +| gpu | Boolean | false | 是否开启硬件加速,图片缩放过程中如果出现元素的“留影”或“重影”效果,可通过该方式解决或减轻这一问题 | +| angleSize | Number | 20 | 四个角尺寸,单位px | +| angleBorderWidth | Number | 2 | 四个角边框宽度,单位px | +| zIndex | Number/String | | 调整组件层级 | +| radius | Number | | 裁剪图片圆角半径,单位px | +| fileType | String | png | 生成文件的类型,只支持 'jpg' 或 'png'。默认为 'png' | +| delay | Number | 1000 | 图片从绘制到生成所需时间,单位ms
微信小程序平台使用 `Canvas 2D` 绘制时有效
如绘制大图或出现裁剪图片空白等情况应适当调大该值,因 `Canvas 2d` 采用同步绘制,需自己把控绘制完成时间 | +| navigation | Boolean | true | 页面是否是原生标题栏:
H5平台当 showAngle 为 true 时,使用插件的页面在 `page.json` 中配置了 `"navigationStyle": "custom"` 时,必须将此值设为 false ,否则四个可拉伸角的触发位置会有偏差。
注:因H5平台的窗口高度是包含标题栏的,而屏幕触摸点的坐标是不包含的 | +| @crop | EventHandle | | 剪裁完成后触发,event = { tempFilePath }。在H5平台下,tempFilePath 为 base64 | + +### 基本用法 +``` + + + +``` +通过ref组件实例可在进入页面后直接打开相册选择图片 +``` +mounted() { + this.$refs.qfImageCropper.chooseImage({ sourceType: ['album'] }); +} +``` +### 使用说明 +1.建议在`pages.json`中将引用插件的页面添加一下配置禁止下拉刷新和禁止页面滑动,防止出现性能或页面抖动等问题。 +``` +{ + "enablePullDownRefresh": false, + "disableScroll": true +} +``` +2.建议使用本插件不要设置过大宽高的目标图片尺寸,建议1365x1365以内,否则可能会导致如下问题: +``` +1.界面卡顿,内存占用过高 +2.生成图片失真(模糊) +3.确定裁剪后一直显示 `裁剪中...`,该问题是由 `uni.canvasToTempFilePath` 无法回调导致,不同平台不同设备限制可能有所不同。 +``` +3.如裁剪后的图片存在偏移的问题,请检查是否受自己项目中父组件或全局样式影响。 +4.src属性设置网络图片时,图片资源必须是能触发 `getImageInfo` API 的 success 回调才可用于插件裁剪。因此小程序平台获取网络图片信息需先配置download域名白名单才能生效。 \ No newline at end of file diff --git a/unpackage/dist/dev/.nvue/app.css.js b/unpackage/dist/dev/.nvue/app.css.js new file mode 100644 index 0000000..c5ba808 --- /dev/null +++ b/unpackage/dist/dev/.nvue/app.css.js @@ -0,0 +1,11 @@ +var __getOwnPropNames = Object.getOwnPropertyNames; +var __commonJS = (cb, mod) => function __require() { + return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports; +}; +var require_app_css = __commonJS({ + "app.css.js"(exports) { + const _style_0 = {}; + exports.styles = [_style_0]; + } +}); +export default require_app_css(); diff --git a/unpackage/dist/dev/.nvue/app.js b/unpackage/dist/dev/.nvue/app.js new file mode 100644 index 0000000..8236d9e --- /dev/null +++ b/unpackage/dist/dev/.nvue/app.js @@ -0,0 +1,2 @@ +Promise.resolve("./app.css.js").then(() => { +}); diff --git a/unpackage/dist/dev/app-plus/__uniappautomator.js b/unpackage/dist/dev/app-plus/__uniappautomator.js new file mode 100644 index 0000000..0f9252f --- /dev/null +++ b/unpackage/dist/dev/app-plus/__uniappautomator.js @@ -0,0 +1,16 @@ +var n; +/*! ***************************************************************************** +Copyright (c) Microsoft Corporation. + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +PERFORMANCE OF THIS SOFTWARE. +***************************************************************************** */ +function __spreadArrays(){for(var s=0,i=0,il=arguments.length;in;n++)r(e,e._deferreds[n]);e._deferreds=null}function c(e,n){var t=!1;try{e((function(e){t||(t=!0,i(n,e))}),(function(e){t||(t=!0,f(n,e))}))}catch(o){if(t)return;t=!0,f(n,o)}}var a=setTimeout;o.prototype.catch=function(e){return this.then(null,e)},o.prototype.then=function(e,n){var o=new this.constructor(t);return r(this,new function(e,n,t){this.onFulfilled="function"==typeof e?e:null,this.onRejected="function"==typeof n?n:null,this.promise=t}(e,n,o)),o},o.prototype.finally=e,o.all=function(e){return new o((function(t,o){function r(e,n){try{if(n&&("object"==typeof n||"function"==typeof n)){var u=n.then;if("function"==typeof u)return void u.call(n,(function(n){r(e,n)}),o)}i[e]=n,0==--f&&t(i)}catch(c){o(c)}}if(!n(e))return o(new TypeError("Promise.all accepts an array"));var i=Array.prototype.slice.call(e);if(0===i.length)return t([]);for(var f=i.length,u=0;i.length>u;u++)r(u,i[u])}))},o.resolve=function(e){return e&&"object"==typeof e&&e.constructor===o?e:new o((function(n){n(e)}))},o.reject=function(e){return new o((function(n,t){t(e)}))},o.race=function(e){return new o((function(t,r){if(!n(e))return r(new TypeError("Promise.race accepts an array"));for(var i=0,f=e.length;f>i;i++)o.resolve(e[i]).then(t,r)}))},o._immediateFn="function"==typeof setImmediate&&function(e){setImmediate(e)}||function(e){a(e,0)},o._unhandledRejectionFn=function(e){void 0!==console&&console&&console.warn("Possible Unhandled Promise Rejection:",e)};var l=function(){if("undefined"!=typeof self)return self;if("undefined"!=typeof window)return window;if("undefined"!=typeof global)return global;throw Error("unable to locate global object")}();"Promise"in l?l.Promise.prototype.finally||(l.Promise.prototype.finally=e):l.Promise=o},"object"==typeof exports&&"undefined"!=typeof module?n():"function"==typeof define&&define.amd?define(n):n();var getRandomValues="undefined"!=typeof crypto&&crypto.getRandomValues&&crypto.getRandomValues.bind(crypto)||"undefined"!=typeof msCrypto&&"function"==typeof msCrypto.getRandomValues&&msCrypto.getRandomValues.bind(msCrypto),rnds8=new Uint8Array(16);function rng(){if(!getRandomValues)throw new Error("crypto.getRandomValues() not supported. See https://github.com/uuidjs/uuid#getrandomvalues-not-supported");return getRandomValues(rnds8)}for(var byteToHex=[],i=0;i<256;++i)byteToHex[i]=(i+256).toString(16).substr(1);function v4(options,buf,offset){var i=buf&&offset||0;"string"==typeof options&&(buf="binary"===options?new Array(16):null,options=null);var rnds=(options=options||{}).random||(options.rng||rng)();if(rnds[6]=15&rnds[6]|64,rnds[8]=63&rnds[8]|128,buf)for(var ii=0;ii<16;++ii)buf[i+ii]=rnds[ii];return buf||function(buf,offset){var i=offset||0,bth=byteToHex;return[bth[buf[i++]],bth[buf[i++]],bth[buf[i++]],bth[buf[i++]],"-",bth[buf[i++]],bth[buf[i++]],"-",bth[buf[i++]],bth[buf[i++]],"-",bth[buf[i++]],bth[buf[i++]],"-",bth[buf[i++]],bth[buf[i++]],bth[buf[i++]],bth[buf[i++]],bth[buf[i++]],bth[buf[i++]]].join("")}(rnds)}var hasOwnProperty=Object.prototype.hasOwnProperty,isArray=Array.isArray,PATH_RE=/[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(?:\.|\[\])(?:\.|\[\]|$))/g;function getPaths(path,data){if(isArray(path))return path;if(data&&(val=data,key=path,hasOwnProperty.call(val,key)))return[path];var val,key,res=[];return path.replace(PATH_RE,(function(match,p1,offset,string){return res.push(offset?string.replace(/\\(\\)?/g,"$1"):p1||match),string})),res}function getDataByPath(data,path){var dataPath,paths=getPaths(path,data);for(dataPath=paths.shift();null!=dataPath;){if(null==(data=data[dataPath]))return;dataPath=paths.shift()}return data}var elementMap=new Map;function transEl(el){var _a;if(!function(el){if(el){var tagName=el.tagName;return 0===tagName.indexOf("UNI-")||"BODY"===tagName||0===tagName.indexOf("V-UNI-")||el.__isUniElement}return!1}(el))throw Error("no such element");var element,elementId,elem={elementId:(element=el,elementId=element._id,elementId||(elementId=v4(),element._id=elementId,elementMap.set(elementId,{id:elementId,element:element})),elementId),tagName:el.tagName.toLocaleLowerCase().replace("uni-","")};if(el.__vue__)(vm=el.__vue__)&&(vm.$parent&&vm.$parent.$el===el&&(vm=vm.$parent),vm&&!(null===(_a=vm.$options)||void 0===_a?void 0:_a.isReserved)&&(elem.nodeId=function(vm){if(vm._$weex)return vm._uid;if(vm._$id)return vm._$id;if(vm.uid)return vm.uid;var parent_1=function(vm){for(var parent=vm.$parent;parent;){if(parent._$id)return parent;parent=parent.$parent}}(vm);if(!vm.$parent)return"-1";var vnode=vm.$vnode,context=vnode.context;return context&&context!==parent_1&&context._$id?context._$id+";"+parent_1._$id+","+vnode.data.attrs._i:parent_1._$id+","+vnode.data.attrs._i}(vm)));else var vm;return"video"===elem.tagName&&(elem.videoId=elem.nodeId),elem}function getVm(el){return el.__vue__?{isVue3:!1,vm:el.__vue__}:{isVue3:!0,vm:el.__vueParentComponent}}function getScrollViewMain(el){var _a=getVm(el),isVue3=_a.isVue3,vm=_a.vm;return isVue3?vm.exposed.$getMain():vm.$refs.main}var FUNCTIONS={input:{input:function(el,value){var _a=getVm(el),isVue3=_a.isVue3,vm=_a.vm;isVue3?vm.exposed&&vm.exposed.$triggerInput({value:value}):(vm.valueSync=value,vm.$triggerInput({},{value:value}))}},textarea:{input:function(el,value){var _a=getVm(el),isVue3=_a.isVue3,vm=_a.vm;isVue3?vm.exposed&&vm.exposed.$triggerInput({value:value}):(vm.valueSync=value,vm.$triggerInput({},{value:value}))}},"scroll-view":{scrollTo:function(el,x,y){var main=getScrollViewMain(el);main.scrollLeft=x,main.scrollTop=y},scrollTop:function(el){return getScrollViewMain(el).scrollTop},scrollLeft:function(el){return getScrollViewMain(el).scrollLeft},scrollWidth:function(el){return getScrollViewMain(el).scrollWidth},scrollHeight:function(el){return getScrollViewMain(el).scrollHeight}},swiper:{swipeTo:function(el,index){el.__vue__.current=index}},"movable-view":{moveTo:function(el,x,y){el.__vue__._animationTo(x,y)}},switch:{tap:function(el){el.click()}},slider:{slideTo:function(el,value){var vm=el.__vue__,slider=vm.$refs["uni-slider"],offsetWidth=slider.offsetWidth,boxLeft=slider.getBoundingClientRect().left;vm.value=value,vm._onClick({x:(value-vm.min)*offsetWidth/(vm.max-vm.min)+boxLeft})}}};function createTouchList(touchInits){var _a,touches=touchInits.map((function(touch){return function(touch){if(document.createTouch)return document.createTouch(window,touch.target,touch.identifier,touch.pageX,touch.pageY,touch.screenX,touch.screenY,touch.clientX,touch.clientY);return new Touch(touch)}(touch)}));return document.createTouchList?(_a=document).createTouchList.apply(_a,touches):touches}var WebAdapter={getWindow:function(pageId){return window},getDocument:function(pageId){return document},getEl:function(elementId){var element=elementMap.get(elementId);if(!element)throw Error("element destroyed");return element.element},getOffset:function(node){var rect=node.getBoundingClientRect();return Promise.resolve({left:rect.left+window.pageXOffset,top:rect.top+window.pageYOffset})},querySelector:function(context,selector){return"page"===selector&&(selector="body"),Promise.resolve(transEl(context.querySelector(selector)))},querySelectorAll:function(context,selector){var elements=[],nodeList=document.querySelectorAll(selector);return[].forEach.call(nodeList,(function(node){try{elements.push(transEl(node))}catch(e){}})),Promise.resolve({elements:elements})},queryProperties:function(context,names){return Promise.resolve({properties:names.map((function(name){var value=getDataByPath(context,name.replace(/-([a-z])/g,(function(g){return g[1].toUpperCase()})));return"document.documentElement.scrollTop"===name&&0===value&&(value=getDataByPath(context,"document.body.scrollTop")),value}))})},queryAttributes:function(context,names){return Promise.resolve({attributes:names.map((function(name){return String(context.getAttribute(name))}))})},queryStyles:function(context,names){var style=getComputedStyle(context);return Promise.resolve({styles:names.map((function(name){return style[name]}))})},queryHTML:function(context,type){return Promise.resolve({html:(html="outer"===type?context.outerHTML:context.innerHTML,html.replace(/\n/g,"").replace(/(]*>)(]*>[^<]*<\/span>)(.*?<\/uni-text>)/g,"$1$3").replace(/<\/?[^>]*>/g,(function(replacement){return-1":""===replacement?"":0!==replacement.indexOf("0?xe:we)(e)},ke=Se,Ce=Math.min,Te=Se,Ae=Math.max,Me=Math.min,Ee=Z,Oe=function(e){return e>0?Ce(ke(e),9007199254740991):0},Le=function(e,t){return(e=Te(e))<0?Ae(e+t,0):Me(e,t)},ze=f("keys"),Ne=g,Ie=function(e){return ze[e]||(ze[e]=Ne(e))},Pe=J,De=Z,Be=(me=!1,function(e,t,n){var r,i=Ee(e),a=Oe(i.length),o=Le(n,a);if(me&&t!=t){for(;a>o;)if((r=i[o++])!=r)return!0}else for(;a>o;o++)if((me||o in i)&&i[o]===t)return me||o||0;return!me&&-1}),Re=Ie("IE_PROTO"),Fe="constructor,hasOwnProperty,isPrototypeOf,propertyIsEnumerable,toLocaleString,toString,valueOf".split(","),qe=function(e,t){var n,r=De(e),i=0,a=[];for(n in r)n!=Re&&Pe(r,n)&&a.push(n);for(;t.length>i;)Pe(r,n=t[i++])&&(~Be(a,n)||a.push(n));return a},je=Fe,Ve=Object.keys||function(e){return qe(e,je)},$e=k,He=A,We=Ve,Ue=E?Object.defineProperties:function(e,t){He(e);for(var n,r=We(t),i=r.length,a=0;i>a;)$e.f(e,n=r[a++],t[n]);return e};var Ye=A,Xe=Ue,Ze=Fe,Ge=Ie("IE_PROTO"),Ke=function(){},Je="prototype",Qe=function(){var e,t=O()("iframe"),n=Ze.length;for(t.style.display="none",function(){if(ye)return _e;ye=1;var e=l.document;return _e=e&&e.documentElement}().appendChild(t),t.src="javascript:",(e=t.contentWindow.document).open(),e.write("