From a564ef78bd545c99c4c5a49f4e34386c003c9971 Mon Sep 17 00:00:00 2001 From: wangxiaowei <1121133807@qq.com> Date: Tue, 6 Jan 2026 16:16:57 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E4=BA=BA=E8=84=B8=E8=AF=86?= =?UTF-8?q?=E5=88=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bundle/face/face-info.vue | 214 +++++++++ bundle/face/face-photo.vue | 46 ++ bundle/reserve/details.vue | 6 +- bundle/reserve/notice.vue | 73 ++- manifest.json | 3 + pages.json | 12 + pages/order/cg-my-order.vue | 15 + uni_modules/face-bio-assay/changelog.md | 36 ++ .../face-bio-assay/ActionContainer.js | 59 +++ .../face-bio-assay/actions/Action.js | 57 +++ .../face-bio-assay/actions/NodHead.js | 24 + .../face-bio-assay/actions/ShakeHead.js | 24 + .../face-bio-assay/actions/StraightenHead.js | 27 ++ .../face-bio-assay/actions/index.js | 9 + .../face-bio-assay/face-bio-assay.vue | 436 ++++++++++++++++++ uni_modules/face-bio-assay/package.json | 81 ++++ uni_modules/face-bio-assay/readme.md | 185 ++++++++ .../face-bio-assay/static/images/cover.png | Bin 0 -> 2824 bytes 18 files changed, 1300 insertions(+), 7 deletions(-) create mode 100644 bundle/face/face-info.vue create mode 100644 bundle/face/face-photo.vue create mode 100644 uni_modules/face-bio-assay/changelog.md create mode 100644 uni_modules/face-bio-assay/components/face-bio-assay/ActionContainer.js create mode 100644 uni_modules/face-bio-assay/components/face-bio-assay/actions/Action.js create mode 100644 uni_modules/face-bio-assay/components/face-bio-assay/actions/NodHead.js create mode 100644 uni_modules/face-bio-assay/components/face-bio-assay/actions/ShakeHead.js create mode 100644 uni_modules/face-bio-assay/components/face-bio-assay/actions/StraightenHead.js create mode 100644 uni_modules/face-bio-assay/components/face-bio-assay/actions/index.js create mode 100644 uni_modules/face-bio-assay/components/face-bio-assay/face-bio-assay.vue create mode 100644 uni_modules/face-bio-assay/package.json create mode 100644 uni_modules/face-bio-assay/readme.md create mode 100644 uni_modules/face-bio-assay/static/images/cover.png diff --git a/bundle/face/face-info.vue b/bundle/face/face-info.vue new file mode 100644 index 0000000..9820524 --- /dev/null +++ b/bundle/face/face-info.vue @@ -0,0 +1,214 @@ + + + + + diff --git a/bundle/face/face-photo.vue b/bundle/face/face-photo.vue new file mode 100644 index 0000000..a2ca619 --- /dev/null +++ b/bundle/face/face-photo.vue @@ -0,0 +1,46 @@ + + + + + diff --git a/bundle/reserve/details.vue b/bundle/reserve/details.vue index 55cfd62..6b83f09 100644 --- a/bundle/reserve/details.vue +++ b/bundle/reserve/details.vue @@ -688,7 +688,7 @@ export default { uni.$off("payment") if (params.result) { uni.redirectTo({ - url: `/bundle/reserve/notice?order_id=${params.order_id}` + url: `/bundle/reserve/notice?order_id=${params.order_id}&ground_type=${self.venue.type_id}` }) } else { uni.redirectTo({ @@ -761,11 +761,11 @@ export default { uni.$off("payment") if (params.result) { uni.redirectTo({ - url: `/bundle/reserve/notice?order_id=${params.order_id}` + url: `/bundle/reserve/notice?order_id=${params.order_id}&ground_type=${self.venue.type_id}` }) } else { uni.redirectTo({ - url: '/pages/order/cg-my-order' + url: '/pages/order/cg-my-order?type=2' }) } }, 500) diff --git a/bundle/reserve/notice.vue b/bundle/reserve/notice.vue index 8eb036c..80113ea 100644 --- a/bundle/reserve/notice.vue +++ b/bundle/reserve/notice.vue @@ -12,18 +12,39 @@ 查看订单 完成 + + + + + + + 提示 + + 为了给您提供更安全、便捷的入场体验,请完成人脸识别认证。只需几秒钟,轻松刷脸入场! + + 去录入 + + @@ -111,4 +145,35 @@ } } } + + .notice-popup { + padding-bottom: 20rpx; + + .title { + font-size: 36rpx; + color: #303133; + line-height: 50rpx; + text-align: center; + } + + .desc { + margin-top: 48rpx; + font-weight: 400; + font-size: 32rpx; + color: #303133; + line-height: 52rpx; + text-align: center; + } + + .btn { + font-size: 32rpx; + background: #365A9A; + border-radius: 8rpx; + margin: 92rpx 44rpx 0; + height: 80rpx; + line-height: 80rpx; + text-align: center; + color: #fff; + } + } diff --git a/manifest.json b/manifest.json index 4d4aebd..fb06817 100644 --- a/manifest.json +++ b/manifest.json @@ -157,6 +157,9 @@ "permission" : { "scope.userLocation" : { "desc" : "获取您与体育场馆的距离" + }, + "scope.camera": { + "desc": "需要获取摄像头权限进行人脸录入" } } }, diff --git a/pages.json b/pages.json index a49ddff..91d7df9 100644 --- a/pages.json +++ b/pages.json @@ -1013,6 +1013,18 @@ "style": { "navigationStyle": "custom" } + }, + { + "path": "face/face-info", + "style": { + "navigationBarTitleText": "" + } + }, + { + "path": "face/face-photo", + "style": { + "navigationBarTitleText": "" + } } ] }], diff --git a/pages/order/cg-my-order.vue b/pages/order/cg-my-order.vue index 9036015..3167377 100644 --- a/pages/order/cg-my-order.vue +++ b/pages/order/cg-my-order.vue @@ -133,6 +133,11 @@ + + + + + @@ -251,6 +256,10 @@ } }, onLoad(e) { + if (e.type) { + this.ballType = e.type; + } + if (typeof e.dataType != 'undefined') { this.dataType = e.dataType; } @@ -572,6 +581,12 @@ uni.navigateTo({ url: `/bundle/reserve/details?id=${ground_id}&typeId=${this.ballType}` }); + }, + + onTakePhoto(order_id) { + uni.navigateTo({ + url: `/bundle/face/face-info?order_id=${order_id}` + }); } } }; diff --git a/uni_modules/face-bio-assay/changelog.md b/uni_modules/face-bio-assay/changelog.md new file mode 100644 index 0000000..ba72fba --- /dev/null +++ b/uni_modules/face-bio-assay/changelog.md @@ -0,0 +1,36 @@ +## 1.1.1(2023-04-13) +文档更新:动作容器的使用案例 +## 1.1.0(2023-04-13) +整理代码,补充文档信息 +## 1.0.14(2023-04-13) +更新文档 +## 1.0.13(2023-04-13) +更新文档信息 +## 1.0.12(2023-04-13) +修复授权弹窗bug +## 1.0.11(2023-04-11) +文档调整 +## 1.0.10(2023-04-11) +更新文档 +## 1.0.9(2023-04-11) +1.优化点头摇头动作 +2.解决页面回退在ios出现空白页 +3.调整ui布局 +## 1.0.8(2023-04-03) +修改文档,并提出在ios中二次调用bug的解决方案 +## 1.0.7(2023-03-28) +未通过核验停留两秒,对未授权拍照bug修复 +## 1.0.6(2023-03-27) +提示用户开启摄像头权限 +## 1.0.5(2023-03-22) +bug修复,文档修订 +## 1.0.4(2023-03-21) +更新文档 +## 1.0.3(2023-03-21) +新增 动作容器(ActionContainer) 类 开发者可通过自定义动作 添加到该容器中管理。也可使用已有动作进行排列组合创建动作组 +## 1.0.2(2023-03-16) +调整通过条件,一次行为就可以通过验证 +## 1.0.1(2023-03-15) +参数调整,提高通过率 +## 1.0.0(2023-03-14) +基于微信小程序的人脸识别做的简易版活体检测 diff --git a/uni_modules/face-bio-assay/components/face-bio-assay/ActionContainer.js b/uni_modules/face-bio-assay/components/face-bio-assay/ActionContainer.js new file mode 100644 index 0000000..c2f2117 --- /dev/null +++ b/uni_modules/face-bio-assay/components/face-bio-assay/ActionContainer.js @@ -0,0 +1,59 @@ +class ActionContainer { + //动作组 actions + //起始动作下标 index + //初始提示 tip + //绑定 完成所有动作的回调 endFun + //绑定 动作进行中 ingFun + //绑定 动作完成时 successFun + //绑定 动作失败时 failFun + constructor(actions, index, tip, endFun, ingFun, successFun, failFun) { + this.actions = actions || [] + this.index = index || 0 + this.tip = tip || '检测不到人脸' + this.endFun = endFun || this.__tempFun + this.ingFun = ingFun || this.__tempFun + this.successFun = successFun || this.__tempFun + this.failFun = failFun || this.__tempFun + } + __tempFun(){ + + } + next(faceData) { + if (this.index >= this.actions.length) { + this.endFun() + return this + } + if (this.actions[this.index].state === 'ing') { + this.tip = this.actions[this.index].tip + this.actions[this.index].check(faceData) + this.ingFun() + return this + } else if (this.actions[this.index].state === 'success') { + this.index++; + this.successFun() + return this + } else if (this.actions[this.index].state === 'fail') { + this.failFun() + return this + } + return this + } + end(fun) { //绑定 完成所有动作的回调 + this.endFun = fun || this.__tempFun + return this + } + ing(fun) { //绑定 动作进行中 + this.ingFun = fun || this.__tempFun + return this + } + success(fun) { //绑定 动作完成时 + this.successFun = fun || this.__tempFun + return this + } + fail(fun) { //绑定 动作失败时 + this.failFun = fun || this.__tempFun + return this + } +} + +export default ActionContainer diff --git a/uni_modules/face-bio-assay/components/face-bio-assay/actions/Action.js b/uni_modules/face-bio-assay/components/face-bio-assay/actions/Action.js new file mode 100644 index 0000000..8190fa5 --- /dev/null +++ b/uni_modules/face-bio-assay/components/face-bio-assay/actions/Action.js @@ -0,0 +1,57 @@ +const STATE = { + ING:'ing', + SUCCESS:'success', + FAIL:'fail' +} +export default class Action { + constructor(second=10,fun,limit,initTip) { + this.second = second + this.endTime = Infinity + this.frames = [] + this.tip = initTip + this.initTip = initTip + this.state = STATE.ING + this.fun = fun + this.limit = limit + } + end(){ + if(this.fun){ + this.fun(this.state) + } + } + check(faceData){ + if(this.endTime === Infinity){ + this.endTime = new Date().getTime() + (this.second*1000) + } + if(this.state !== STATE.ING ){ + return + } + if(new Date().getTime()>this.endTime){ + this.state = STATE.FAIL + this.end() + return + } + if(this.frames.length>=this.limit){ + this.state = STATE.SUCCESS + this.end() + return + } + this.takeFrameAfter(faceData)?.takeFrame(faceData) + } + takeFrame(faceData){ + + } + takeFrameAfter(faceData){ + let face = faceData.faceInfo[0] + this.tip = this.initTip + if(faceData.x == -1 || faceData.y == -1) { + this.tip = '检测不到人脸' + return null + } + if(faceData.faceInfo.length > 1) { + this.tip = '请保证只有一人做核验' + return null + } + return this + } +} \ No newline at end of file diff --git a/uni_modules/face-bio-assay/components/face-bio-assay/actions/NodHead.js b/uni_modules/face-bio-assay/components/face-bio-assay/actions/NodHead.js new file mode 100644 index 0000000..319a2a0 --- /dev/null +++ b/uni_modules/face-bio-assay/components/face-bio-assay/actions/NodHead.js @@ -0,0 +1,24 @@ +import Action from "./Action.js" +class NodHead extends Action { + constructor(second=10,fun) { + super(second,fun,1,'请点头') + this.maxPitch = 0 + this.minPitch = 0 + } + takeFrame(faceData){ + let face = faceData.faceInfo[0] + if(face.angleArray.pitch>this.maxPitch){ + this.maxPitch = face.angleArray.pitch + } + if(face.angleArray.pitch0.45 && (this.minPitch || this.maxPitch) ){ + this.frames.push('点头') + this.maxPitch = 0 + this.minPitch = 0 + } + } +} + +export default NodHead \ No newline at end of file diff --git a/uni_modules/face-bio-assay/components/face-bio-assay/actions/ShakeHead.js b/uni_modules/face-bio-assay/components/face-bio-assay/actions/ShakeHead.js new file mode 100644 index 0000000..fbb7eff --- /dev/null +++ b/uni_modules/face-bio-assay/components/face-bio-assay/actions/ShakeHead.js @@ -0,0 +1,24 @@ +import Action from "./Action.js" +class ShakeHead extends Action { + constructor(second = 10, fun) { + super(second,fun,1,'请摇头') + this.minYaw = 0 + this.maxYaw = 0 + } + takeFrame(faceData) { + let face = faceData.faceInfo[0] + if(face.angleArray.yaw>this.maxYaw){ + this.maxYaw = face.angleArray.yaw + } + if(face.angleArray.yaw0.45 && (this.minYaw || this.maxYaw) ){ + this.frames.push('摇头') + this.minYaw = 0 + this.maxYaw = 0 + } + } +} + +export default ShakeHead diff --git a/uni_modules/face-bio-assay/components/face-bio-assay/actions/StraightenHead.js b/uni_modules/face-bio-assay/components/face-bio-assay/actions/StraightenHead.js new file mode 100644 index 0000000..7266472 --- /dev/null +++ b/uni_modules/face-bio-assay/components/face-bio-assay/actions/StraightenHead.js @@ -0,0 +1,27 @@ +import Action from "./Action.js" +class StraightenHead extends Action { + constructor(second = 10, fun) { + super(second, fun, 10, '请平视摄像头') + } + takeFrame(faceData) { + let face = faceData.faceInfo[0] + if (Math.abs(face.angleArray.pitch) >= 0.3 || Math.abs(face.angleArray.roll) >= 0.2 || Math.abs(face + .angleArray.yaw) >= 0.2) { + this.frames = [] + return + } + if (Math.abs(face.confArray.global) <= 0.8 || Math.abs(face.confArray.leftEye) <= 0.8 || Math.abs( + face.confArray.mouth) <= + 0.8 || Math.abs(face.confArray.nose) <= 0.8 || Math.abs(face.confArray.rightEye) <= 0.8) { + this.tip = '请勿遮挡五官' + this.frames = [] + return + } + this.tip = '正在核验,请保持' + this.frames.push('正') + + } +} + + +export default StraightenHead diff --git a/uni_modules/face-bio-assay/components/face-bio-assay/actions/index.js b/uni_modules/face-bio-assay/components/face-bio-assay/actions/index.js new file mode 100644 index 0000000..3bc3ca3 --- /dev/null +++ b/uni_modules/face-bio-assay/components/face-bio-assay/actions/index.js @@ -0,0 +1,9 @@ +import NodHead from "./NodHead.js" +import ShakeHead from "./ShakeHead.js" +import StraightenHead from "./StraightenHead.js" + +export { + NodHead, + ShakeHead, + StraightenHead, +} \ No newline at end of file diff --git a/uni_modules/face-bio-assay/components/face-bio-assay/face-bio-assay.vue b/uni_modules/face-bio-assay/components/face-bio-assay/face-bio-assay.vue new file mode 100644 index 0000000..aeb30ae --- /dev/null +++ b/uni_modules/face-bio-assay/components/face-bio-assay/face-bio-assay.vue @@ -0,0 +1,436 @@ + + + + + diff --git a/uni_modules/face-bio-assay/package.json b/uni_modules/face-bio-assay/package.json new file mode 100644 index 0000000..50f561d --- /dev/null +++ b/uni_modules/face-bio-assay/package.json @@ -0,0 +1,81 @@ +{ + "id": "face-bio-assay", + "displayName": "face-bio-assay", + "version": "1.1.1", + "description": "纯前端简易版活体检测,易扩展,开发者可通过自定义动作 添加到该容器中管理。也可使用已有动作(点头、摇头、平视)进行排列组合创建动作组", + "keywords": [ + "人脸识别", + "活体检测", + "易扩展" +], + "repository": "", +"engines": { + }, + "dcloudext": { + "category": [ + "前端组件", + "通用组件" + ], + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "" + }, + "uni_modules": { + "dependencies": [], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "Vue": { + "vue2": "y", + "vue3": "u" + }, + "App": { + "app-vue": "u", + "app-nvue": "u" + }, + "H5-mobile": { + "Safari": "u", + "Android Browser": "u", + "微信浏览器(Android)": "u", + "QQ浏览器(Android)": "u" + }, + "H5-pc": { + "Chrome": "u", + "IE": "u", + "Edge": "u", + "Firefox": "u", + "Safari": "u" + }, + "小程序": { + "微信": "y", + "阿里": "u", + "百度": "u", + "字节跳动": "u", + "QQ": "u" + }, + "快应用": { + "华为": "u", + "联盟": "u" + } + } + } + } +} \ No newline at end of file diff --git a/uni_modules/face-bio-assay/readme.md b/uni_modules/face-bio-assay/readme.md new file mode 100644 index 0000000..aeb3ac3 --- /dev/null +++ b/uni_modules/face-bio-assay/readme.md @@ -0,0 +1,185 @@ +# props +|参数名|类型|默认值|可选择值|描述| +|----|----|----|---|---| +|buildActionContainer|Function|null|""|创建ActionContainer类的函数,要求返回值必需是ActionContainer类| +|actions|Array|[]|""|动作组(目前已有动作:点头,摇头,平视)| +|isDev|boolen|false|true|是否是开发者模式,开启后可显示人脸的三个角度| + +# emits +|方法名|参数|描述| +|----|---|---| +|detectFailed|[]|核验失败| +|photoChange|[url:'照片路径']|拍照后的回调| +|detectOver|[]|检测完成| +|showData|[faceData:'人脸数据']|每一帧的人脸数据| +# slots +|插槽名|参数|描述| +|----|---|---| +|default|无|用户可配合showData钩子展示人脸数据方便调试| + +# 方法 +## initData +开始进行人脸核验 使用案例: +``` +//html + +//js +//调用 +this.$refs.faceDetect.initData() +``` +## takePhoto +拍照获取照片,配合动作使用: + ``` + 在每个动作创建时第二个参数是动作完成的回调 如平视动作的使用: + const fun = (state) => { + //state 有成功success和fail,ing(进行时不会触发该函数,忽略) + if (state === 'success') { + this.$refs.faceDetect.takePhoto() //调用拍照方法 + } + } + let straightenHead = new StraightenHead(10, fun) + ``` + +# 使用建议 +``` +ios中bug解决方案: +在ios中,二次进入使用该组件有问题,解决办法:单独将该组件作为一个页面(或者下载demo查看),代码如下: + +//主页面 + + + + + + +//face.vue页 + + + + +``` + +# 类的使用 +## Action + 动作类,开发者可继承该类重写 takeFrame 方法 如: +``` +//点头动作 +import Action from "./Action.js" +class NodHead extends Action { + constructor(second=10,fun) { + //时间限制(s),结束时回调,完成次数(limit),基本提示 + super(second,fun,1,'请点头') + this.maxPitch = 0 + this.minPitch = 0 + } + takeFrame(faceData){ + let face = faceData.faceInfo[0] + if(face.angleArray.pitch>this.maxPitch){ + this.maxPitch = face.angleArray.pitch + } + if(face.angleArray.pitch0.45 && (this.minPitch || this.maxPitch) ){ + this.frames.push('点头') //frames 完成的帧数组 根据该数组长度和limit判断是否完成 + this.maxPitch = 0 + this.minPitch = 0 + } + } +} + +export default NodHead +``` +## ActionContainer + 动作容器的使用案例 +``` +buildActions() { + if (this.buildActionContainer) { + return this.buildActionContainer() + } + let actions = [] + if (!this.actions?.length) { + let nodHead = new NodHead() + const fun = (state) => { + if (state === 'success') { + this.takePhoto() //拍照 + } + } + let straightenHead = new StraightenHead(10, fun) //平视 结束拍照 + let straightenHead2 = new StraightenHead(10) //平视 + let shakeHead = new ShakeHead() + actions = [straightenHead2, nodHead, shakeHead, straightenHead] + } else { + actions = this.actions + } + //new ActionContainer(actions,...) + //动作组 actions + //起始动作下标 index + //初始提示 tip + //绑定 完成所有动作的回调 endFun + //绑定 动作进行中 ingFun + //绑定 动作完成时 successFun + //绑定 动作失败时 failFun + let act = new ActionContainer(actions) + act.end(() => { //也可用该方法绑定endFun方法 + this.detectOver() + }).fail(() => { //也可用该方法绑定failFun方法 + this.cameraError() + }) + //....其他方法类似 + return act + } +``` \ No newline at end of file diff --git a/uni_modules/face-bio-assay/static/images/cover.png b/uni_modules/face-bio-assay/static/images/cover.png new file mode 100644 index 0000000000000000000000000000000000000000..b41f26af4b923b7dddbe0b8300fc1bf66d79dcc8 GIT binary patch literal 2824 zcmd5;`8Sk};~mO2n&Q=vo$Og2OLoJENtQ~oy@Lm1Pc?{m>}$3x8CjCNFTK2J@v27B zV@Y^1gAgKy(byWA%#F5GdPo)=mfn z7x;hVKlIDl`H`vmOUfM`oN>SO;C~Wh(ubXYjnj>Ex^Nk>$GelJv7uL?%!qJ1gyjLcu&sw1zi8`LB`{&#ZK`X2;BVAa;z(m7^obtd~} zwf6C3x+E6siEaG)=Ba(1bSUkB9@sLFE&aGlqSJAim?QuOFzMTlJJEfd)PVzqLV9xRv$e3*PL^_zUR>hHMD&zNu9A$OY=~H)t zga(%eQVfB^ zoSn_&kwe%Q=m|pvcW}j$;!}%PU1q@+46cpUkG8;1RFvkhh z;p1NXawydilzoe|5>*d4Y^g#_z1j86Md?meFQXEbeB1Jrg=3%ONtuGZA%O)`2?&SE zdL*!&V+}?NGCp{1i)Fy(?AKi((%yEqa>Vp2A%<^oic0%%4I&G9c8k;Ff9uV`P^)t;c<9h&wXz4-|~qBgt}St2~LiQd$pF( zqSC7zPOC&4Ebp$^Rbr?_Zi~w%&G%ObO;z*~3Vr%R-z9IlD|M3GgeFDC8-ELC7a0xx z5t^^WI`ZW7yE}$wlSWodo}RD*EhB<7bMvu<6a9RwH_$rtuj_zHzVYX3&dzvVj0?Jb z4nEgj(R{a#z@eoqe9fUUSsf9k2SE^&w+Z9JAz4is{rCu(+XKz z-Hbd9uh6Y}^qw5hKB3hDoFqD>Zcdr1sL2s!?P{u$Xvggo2QUrjrM~A#>p@7x@f5JV zh_+X~TUilG6H_gpg%;7SKy{T9+IVpWyK?mzgGjxQ_J1F^%~<1(8`Uq0 zMy?JxD*#EVf3HnigKuv%9kcS4BXX}=5#(5ak8EPV5akeGg4ZFaDmi3!fDaxDjA%z~ zWu>)sU};6uKZ|k94am#qpt+pG=8`7aL?yRJ>TmCr+&l&@6qnq0E zFQU`F1pyqBd-1FU`<*y|^Yw1&CT)#gVsBF653Gb)jtGYvCsZ&NA`_29#4puRQJoe6 zY%AEW^P1xCjOz+?WmTJA&G=7>MEYmm4+r(r=B?*9HbN4!lYaEpsDJX8^`f(K+>V(U zlv#dnw3LZW2oHh$<4E5XKJ7|R zWlAL`^vc&M*ozPMalJ_Lyd_lt(=Ilb%D3jtWyl*?a%bCFuZcuYoF6*i@$|nZTKicKr z9HNwFUc|`H!cESZ{ErQm;N|k)R_O0xPuHYb{_Ul;;y}8_vXmYWRDHb}Ck!!_%283E z1s?pmG2?2b1FcbuJE)??VwB%@qXkQhV!`}}iDTGs$d9#Iv!7K^S*kK4bx2Ac@J^gv z&mwSoK;Vhnd@6@9zy8Te+j1BR_k*L-<~evGF@ovA1r2XRuF+wX0F)pdb`vEI?ZxlX zuy!DmE|*GlbpSuK#{GQyL0$zgc`%?R9xbN^#DaS*{IP|1CmJq_>7S!8N2iE~*laxU z+0-?B3M3pGp**LR-UVm1evIDGyG4c1VPf`vHRuCV*_X6JtgeGEi#N~X3-7x@vr%4? z(6H<7?$ej@7{G}-?5=$`I=`HmaCtB8N7 zn9*%FwnO}<(;Ls8#}9AjuIuk#B8f@s108pdg3Eom?tR>0dOhkmDCN&ozA)`{D;Mxc z7puUe{g%BO_^>m{_xok|PYqx-V=nZ$7N9{JDG^)FseGctB9PkPtc(Zk&3TPDU(iP` zEDs|tm(~wwNsfTS5<1V9se&3fP41zR-dcU&>UrwPuI9k?qhu&$r$(4wpC7F@C>Y7J z+q)dS&NT+5WY()W|KO5!dLXu;rjud&K*PKi0|-4sBJV#r6Mr@d>`1Jtm>y{Nh#$;^ zLtESWT-q}$V~WJwwYW(npT@3yxKHq9O&4R%{EA7L}y5c(};h>-YsMjU3>tZ{w0zQiwfEBfDNZZD6A#GlmyB<3J;a3C(!j zOY@}R(u5+;_5Udg^HxJTyW(e?z9Ml83W+9>~C+<6PfV`-W1vxEcZestR;O1uG+u(0UHMJ$+(>w?vfc98_i}~yK PArQ8>3)auh_@w>^`XSG9 literal 0 HcmV?d00001