- 记录一次云开发数据库查询的简单优化
云开发数据库联表查询,最开始表现优异,随着数据量的增加,某条语句的查询时间居然超过了2秒。后面着手优化查询到100毫秒以内。 优化主要事项: 1、将条件(match)、排序(sort)、分页(skip、limit)移到联表之前,先查出部分结果后再联表操作。 2、取消模糊查询连接后的子表字段。 优化后的语句如下: db.collection("form_answers") .aggregate() .match({ _openid: "xxxx" }) .sort({ createTime: -1 }) .skip(0) .limit(20) .lookup({ from: 'forms', let: { formId: '$formId' }, pipeline: $.pipeline() .match(_.expr($.and([ $.eq(['$_id', '$$formId']), ]))) .project({ _id: 0, name: 1, unionid: 1 }) .done(), as: 'formList', }) .replaceRoot({ newRoot: $.mergeObjects([{ formName: $.arrayElemAt(['$formList.name', 0]), formUnionid: $.arrayElemAt(['$formList.unionid', 0]) }, '$$ROOT']) }) .project({ formList: 0 }) .end()
2024-06-23 - 如何在微信小程序中进行幂运算?
闷了好几天,才找到方法。一般运算很容易,对于幂运算较少提及。有的说用云函数,但在微信小程序的内置的JavaScript就能做到。 下面是我的体会。 用Math.pow内置函数。 如算2的3次方。就是Math.pow(2,3)。 wxml <view>{{ math2 }}</view> <view>{{ math }}</view> <button bind:tap="bindMath">Math power</button> js bindMath:function (e) { console.log("Math.abs(-1)="+Math.abs(-1)), console.log("Math2.pow(2,3)="+Math.pow(2,3)), this.setData({ math2:Math.pow(2,3) }), console.log("Math.pow(4.2,-3)="+Math.pow(4.2,-3)), this.setData({ math:Math.pow(4.2,-3) }), console.log('Mathpower启动...') }, 运行结果 [图片] 在此学到挺多,我也要分享给大家。不敢给专业人士见笑,只为业余爱好者。
2023-12-02 - 云开发省钱小能手之:更新文档,有则update,没有就insert
以前的方案: let data= {a:1} let query = {b:2} let res = await col.where(query).get() if(res.data.length) await col.update({data}) else await col.add({data}) 该方案如果更新1000次,调用次数是2000次; 新方案: let data = { a:1 updateTime:Date.now()//一定要加上时间 } let query = {b:2} let res = await await col.where(query).update({data}) if(res.stats.updated) { }else await col.add({data}) 该方案如果更新1000次,调用次数是1001次; 省钱小能手吧。
2023-11-27 - 小程序文字循环滚动效果
一、上下滚动效果 1、利用小程序组建swiper实现上下循环滚动(为了美观,wxss中我增加了高度和背景色)。 <swiper class='swiper-box' autoplay='{{true}}' interval='2000' vertical='{{true}}' circular='{{true}}'> <swiper-item class='item' wx:for='{{txtlist}}'>{{index + 1}}、{{item}}</swiper-item> </swiper> .swiper-box{ position: relative; height: 80rpx; line-height: 80rpx; padding: 0 40rpx; background-color: #FFEBCD; overflow: hidden; } .swiper-box .item{ white-space: nowrap; text-overflow: ellipsis; } Page({ data: { txtlist: [ '这是第一条公告内容', '小程序上下滚动效果总结', '这是一行上下滚动的文字,文字最好短点,超过一行省略' ] } }) 二、水平滚动效果1、是利用CSS3中keyframes实现的,效果是文字从屏幕右边开始向左滚动 2、文字从屏幕左边向左滚动,滚动完成后从屏幕右边开始再向左滚动,以此循环 3、文字展示两次,两段文字之间设置适当的间距,从右向左开始滚动,当第二条文字滚动到屏幕左边时设置重新滚动。 <view class='wp'> <!-- 纯css实现 --> <view class='box'> <view id='txt1' class='txt' style='animation: roll linear {{duration}}s infinite;'>{{text}}</view> </view> <!-- 显示完后再显示 --> <view class='box'> <view id='txt2' class='txt' style='left: {{posLeft1}}px'>{{text}}</view> </view> <!-- 出现空白后就显示 --> <view class='box'> <view class='flex-box' style='left: {{posLeft2}}px'> <view id='txt3' class='txt'>{{text}}</view> <view class='txt' style='margin-left: {{marginLeft}}px'>{{text}}</view> </view> </view> </view> .box{ position: relative; width: 100%; height: 80rpx; line-height: 80rpx; overflow: hidden; } .txt{ white-space: nowrap; } #txt1{ position: relative; white-space: nowrap; overflow: hidden; transition: left 1s linear; } #txt2,.flex-box{ position: absolute; top: 0; } .flex-box{ display: flex; flex-wrap: nowrap; justify-content: flex-start; } @keyframes roll { 0% {left: 750rpx;} 100% {left: -100%;} } let interval1,interval2; Page({ data: { text: '这是一行文字水平滚动效果,在小程序中实现的', //滚动文字 duration: 0, //水平滚动方法一中文字滚动总时间 pace: 1, //滚动速度 posLeft1: 0, //水平滚动方法二中left值 posLeft2: 0, //水平滚动方法三中left值 marginLeft: 60 //水平滚动方法三中两条文本之间的间距 }, roll1: function (that, txtLength, windowWidth) { interval1 = setInterval(function() { if (-that.data.posLeft1 < txtLength) { that.setData({ posLeft1: that.data.posLeft1 - that.data.pace }) } else { that.setData({ posLeft1: windowWidth }) } }, 20) }, roll2: function (that, txtLength, windowWidth) { interval2 = setInterval(function () { if (-that.data.posLeft2 < txtLength + that.data.marginLeft) { that.setData({ posLeft2: that.data.posLeft2 - that.data.pace }) } else { // 第二段文字滚动到左边后重新滚动 that.setData({ posLeft2: 0 }) clearInterval(interval2); that.roll2(that, txtLength, windowWidth); } }, 20) }, onShow: function () { let that = this; let windowWidth = wx.getSystemInfoSync().windowWidth; //屏幕宽度 wx.createSelectorQuery().select('#txt1').boundingClientRect(function (rect) { let duration = rect.width * 0.03;//滚动文字时间,滚动速度为0.03s/px that.setData({ duration: duration }) }).exec() wx.createSelectorQuery().select('#txt2').boundingClientRect(function (rect) { let txtLength = rect.width;//滚动文字长度 that.roll1(that, txtLength, windowWidth); }).exec() wx.createSelectorQuery().select('#txt3').boundingClientRect(function (rect) { let txtLength = rect.width;//文字+图标长度 that.setData({ marginLeft: txtLength < windowWidth - that.data.marginLeft ? windowWidth - txtLength : that.data.marginLeft }) that.roll2(that, txtLength, windowWidth); }).exec() }, onHide: function() { clearInterval(interval1); clearInterval(interval2); } })
2023-03-23 - 小程序段落文本太长优化方案
写了个关于段落文本太长优化方案的组件推荐给大家,有这个方向需求的同学可以看看: 效果及相关API: [图片] 插件地址:https ://ext.dcloud.net.cn/plugin?id=11511 使用方式: <template> <view class="content"> <kevy-ellipsis content="这是一个uniapp通用的超长文本处理组件,简单的设置达到自动适配超长文本,如需纯微信开发者工具版本的可私信领取。祝您使用愉快,本插件会长期维护更新,有问题及时反馈哦,开源不易,如果本插件对您有帮助的话请及时点个好评吧,或者赞赏一下,总之谢谢您的鼓励啦。" font-color="#666666" :font-size="32" :rows="3" @contentClick="myclick" collapseText="收起" expandText="展开" actionFontColor="#007aff"></kevy-ellipsis> </view> </template> <script> import kevyEllipsis from '@/components/kevy-ellipsis/kevy-ellipsis' export default { components: { kevyEllipsis }, data() { return { } }, onLoad() { }, methods: { //点击文本 myclick(){ console.log("哈哈,你点击文本了。。。"); } } } </script> <style> .content { box-sizing: border-box; width: 100%; padding: 24rpx; } </style>
2023-04-04 - 云函数获取用户IP归属地
云函数获取当前用户IP归属地的极简代码: const cloud = require('wx-server-sdk') cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV }) exports.main = async () => { const wxContext = cloud.getWXContext() let opt = { uri: 'https://apis.map.qq.com/ws/location/v1/ip', qs: { ip: wxContext.CLIENTIP, key: 'HCDBZ-OHMA3-IMQ3R-*****-*****-YNBVU'//在这里获取这个key:https://lbs.qq.com }, json: true } return await rp(opt) } 10行代码,简单地实现了该功能。还不需要申请wx.getLocation接口。
2022-05-26 - 小程序最简单的滑动删除代码
网上看过不少关于微信小程序滑动删除的例子,感觉有点复杂。于是写了个简单点的例子,希望对大家有所帮助。 片段代码下载:https://developers.weixin.qq.com/s/sNy7ZfmI7mvj 问题关键点: 1、wxml的list中,添加: bindtouchmove="btn_touch" data-addr_id="{{item.addr_id}}" 然后,根据addr_id的值, <view class="footer" wx:if="{{del_id!=item.addr_id}}"> ...... <view class="footer_long" wx:else> 2、在js中,(1)初始化设置: data: { del_id: 0, }, (2)函数: btn_touch:function(e){ var addr_id = e.currentTarget.dataset.addr_id; this.setData({ del_id: addr_id, }); }, //取消删除 btn_cancel:function(e){ this.setData({ del_id: 0, }) }, 效果,如图,手指滑动时,内容向左滑动。 [图片] [图片] 主要代码如下: 一、js代码: // pages/index/index.js const app = getApp() Page({ data: { del_id: 0, }, onLoad: function (options) { var list=[{addr_id: "8", address: "广东省广州市四川省新港中路397号", is_default: "0", receiver_name: "张三", telphone: "020-81167888" },{ addr_id: "7", address: "辽宁省大连市新港中路39号", is_default: "0", receiver_name: "张三", telphone: "020-81167888" },{ addr_id: "4", address: "辽宁省大连市东华门街道小小园11号楼1004-5023", is_default: "1", receiver_name: "徐连科", telphone: "13840888081" }]; this.setData({ list: list, }) }, //删除 btn_del:function(para){ wx.request({ url: '', data: { openid:wx.getStorageSync('openid'), addr_id: para.currentTarget.dataset.addr_id, }, success: function (response) { wx.showModal({ title: '删除成功', content: '删除地址成功', success: function (res) { if (res.cancel) { //点击取消,默认隐藏弹框 } else { //点击确定 wx.redirectTo({ url: '/pages/index/index', }) } }, }); } }) }, radioChange(e) { wx.request({ url: '', data: { openid:wx.getStorageSync('openid'), addr_id: e.detail.value, } }) }, btn_add:function(){ wx.redirectTo({ url: '/pages/address/add', }) }, chooseAddress() { wx.chooseAddress({ success: (res) => { wx.request({ url: '', data: { openid:wx.getStorageSync('openid'), receiver_name: res.userName, telphone: res.telNumber, postal_code: res.postalCode, province: res.provinceName, city: res.cityName, district: res.nationalCode, address: res.detailInfo, }, success: function(res){ wx.showModal({ title: '添加成功', content: '添加地址成功', success: function (res) { if (res.cancel) { //点击取消,默认隐藏弹框 } else { //点击确定 wx.redirectTo({ url: '/pages/index/index', }) } }, }); } }) }, fail: function(err) { console.log(err) } }) }, btn_touch:function(e){ var addr_id = e.currentTarget.dataset.addr_id; this.setData({ del_id: addr_id, }); }, btn_cancel:function(e){ this.setData({ del_id: 0, }) }, }) 二、WXML代码: <!--pages/index/index.wxml--> <view class="main_view"> <view class="contain" wx:for="{{list}}" wx:key="addr_id"> <view class="list" bindtouchmove="btn_touch" data-addr_id="{{item.addr_id}}"> <view class="footer" wx:if="{{del_id!=item.addr_id}}"> <view class="footLeft"> <view class="Header"> <view class="title {{item.is_default === '1' ? 'txt-default' : ''}}"> <text>{{item.receiver_name}}</text> <text>{{item.telphone}}</text> </view> <view class="v-address"> <text>{{item.address}}</text> </view> </view> </view> <view class="footRight"> <navigator url="" hover-class="noner"> <view style="text-align:right;">修改</view> </navigator> <view class="line"></view> <view style="text-align:right;" class="{{item.is_default === '1' ? 'txt-default' : ''}}" data-addr_id="{{item.addr_id}}">默认</view> </view> </view> <view class="footer_long" wx:else> <view class="footLeft"> <view class="Header"> <view class="title {{item.is_default === '1' ? 'txt-default' : ''}}"> <text>{{item.receiver_name}}</text> <text>{{item.telphone}}</text> </view> <view class="v-address"> <text>{{item.address}}</text> </view> </view> </view> <view class="footRight"> <navigator url="" hover-class="noner"> <view style="text-align:right;">修改</view> </navigator> <view class="line"></view> <view style="text-align:right;" class="{{item.is_default === '1' ? 'txt-default' : ''}}" data-addr_id="{{item.addr_id}}">默认</view> </view> <view class="foot-del"> <view style="text-align:right;color:red" bindtap="btn_cancel">取消</view> <view class="line"></view> <view style="text-align:right;color:red" bindtap="btn_delete" data-addr_id="{{item.addr_id}}">删除</view> </view> </view> </view> </view> <view class="foot_margin"></view> <!-- <navigator url="/pages/address/add" hover-class="noner"></navigator> --> <view class="v-add_address" style="bottom:20rpx"> <view class="v-left" bindtap="chooseAddress">导入微信地址</view> </view> </view> 三、wxss代码: /* pages/index/index.wxss */ .contain{ margin-top: 0rpx; width: 100%; } .list{ border-top:13rpx solid #f2f2f2; background: #fff; width: 100%; padding: 0rpx 30rpx; } .Header{ border-bottom: 1px solid #f2f2f2; line-height: 50rpx; padding-top: 20rpx; } .title{ font-size:14px; } .footer{ display: flex; line-height: 82rpx; color:#666666; width: 100%; } .footer_long{ display: flex; line-height: 82rpx; color:#666666; width: 125%; margin-left: -25%; } .footLeft{ flex:3; padding-left: 36rpx; } .footRight{ flex:1; text-align:right; margin-right:40rpx; margin-top: 25rpx; display: flex; flex-direction:row-reverse; } .foot-del{ flex:1; text-align:right; margin-right:40rpx; margin-top: 25rpx; display: flex; flex-direction:row; } .line{ height:30rpx; width:1px; margin:25rpx 10rpx 10rpx 10rpx; background: #888; } .nav{ width:100%; height:89rpx; display:flex; justify-content:center; line-height: 45rpx; padding-top: 20rpx; position: relative; border-top: solid 4rpx #f1f1f1; } .navCent{ width:354rpx; height: 45rpx; border-radius: 16rpx; background: #ff4200; border:1px solid #318cff; display: flex; } .navText{ font-size: 12px; text-align: center; color: #fff; flex:1; } .navClick{ width: 100%; border-radius: 16rpx; height: 100%; background: #fff; color: #318cff; } .foot{ width: 100%; padding:0rpx 56rpx; padding-bottom: 120rpx; bottom: 0rpx; position: fixed; } .foot .btn{ border-radius: 30rpx; height: 98rpx; line-height: 98rpx; text-align: center; background: #ff4200; font-size: 18px; color: #fff; width: 634rpx; } .foot_margin{ height: 140rpx; } .v_empty{ width: 100%; height: 100%; background-color: #fff; } .img{ width:100%; margin-top: 30%; } .v-address{ font-size: 24rpx; color: #666; } .txt-default{ color: #ff4200; } .v-add_address{ display: flex; flex-direction: row; width: 100%; padding:0rpx 56rpx; bottom: 0rpx; position: fixed; text-align: center; } .v-left{ background: #ff4200; width: 80%; padding: 20rpx; border-radius: 10rpx; color: #fff; line-height: 60rpx; height: 60rpx; } 完毕。希望能帮到你。 片段代码下载:https://developers.weixin.qq.com/s/sNy7ZfmI7mvj
2021-12-03 - 提高性能的几种写法
任何优秀的大软件里面都是一个优秀的小程序。<br> 点完赞,在查看哦 setData 频繁使用setData会造成js阻塞问题,甚至卡顿,崩溃(崩溃没试过哈);所以我们应该如何用正确的姿势提高性能呢? 尽量把需要setData的内容放到一起setData,不要出现一个函数多个或者同时使用setData; 更新对象内的某个值,不应该整个对象重新更新,只需要更新对应的值即可,例如<br> [代码]this.setData({'userInfo.headImg': '更新的内容'})[代码]; 如果是列表数据,需要改变某个值变成true,也不应该全部数据更新一遍,只需要更新某一个值即可,例如<br> [代码]this.setData({[`list[${index}].show`]: true})[代码]; 页面不需要渲染的数据,尽量不要写在js里面的data里面,你可以定义一个全局参数都可以或者this.timer等生成一个全局变量; setData单次不能超过1M,超过会导致设置不成功,建议分页数据可以使用二维数组设置,例如 [代码]// 1.通过一个二维数组来存储数据 let feedList = [[array]]; // 2.维护一个页面变量值,加载完一次数据page++ let page = 1 // 3.页面每次滚动到底部,通过数据路径更新数据 onReachBottom:()=>{ fetchNewData().then((newVal)=>{ this.setData({ ['feedList[' + (page - 1) + ']']: newVal, }) } } // 4.最终我们的数据是[[array1],[array2]]这样的格式,然后通过wx:for遍历渲染数据 [代码] relativeToViewport 我们会发现很多时候,列表展示都是图文展示,而图片后端又没有提供缩略图,这会导致浏览器一次请求多张图片会非常消耗http请求,并且浏览器也有请求数量限制,例如chrome浏览器就限制一次性最多6个,严重还会影响我们业务接口请求,这时我们使用[代码]IntersectionObserver[代码]就太美妙了 解决思路 先创建对象实例[代码]wx.createIntersectionObserver()[代码]; 用数据列表遍历监听页面渲染的元素; 条件判断,动态设置状态显示图片,如果返回滚动则过滤不需要在setData,减轻js工作任务; js代码 [代码] data: { list: [ // 测试数据自己随便造 {id: 0, url: 'https://devapicard.itop123.com/files/img/20201213/20201213141209123634220.png'}, {id: 1, url: 'https://devapicard.itop123.com/files/img/20201213/20201213141209123634220.png'}, {id: 2, url: 'https://devapicard.itop123.com/files/img/20201213/20201213141209123634220.png'}, ] }, onReady() { let list = this.data.list; list.forEach((item,index)=>{ // 遍历监听元素在页面的位置 wx.createIntersectionObserver().relativeToViewport({bottom: 0}).observe(`.img-${index}`,res=>{ if (res.intersectionRatio > 0 && !list[index].show){ // intersectionRatio值大于0,说明元素出现在视图中了 // console.log(item, 'list', res) this.setData({ [`list[${index}].show`]: true }) } }) }) }, [代码] wxml代码 [代码] <view> <block wx:for="{{list}}" mode="aspectFill" wx:key="index"> <view class="test-img img-{{index}}"> <image class="image" wx:if="{{item.show}}" src="{{item.url}}"></image> </view> </block> </view> [代码] wxss代码 [代码].test-img{ width: 600rpx; height: 400rpx; margin-bottom: 20rpx; overflow: hidden; } .image{ width: 100%; } [代码] onPageScroll 这个监听页面滚动事件,输出顶部滚出页面多少距离,单位是px;这个事件非必要时不要写也不要输出内容,这样会很消耗性能,如果必须要使用到,我们要学会写节流或者防抖事件,减少过快的去setData;下面代码是网上复制,结合自己业务改造下即可 [代码]// 防抖 function debounce(fn, wait) { var timeout = null; return function() { if(timeout !== null) clearTimeout(timeout); timeout = setTimeout(fn, wait); } } // 处理函数 function handle() { console.log(Math.random()); } // 滚动事件 window.addEventListener('scroll', debounce(handle, 1000)); // 节流throttle代码(定时器): var throttle = function(func, delay) { var timer = null; return function() { var context = this; var args = arguments; if (!timer) { timer = setTimeout(function() { func.apply(context, args); timer = null; }, delay); } } } function handle() { console.log(Math.random()); } window.addEventListener('scroll', throttle(handle, 1000)); [代码] 代码分包 代码肯定是越写越多,需求也是越来越多的,小程序单个分包最大只能是2M,这就不能让我们的业务代码都写在主包,这会导致我们无法提交代码,并且也会导致我们打开小程序会很慢,体验很差。 为了解决这个问题,微信提供了小程序总共代码包支持最大16M,还支持我们分包功能,这样我们就可以开发更强大的功能,分包又分为普通分包和独立分包,他们又是什么关系,有何不同? 总的不同是,主包不能调用任何分包的东西,例如js,组件等,但是分包或者独立分包是可以调用或者使用主包的任何东西 分包:分包就是一个可以节省主包代码的一个普通分包,启动分包页面,会把主包下载,普通分包可以使用主包的 js 文件、template、wxss、自定义组件、插件等 独立分包:独立分包不依赖主包即可运行,不需要下载主包,可以很大程度上提升分包页面的启动速度,固并不能使用主包的 js,组件,wxss,插件等 预加载:分包后,如果想打开主包后,跳转不出现加载模块中的等待提示,可以使用预加载,这样就可以做到无缝跳转,增强了用户体验,跳转就像主包跳转主包页面一样顺滑。更多分包讲解请查看微信文档 [代码]{ "pages": [ "pages/index", "pages/logs" ], "subpackages": [ { "root": "moduleA", // 分包 "pages": [ "pages/rabbit", "pages/squirrel" ] }, { "root": "moduleB", // 独立分包 "pages": [ "pages/pear", "pages/pineapple" ], "independent": true // 独立分包的标志 } ], "preloadRule": { // 预加载写法 "pages/index": { "network": "all", "packages": ["moduleA"] // 需要预加载的分包 }, "pages/logs": { "packages": ["moduleB"] // 需要预加载的分包 } } } [代码] 结束 本次写作先到这里,更多丰富好用的内容,请等待下一期~ 都看完了,别忘了给个赞再走~,创作不容易,谢谢哦 本文与掘金号文章同步,欢迎查阅掘金对应的文章
2021-07-09 - 【汇总】wx.getUserProfile 改造常见问题
书接上文 1、如何做版本兼容? 我在项目中使用的是wx.canIUse('getUserProfile')判断getUserProfile API 是否可以使用(切换版本库2.10以下可以模拟旧场景),如果有其他好方法,欢迎在评论区指出。 2、问什么改造过程中遇到了报错?'getUserProfile:fail can only be invoked by user TAP gesture' 一般由于直接使用了这种写法。 应该把wx.login和wx.getUserProfile分开调用,(建议wxlogin获取的code单独保存,每用一次单独刷新一次(code5分钟有效)),据说反着写也行,就是getUserProfile的success 里再调wx.login。 3、授权弹窗没有弹出? 检查下wx.getUserProfile 中的desc字段是否填写(desc为必填,官方意思后续可以展示在弹窗内)。 ⚠️ wx.getUserProfile 调用必须要在catchtap 、bindtap、showmodal 里绑定方法,依旧需要用户主动触发。 手写不易,麻烦乡亲们点个赞,我好完成主人的任务🤓。
2021-04-09 - 小程序单独设置数组对象中某一个属性值
如题,js代码如下 page({ data:{ list:[{a:1,b:2,c:3},{a:12,b:22,c:32},{a:13,b:23,c:33}], test:{aa:1,bb:2,cc:3} }, jian(e){ let i = e.currentTarget.dataset.index let that = this let currentDysl = `list[${i}].a` this.setData({ [currentDysl]:parseInt(that.data.list[i].a) - 1 }) }, jia(e){ this.setData({ ['test.aa']:22 }) } })
2020-10-12 - 关于云开发的反爬、反抓、反刷、反反翻译的一些思考
最近接了个项目,甲方对于反爬、反抓包、反刷流量的要求令人发指,对我有限的代码保安知识真是一个巨大的挑战; 以下是一些经验总结: 一、关于反刷 本项目有大量的图片,放在云存储里,甲方怕有黑方用户不停地刷流量,造成套餐爆掉,所以特别要求,实际情况也确实发生了,仅不到100用户的时候,存储读取次数,一天就有10多万次,免费套餐被爆。采取措施如下: 1、关闭的有官方的页面收藏sitemap { "desc": "", "rules": [{ "action": "disallow", "page": "*" }] } 2、限制用户访问次数,比如限时超过1000次打开详情页就禁止,结果该方案被黑方破解数次,前后经历了多次方案: 将次数限制写在缓存里(删除小程序清缓存被破解); 将限时按手机时间来获取(修改手机时间被破解); 限时按服务器时间,次数写在云表里,才算解决; 二、关于反爬反抓 甲方有一些保密数据,由一些保密参数组成,通过运算得到一个价格再公布出去,一开始以为是云开发,读数据库和云函数都是保密的,结果惨遭泄密。 方案一:通过aggregate聚合,计算参数聚合出最终价格,悲伤的是,集合里的数字保存的字符串,不支持聚合的计算操作,云开发还没有任何办法批量将字符串形式的数字转成number,除非删库重建,一开始就保存成number。惨痛的教训,今后慎重,该是number的就number,别看前端可以随意,数据库里必须区分; 方案二:写一个页面,管理员输入参数,然后生成价格,应用到每一个doc里,结果数据量太大,批量修改集合所有数据的某字段值,目前用云开发完全没法实现。 最终方案:通过云函数来获取doc列表,在云函数里读取参数表,计算后生成价格,应用到每一条列表,然后再返回给小程序端; 结论:云函数是运行在微信服务器上的,还是安全的。 关于图片反爬反抓,结论:无解; 三、关于反翻译 官方的事,我们毫无办法。 设置里,什么混淆、什么代码保护等都勾上了,还是挡不住; 更多内容: [图片]
2020-10-20 - 云开发实战-如何维护用户表?(优化版)
前言 之前写过一篇《云开发-如何维护用户表?》,这种方式是最简单的,经过阅读了一些开源项目的代码,我优化了部分写法。 对比 优化前实现思路: 通过 login 云函数获取 openid 存放到本地 在授权信息的时候去添加 userInfo 根据 openid 去查询是否已经存储 没有查到就是新用户进行添加并存放 id 到本地 查看后老用户就根据 id 进行更新信息 优化后实现思路: 在 app.js 通过 queryCurrentUser 云函数查询 openid 是否在用户表 得到状态后存放在 app.js 的全局变量 authorized 属性里面 当需要用户授权的时候判断状态,没有就跳转到授权页面 进行授权调用 authorize 云函数添加用户 代码 在 app.js 通过 queryCurrentUser 云函数查询 openid 是否在用户表 得到状态后存放在 app.js 的全局变量 authorized 属性里面。 [代码]wx.cloud.callFunction({ // 云函数名称 name: 'user', // 传给云函数的参数 data: { action: 'queryCurrentUser' } }).then(res => { if (res.result.errMsg === 'user.query.ok') { this.onAuthorized(res.result.data.userInfo); this.authorized = true; } wx.hideLoading(); }) onAuthorized(userInfo) { this.authorized = true; this.globalData.userInfo = userInfo; }, [代码] queryCurrentUser 云函数 [代码]async queryCurrentUser(context, params) { const { OPENID } = context; let res = await db.collection('users').where({ openid: OPENID }).get(); if (res.data.length === 0) { return { errMsg: 'user.query.none' }; } return { errMsg: 'user.query.ok', data: { userInfo: res.data[0].userInfo } }; }, [代码] 当需要用户授权的时候判断状态,没有就跳转到授权页面 index.js [代码]toInfo(res) { if (app.authorized !== true) { wx.navigateTo({ url: '/pages/authorize/authorize' }); return; } // 省略业务代码.... } [代码] 进行授权调用 authorize 云函数添加用户 authorize.js [代码]wx.cloud.callFunction({ // 云函数名称 name: 'user', // 传给云函数的参数 data: { action: 'authorize', userInfo: userInfo } }).then(res => { if (res.result.errMsg === 'user.authorize.ok' || res.result.errMsg === 'user.authorize:authorized') { app.onAuthorized(res.result.data.userInfo); wx.showLoading({ title: '授权成功' }); setTimeout(() => { wx.hideLoading(); app.navigateBack(); }, 1000); return; } wx.nextTick(() => { wx.showToast({ title: '授权失败', icon: 'none', duration: 1000 }); }); }); [代码] authorize 云函数 [代码]const authorizedRes = { env: cloud.DYNAMIC_CURRENT_ENV, errMsg: 'user.authorize:authorized' }; async authorize(context, params) { const { OPENID } = context; let getRes = await db.collection('users').where({ openid: OPENID }).get(); if (getRes.errMsg !== 'collection.get:ok') { return errorAuthorizeRes; } if (getRes.data.length > 0) { return authorizedRes; } let addRes = await db.collection('users').add({ data: { openid: OPENID, userInfo: params.userInfo, authorizedTime: new Date(), } }); return { errMsg: 'user.authorize.ok', data: { userInfo: params.userInfo } }; } [代码] 总结 这种方式优点如下: 用云函数来验证,云函数可以直接获取 openid 通用统一的授权页面进行授权,这样就不需要在不同的地方写同样的授权代码 添加逻辑在云函数中实现,改小程序前端代码需要重新发版,云函数部署就行 代码需要不断优化才能更好。
2020-09-16 - 解决遮罩层的防触底思路,如果有更优解,欢迎大神补充。
一、场景 一般来说,在写程序涉及到弹出层的时候,一般都会做防触底处理的。就比如这样的弹出层: [图片] 二、问题 一般来说呢,只需要给遮罩层设置 catchtouchmove 即可解决。 但是这样有弊端,假如弹框内容不可滚动,不会有太大问题, 假如当弹出层内容较多,并且可以滚动的时候,问题就会比较严重, 触摸弹出框内没有滚动的区域就会发现,滚动可以穿透,并且传递给底层的列表页面。 下面是我踩过的坑: [图片] 因为我的弹出层是三级联动菜单,有滚动列表。所以无法直接设置 catchtouchmove 。 三、解决方案 通过反复研究我们公司的小程序还有网上的商城红包demo: [图片][图片] 首先,弹出层与遮罩层通过按钮触发。 然后设置弹出层的样式,并且将遮罩层的样式设置为满屏 width: 100%; height: 100%; position:fixed; 且遮罩层的z-index要低于弹出层,保证弹出层可以正常展示,不会被遮盖! 最后,将弹出层、遮罩层均设置 catchtouchmove,使其无法触底。再将弹出层内可滚动区域的view修改为scroll-view。 至于希望水平滚动还是垂直滚动,看自己需求设置即可。 至此,大功告成。目前在iOS与安卓系统上进行了简单测试,均未出现触底以及底部列表位置发生改变的情况。 因为苹果手机录屏没有触摸点,成品录下来也看不出效果,所以这个环节就省略了。 四、尾声 好了,本文没有写什么代码,只是分享一个解题思路,希望对大家有用。欢迎大神修正补充,问题探讨也可以私信我。 Ending........
2020-09-08 - 【笔记】云开发数据库有哪些常用操作
1、批量删除一个集合内的多条记录 比如我们要删除集合为question的所有记录: db.collection('question') .where({ _id: _.exists(true) }) .remove() 由于remove请求只支持通过匹配 where 语句来删除,我们可以在where里包含一个条件只要存在_id就删除,由于基本每个记录都有_id,所以就能都删除了。 2、如何给集合内所有数据都新增一个字段 比如我们想给question集合内的所有记录都新增一个updateTime的字段,我们可以查询到需要新增字段的记录,然后使用update请求,当记录内没有updateTime字段就会新增: const serverDate = db.serverDate db.collection('question') .where({ _id: _.exists(true) }) .update({ data: { updateTime: serverDate(), } })
2020-08-27 - 实战丨如何制作一个完整的外卖小程序(已开源)
最近微信小店开放了,赶着微信全面开放之前,把自己的小程序开源出来给大家使用~ 小程序效果 [图片] [图片] [图片] 开发心得 如何在项目中集成云开发 一开始项目并非基于云开发而开发的,目前考虑用云开发,因此,需要在项目中开启云开发的相关选项。 首先,在小程序文件夹中建立 [代码]cloud[代码] 文件夹,并在package文件中配置,建立用户登录的云函数并上传到微信小程序云中。相关的操作可以参考官方文档。 我在项目目录中添加了 [代码]cloud[代码] 和 [代码]miniprogram[代码] 两个目录,并在 [代码]project.config.json[代码] 文件夹进行配置 [代码]{ "miniprogramRoot": "./miniprogram" "cloudfunctionRoot": "./cloud/" } [代码] 开通云开发 配置完成后,可以点击控制台中的「云开发」来开通云开发。 [图片] 在云开发的界面中配置,并开通云开发。 [图片] 开通数据库集合 云开发不会自动创建数据库集合,因此,你需要手动创建集合。分别创建 店铺表Seller、分类表Category、商品表Food、订单表Order、地址表Address、用户表*_User*。 [图片] 数据操作 有了数据库的表后,就可以在代码中对数据进行操作了。 下方是我进行目录操作的代码。 [代码]const db = wx.cloud.database() const { showModal } = require('../../utils/utils') Page({ onLoad: function(options) { // 管理员认证 getApp().auth() if (options.objectId) { // 缓存数据 this.setData({ isEdit: true, objectId: options.objectId }) // 请求待编辑的分类对象 db.collection('Category') .doc(options.objectId) .get() .then(res => { // 获取分类信息 this.setData({ category: res.data }) }) } }, add: function(e) { var form = e.detail.value if (form.title == '') { wx.showModal({ title: '请填写分类名称', showCancel: false }) return } form.priority = Number.parseInt(form.priority) // 添加或者修改分类 // 修改模式 if (this.data.isEdit) { const category = this.data.category db.collection('Category') .doc(category._id) .update({ data: form }) .then(res => { console.log(res) showModal() }) } else { db.collection('Category') .add({ data: form }) .then(res => { console.log(res) showModal() }) } }, showModal() { // 操作成功提示并返回上一页 wx.showModal({ title: this.data.isEdit ? '修改成功' : '添加成功', showCancel: false, success: () => { wx.navigateBack() } }) }, delete: function() { // 确认删除对话框 wx.showModal({ title: '确认删除', success: res => { if (res.confirm) { const category = this.data.category db.collection('Category') .doc(category._id) .remove() .then(res => { console.log(res) wx.showToast({ title: '删除成功' }) wx.navigateBack() }) } } }) } }) [代码] 联表查询 在使用数据库时,难免要进行联表查询,云开发支持在云函数侧进行联表查询,你可以参考我的代码,来实现联表查询的功能。 [代码]const cloud = require('wx-server-sdk') cloud.init() const db = cloud.database() // 云函数入口函数 exports.main = async (event, context) => { const result = await db.collection('Food') .aggregate() .lookup({ from: 'Category', localField: 'category', foreignField: '_id', as: 'categories' }) .end() // .orderBy('priority', 'asc') // .get() console.log(result) return result.list } [代码] 文件上传 在小程序的操作中,难免会遇到需要进行图片上传的场景。在进行图片上传时,云开发提供了方便的云存储供我们查询数据。 在获取到文件的本地路径后,调用 [代码]wx.cloud.uploadFile[代码] 即可上传文件。 [代码]chooseImage() { wx.chooseImage({ count: 1, // 默认9 sizeType: ['compressed'], // 可以指定是原图还是压缩图,默认二者都有 sourceType: ['album', 'camera'], // 可以指定来源是相册还是相机,默认二者都有 success: res => { const tempFilePaths = res.tempFilePaths const file = tempFilePaths[0] const name = utils.random_filename(file) //上传的图片的别名,建议可以用日期命名 console.log(name) wx.cloud.uploadFile({ cloudPath: name, filePath: file, // 文件路径 }).then(res => { console.log(res) const fileId = res.fileID // 将文件id保存到数据库表中 db.collection('Seller').doc(this.data.seller._id) .update({ data: { logo_url: fileId } }).then(() => { wx.showToast({ title: '上传成功' }) // 渲染本地头像 this.setData({ new_logo: fileId }) }, err => { console.log(err) wx.showToast({ title: '上传失败' }) }) }) } }) } [代码] 微信支付逻辑的实现 作为一个商城,难免会有微信支付相关逻辑的实现。在这种情况下,可以借助云开发提供的微信支付云调用功能实现快速的 API 调用和接口的实现。 绑定商户 在使用云开发提供的微信支付时,需要先执行微信支付的绑定,在云开发控制台添加相应的商户号 [图片] 添加后微信会发来通知 [图片] 根据提示,开通账号即可。 [图片] 如果不绑定,将报“受理关系不存在”的错误 [图片] 函数代码调用 配置完成后,只需要在云函数中调用微信支付的接口,就可以实现相关调用的能力 [代码]const cloud = require('wx-server-sdk') cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV }) // 云函数入口函数 exports.main = async (event, context) => { console.log('请求中') console.log(cloud.getWXContext().ENV) let { orderId, amount, body } = event const wxContext = cloud.getWXContext() const res = await cloud.cloudPay.unifiedOrder({ body: body, outTradeNo: orderId, spbillCreateIp: '127.0.0.1', subMchId: '1447716902', totalFee: amount, envId: 'dinner-cloud', functionName: 'pay_cb' }) return res.payment } [代码] 这里 [代码]functionName: 'pay_cb'[代码]指的就是支付成功后,微信支付那侧给我的回调信息,后面我们就用它来更新我们的订单状态 小程序端代码调用 调用云函数后,会获得微信支付所需要的各种参数, [图片] 这个时候,就可以在小程序端调用微信支付接口,进行支付,相关代码可以参考 [代码]const { result: payData } = res wx.requestPayment({ timeStamp: payData.timeStamp, nonceStr: payData.nonceStr, package: payData.package, signType: 'MD5', paySign: payData.paySign, success: res => { console.log('支付成功', res) wx.showModal({ title: '支付成功', showCancel: false, success: () => { // 跳转订单详情页 wx.navigateTo({ url: '/order/detail/detail?objectId=' + order._id }) } }) }, ... [代码] 微信支付回调处理 微信统一下单里一个pay_cb回调函数,它是一个云函数,后续微信支付的支付信息将会发送在这个函数中,相应的,我们需要编写处理的方法 [代码]// 云函数入口文件 const cloud = require('wx-server-sdk') cloud.init({ // API 调用都保持和云函数当前所在环境一致 env: cloud.DYNAMIC_CURRENT_ENV }) const db = cloud.database() // 云函数入口函数 exports.main = async (event, context) => { console.log('支付回调') console.log(event) console.log(cloud.getWXContext().ENV) const orderId = event.outTradeNo const resultCode = event.resultCode if (resultCode === 'SUCCESS') { const res = await db .collection('Order') .doc(orderId) .update({ data: { status: 1 } }) console.log(res) return { errcode: 0 } } } [代码] 总结 云开发体验下来,优点自不必多说,微信登录与支付原生支持,调用与调试都很方便,特别是不用启本地服务开发,真的好用; 这个小程序的源码我已经开源了,你可以访问社区官网 获取源码,自行使用~ 作者:黄秀杰,16年开始从事小程序开发与技术布道,同名个人公众号「黄秀杰」。 云开发(Tencent CloudBase,TCB)是腾讯云提供的云原生一体化开发环境和工具平台,为开发者提供高可用、自动弹性扩缩的后端云服务,包含计算、存储、托管等serverless化能力,可用于云端一体化开发多种端应用(小程序,公众号,Web 应用,Flutter 客户端等),帮助开发者统一构建和管理后端服务和云资源,避免了应用开发过程中繁琐的服务器搭建及运维,开发者可以专注于业务逻辑的实现,开发门槛更低,效率更高。 产品文档:https://cloud.tencent.com/product/tcb 技术文档:https://cloudbase.net 技术交流加Q群:601134960 最新资讯关注微信公众号【腾讯云云开发】
2020-07-29 - 专治按钮效果不明显(扩散动画效果)
效果 [图片] 需求 背景 由于最近自家小程序用户活跃用户下滑,老板看看自家小程序,发现分享按钮不够明显,于是乎有了下面这段对话。 老板:小明,你过来下,看看这个分享按钮不明显 小明:好的,给它点颜色瞧瞧 小明给按钮来了个红色,发给了BOSS BOSS:还是不明显 小明:好的,给它点放大瞧瞧 小明把按钮从原来的60rpx放大到了230rpx,发给了BOSS BOSS:还是不明显 小明:好的,让它动起来! 需求:提高分享率,做个扩散动画效果让这个按钮成为整个页面最靓的仔。 思路分析: 从小到大的变化 从颜色从深到浅 反复进行该动作 动画代码 实用 CSS3 的 animation 属性 代码 [代码].share-btn { width: 200rpx; height: 200rpx; } .share-btn::before { // 省略无关代码 animation: wave 1.5s ease-out infinite; } @keyframes wave { 50%, 75% { width: 230rpx; height: 230rpx; } 80%, 100% { opacity: 0; } } [代码] 分析 我们先来看看 animation 参数: animation: wave 1.5s ease-out infinite; animation: 关键帧名称 动画时长 动画形式 播放次数; ease-out:动画以低速结束 infinite:无限次播放 从参数可以得出来使用了wave这个关键帧参数,1.5完成一次扩散的动画,从快到满的速度,无限重复这个动画。 然后我们再来看看 keyframes 参数: 百分比:动画持续时间的百分比。 属性:CSS样式属性 从参数可以得出来 时间 50%->75% 的时候就会改变大小从200rpx-230rpx。 时间 80%->100% 的时候会改变透明度从0-1。 第一步:原始大小(高度:200,宽度:200,透明度:1) [图片] 第二步:改变大小(高度:230,宽度:230,透明度:1) [图片] 第三步:改变透明度(高度:230,宽度:230,透明度:0) [图片] 第四步:回到第一步 总结 做动画先分析步骤,然后设置 animation 参数。如果你觉得CSS比较麻烦的话,还可以使用小程序提供 Animation 对象实现。 css3 的 animation 的所有参数不局限以上这些,了解更多点击传送门 Animation 对象,了解更多点击传送门
2020-08-17 - 正确认识并理解setData
本文背景今天下午真的脑回路比较清奇,看一篇文章能延伸很多,并且有很多自己的思考,站在问题上对解决问题的思考,本文是看到下面文章进行的思考,这个思考目前尚未落地,有待检验 本文内容今天在翻看CSDN,我之前一直感觉CSDN广告太多,没有太多高质量文章,从昨天开始,看了几篇关于小程序的文章发现,CSDN的文章竟然也很不错,作为主战场在开放社区的我,我就很纳闷,言归正传,今天的思考来源于下面的文章,当然这种文章我之前也看过,但是之前没有今天的思考, 如下图所示,其实这本身就是官方对setData的定义,我们在setData的时候,有两种方式 (1)直接按照name来 (2)按照路径来 我之前一直采用方案1,对setData的理解也是1,但是对于2,能解决我在setData大数据的问题,比如我题库1000道题,每次选择的时候都是仅仅处理其中一道题,那么我们在更新状态的时候,完全没必要setData整个题库,而是可以按照索引,更新局部当前一个题库就可以 这个思路是我今天突然间想到了,脑子灵光一念间, 不过这个方案有待我在实际开发中去检验 f [图片] f [图片] f 参考文章https://juejin.im/post/6844903693544849415 https://blog.csdn.net/aspire_cj/article/details/107434166 https://blog.csdn.net/weixin_37880401/article/details/89848353 https://www.cnblogs.com/memphis-f/p/12073303.html https://blog.csdn.net/rolan1993/article/details/81738613 http://www.wxapp-union.com/article-5646-1.html https://juejin.im/post/6844903902115020814 本文总结本文通过对setData的两种方式进行总结,通过对第二种方式进行延伸,用来解决setData大数据量的问题,这个思路我感觉非常出奇,有待我在实际开发中去检验。
2020-08-12 - 使用聚合函数实现打卡排行榜
效果展示 先上图,有图有真相。 [图片] 需求实现分析 需求:根据打卡天数进行排序,实现累积排行榜,查询前100名。 这里涉及到两张表:用户表,打卡记录表 实现思路 用户表和打卡记录表通过openid进行联合查询 统计每个用户到打卡次数 根据次数进行排序 查询前100名 代码实现 根据以上思路实现代码如下: exports.main = async (event, context) => { // 获取操作符 const $ = db.command.aggregate // 用户表和打卡记录表联合查询 let res = await db.collection(‘用户表’).aggregate() .lookup({ from: ‘打卡记录表’, localField: ‘_openid’, foreignField: ‘_openid’, as: ‘list’, }) // 统计每个人打卡次数 .project({ userInfo:1, _openid:1, size: $.size(’$list’) }) // 进行排序 .sort({ size: -1, }) // 限制100名 .limit(100) .end() return res } 总结 这里面用到了4个关键函数:lookup、project、sort、limit。 lookup:官方文档传送门 project:官方文档传送门 sort:官方文档传送门 limit:官方文档传送门
2020-08-13 - 使用云开发做内容安全检查
效果展示 少啰嗦,看图! [图片] 需求实现分析 需求:输入违规内容,点击提交按钮,提示请注意言论。 技术方案:使用微信提供的 msgSecCheck 函数进行内容校验。 新增一个云函数 配置openapi权限 实现msgSecCheck函数 调用返回验证结果 根据结果给出提示 代码实现 新增一个 msgSecCheck 的云函数 [图片] 配置 config.json [图片] 实现 msgSecCheck 函数 [图片] 调用 msgSecCheck 云函数,根据不同结果进行处理 [图片] 总结 这里面用到了1个关键函数:msgSecCheck。 msgSecCheck:官方文档传送门 如果觉得有收获,欢迎点赞收藏~
2020-08-13 - 关于自定义客服会话contact同时兼容自动和人工客服使用
首先,我们知道,可以通过button中的属性open-type="contact",实现小程序用户和小程序所有者客服对话。
2020-08-12 - 关于云开发的一次性订阅消息
前段时间看到了这位老哥的一篇关于订阅消息的文章:https://developers.weixin.qq.com/community/develop/article/doc/0008802e8381e0eeabb92c9975b013 这篇文章对于程序员来说非常直观的说明了一次性订阅消息的逻辑:订阅1次,可以收到订阅消息一次,订阅10次,可以收到订阅消息10次。 但是我觉得这个方案对于一个普通用户来说,并不够友好,如果我是一个不懂订阅消息的普通用户,我根本不会花时间去点这样一个点1加1的订阅消息。我觉得对于开发者来说,用户能够点一次允许并且勾上不在询问就已经很不错了,剩下的完全可以交给程序来处理。下面是我的方案。让一次性订阅消息达到长期订阅的效果。 首先明确以下逻辑: 通过 wx.getSetting({ withSubscriptions: true }) 的 success 回调 res 可以得出订阅消息的以下5种状态[图片]当用户勾选了“不在询问”之后,不管你后面怎么调 wx.requestSubscribeMessage ,订阅消息的弹窗都是不会弹起的wx.requestSubscribeMessage 需要用户手动点击触发当得到以上几种状态之后,接下来就可以根据需要做自己想要做的操作 如我的小程序首页是一个版本列表 [图片] 我在列表的头部设计了一个跟小程序同风格的授权卡片,这样不会显得突兀同时告诉用户点击授权并且勾选“不在询问”,并告诉用户这样做的目的是什么。 然后根据上面得到的不同状态来显示不同的提示语: 总开关关闭了: [图片] 勾选了“不在询问”并且选项是取消 [图片] 接下来就是实现订阅消息+1的步骤,上面提到了当用户勾选了“不在询问”之后,不管你后面怎么调 wx.requestSubscribeMessage ,订阅消息的弹窗都是不会弹起的 这时在用户点击你应用中必点的操作时,比如知乎微博的点击列表进入详情,或者我这个小程序点击版本列表进入版本详情时就可以根据以上得到的状态来判断:当授权状态是“选择了不在询问并且选项是允许” 时,直接调用 wx.requestSubscribeMessage ,这时 wx.requestSubscribeMessage 的回调必定是success,而且不会出现授权弹窗,自然也就实现了+1效果。 最后把订阅次数+1记录到数据库,推送时推送订阅次数大于0的就ok了 [图片] 这样一个普通用户需要做的操作就只有点击授权-勾选不在询问-允许 这样一个步骤,同时就实现了无形中增加订阅次数的效果,替代让用户手动去点+1增加订阅消息的操作。 另外不用担心这种操作会使用户感觉像垃圾广告一样一直被推送,因为不管是在服务通知页面,还是在设置页面,用户都是可以很轻松的一键关闭通知。 [图片] 然后说下订阅消息的几个特殊情况: 1.当你的账号在开发者工具上面点过允许或取消的时候,wx.getSetting({ withSubscriptions: true }) 的 success 回调结果是这样的 [图片] 手机上的设置界面是这样的 [图片] 回调的itemSettings属性消失了,界面上有订阅消息的开关,但是订阅消息的选项却没了,正常情况应该是这样的 [图片] 这样的 [图片] 2.当用户点了“不在询问并允许”但是又手动通过服务通知页面,或者设置页面关闭了消息通知,这时就算该用户之前已经订阅过了很多次,都会被系统自动清0,这时你的数据库可能存的该用户还有比如5次订阅消息,但是通过cloud.openapi.subscribeMessage.send推送消息的时候,会进catch,errCode是43101。 3.当用户手速过快连续点击了授权按钮触发wx.requestSubscribeMessage时,会进入fail回调,errMsg是 requestSubscribeMessage:fail last call,这个文档是没写的。 最后可以扫码体验一下: [图片]
2020-07-14 - 带过期时间的小程序StorageSync缓存实现
var dtime = '_deadtime'; //设置缓存 function cachePut(k, v, t) { wx.setStorageSync(k,v) var seconds = parseInt(t); if (seconds > 0) { var timestamp = Date.parse(new Date()); timestamp = timestamp / 1000 + seconds; wx.setStorageSync(k + dtime, timestamp + "") } else { wx.removeStorageSync(k + dtime) } } //获取缓存 function cacheGet(k, def) { var deadtime = parseInt(wx.getStorageSync(k + dtime)) if (deadtime) { if (parseInt(deadtime) < Date.parse(new Date()) / 1000) { if (def) { return def; } else { return; } } } var res = wx.getStorageSync(k); if (res) { return res; } else { return def; } } //删除指定缓冲 function cacheRem(k) { wx.removeStorageSync(k); wx.removeStorageSync(k + dtime); } //清空所有缓存 function cacheClear() { wx.clearStorageSync(); } module.exports = { cachePut: cachePut, cacheGet: cacheGet, cacheRem: cacheRem, cacheClear: cacheClear, }
2020-06-08 - 【好文】如何管理页面的多弹窗实现
如何管理页面的多弹窗实现 最近在做一个小程序老项目的关于页面活动弹窗的修改,很多人一听到“老”字,汗毛竖起;当然我也不例外,我大概总结了一下,存在以下问题: [代码]js[代码] 文件判断弹窗组件的逻辑代码嵌套多层 if else 判断,难以抽离; [代码]wxss[代码] 文件关于处理弹窗组件的样式也是嵌套了多层 wx:if 判断,代码十分难看,字段判断嵌套过深,组件元素处理过于分散; 编写弹窗初期没有做好前期规划,多种弹窗样式耦合其中,文件代码冗余过多,实在难以快速定位到合适的样式进行修改; 历史剖析 重构老代码最怕就是缺少个别的逻辑处理,导致在某一个条件下无法触发窗口弹出,出现bug; 重构毕竟是一个吃力不讨好的活儿,毕竟功能在线上运行了这么久没出问题,万一自己踩到雷区,接盘侠妥妥的了,背锅也是妥妥的了,还会惹来一身麻烦,更别提绩效了,这也是很多开发不愿意触碰老代码的最主要原因; 由于业务关系,本文不会用公司项目的代码作为例子,我会重新写一个例子以供大家参考,这是抽丝拔茧后的解决方案,希望可以帮助大家。 调研 我大概分析了一下,弹窗大致分为以下几种样式: 通知弹窗 (纯文本) 广告弹窗(大图) 活动弹窗(即:自定义弹窗,样式不定,有可能是领优惠券,有可能是特定节日活动) 小记:通知类型弹窗和广告大图弹窗是固定样式,可以作为一个组件单独实现;活动弹窗具有时效性,根据自己需要自行扩展实现。 文字通知弹窗 + 广告大图弹窗实现 场景描述: 很多时候运营需要在同个页面顺序弹出多个窗口(可以参考拼夕夕,一打开首页疯狂弹窗,让人抓狂),于是,我这里用到了上一篇文章提到的 发布订阅模式 实现,有需要的同学可以先去简单阅读一下。 编写通用组件实现 编写静态文件 [代码]// modal.wxml <view class="modal" wx:if="{{ show }}"> <view class="modalContent"> <!-- 文本弹窗 --> <view class="textModal" wx:if="{{ type == 'text' }}">{{ value }}</view> <!-- 广告大图弹窗 --> <view class="picModal" wx:elif="{{ type == 'pic' }}"> <image class="adImg" mode="widthFix" src="{{ value }}"></image> </view> <view class="customModal"> <slot name="customModal"></slot> </view> <view class="flex flex-center" bindtap="hide"> <image class="close-icon" src="/asserts/icon_bt_close_white.png" /> </view> </view> </view> [代码] 处理组件逻辑代码 [代码]// modal.js const Event = require('../../designPatterns/observer'); Component({ options: { multipleSlots: true, }, properties: { }, data: { value: '', type: 'pic', show: false, }, attached() { // 建立监听 Event.listen('modal', (props) => { this.setData({ ...props, show: true, }) }) }, methods: { hide() { this.setData({ show: false }) // 关闭弹窗通知回调页面,用于处理下一个弹窗继续弹出 this.triggerEvent('hide') }, show() { this.setData({ show: true }) } } }) [代码] 页面调用弹窗组件实现 页面引用组件 [代码]// modalPage.json { "usingComponents": { "modal": "/components/modal/modal" } } [代码] [代码]// modalPage.wxml <view> <modal bind:hide="triggerModal"/> </view> [代码] 页面触发弹窗调用 [代码]// modalPage.js const Event = require('../../designPatterns/observer'); Page({ // 弹窗mock数据 modalData: [ { id: 1, level: 10, // 弹出的顺序,数字越大代表越先弹出 type: 'text', value: '我是文本弹窗1', }, { id: 2, level: 11, type: 'pic', value: '/asserts/OIP.jpeg' }, { id: 3, level: 9, type: 'text', value: '我是文本弹窗2', } ], onLoad() { this.modalData = this.modalData.sort((prev, next) => -(prev.level - next.level)) this.triggerModal() }, triggerModal() { const targetModalList = this.modalData.splice(0, 1) if (targetModalList && targetModalList.length > 0) { // 继续触发弹窗 Event.trigger('modal', targetModalList[0]) } } }) [代码] 小记: 本着一次编码,幸福后代的原则,要是简单地把弹窗的管理交由各自页面单独处理,其实也是不美观的; 在我看来,弹窗们应该是有一个管理员的角色,用来管理它们,由管理员去负责它们(排序,触发等动作),接下来我们在自定义弹窗组件编码中来看看应该如何实现 自定义活动弹窗 多余代码不再复制累赘了,代码可以参考 [代码]/components/modal/activityModal[代码] 的相关文件 弹窗管理类:ModalManage [代码]// ModalManage.js const Event = require('../designPatterns/observer'); class ModalManage { // 弹窗类型,用于触发特定弹窗 modalType = { text: 'modal', pic: 'modal', activity: 'activityModal' } constructor(modalList) { // 初始化的时候需要排序,决定弹出优先级 this.modalList = modalList.sort((prev, next) => -(prev.level - next.level)) } triggerModal() { const targetModalList = this.modalList.splice(0, 1) if (targetModalList.length > 0) { const currentModal = targetModalList[0] // 发送弹窗通知 Event.trigger(this.modalType[currentModal.type], currentModal) } } } module.exports = ModalManage [代码] 页面触发弹窗调用 [代码]const Event = require('../../designPatterns/observer'); const ModalManage = require('../../model/ModalManage'); Page({ modalData: [ { id: 1, level: 10, type: 'text', value: '我是文本弹窗1', }, { id: 2, level: 11, type: 'pic', value: '/asserts/OIP.jpeg' }, { id: 3, level: 9, type: 'text', value: '我是文本弹窗2', }, { id: 4, level: 10, type: 'activity', value: '我是活动弹窗啦啦啦啦' } ], onLoad() { this.modalManage = new ModalManage(this.modalData) this.modalManage.triggerModal() }, triggerModal() { this.modalManage.triggerModal() } }) [代码] 小记:相比于第一次的处理方式,这次更加的浅显易懂,简单明了。 示例展示 [图片] 项目地址 项目地址:https://github.com/csonchen/mina-app 我想记录一些关于小程序日常开发所遇到的问题,进而引起的一些思考,能否给大家提供多一些角度去思考问题,解决问题,能帮助大家就好。希望大家多多支持,多多star哈
2020-05-29 - 小程序云开发如何更优雅导入数据
在之前我开放过一个工具,用来将excel转化成对应的json格式文件,具体链接如下 https://developers.weixin.qq.com/community/develop/article/doc/000822ded88e80e80d4a1c89c51c13 界面如下图所示,我通过后台,监控到目前有非常多的小伙伴使用该工具,我也陆陆续续收到不少打赏,在这里感谢大家,我会持续为大家提供好的服务 [图片] 由于该工具还单纯作为一个工具使用,而我想要的并不仅仅是这些,我需要一个完善的题库导入系统 最近我在优化小程序数据导入的工作,体验不少答题小程序,现通过这篇文章把一些导入方式在这里整理下 海量题库管理 多种方式支持导入题目、支持判断、单选、多选、填空、简答,可分类清晰管理和搜索。 [图片] 通过excel生成的题目,最后再界面上呈现如下 [图片] 第一步:找到对应的导入工具链接, https://www.xiaomutong.com.cn/index20200501.html 友情提示: ①该工具链接并不适合所有答题小程序,在您确定使用该工具之前,请联系开发者确认最后链接地址 ②建议用谷歌浏览器访问该链接 下面文本框是题库的编号,如果是二级结构题库那么,这个编号是第二层题库的编号,具体我举个例子 1、假如说是一层结构题库,有以下三个题库,分别是 语文 、001, 数学 、002, 英语 、003 那么如果你要导入语文,你应该在文本框内输入001 [图片] 第二步:文件上传后,点击开始生成JSON,会看到生成一个文件 第三步、开始导入,打开云开发控制台,找到question进行导入 如果云开发控制台不会打开,可以看评论区里面,系列文章的第六篇,有描述如何打开云开发控制台 [图片] 第六步:导入成功后的样子 [图片] 6 题库已经导入完成 友情提示: 每个题库,每天最多可以导入一次!!!
2021-01-13 - 微信小程序分页加载数据~上拉加载更多~小程序云数据库的分页加载
我们在开发小程序时,一个列表里难免会有很多条数据,比如我们一个列表有1000条数据,我们一下加载出来,而不做分页,将会严重影响性能。所以这一节,我们来讲讲小程序分页加载数据的实现。 老规矩,先看效果图 [图片] 可以看到我们每页显示10条数据,当滑动到底部时,会加载第二页的数据,再往下滑动,就加载第三页的数据。由于我们一共21条数据,所以第三页加载完以后,会有一个“已加载全部数据”的提示。 本节知识点 1,小程序分页加载 2,小程序列表显示 3,云数据库的使用 4,云数据库分页请求数据的实现 一,先定义数据 我们做分页数据加载,肯定要先准备好数据,数据已经给大家准备好,如下图,文章末尾会贴出数据源和本节课源码的下载地址。 [图片] 然后把数据导入到我们的云开发的数据库里,关于数据如何导入,这里不再讲解,不知道的同学,请看下面这篇文章。或者去老师历史文章里找一下。 《小程序云开发入门—云数据库数据源的导入与导出》 下面给大家看下我们的数据源,长什么样。其实很简单,就是简单的定义21条数据。 [图片] 然后在看导入到数据库的样子。 [图片] 二,分页请求数据 我们第一步准备好了数据以后,接下来就来讲讲如何在js里做分页加载数据。 首先我们这里用到了小程序云开发数据库的知识点 1,get方法:获取云数据库数据 2,skip方法:跳过前面几条数据,请求后面的数据 3,limit方法:请求多少条数据。 比如下面这段代码,就是跳过前5条,请求从第6条开始往后的10条数据,就是请求6~15的数据,我们做分页加载也就是基于这个原理。 [代码] wx.cloud.database().collection("list") .skip(5) //从第几个数据开始 .limit(10) [代码] 下面把我们index.js的完整代码贴给大家。 [代码]//老师微信:2501902696 let currentPage = 0 // 当前第几页,0代表第一页 let pageSize = 10 //每页显示多少数据 Page({ data: { dataList: [], //放置返回数据的数组 loadMore: false, //"上拉加载"的变量,默认false,隐藏 loadAll: false //“没有数据”的变量,默认false,隐藏 }, //页面显示的事件 onShow() { this.getData() }, //页面上拉触底事件的处理函数 onReachBottom: function() { console.log("上拉触底事件") let that = this if (!that.data.loadMore) { that.setData({ loadMore: true, //加载中 loadAll: false //是否加载完所有数据 }); //加载更多,这里做下延时加载 setTimeout(function() { that.getData() }, 2000) } }, //访问网络,请求数据 getData() { let that = this; //第一次加载数据 if (currentPage == 1) { this.setData({ loadMore: true, //把"上拉加载"的变量设为true,显示 loadAll: false //把“没有数据”设为false,隐藏 }) } //云数据的请求 wx.cloud.database().collection("list") .skip(currentPage * pageSize) //从第几个数据开始 .limit(pageSize) .get({ success(res) { if (res.data && res.data.length > 0) { console.log("请求成功", res.data) currentPage++ //把新请求到的数据添加到dataList里 let list = that.data.dataList.concat(res.data) that.setData({ dataList: list, //获取数据数组 loadMore: false //把"上拉加载"的变量设为false,显示 }); if (res.data.length < pageSize) { that.setData({ loadMore: false, //隐藏加载中。。 loadAll: true //所有数据都加载完了 }); } } else { that.setData({ loadAll: true, //把“没有数据”设为true,显示 loadMore: false //把"上拉加载"的变量设为false,隐藏 }); } }, fail(res) { console.log("请求失败", res) that.setData({ loadAll: false, loadMore: false }); } }) }, }) [代码] 上面的代码就是我们实现分页加载的所有逻辑代码。简单说下代码 1,我们首先进页面时会请求前10条内容 2,10条内容请求成功以后,我们会把请求到的内容加入dataList数组,然后把dataList里的数据显示到页面上。并将currentPage加一,用于请求第二页数据。 3,当用户滑动到底部时,会触发onReachBottom事件,在这个事件里做第二页到请求。然后第二页数据请求成功以后。继续将currentPage加1,这里要记住一定,一定要请求成功以后才将currentPage +1。 三,列表布局和样式 其实index.wxml和index.wxss的代码很简单,给大家把代码贴出来。 1,index.wxml [代码]<scroll-view scroll-y="true" bindscrolltolower="searchScrollLower"> <view class="result-item" wx:for="{{dataList}}" wx:key="item"> <text class="title">{{item.content}}</text> </view> <view class="loading" hidden="{{!loadMore}}">正在载入更多...</view> <view class="loading" hidden="{{!loadAll}}">已加载全部</view> </scroll-view> [代码] 2,index.wxss [代码]page { display: flex; flex-direction: column; height: 100%; } .result-item { display: flex; flex-direction: column; padding: 20rpx 0 20rpx 110rpx; overflow: hidden; border-bottom: 2rpx solid #e5e5e5; } .title { height: 110rpx; } .loading { position: relative; bottom: 5rpx; padding: 10rpx; text-align: center; } [代码] 到这里我们就完整的实现里分页加载功能了。 [图片] 源码和数据源,已经给大家放到网盘里了,有需要的同学请在文章底部留言,或者私信老师。 视频讲解:https://edu.csdn.net/course/detail/9604
2019-11-07 - 极简代码之云开发的触底无限加载
js: [代码]const db = wx.cloud.database() const _ = db.command const col = "test" const sql = { _id: _.neq(1) } //获取所有记录 Page({ data: { isEndOfList: false, list: [], limit: 20 //每次拉取数量 }, onLoad: function(options) { this.getData() }, getData: function() { db.collection(col) .where(sql) .skip(this.data.list.length) .limit(this.data.limit) .get() .then(res => { this.setData({ list: [...this.data.list, ...res.data], //合并数据 isEndOfList: res.data.length < this.data.limit ? true : false //判断是否结束 }) }) }, onReachBottom: function() { this.data.isEndOfList || this.getData() } }) [代码] wxml [代码]<view style="height:100px" wx:for='{{list}}' wx:key='none'>{{index}}</view> <view style="padding:15px;text-align:center;color:grey" wx:if='{{list.length>limit}}'> <view wx:if='{{(!isEndOfList)}}'>正在加载数据...</view> <view wx:else>----END----</view> </view> [代码]
2020-06-16