- 小程序开发之分享海报并保存到相册功能
项目地址:https://github.com/yancekang/wechat-plug 欢迎star 演示: [图片] 小程序生成海报并不能像客户端一样,采用截取页面上指定部分作为生成海报,而是需要用到[代码]canvas[代码]按照海报模板生成一张图片。 首先我们看下实现的效果 [图片] 首先我们需要在页面上插入[代码]canvas[代码]标签 [代码]<canvas canvas-id="my_canvas"></canvas>[代码] 通过 [代码]css[代码]隐藏[代码]canvas[代码], 需要将[代码]canvas[代码]定位到页面不可见部位。设置如下属性将[代码]canvas[代码]定位页面之外。 [代码] position: fixed; left: -200vw; [代码] 注意:设置[代码]canvas[代码]尺寸是尽量使用[代码]px[代码],是为了尺寸大小和生成海报是尺寸一致 不能使用 [代码]display: none[代码]隐藏[代码]canvas[代码] 开始绘制 创建 [代码]canvas[代码]的绘图对象 [代码]wx.createCanvasContext[代码] [代码] let canvasW = 700 //定义画布宽高,具体根据海报模板定义 let canvasH = 1200 const ctx = wx.createCanvasContext('my_canvas') // 获取绘图对象 my_canvas 为页面canvas的id ctx.clearRect(0, 0, canvasW, canvasH) // 清空画布 ctx.setFillStyle('#ffffff') //设置填充色 ctx.fillRect(0, 0, canvasW, canvasH) //填充画布 [代码] 之后,画布就是一个700*1200白色背景的白板。 首先绘制一个高度为800px蓝色背景 [代码]//绘制蓝色背景 ctx.setFillStyle('#0B5FA5') //设置填充色 ctx.fillRect(0, 0, 690, 800) [代码] 绘制每日一语,这里需要计算文字长度,并且内容在蓝色区域上下居中 [代码]ctx.setFontSize(40); //设置字体大小 ctx.setFillStyle("#fff") //设置字体颜色 [代码] 根据内容长度进行换行,当每一行超过630时,进入第二行绘制,这里将每一行文字放入到一个数组中,然后进行绘制数组里面的数据。 [代码]for (var a = 0; a < yiyu.length; a++) { if (ctx.measureText(temp).width < 630 && ctx.measureText(temp + (yiyu[a])).width <= 630) { temp += yiyu[a]; }//ctx.measureText(text).width 测量文本text的宽度 else { row.push(temp); temp = yiyu[a]; } } row.push(temp); [代码] [代码]// 寻找绘制内容的Y坐标,也就是蓝色区域高度-内容高度(行数* 行高)/ 2 let top = (800 - row.length * 48) / 2 for (var b = 0; b < row.length; b++) { console.log(((800 - top) / 2) + (b + 1) * 48) ctx.fillText(row[b], 30, top + (b + 1) * 48); } [代码] 至此,完成蓝色区域及内容绘制 接下来绘制用户昵称及头像、二维码,接下来比较简单,一层一层绘制就行了。 在绘制图片时,值得注意的是需要将图片先转换为本地图片 所要绘制的图片资源(网络图片要通过 getImageInfo / downloadFile 先下载) 绘制头像 [代码]// 45为圆角,90为头像宽高 ctx.save() ctx.arc(30 + 45, 855 + 45, 45, 0, 2 * Math.PI); ctx.clip() //裁剪圆角 ctx.drawImage(this.data.user_img, 30, 855, 90, 90) ctx.restore() // 还原上次保存 [代码] 绘制昵称及提示信息 [代码]// 绘制昵称 ctx.setFontSize(32); ctx.setFillStyle("#333") ctx.fillText('科技男', 150, 890) ctx.setFontSize(24); ctx.setFillStyle("#999") ctx.fillText('邀请您使用微信小程序', 150, 930) [代码] 绘制二维码 [代码]// 绘制二维码 ctx.drawImage(this.data.qr_img, 540, 835, 100, 100) ctx.fillText('长按识别二维码', 500, 970) [代码] 至此整个海报绘制完成。 保存到相册时需要获取相册权限,可用 [代码]wx.getSetting({ success(res) { if (!res.authSetting['scope.writePhotosAlbum']) { wx.authorize({ scope: 'scope.writePhotosAlbum', success() { that.saveImages() }, fail() { wx.showToast({ title: '保存失败', icon: "none" }) } }) } else { that.saveImages() } } }) }) [代码] 将canvas生成图片并保存 [代码]wx.canvasToTempFilePath({ canvasId: 'my_canvas', success(res) { wx.saveImageToPhotosAlbum({ filePath: res.tempFilePath, success(res) { wx.hideLoading() wx.showToast({ title: '保存成功', icon: 'none' }) }, complete: function () { wx.hideLoading() } }) } }) [代码]
2019-10-29 - 小程序跳转页面加载优化
适应场景: 小程序页面跳转redirect/navigate/其它方式 分析: 从用户触发跳转行为到下一个页面onload生命周期函数内时间差会有500ms左右,如果在页面跳转之后进行onload函数内才开始去加载页面数据,那么这500ms左右的时间就浪费了。 改进: 在页面触发跳转行为的处理函数里结合promise预先加载下个页面的数据,并将promise对象缓存,此时页面跳转和加载数据同时进行,到了目标页面再取出缓存的promise对象进行判断和取数据操作。 效果: 跳转页面加载速度提高了600ms。 示例: 代码结构 [图片] pageManager.js [代码]// 写在utils里的公用方法 const pageList = {}; module.exports = { putData:function(pageName, data){ pageList[pageName] = data; }, getData:function(pageName){ return pageList[pageName]; } } [代码] util.js [代码]const myPromise = fn => obj => { return new Promise((resolve, reject) => { obj.complete = obj.success = (res) => { resolve(res); } obj.fail = (err) => { reject(err); } fn(obj); }) } module.exports = { myPromise : myPromise } [代码] index.js [代码]// 跳转页面 const {myPromise} = require('../../utils/util'); const pageManager = require('../../utils/pageManager'); page({ data: { }, onLoad:function(){ }, gotoPageA:function(){ const PromisePageA = myPromise(wx.request)({ url : '' }).then((res)=>{ return res.data; }) pageManager.putData('pageA',promisePageA); wx.navigateTo({ url: 'pages/pageA/pageA' }) } }) [代码] pageA.js [代码]// 被跳转页面 const util = require('../../utils/util.js'); const pageManager = require('../../utils/pageManager'); const {myPromise} = require('../../utils/util'); Page({ data:{ logs:[] }, onLoad: function(){ const promisePageA = pageManager.getData('pageA'); if(promisePageA){ const resData = promisePageA.then( function(data){ }, function(){ console.log("err"); } ) } } }) [代码]
2019-10-31 - 小程序- SaUi 之添加城市选择
趁着最近有时间,又搞了个经常会用到的城市选择器起来啦~~ 以下是tabs的页面图片 [图片] 主要实现了: tab的切换,这里需要注意一个问题,当你滚动一个tab页到某个位置的时候 再切换tab,另一tab的scroll也会定位在那里。所以在点击时,我作了处理。 [代码] scroll-into-view [代码] 滚动时,经过指定的父位置时 会有fixed。这里用到了新的样式类型 [代码] position: sticky; [代码] 3.点击右边的菜单,给出了提示框以及左边可以快速,准确的定位到相对的位置,需要注意的事scroll-into-view对应的是id [代码] scroll-into-view [代码] 4.点击右边菜单时,应该给上对应的选中类才是,这里漏了… 以上是携程所有的功能。同时也想添加新的功能,就是滚动左边的列表,同时切换右边的菜单,这里是目前还差的,待更新一版…
2019-09-10 - 纯云开发二手书商城的全开源demo
这是为母校写的一个纯粹的公益小程序,原生+云开发,写文章太累了,所以所有代码我都写了注释,还是很适合入门学习的,特别是云开发 [图片] [图片] [图片] 程序本身来说,我认为没啥多大的亮点,只不过把很多单个案例综合起来了,云开发方面,比如:支付、提现、获取用户手机号、发短信、发邮箱。。。。。。。界面上,清一色的flex布局。 和完整版得商城小程序,还差了一丢丢–购物车,因为思考了一下,这个小程序着实用不着,用来学习还是可以了滴 源码和使用教程发在Github: https://github.com/xuhuai66/used-book-pro
2019-09-18 - 【开箱即用】分享几个好看的波浪动画css效果!
以下代码不一定都是本人原创,很多都是借鉴参考的(模仿是第一生产力嘛),有些已忘记出处了。以下分享给大家,供学习参考!欢迎收藏补充,说不定哪天你就用上了! 一、第一种效果 [图片] [代码]//index.wxml <view class="zr"> <view class='user_box'> <view class='userInfo'> <open-data type="userAvatarUrl"></open-data> </view> <view class='userInfo_name'> <open-data type="userNickName"></open-data> , 欢迎您 </view> </view> <view class="water"> <view class="water-c"> <view class="water-1"> </view> <view class="water-2"> </view> </view> </view> </view> //index.wxss .zr { color: white; background: #4cb4e7; /*#0396FF*/ width: 100%; height: 100px; position: relative; } .water { position: absolute; left: 0; bottom: -10px; height: 30px; width: 100%; z-index: 1; } .water-c { position: relative; } .water-1 { background: url("data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+Cjxzdmcgd2lkdGg9IjYwMHB4IiBoZWlnaHQ9IjYwcHgiIHZpZXdCb3g9IjAgMCA2MDAgNjAiIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgeG1sbnM6c2tldGNoPSJodHRwOi8vd3d3LmJvaGVtaWFuY29kaW5nLmNvbS9za2V0Y2gvbnMiPgogICAgPCEtLSBHZW5lcmF0b3I6IFNrZXRjaCAzLjQgKDE1NTc1KSAtIGh0dHA6Ly93d3cuYm9oZW1pYW5jb2RpbmcuY29tL3NrZXRjaCAtLT4KICAgIDx0aXRsZT53YXRlci0xPC90aXRsZT4KICAgIDxkZXNjPkNyZWF0ZWQgd2l0aCBTa2V0Y2guPC9kZXNjPgogICAgPGRlZnM+PC9kZWZzPgogICAgPGcgaWQ9IuaIkSIgc3Ryb2tlPSJub25lIiBzdHJva2Utd2lkdGg9IjEiIGZpbGw9Im5vbmUiIGZpbGwtcnVsZT0iZXZlbm9kZCIgc2tldGNoOnR5cGU9Ik1TUGFnZSI+CiAgICAgICAgPGcgaWQ9Ii0iIHNrZXRjaDp0eXBlPSJNU0FydGJvYXJkR3JvdXAiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0xMjEuMDAwMDAwLCAtMTMzLjAwMDAwMCkiIGZpbGwtb3BhY2l0eT0iMC4zIiBmaWxsPSIjRkZGRkZGIj4KICAgICAgICAgICAgPGcgaWQ9IndhdGVyLTEiIHNrZXRjaDp0eXBlPSJNU0xheWVyR3JvdXAiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDEyMS4wMDAwMDAsIDEzMy4wMDAwMDApIj4KICAgICAgICAgICAgICAgIDxwYXRoIGQ9Ik0wLDcuNjk4NTczOTUgTDQuNjcwNzE5NjJlLTE1LDYwIEw2MDAsNjAgTDYwMCw3LjM1MjMwNDYxIEM2MDAsNy4zNTIzMDQ2MSA0MzIuNzIxMDUyLDI0LjEwNjUxMzggMjkwLjQ4NDA0LDcuMzU2NzQxODcgQzE0OC4yNDcwMjcsLTkuMzkzMDMwMDggMCw3LjY5ODU3Mzk1IDAsNy42OTg1NzM5NSBaIiBpZD0iUGF0aC0xIiBza2V0Y2g6dHlwZT0iTVNTaGFwZUdyb3VwIj48L3BhdGg+CiAgICAgICAgICAgIDwvZz4KICAgICAgICA8L2c+CiAgICA8L2c+Cjwvc3ZnPg==") repeat-x; background-size: 600px; -webkit-animation: wave-animation-1 3.5s infinite linear; animation: wave-animation-1 3.5s infinite linear; } .water-2 { top: 5px; background: url("data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+Cjxzdmcgd2lkdGg9IjYwMHB4IiBoZWlnaHQ9IjYwcHgiIHZpZXdCb3g9IjAgMCA2MDAgNjAiIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgeG1sbnM6c2tldGNoPSJodHRwOi8vd3d3LmJvaGVtaWFuY29kaW5nLmNvbS9za2V0Y2gvbnMiPgogICAgPCEtLSBHZW5lcmF0b3I6IFNrZXRjaCAzLjQgKDE1NTc1KSAtIGh0dHA6Ly93d3cuYm9oZW1pYW5jb2RpbmcuY29tL3NrZXRjaCAtLT4KICAgIDx0aXRsZT53YXRlci0yPC90aXRsZT4KICAgIDxkZXNjPkNyZWF0ZWQgd2l0aCBTa2V0Y2guPC9kZXNjPgogICAgPGRlZnM+PC9kZWZzPgogICAgPGcgaWQ9IuaIkSIgc3Ryb2tlPSJub25lIiBzdHJva2Utd2lkdGg9IjEiIGZpbGw9Im5vbmUiIGZpbGwtcnVsZT0iZXZlbm9kZCIgc2tldGNoOnR5cGU9Ik1TUGFnZSI+CiAgICAgICAgPGcgaWQ9Ii0iIHNrZXRjaDp0eXBlPSJNU0FydGJvYXJkR3JvdXAiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0xMjEuMDAwMDAwLCAtMjQ2LjAwMDAwMCkiIGZpbGw9IiNGRkZGRkYiPgogICAgICAgICAgICA8ZyBpZD0id2F0ZXItMiIgc2tldGNoOnR5cGU9Ik1TTGF5ZXJHcm91cCIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMTIxLjAwMDAwMCwgMjQ2LjAwMDAwMCkiPgogICAgICAgICAgICAgICAgPHBhdGggZD0iTTAsNy42OTg1NzM5NSBMNC42NzA3MTk2MmUtMTUsNjAgTDYwMCw2MCBMNjAwLDcuMzUyMzA0NjEgQzYwMCw3LjM1MjMwNDYxIDQzMi43MjEwNTIsMjQuMTA2NTEzOCAyOTAuNDg0MDQsNy4zNTY3NDE4NyBDMTQ4LjI0NzAyNywtOS4zOTMwMzAwOCAwLDcuNjk4NTczOTUgMCw3LjY5ODU3Mzk1IFoiIGlkPSJQYXRoLTIiIHNrZXRjaDp0eXBlPSJNU1NoYXBlR3JvdXAiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDMwMC4wMDAwMDAsIDMwLjAwMDAwMCkgc2NhbGUoLTEsIDEpIHRyYW5zbGF0ZSgtMzAwLjAwMDAwMCwgLTMwLjAwMDAwMCkgIj48L3BhdGg+CiAgICAgICAgICAgIDwvZz4KICAgICAgICA8L2c+CiAgICA8L2c+Cjwvc3ZnPg==") repeat-x; background-size: 600px; -webkit-animation: wave-animation-2 6s infinite linear; animation: wave-animation-2 6s infinite linear; } .water-1, .water-2 { position: absolute; width: 100%; height: 60px; } .back-white { background: #fff; } @keyframes wave-animation-1 { 0% { background-position: 0 top; } 100% { background-position: 600px top; } } @keyframes wave-animation-2 { 0% { background-position: 0 top; } 100% { background-position: 600px top; } } .user_box { display: flex; z-index: 10000 !important; opacity: 0; /* 透明度*/ animation: love 1.5s ease-in-out; animation-fill-mode: forwards; } .userInfo_name { flex: 1; vertical-align: middle; width: 100%; margin-left: 5%; margin-top: 5%; font-size: 42rpx; } .userInfo { flex: 1; width: 100%; border-radius: 50%; overflow: hidden; max-height: 50px; max-width: 50px; margin-left: 5%; margin-top: 5%; border: 2px solid #fff; } [代码] 二、第二种效果 [图片] [代码]//index.wxml <view class="waveWrapper waveAnimation"> <view class="waveWrapperInner bgTop"> <view class="wave waveTop" style="background-image: url('https://s2.ax1x.com/2019/09/26/um8g7n.png')"></view> </view> <view class="waveWrapperInner bgMiddle"> <view class="wave waveMiddle" style="background-image: url('https://s2.ax1x.com/2019/09/26/umGZ38.png')"></view> </view> <view class="waveWrapperInner bgBottom"> <view class="wave waveBottom" style="background-image: url('https://s2.ax1x.com/2019/09/26/umGuuQ.png')"></view> </view> </view> //index.wxss .waveWrapper { overflow: hidden; position: absolute; left: 0; right: 0; height: 300px; top: 0; margin: auto; } .waveWrapperInner { position: absolute; width: 100%; overflow: hidden; height: 100%; bottom: -1px; background-image: linear-gradient(to top, #86377b 20%, #27273c 80%); } .bgTop { z-index: 15; opacity: 0.5; } .bgMiddle { z-index: 10; opacity: 0.75; } .bgBottom { z-index: 5; } .wave { position: absolute; left: 0; width: 500%; height: 100%; background-repeat: repeat no-repeat; background-position: 0 bottom; transform-origin: center bottom; } .waveTop { background-size: 50% 100px; } .waveAnimation .waveTop { animation: move-wave 3s; -webkit-animation: move-wave 3s; -webkit-animation-delay: 1s; animation-delay: 1s; } .waveMiddle { background-size: 50% 120px; } .waveAnimation .waveMiddle { animation: move_wave 10s linear infinite; } .waveBottom { background-size: 50% 100px; } .waveAnimation .waveBottom { animation: move_wave 15s linear infinite; } @keyframes move_wave { 0% { transform: translateX(0) translateZ(0) scaleY(1) } 50% { transform: translateX(-25%) translateZ(0) scaleY(0.55) } 100% { transform: translateX(-50%) translateZ(0) scaleY(1) } } [代码] 三、第三种效果 [图片] [代码]//index.wxml <view class="container"> <image class="title" src="https://ftp.bmp.ovh/imgs/2019/09/74bada9c4143786a.png"></image> <view class="content"> <view class="hd" style="transform:rotateZ({{angle}}deg);"> <image class="logo" src="https://ftp.bmp.ovh/imgs/2019/09/d31b8fcf19ee48dc.png"></image> <image class="wave" src="wave.png" mode="aspectFill"></image> <image class="wave wave-bg" src="wave.png" mode="aspectFill"></image> </view> <view class="bd" style="height: 100rpx;"> </view> </view> </view> //index.wxss image{ max-width:none; } .container { background: #7acfa6; align-items: stretch; padding: 0; height: 100%; overflow: hidden; } .content{ flex: 1; display: flex; position: relative; z-index: 10; flex-direction: column; align-items: stretch; justify-content: center; width: 100%; height: 100%; padding-bottom: 450rpx; background: -webkit-gradient(linear, left top, left bottom, from(rgba(244,244,244,0)), color-stop(0.1, #f4f4f4), to(#f4f4f4)); opacity: 0; transform: translate3d(0,100%,0); animation: rise 3s cubic-bezier(0.19, 1, 0.22, 1) .25s forwards; } @keyframes rise{ 0% {opacity: 0;transform: translate3d(0,100%,0);} 50% {opacity: 1;} 100% {opacity: 1;transform: translate3d(0,450rpx,0);} } .title{ position: absolute; top: 30rpx; left: 50%; width: 600rpx; height: 200rpx; margin-left: -300rpx; opacity: 0; animation: show 2.5s cubic-bezier(0.19, 1, 0.22, 1) .5s forwards; } @keyframes show{ 0% {opacity: 0;} 100% {opacity: .95;} } .hd { position: absolute; top: 0; left: 50%; width: 1000rpx; margin-left: -500rpx; height: 200rpx; transition: all .35s ease; } .logo { position: absolute; z-index: 2; left: 50%; bottom: 200rpx; width: 160rpx; height: 160rpx; margin-left: -80rpx; border-radius: 160rpx; animation: sway 10s ease-in-out infinite; opacity: .95; } @keyframes sway{ 0% {transform: translate3d(0,20rpx,0) rotate(-15deg); } 17% {transform: translate3d(0,0rpx,0) rotate(25deg); } 34% {transform: translate3d(0,-20rpx,0) rotate(-20deg); } 50% {transform: translate3d(0,-10rpx,0) rotate(15deg); } 67% {transform: translate3d(0,10rpx,0) rotate(-25deg); } 84% {transform: translate3d(0,15rpx,0) rotate(15deg); } 100% {transform: translate3d(0,20rpx,0) rotate(-15deg); } } .wave { position: absolute; z-index: 3; right: 0; bottom: 0; opacity: 0.725; height: 260rpx; width: 2250rpx; animation: wave 10s linear infinite; } .wave-bg { z-index: 1; animation: wave-bg 10.25s linear infinite; } @keyframes wave{ from {transform: translate3d(125rpx,0,0);} to {transform: translate3d(1125rpx,0,0);} } @keyframes wave-bg{ from {transform: translate3d(375rpx,0,0);} to {transform: translate3d(1375rpx,0,0);} } .bd { position: relative; flex: 1; display: flex; flex-direction: column; align-items: stretch; animation: bd-rise 2s cubic-bezier(0.23,1,0.32,1) .75s forwards; opacity: 0; } @keyframes bd-rise{ from {opacity: 0; transform: translate3d(0,60rpx,0); } to {opacity: 1; transform: translate3d(0,0,0); } } [代码] wave.png(可下载到本地) [图片] 在这个基础上,再加上js的代码,即可实现根据手机倾向,水波晃动的效果 wx.onAccelerometerChange(function callback) 监听加速度数据事件。 [图片] [代码]//index.js Page({ onReady: function () { var _this = this; wx.onAccelerometerChange(function (res) { var angle = -(res.x * 30).toFixed(1); if (angle > 14) { angle = 14; } else if (angle < -14) { angle = -14; } if (_this.data.angle !== angle) { _this.setData({ angle: angle }); } }); }, }); [代码] 四、第四种效果 [图片] [代码]//index.wxml <view class='page__bd'> <view class="bg-img padding-tb-xl" style="background-image:url('http://wx4.sinaimg.cn/mw690/006UdlVNgy1g2v2t1ih8jj31hc0p0qej.jpg');background-size:cover;"> <view class="cu-bar"> <view class="content text-bold text-white"> 悦拍屋 </view> </view> </view> <view class="shadow-blur"> <image src="https://raw.githubusercontent.com/weilanwl/ColorUI/master/demo/images/wave.gif" mode="scaleToFill" class="gif-black response" style="height:100rpx;margin-top:-100rpx;"></image> </view> </view> //index.wxss @import "colorui.wxss"; .gif-black { display: block; border: none; mix-blend-mode: screen; } [代码] 本效果需要引入ColorUI组件库
2019-09-26 - 小程序云开发攻略,最棘手的问题
背景 最近小程序非常的火,应公司业务发展要求,开发维护了几款小程序,公司开发的小程序都是由后端提供的接口,开发繁琐而复杂,直到小程序出现了云开发,仔细研读了文档之后,欣喜不已,于是我着手开发了本人的第一款小程序 小程序云开发教程地址 点我查看>> 分析 云开发为开发者提供完整的原生云端支持和微信服务支持,弱化后端和运维概念,无需搭建服务器,使用平台提供的 API 进行核心业务开发,即可实现快速上线和迭代,同时这一能力,同开发者已经使用的云服务相互兼容,并不互斥。 优势 无需自建服务器,数据库,无需自建存储和CDN 数据库模型很简单,就是一个json形式的对象格式 调用服务端云函数自动获取openid,再也没有繁琐的授权登陆流程了,只要进入小程序就是登陆状态,体验真的好 开发迅速,只需要前端就能搞定所有开发工作 需要解决的问题 数据库切换问题 使用过云开发的人都发现云开发切换数据库环境是最头疼的,如果手动去切换容易搞错,不小心在当前环境修改了线上数据库数据 直到官方出了这个函数问题也就迎刃而解 [代码]cloud.updateConfig({ env: ENV === 'local' ? 'dev-aqijb' : ENV }); [代码] 我使用的是服务端云开发功能,为什么要这样判断,因为在开发工具中ENV = ‘local’,所以这么判断一下,保证开发工具中使用的是测试环境数据库 使用taro多端开发框架,借助于webpack,还可以通过process.env.NODE_ENV值区分当前代码开发环境 [代码]await Taro.cloud.init({ env: `${process.env.NODE_ENV === 'development' ? 'dev-aqijb' : 'pro-hljv7'}` /* env: 'pro-hljv7' */ }); [代码] 这样可以保证开发环境和线上环境可以使用对应环境的数据库 数据库字段定义问题 因为JS是弱类型语言,不能像typescript那样静态定义变量类型,这样添加到数据库的字段数量和字段类型都无法控制 我不想用typescript,能不能实现这样的功能呢,可以用superstruct库来实现这个功能 superstruct git地址 点我查看>> 详细使用案例见下方代码 函数文件太多的问题 官方和他人教程的例子都是一个文件对应一个云函数,通过开发体验我发现这样做并不好,当项目有多个表的时候,找个函数文件真的太难了 我们可以将一个表的增删改查函数全部写入一个文件中 教程: 首先每个云函数文件中package.json引入superstruct [代码]{ "dependencies": { "wx-server-sdk": "latest", "superstruct": "latest" } } [代码] 以下代码是一个完整的云函数例子 [代码]const cloud = require('wx-server-sdk'); const { struct, superstruct } = require('superstruct'); cloud.init(); //小区信息 const Model = () => { const db = cloud.database(); const _ = db.command; const collection = db.collection('address'); return { async add(data) { try { data = struct({ name: 'string', //名字 phone: 'string', unit: 'number', //楼单元号 doorNumber: 'string', //门号 communityId: 'string', //小区id _openid: 'string' //用户的id //isDefault: 'boolean' //是否默认地址 })(data); } catch (e) { const { path, value, type } = e; const key = path[0]; if (value === undefined) { const error = new Error(`${key}_required`); error.attribute = key; throw error; } if (type === undefined) { const error = new Error(`attribute_${key}_unknown`); error.attribute = key; throw error; } const error = new Error(`${key}_invalid`); error.attribute = key; error.value = value; throw error; } let res = await this.getList({ _openid: data._openid }); if (res.data.length >= 1) { return { msg: '当前只支持保存一个地址' }; } res = await collection.add({ data, createTime: db.serverDate(), updateTime: db.serverDate() }); return res; }, async getAdressById({ _openid, _id }) { const user = await collection .where({ _openid, _id: _.eq(_id) }) .get(); return user; }, //更新指定的id 先判断手机号修改没,没修改直接就改数据,修改过判断一下库中有没有这条数据 async update(data) { //更新表的操作 }, //删除指定id的shop async remove({ _id, _openid }) { //删除表的操作 }, /** * 获取商列表 * @param {*} option {category 类别, pagenum 页码} */ async getList({ _openid }) { const shopList = await collection .where({ _openid }) .get(); return shopList; } }; }; exports.main = async (event, context) => { const { func, data } = event; const { ENV, OPENID } = cloud.getWXContext(); // 更新默认配置,将默认访问环境设为当前云函数所在环境 console.log('ENV', ENV); cloud.updateConfig({ env: ENV === 'local' ? 'dev-aqijb' : ENV }); let res = await Model()[func]({ ...data, _openid: OPENID }); return { ENV, data: res }; }; [代码] 函数使用方式 [代码]wx.cloud.callFunction({ 'address', //云函数文件名 data: { func: 'add', //云函数中定义的方法 data: {} //需要上传的数据 } }); [代码] 图片 视频等文件 直接打开云开发控制台选择存储直接上传文件,复制url地址就可以放到代码中使用了 扫码体验我的小程序: [图片]
2019-09-29 - 借助云开发数据库实现小程序列表上拉刷新功能丨云开发101
上一期101专栏中,我们介绍了如何借助云开发数据库实现小程序的列表触底自动加载功能,相对应的,小程序列表上拉刷新又该如何实现呢?本期专栏就来为大家解答。 原理说明 在小程序中,如果我们希望实现上拉刷新的功能,则需要我们监听小程序页面的 [代码]onPullDownRefresh[代码] 事件,我们可以在这个方法中实现数据的加载和替换,从而实现数据的刷新和页面的更新。 实现代码 正常情况下的上拉刷新 首先,我们先来看一看一般情况下的上拉刷新功能的实现。这里的「一般情况」是指你没有使用 [代码]scroll-view[代码] 组件的场景,或者使用的是横向的 [代码]scroll-view[代码] 的场景,列表是直接构建在页面内部的,而不是构建在 [代码]scroll-view[代码] 内部的。 在这种情况下,我们如果希望在页面上实现上拉刷新,需要在 [代码]app.json[代码] 中的 [代码]window[代码] 选项中,或者是页面的配置中加入 [代码]enablePullDownRefresh[代码],并将其配置为 [代码]true[代码]。 [代码]// app.json { ... "window":{ "enablePullDownRefresh":true } ... } // page.json { "enablePullDownRefresh":true } [代码] 配置好以后,我们就可以开始配置了。进入到我们需要实现下拉刷新的页面,在这个页面中,我们需要在 [代码]Page[代码] 的构造函数中添加 [代码]onPullDownRefresh[代码] 函数的监听。 [代码]Page({ onPullDownRefresh:function(){ // 这里我们需要进行页面的加载。 } }) [代码] 我们需要在 [代码]onPullDownRefresh[代码] 函数中,加入我们查询数据的代码,从而实现数据的更新和替换,大体上写好的代码如下: [代码]Page({ onPullDownRefresh:function(){ let db = wx.cloud.database(); // 查询数据 db.collection('records').get().then(res => { // 更新数据 this.setData({ data: res.data },()=>{ wx.stopPullDownRefresh(); // 数据同步完成后,停止掉上拉刷新的动画效果。 }) }).catch(err => { console.error(err) }) } }) [代码] 在上面这段代码中,我们在 [代码]onPullDownRefresh[代码] 方法中加入了 云开发的数据库查询方法,并在数据库查询方法的成功回调中加入了 [代码]setData[代码] 的方法来更新数据,从而确保我们的小程序的页面数据可以完成更新。 在 [代码]setData[代码] 方法的回调中,我加入了对 [代码]wx.stopPullDownRefresh[代码] 的调用,这个 API 可以停止掉小程序的 [代码]PullDownRefresh[代码] 的动效,从而避免掉数据更新完成了,但是页面还处在下拉加载的状态。 这样,我们就足以处理绝大多数场景下的上拉刷新的情况了。 特殊情况下的上拉刷新 除了直接在页面上使用上拉刷新的 API 以外,还会有另外一种场景,就是我们会将页面顶部固定,下方的部分滚动,比如腾讯视频的视频播放页面。这个页面是在上方放置一个 Video Player,下方放置一个 [代码]scroll-view[代码] 来完成滑动,从而实现顶部的 Video Player 固定的效果。在这种情况下,我们应该如何实现上拉刷新呢? [图片] 其实也很简单,我们只需要利用 [代码]scroll-view[代码] 组件的 [代码]bindscrolltoupper[代码] 事件来完成上拉刷新的功能。 在具体实现的时候,我们需要在 [代码]scroll-view[代码] 组件中加入对应的配置。 [代码]<scroll-view upper-threshold="50" bindscrolltoupper="onTopper"> <!--- 具体的内容 --> </scroll-view> [代码] 然后,在对应的页面中,加入具体的事件触发,参考代码如下: [代码]Page({ onTopper:function(){ wx.startPullDownRefresh(); let db = wx.cloud.database(); db.collection('records').get().then(res => { // 更新数据 this.setData({ data: res.data },()=>{ wx.stopPullDownRefresh(); // 数据同步完成后,停止掉上拉刷新的动画效果。 }) }).catch(err => { console.error(err) }) } }) [代码] 这样,我们就可以实现在 ScrollView 内部实现了一个上拉触底的效果。 在这段代码中,因为我们没有直接触发页面的上拉效果,所以这里我们使用 [代码]wx.startPullDownRefresh[代码] 来触发页面的上拉效果,然后再使用云开发的 API 来完成数据的加载,加载完成后,调用 [代码]wx.stopPullDownRefresh[代码] 方法,停掉上拉刷新的动画效果。 这样,即使我们没有直接触发页面的事件,但可以达到类似的页面效果。 总结 上拉刷新是一个我们很常用的功能,在使用时其实非常简单,你只需要在特定的方法中调用相关的函数,来实现数据的重载,就可以实现上拉刷新的功能。 如果你有关于使用云开发CloudBase相关的技术故事/技术实战经验想要跟大家分享,欢迎留言联系我们哦~比心! [图片]
2019-10-08