- 小程序实战中登录页面代码分享
由于视频教程(https://developers.weixin.qq.com/community/business/doc/00008c2f9d83a040de5ad3a945b80d)中仅仅是布置了编写登录页面的作业,在下一节并没有代码呈现,所以我把我编写好的代码发出来,供大家交流使用: login.json文件{ "usingComponents": {}, "navigationBarBackgroundColor": "#fff", "navigationBarTitleText": "洞主登录", "navigationBarTextStyle":"black" } login.wxml文件<!--pages/login/login.wxml--> <view class="background"> <view class="first"> <input id='account' placeholder="用户名/邮箱/手机号" placeholder-class="plas" type="text" class="inputs" bindinput="accountInput" /> </view> <view class="second"> <input id='pwd' placeholder="登录密码" placeholder-class="plas" type="password" class="inputs" bindinput=passwordInput" /> </view> <view id='btn' class="click" bindtap='signin'>注册</view> <view class="cha"> <text class="no" bindtap="regist">还没有账号?点我注册</text> </view> </view> login.wxss/* pages/login/login.wxss */ pages { left: 0rpx; right: 0rpx; background-color: white; } .first { width: 90%; height: 100rpx; margin-top: 80rpx; margin-left: 5%; margin-right: 5%; display: flex; /*Flex是Flexible Box的缩写,意为"弹性布局",用来为盒状模型提供最大的灵活性*/ flex-direction: row; align-items: center; justify-content: center; background-color: #f2f2f2; } .second { width: 90%; height: 100rpx; margin-top: 30rpx; margin-left: 5%; margin-right: 5%; display: flex; /*Flex是Flexible Box的缩写,意为"弹性布局",用来为盒状模型提供最大的灵活性*/ flex-direction: row; align-items: center; justify-content: center; background-color: #f2f2f2; } .flas { font-size: 30rpx; color: #CCCCCC; } .inputs { line-height: 100rpx; font-size: 30rpx; color: #000000; margin: auto; margin-left: 20rpx; width: 100%; } .click { width: 90%; height: 100rpx; line-height: 100rpx; margin: auto; margin-top: 80rpx; margin-left: 5%; margin-right: 5%; background-color: #F76968; text-align: center; color: white; font-size: 33rpx; } .cha { width: 90%; height: 50rpx; margin: auto; margin-top: 30rpx; margin-left: 5%; margin-right: 5%; } .no { color: black; font-size: 28rpx; margin-left: 15rpx; font-family: PingFangSC-regular; } login.js// pages/login/login.js Page({ /** * 页面的初始数据 */ data: { account:'', password:'' }, accountInput:function (e) { this.data.account = e.detail.value }, passwordInput:function (e) { this.data.password = e.detail.value }, regist:function (e) { wx.navigateTo({ url: '../enroll/enroll', }) }, signin:function (e) { var that = this if(that.data.account==''){ wx.showModal({ title:"提示", content:"请输入用户名/邮箱/手机号", showCancel:false, success(res){} }) }else if(that.data.password==''){ wx.showModal({ title:"提示", content:"请输入密码", showCancel:false, success(res){} }) }else{ console.log('success') } }, /** * 生命周期函数--监听页面加载 */ onLoad: function (options) { }, /** * 生命周期函数--监听页面初次渲染完成 */ onReady: function () { }, /** * 生命周期函数--监听页面显示 */ onShow: function () { }, /** * 生命周期函数--监听页面隐藏 */ onHide: function () { }, /** * 生命周期函数--监听页面卸载 */ onUnload: function () { }, /** * 页面相关事件处理函数--监听用户下拉动作 */ onPullDownRefresh: function () { }, /** * 页面上拉触底事件的处理函数 */ onReachBottom: function () { }, /** * 用户点击右上角分享 */ onShareAppMessage: function () { } }) 代码如上。希望我的代码能给你们带来帮助。 如果觉得对你们有帮助,可以给文章点个赞哦~ [图片]
2021-11-15 - 云开发私人实时聊天室
云开发私人实时聊天室说明 在最开始开发小程序时,本人和团队成员实现小程序的聊天室时遇到一些困难,查阅了一些资料,有些讲得太泛,有些讲的太难,在一个阶段克服了这个困难后,收获了很多,对整个流程也熟悉了很多,在这里记录自己的一个思路,希望也能对开发新手有帮助。 项目基本配置 1.项目创建及云开发配置: 官方文档:https://developers.weixin.qq.com/miniprogram/dev/wxcloud/quick-start/miniprogram.html PS:注意云函数目录是否为此样式:[图片] 若是普通目录样式记得在project.config.json中配置加入: [图片] 2.添加包colorui,用于样式使用,并在app.wxss中导入改包 [图片][图片] 3.在pages下新建文件夹index和新建page:index [图片] 聊天室静态页面 最终呈现的效果: 自己: [图片] 对方: [图片] 1. wxml 整体结构: [图片] 整一个页面说白了就是由一个scroll-view和一个回复框组成,scroll-view中由消息数组构成,消息的内容可以自己定义(时间,头像,消息内容等等) 具体源码: <!-- scroll-view来实现页面拖动 --> <scroll-view id='page' scroll-into-view="{{toView}}" upper-threshold="100" scroll-y="true" enable-back-to-top="true" class="message-list"> <!-- 每一条消息 --> <view class="cu-chat" wx:for="{{3}}" wx:key="index" id="row_{{index}}"> <!-- 自己发出的消息 --> <block wx:if="{{false}}"> <block wx:if="{{true}}"> <view class="datetime" style="width:100%">2021-11-16 18:10</view> </block> <view class="cu-item self" style="width: 750rpx; height: 120rpx; display: flex; box-sizing: border-box; left: 0rpx; top: 0rpx"> <view class="main"> <view class="content bg-green shadow" style="position: relative; left: 0rpx; top: 22rpx;border-radius: 10rpx"> <text style="font-size:33rpx">这是一条消息</text> </view> </view> <view class="cu-avatar radius center" style="background-image: url({{useravatar}}); width: 71rpx; height: 71rpx; display: flex; box-sizing: border-box; left: 0rpx; top: 0rpx" bindtap="go_myinfo"></view> </view> </block> <!-- 对方发出的消息 --> <block wx:else> <block wx:if="{{true}}"> <view class="datetime" style="width:100%">2021-11-16 19:10</view> </block> <view class="cu-item" style="width: 750rpx; height: 120rpx; display: flex; box-sizing: border-box; left: 0rpx; top: 0rpx"> <view class="cu-avatar radius center" style="background-image: url({{match_avatar}}); width: 71rpx; height: 71rpx; display: flex; box-sizing: border-box; left: 0rpx; top: 0rpx"> </view> <view class="main"> <view class="content bg-white shadow" style="position: relative; left: 0rpx; top: 22rpx;border-radius: 10rpx"> <text style="font-size:33rpx">这是对面的一条消息</text> </view> </view> </view> </block> </view> </scroll-view> <!-- 回复框 --> <view class="reply cu-bar"> <!-- 输入框 --> <view class="opration-area"> <input type="text" bindinput="getContent" value="{{textInputValue}}" maxlength="300" cursor-spacing="10" style="width: 544rpx; height: 64rpx; display: block; box-sizing: border-box;"></input> </view> <!-- 发送按钮 --> <button class="cu-btn bg-green shadow" bindtap='sendMsg' style="width: 150rpx; height: 64rpx; display: flex; box-sizing: border-box; left: -22rpx; top: 0rpx; position: relative">发送</button> </view> 2. wxss一些样式的配置,具体就不详细叙述了,见源码: /*消息窗口*/ .message-list { margin-bottom: 54px; } /*文本输入或语音录入*/ .reply .opration-area { flex: 1; padding: 8px; } /*回复文本框*/ .reply input { background: rgb(252, 252, 252); height: 36px; border: 1px solid rgb(221, 221, 221); border-radius: 6px; padding-left: 3px; } /*回复框*/ .reply { display: flex; flex-direction: row; justify-content: flex-start; align-items: center; position: fixed; bottom: 0; width: 100%; height: 108rpx; border-top: 1px solid rgb(215, 215, 215); background: rgb(245, 245, 245); } /*日期*/ .datetime { font-size: 10px; padding: 10px 0; color: #999; text-align: center; } 到此,静态的页面就已经做好啦,现在主要的难题也是数据部分,下面将先讲述数据库chatroom的设计及解释,最后进行js的代码编写。 数据库创建及设计1.数据库表创建:在编辑器打开云开发控制台,点击数据库,再点击集合名称右边加号,创建一个集合名称为chatroom的表。 [图片][图片] [图片] [图片] 2.chatroom设计具体页面如图: [图片] 其中, _id为记录创建时自动创建的标识属性,即主键 _openid和match_openid代表了自身和对方 records为一个对象数组,每个对象的属性分别是: msgText:消息属性(此案例中只有text属性,即文本,可自扩展为图片、音频等) openid:发送人的标识 sendTime:消息创建时间 sendTimeTS:消息创建时的时间戳(用于做时间比较,判断时间显示) showTime:消息是否显示时间 textContent:具体文本内容 其中, records:array类型, records中的记录:object类型 records中的sendTimeTS:number类型 records中的showTime:boolean类型 其余全为string类型 PS: 1、openid和match_openid可标识一个聊天室,是唯一不变的; 2、用户本身的openid是有可能在记录中的match_openid位置上的,谁发起了这个聊天室,openid这个位置就是那个发起用户的openid,所以在开发中,想要获取自己和所有其他人的聊天室,要查每条记录中的openid或者match_openid与自身openid是否匹配。 3.权限设置因为该表中的记录,非记录创建者也可以进行读写,这里的权限记得设置,不然会出问题: [图片] [图片] [图片] 具体功能实现(JS写法)1.先配置Page.data:6个属性,如有需要可自行扩展 [图片] chats存储数据库表中的records的所有信息; textInputValue是输入框内容。 2.绑定数据库表onChange函数: [图片] [图片] 这里的onChange输出e是这样的: [图片] type=init,获取了数据库表中该记录的所有内容,在这里将js中的chats进行赋值即可; 另外,当该记录内容变化时,type是update类型 3.wxml修改,wx-for将chats显示,以及一些判断和内容显示的设置: [图片] 到此,显示效果就有啦 [图片] 接下来,就是信息的添加了,下面将显示如何添加新信息到数据库 4.发送信息 先获取输入框内容: [图片] [图片] 发送函数: 增加一条信息,就是在records数组中加一条记录,所以在函数内部要对新纪录的属性进行一些赋值和判断等。 对showTime的处理: [图片] 消息空白处理: [图片] 对消息内的所有属性进行一个打包处理: [图片] 存储记录,并滑动页面: [图片] 最后,清空消息框内容 [图片] 发送一条消息,最终效果如图: [图片][图片] js源码const app = getApp() const db = wx.cloud.database() const _ = db.command const chatroomCollection = db.collection("chatroom") var util = require('../../utils/util.js'); Page({ data: { //这里的openid和match_openid应该是在上一级页面传进来的属性,这里由于只有聊天室所以暂时设置为一些固定值,用于测试 openid:'', match_openid:'', //这里的avatar是头像,具体传参方式自己设定,这里暂时设置为固定值,用于测试 useravatar:'', match_avatar:'', chats:[], textInputValue:'' }, onReady() { var that = this //查询openid和match_openid所标识的唯一聊天室 chatroomCollection.where({ _openid: _.or(_.eq(that.data.openid), _.eq(that.data.match_openid)), match_openid: _.or(_.eq(that.data.openid), _.eq(that.data.match_openid)) }) //绑定onChange,直观而言即表中该记录发生变动时,调用该函数 .watch({ onChange: this.onChange.bind(this), onError(err) { console.log(err) } }) }, //数据库表onchange绑定函数 onChange(e) { let that = this //type="init"的情况:初始化聊天窗口信息 if (e.type == "init") { that.initchats(e.docs[0].records) } //type="update"的情况:records中增加了一条记录 else { //在chats数组中增加该新消息 let i = that.data.chats.length const new_chats = [...that.data.chats] if (e.docs.length) new_chats.push(e.docs[0].records[i]) this.setData({ chats: new_chats }) } }, initchats(records) { this.setData({ chats: records }) //跳转到页面底部 this.goBottom() }, //获取输入文本 getContent(e) { this.data.textInputValue = e.detail.value }, sendMsg(){ let that = this //show代表了数据库表中的showTime属性,是否显示消息时间 var show = false //无记录时,true if (this.data.chats.length == 0) show = true //判断上下两条消息的时间差决定是否显示时间,这里设置了2分钟:120000毫秒,可自行修改 else { if (Date.now() - this.data.chats[this.data.chats.length - 1].sendTimeTS > 120000) show = true } const _ = db.command //消息空白处理 if (!that.data.textInputValue) { wx.showToast({ title: '不能发送空白信息', icon: 'none', }) return } //消息内容赋值 const doc = { openid: that.data.openid, msgText: "text", textContent: that.data.textInputValue, sendTime: util.formatTime(new Date()), sendTimeTS: Date.now(), showTime: show, } //添加数据库表中该记录的records数组,并跳转页面到底部 chatroomCollection.where({ _openid: _.or(_.eq(that.data.openid), _.eq(that.data.match_openid)), match_openid: _.or(_.eq(that.data.openid), _.eq(that.data.match_openid)) }) .update({ data: { records: _.push(doc) } }) .then(res => { that.goBottom() }) //消息设空 that.setData({ textInputValue: "" }) }, goBottom() { wx.createSelectorQuery().select('#page').boundingClientRect(function (rect) { if (rect) { // 使页面滚动到底部 wx.pageScrollTo({ scrollTop: rect.height + 4 }) } }).exec() }, }) 其中,util.js内容如下: const formatTime = date => { const year = date.getFullYear() const month = date.getMonth() + 1 const day = date.getDate() const hour = date.getHours() const minute = date.getMinutes() const second = date.getSeconds() return `${[year, month, day].map(formatNumber).join('/')} ${[hour, minute, second].map(formatNumber).join(':')}` } const formatNumber = n => { n = n.toString() return n[1] ? n : `0${n}` } module.exports = { formatTime } 一个简单的demo就完成了,大家有什么问题欢迎随时q我。 -----------完结撒花----------
2021-11-18 - 如何保证小程序的每个页面,在执行页面周期时,都是已登录(解决方案)
1. 实现效果: 不管用户第一个访问的页面是:首页、详情页、购物车、个人中心...任意页面,保障该页面周期onLoad、onShow、onReady运行时,都是处于已登录(登录态)。 2. 遇到的问题: 由于js是异步执行,直接把登录写在onLaunch,在执行页面onLoad时,可能会因为登录接口未返回,页面onLoad拿不到登录信息,导致异常。 要么每个页面都需要加登录判断,维护难度很大。 3. 解决思路 挟持Page并使用发布订阅模式,可保障任意页面执行onLoad、onShow时,自动执行:先判断当前是否已登录,未登录先订阅,已登录则执行onLoad。 4. 代码实现 // app.js // 引入login-sdk(几十行代码),并在登录后触发登录事件,即可实现所有页面登录。 import { publisher } from "./utils/login-sdk"; App({ onLaunch() { this.toLogin(); }, async toLogin() { // 模拟openid静默登录 let { code } = await wx.login(); setTimeout(() => { publisher.emit("login"); }, 50); }, }); 5. 代码片段 https://developers.weixin.qq.com/s/bBkO2Umv7Avd 6. 挟持Page稳定性? 目前我们应用在电商小程序里,已有2年,服务累计3000万用户,亲测没遇到什么问题。
2021-12-29 - 小程序app.onLaunch与page.onLoad异步问题的最佳实践
场景: 在小程序中大家应该都有这样的场景,在onLaunch里用wx.login静默登录拿到code,再用code去发送请求获取token、用户信息等,整个过程都是异步的,然后我们在业务页面里onLoad去用的时候异步请求还没回来,导致没拿到想要的数据,以往要么监听是否拿到,要么自己封装一套回调,总之都挺麻烦,每个页面都要写一堆无关当前页面的逻辑。 直接上终极解决方案,公司内部已接入两年很稳定: 1.可完美解决异步问题 2.不污染原生生命周期,与onLoad等钩子共存 3.使用方便 4.可灵活定制异步钩子 5.采用监听模式实现,接入无需修改以前相关逻辑 6.支持各种小程序和vue架构 。。。 //为了简洁明了的展示使用场景,以下有部分是伪代码,请勿直接粘贴使用,具体使用代码看Github文档 //app.js //globalData提出来声明 let globalData = { // 是否已拿到token token: '', // 用户信息 userInfo: { userId: '', head: '' } } //注册自定义钩子 import CustomHook from 'spa-custom-hooks'; CustomHook.install({ 'Login':{ name:'Login', watchKey: 'token', onUpdate(token){ //有token则触发此钩子 return !!token; } }, 'User':{ name:'User', watchKey: 'userInfo', onUpdate(user){ //获取到userinfo里的userId则触发此钩子 return !!user.userId; } } }, globalData) // 正常走初始化逻辑 App({ globalData, onLaunch() { //发起异步登录拿token login((token)=>{ this.globalData.token = token //使用token拿用户信息 getUser((user)=>{ this.globalData.user = user }) }) } }) //关键点来了 //Page.js,业务页面使用 Page({ onLoadLogin() { //拿到token啦,可以使用token发起请求了 const token = getApp().globalData.token }, onLoadUser() { //拿到用户信息啦 const userInfo = getApp().globalData.userInfo }, onReadyUser() { //页面初次渲染完毕 && 拿到用户信息,可以把头像渲染在canvas上面啦 const userInfo = getApp().globalData.userInfo // 获取canvas上下文 const ctx = getCanvasContext2d() ctx.drawImage(userInfo.head,0,0,100,100) }, onShowUser() { //页面每次显示 && 拿到用户信息,我要在页面每次显示的时候根据userInfo走不同的逻辑 const userInfo = getApp().globalData.userInfo switch(userInfo.sex){ case 0: // 走女生逻辑 break case 1: // 走男生逻辑 break } } }) 具体文档和Demo见↓ Github:https://github.com/1977474741/spa-custom-hooks 祝大家用的愉快,记得star哦
2023-04-23 - 使用微搭自定义组件能力实现「搜索」组件
作者:布道师 韩锴 微搭作为国内第一个微信小程序原生的 Serverless 低代码开发平台,亮点之一就是拖拽化开发,所见即所得,我们只需要拖选组件就可以快速的完成页面的搭建。但在实际的开发过程中,不少开发者担心官方组件库不够丰富限制了应用的开发。 作为一个在飞速发展的平台,微搭考虑到除了平台提供的组件,用户可能还有扩展自己组件的需求,因此微搭上线了「自定义组件」能力,目前支持低码组件和源码组件的引入。 其中,低码组件可以应用官方的低码组件组装出适合自己的业务组件,源码组件支持代码上传这样就不限制开发人员自由发挥了。 本文就利用低码组件来定义一个搜索的组件。总体的步骤为 创建自定义组件库 - 创建自定义组件 - 在应用中使用 这三个步骤。 一、创建自定义组件库登录低码控制台,找到组件库管理菜单,点击【新建组件库】按钮,输入组件库的名称和标识。 [图片][图片][图片] 二、创建组件点击组件库的名称进入到自定义组件页面,点击【添加组件】按钮: [图片] 我们需要定义组件的名称、标识、分类,上传组件的图标,点击【确定】按钮[图片][图片] 组件创建好后后续的操作需要说明一下,如果点击组件的图标可以修改刚才录入的信息,如果点击编辑按钮就进入到了设计界面: [图片] 三、设计组件我们点击编辑按钮,进入到组件设计界面: [图片] 先看一下本次实战要设计的组件的最终效果: [图片] 目标是实现一个常用的「搜索组件」,可以输入关键词,可以点击搜索按钮。其实低码组件和我们在应用里搭建的思路是一样的,也是先放置容器,然后放置文本输入组件和按钮组件,下边我们一步步的实现一下。 首先增加一个容器组件: [图片] 然后,切换到样式页签,设置一下组件的高度、边框和布局: [图片][图片][图片] 接着,在容器里放置一个文本输入组件,把标题清除就可以: [图片] 再增加一个按钮组件,我们需要依次设置一下按钮的标题、大小、显示效果: [图片] 按钮还有一个默认的边框的颜色,这里将边框设置成白色,并设置按钮的布局、外边距和高度; [图片][图片][图片] 这样组件的效果就做好了,一个自定义组件光有显示效果还不行,还需要可以绑定数据和对外暴露响应的事件,我们切换到组件编辑页签: [图片] 先设置组件可以绑定哪些数据,点击编辑数据属性按钮,我们接收一个文本信息,字段名称定义为text,给一个默认值,然后点击>>按钮,编辑器会自动生成代码: [图片] 然后修改一下title,这里修改为中文: [图片] 设置完点击确定就可以,接着切换到事件属性页签,点击事件属性按钮: [图片] 输入事件ID和事件名称点击确定按钮即可: [图片] 这样数据和事件都定义好了。 四、使用自定义组件组件定义好后,点击发布: [图片] 然后,在应用管理就可以看到自己定义的组件了,是不是很方便呢?赶紧试试吧: [图片] 产品介绍腾讯云微搭低代码是高效、高性能的拖拽式低代码开发平台,向上连接前端的行业业务,向下连接云计算的海量能力,助力企业垂直上云。腾讯云微搭低代码将繁琐的底层架构和基础设施抽象化为图形界面,通过行业化模板、拖放式组件和可视化配置快速构建多端应用(小程序、H5 应用、Web 应用等),免去了代码编写工作,让您能够完全专注于业务场景。腾讯云微搭低代码以云开发作为底层支撑,云原生能力将应用搭建的全链路打通,提供高度开放的开发环境,且时刻为您的应用保驾护航。 开通低代码:https://cloud.tencent.com/product/lowcode 产品文档:https://cloud.tencent.com/document/product/1301/48874 技术交流群、最新资讯关注微信公众号【腾讯云低代码】
2021-06-10 - 微信小程序开发-将数据写入全局数据
微信小程序的全局数据写在 [代码]app.js[代码]中,需要现在里面声明存储数据的变量如下: [代码]//app.js this.globalData = {} this.userInfo = {}//这个是我们。等下要用到的变量// this.userMessage = [] [代码] 然后在需要使用以上声明的全局变量的时候声明一下: [代码]const app = getApp()//这个声明是为了后面调用的方便 [代码] 然后使用[代码]Object.assign()[代码]将数据拷贝到全局变量中。 注:[代码]Object.assign()[代码]用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。它将返回目标对象。 [代码]Object.assign( app.userInfo , res.data ); [代码] 此时res.data中的数据已经传输到全局变量中。在其他页面使用使用[代码]getApp()[代码]即可调用。
2021-11-16 - 用户引导组件开发(1)理下思路
个人开发者一枚。。之前为了偷懒,做的小程序都没有好好地做用户引导,导致新用户上手的不多。 所以这次下决心好好地做一个用户引导。以下是最终效果(当然,我目前还没上新版本,所以你就算找到这个小程序也看不到这个效果): [图片] 初步设想 做之前,先说说我想做成怎样的。 1、要井水不犯河水。我的几个小程序功能都已经做全了,我不想再为了这个用户引导功能去大改里面的逻辑,包括页面啥的。所以用户引导的功能实现最好能独立模块,与原来的代码尽量低耦合。 2、要能复用。用户引导可能会在各个小程序的不同页面中出现,所以必须能够复用。 因为这两个原因,做成组件是再好不过的了。把各个用户引导界面所需要展示的元素用配置数组的方式传给组件,再由组件去呈现出来。 配置数组 初步设计中,配置数组应该包括如下内容: 1、突出元素。即在遮罩层中需要突出展示的页面元素,如上面示意图中的按钮。配置数组应传入元素ID。 2、说明文字,为了美观,说明文字最好用气泡框。配置数组要传入文字内容,可能要允许多段文字,然后可以配置相关的位置大小样式。 3、说明图片。本人美工不行,但是如果是专业的团队应该会想用更生动的说明图片来替代说明文字。如以下这种的: [图片] 配置数组必须传入图片路径,且应该允许多张图片,并且可以自定义图片的位置大小等。 相关事件 遮罩层的相关事件其实很简单,就是点击。一点就没了。所以我们必须把这个事件再反馈出来。 大致想法就是这些,我们开工吧。
2020-05-10 - 小程序初级指南--图片及其优化
图片格式 开发中常见的图片格式有 GIF、PNG8、PNG24、JPEG、WEBP。 我们需要根据图片格式的特性和场景需要选取适合的图片格式,而不是设计给什么用什么。 PNG PNG 的目的是替代GIF和TIFF文件格式,同时增加一些GIF文件格式所不具备的特性。流式网络图形格式(Portable Network Graphic Format,PNG)名称来源于非官方的“PNG’s Not GIF”,是一种位图文件(bitmap file)存储格式,读成“ping”。PNG用来存储灰度图像时,灰度图像的深度可多到16位,存储彩色图像时,彩色图像的深度可多到48位,并且还可存储多到16位的α通道数据。PNG使用从LZ77派生的无损数据压缩算法。 特性 支持256色调色板技术,文件体积小。无损压缩最高支持48位真彩色图像以及16位灰度图像。支持Alpha通道的透明/半透明特性。支持图像亮度的Gamma校准信息。支持存储附加文本信息,以保留图像名称、作者、版权、创作时间、注释等信息。渐近显示和流式读写,适合在网络传输中快速显示预览效果后再展示全貌。使用CRC防止文件出错。最新的PNG标准允许在一个文件内存储多幅图像。 更多 PNG官方站 - PNG General Information PNG格式 维基百科 - PNG JPEG JPEG是一种针对照片视频而广泛使用的一种有损压缩标准方法.特性 适用于储存24位元全采影像采取的压缩方式通常为有损压缩不支持透明或动画压缩比越高影像耗损越大,失真越严重压缩比在10左右肉眼无法辨出压缩图与原图的差别更多 维基百科 - JPEG WEBP WebP,是一种同时提供了有损压缩与无损压缩的图片文件格式,WebP支持无损压缩和透明色的功能。特性 同时提供有损压缩和无损压缩两种图片文件格式文件体积小,无损压缩后,比 PNG 文件少了 45% 的文件大小;有损压缩后,比 JPEG 文件少了 25% - 34% 文件大小浏览器兼容差,目前只支持客户端 Chrome 和 Opera 浏览器以及安卓原生浏览器(Andriod 4.0+),WebP兼容性更多 更多关于WebP: 维基百科 - WEBP WEBP探寻之路 GIF GIF图象是基于颜色列表的(存储的数据是该点的颜色对应于颜色列表的索引值),最多只支持8位(256色)。GIF文件内部分成许多存储块,用来存储多幅图象或者是决定图象表现行为的控制块,用以实现动画和交互式应用。特性 优秀的压缩算法使其在一定程度上保证图像质量的同时将体积变得很小。可插入多帧,从而实现动画效果。可设置透明色以产生对象浮现于背景之上的效果。由于采用了8位压缩,最多只能处理256种颜色,故不宜应用于真彩色图片更多 维基百科 - GIF GIF文档 团队约定 内容图 内容图多以商品图等照片类图片形式存在,颜色较为丰富,文件体积较大。优先考虑 JPEG 格式,条件允许的话优先考虑 WebP 格式尽量不使用PNG格式,PNG8 色位太低,PNG24 压缩率低,文件体积大 背景图 背景图多为图标等颜色比较简单、文件体积不大、起修饰作用的图片。PNG 与 GIF 格式,优先考虑使用 PNG 格式,PNG格式允许更多的颜色并提供更好的压缩率图像颜色比较简单的,如纯色块线条图标,优先考虑使用 PNG8 格式,避免不使用 JPEG 格式图像颜色丰富而且图片文件不太大的(40KB 以下)或有半透明效果的优先考虑 PNG24 格式图像颜色丰富而且文件比较大的(40KB - 200KB)优先考虑 JPEG 格式条件允许的,优先考虑 WebP 代替 PNG 和 JPEG 格式 优化 图片是页面显示中很重要的部分,图片加载关系到用户体验、应用性能常见处理方式 减少文件体积大小 上线的图片都应该经过压缩处理,压缩后的图片不应该出现肉眼可感知的失真区域。压缩优化图片大小 采用合适的图片格式 减少图片资源请求数 合成雪碧图使用建议 适合使用频率高更新频率低的小图标尽量不留太多的空白体积较大的图片不合并确保要合并的小图坐标数值和合并后的 Sprites 图尺寸均为偶数预加载 图片预加载可以提高用户体验,对于图片长列表和图片占比很大的背景图尤其重要。 css 预加载 利用css的background属性可以预先加载图片。加载后隐藏。在其他地方在请求一样的地址时会优先去加载缓存内的图片进行显示,达到一个预加载的效果。不好的地方就是会影响影响页面渲染速度 显性预加载 显性预加载指的则是处于预加载过程时页面有明确的加载提示,比如进度条或者是Loading图标,让用户有个心理预期,减少等待的烦躁感。 隐形预加载(基于用户行为的资源预加载 通过触屏页面进度加载对应的资源。常见tabs切换,通常的处理是当用户去点击选项卡按钮的时候,在对应面板呈现的时候,我们再去加载图片内容。于是,就存在这样一个不好的体验——由于内容呈现瞬时,而图片呈现是异步的,就很容易出现选项卡主体内容切换过来了,结果是个空白,过了会儿图片才出现。 预加载组件 先加载一张缩略图,该缩略图通过样式设置为和原图一样的宽高,这样用户首先能很快速地看到一张模糊的图片,此时再去对原图做预加载,加载完成之后对缩略图进行替换,因为此时图片已经下载过了,所以界面上能无缝地切换为原图显示 链接:https://aotu.io/notes/2017/01/06/wxapp-img-loader/index.html 懒加载 指的是图片在页面渲染的时候先不加载,页面渲染完成后在指定动作触发后再加载图片。这种方式通常比较合适于篇幅较长的页面,并且图片内容的重要性低于页面信息内容,能够快速地先将重要的页面信息呈现给用户。 lazy-load image 自带属性。 图片懒加载,在即将进入一定范围(上下三屏)时才开始加载。lazy-loadbooleanfalse图片懒加载,在即将进入一定范围(上下三屏)时才开始加载 官方推荐优化方式--关于图片资源的优化 目前图片资源的主要性能问题在于大图片和长列表图片上,这两种情况都有可能导致 iOS 客户端内存占用上升,从而触发系统回收小程序页面。建议开发者尽量减少使用大图片资源 控制代码包内的图片资源 小程序代码包经过编译后,会放在微信的 CDN 上供用户下载,CDN 开启了 GZIP 压缩,所以用户下载的是压缩后的 GZIP 包,其大小比代码包原体积会更小。 但我们分析数据发现,不同小程序之间的代码包压缩比差异也挺大的,部分可以达到 30%,而部分只有 80%,而造成这部分差异的一个原因,就是图片资源的使用。GZIP 对基于文本资源的压缩效果最好,在压缩较大文件时往往可高达 70%-80% 的压缩率,而如果对已经压缩的资源(例如大多数的图片格式)则效果甚微。 写在最后 凡事都是实践出真知。围绕着业务,切合实际的进行优化处理。 不要为了优化而优化。 参考链接: https://guide.aotu.io/index.html https://aotu.io/notes/2017/01/06/wxapp-img-loader/index.html https://developers.weixin.qq.com/miniprogram/dev/framework/
2019-12-30 - 开发了一个完整的小程序,用的是云开发。涉及的技术比较多,就不一一写了。看看有没有你需要的技术,给我留言,我会一一解答。
一,云开发端: 1,图片和文字的内容安全检测。 2,将数据导出Excel表, 3,定时触发器(定时器), 4,订阅消息。结合定时触发器,可实现定时推送。 5,生成数据表在云端的好处 6,生成带参数小程序码 7,模糊查询 8,数据实时更新 二,小程序端: 1,用的是原生开发 2,自定义导航栏,且可监听点击右上键箭头,可配置,可弹窗。 3,分享生成的canvas图片 4,统一报错处理, 5,请求封装,状态统一处理。 6,过期头像检测 7,小程序端没啥写的,自己去看页面吧。 三,其它: 1,接入广告流程 四:小程序体验(带有参数的小程序码 + canvas绘图) [图片]
2020-05-08 - 微信小程序答题页——swiper渲染优化及swiper分页实现
前言 swiper的加载太多问题,网上资料好像没有一个特别明确的,就拿这个答题页,来讲讲我的解决方案 这里实现了如下功能和细节: 保证swiper-item的数量固定,加载大量数据时,大大优化渲染效率记录上次的位置,页面初次加载不一定非得是第一页,可以是任何页答题卡选择某一index回来以后的数据替换,并去掉swiper切换动画,提升交互体验示例动图 [图片] 截图 [图片] [图片] 问题原因 当swiper-item数量很多的时候,会出现性能问题 我实现了一个答题小程序,在一次性加载100个swipe-item的时候,低端手机页面渲染时间达到了2000多ms 也就是说在进入答题页的时候,会卡顿2秒多去加载这100个swiper-item 思考问题 那我们能不能让他先加载一部分,然后滑动以后再去改变item的数据,让swiper一直保持一定量的swiper-item? 注意到官方文档有这么两个属性可以利用,我们可以开启衔接滑动,然后再bindchange方法中去修改data [图片] 1、保证swiper-item的数量固定,加载大量数据时,优化渲染效率 假设我们请求到的数据的为list,实际渲染的数据为swiperList 我们现在给他就固定3个swiper-item,前后滑动的时候去替换数据 正向滑动的时候去替换滑动后的下一页数据,反向滑动的时候去替换滑动后的上一页数据 当我们知道了要替换的条件,我们便可以去替换数据了 但是我们应该考虑到临界值的问题,如果当前页是list第一项和最后一项该怎么办,向左向右滑是不是得禁止啊 这边是判断没数据会让它再弹回去 2、记录上次的位置,页面初次加载不一定非得是第一页,可以是任何页 有很多时候,我们是从某一项直接进来的,比如说上次答题答到了第五题,我这次进来要直接做第六题 那么我们需要去初始化这个swiperList,让它当前页、上一页、下一页都有数据 3、答题卡选择某一index回来以后的数据替换,并去掉swiper切换动画,提升交互体验 从答题卡选择index,那就不仅仅是滑动上下页了,它可以跳转到任何页,所以也采用类似初始化swiperList的方法 swiper切换动画我这边是默认250ms,但是发现有时候从答题卡点击回来,你在答题卡点击的下一项不知道会从左还是从右滑过来 体验真的很差,一开始不知道怎么禁掉动画,其实在跳转到答题卡页的时候把duration设为0就可以了 然后在答题卡页的unload方法中恢复 关键点: 在固定3个swiper-item的同时,要保证我们可以有办法来替代微信自带swiper的current属性和change方法 swiper-limited-load使用方法及说明: 将components中的swiper-limited-load复制到您的项目中在需要的页面引用此组件,并且创建自己的自定义组件item-view在初始化数据时,为你的list的每一项指定index属性具体可以参照项目目录start-swiper-limited-load中的用法说明:其它属性和swiper无异,你们可以自己单独添加你们需要的属性总结 一开始很头疼,为什么微信小程序提供的这个swiper,没去考虑这方面 然后在网上和社区找也没有一个特别好的解决方案。 后来想想,遇到需求就静下来解决吧。 项目地址:https://github.com/pengboboer/swiper-limited-load 如果错误,欢迎指出。 如有新的需求也可以提出来,如果有时间的话,我会帮你们完善。 如果能帮到你们,记得给一个star,谢谢。 ---补充 有很多朋友在评论区提到了分页的需求,抽时间写了一个分页的Demo和大家分享一下。 还是以答题为例,比如我们一共有500条数据,一页20条,可能需要如下功能,乍一看不就加了个分页,挺简单的,其实实现起来挺麻烦的,下面说一下思路和一些需要特别注意的点: 1、从其他页面跳转到答题页时,不光只能默认在第一题,可以是任意一题,比如第80题。 跳转到任意一题,那么需要我们根据index算出该数据在第几页,然后需要请求该页数据,最后显示对应的index。我的思路更注重用户体验,不可能是上滑或者下滑才开始去请求数据,一定是要用户滑动前提前请求好数据。所以起码要保证左右两侧在初始化那一刻都有数据。如果此题和它的上一题下一题都在同一页,那么我们只需要请求一页数据(第15题,那么只需请求第1页数据)。如果此题和它的上一题或者下一题不在同一页,那么我们可能需要请求两页数据。(第20题,那么需要请求第1页和第2页数据) 2、左滑、右滑没数据时,都可以加载新数据。直到滑到第一题或者最后一题。 如果我们初始化时是第24题,那么我们左滑到第21题时,就应该去请求第一页的数据。那么用户在看完21题时,再滑到20题,可能就根本不会感知到通过网络请求了数据。但是如果用户此刻滑动特别快:滑到21题时请求了网络,请求还没成功,就又向左滑了。那么我们需要限制用户的滑动,给用户一个提示:数据正在加载中。 3、从答题卡点击任意一题可以跳转到相应的题目,并且左右滑动显示正常数据 比如我们初始化是跳转到了第80题,不一会点击答题卡又要跳转到200题,一会又跳转到150题。各种无序操作,你也不知道用户要往哪里点。 一开始是想着维护一个主list,点到哪道题往list中添加这道题所在的当页的数据,但是还得判断这一页或者左滑右滑请求新一页的数据得往list的哪个位置添加。这来回来去乱七八糟的判断就很麻烦了,很容易出bug。而且list长度太长了以后insert的性能也不好。 后来就去想,要不答题卡点击任意一题都清空旧的list,然后请求新的数据,左右滑动没数据了再请求新的数据呗。但是这样很浪费资源,并且用户体验也不好,用户已经从第1题答到第200题了,这时用户从答题卡选择了一个25题,还得重新请求网络。而且200道题的数据都没了,那再选个26题,再重新请求网络?网络有延时不说,还浪费资源。 最后转念一想,这时候就需要弄一个缓存了。所以最终的解决方法就出来了:我们维护一个map,在网络请求成功后,在map中保存对应页的数据,同时我们维护一个主list来显示对应的题目。当我们在答题卡选择某一题目,就清空list,然后判断map中有没有该页的数据,如果有就直接拿来,没有就再去网络请求。这个处理方式,写法相对来说简单,不需要乱七八糟的判断,也不浪费资源,用户体验也很不错。 总结 以上就是一些思路和要注意的地方。这个Demo断断续续花了好几天时间写出来的。可能我说的比较啰嗦比较细,只是想让需要用到这个分页Demo的同学能理解我是如何实现的。 如果觉得能帮到你,记得给一个star,谢谢。同时如果这个demo有bug或者你们有新想法,欢迎提出来。
2021-01-07 - 小程序悬浮按钮,悬浮导航球
[图片] 一个开源的悬浮按钮组件,小程序原生支持。 一直很喜欢华为的导航按钮,能够完美适合大屏手机,自由停放位置,不论是左手习惯还是右手习惯,都很方便(可能我手比较小,左右上角够不着)。 支持功能 支持自由拖动,停放 支持自定义事件(单击,双击,长按) 支持自定义导航球中间的文字/图片 开发难点 使用wxs 悬浮球的开发思路比较简单,一个view,样式[代码]position:fixed[代码],支持拖动。在web开发中,我们能够比较容易实现这样的功能。要想在小程序中实现高性能的交互动画(touch类),一定要了解如何使用页面的[代码]wxs[代码]这个残疾JS来操作对象(调试很麻烦,js极度残疾) [代码]<wxs module="tool"> function tStart(e, ins){} function tMove(e, ins){ e.instance.setStyle('transform: translate3d(...)') // e.instance指向当前操作对象 // setStyle 设置该对象的style样式 } function tEnd(e, ins){} module.exports = { tStart: tStart, tMove: tMove, tEnd: tEnd } </wxs> <view catch:touchstart="{{tool.tStart}}" catch:touchmove="{{tool.tMove}}" ... /> [代码] 这里使用catch,而不是使用bind来绑定事件,事件指向[代码]wxs[代码]的方法。考虑到悬浮导航球是作为工具在其他场景中使用,为了不会污染touch事件,或者导致页面不必要的滚动。 位移距离 手机宽高不一致,即x轴的运动距离小于y轴运动距离(单位时间),假定手机宽高比为1:2,x轴运动1px,y轴则运动了2px,我们可以设置一定的系数,使得拖动效果符合预期。 监听事件 最终的事件响应一定是在page页面(或者组件内部)实现事件监听,wxs有一套事件调用机制 [代码]function tStart(e, ins){ ins.callMethod('onTouchStart', e) // 调用当前组件/页面在逻辑层(App Service)定义的函数。funcName表示函数名称,args表示函数的参数 } [代码] wxs相关文档 GITHUB开源 DEMO及文档关注小程序 [图片]
2020-03-06 - 云开发,从0到1实现小程序内微信支付功能
首先很感谢云开发提供的生态以及示例代码,安耐不住内心的激动,在这一刻实现了小程序内微信支付,在3年前做过基于公众号的微信支付以及企业红包当时玩的很溜,动不动就给群里的小伙伴定向推送红包 本次微信支付参考以下官方开源项目 https://github.com/TencentCloudBase/mp-book https://github.com/TencentCloudBase/tcb-demo-basic 具体交互截图如下所示: [图片] [图片] [图片] [图片] 我总结以下几点把,以下3点不全,但是对于一个有云开发经验的同学,这足够了。 1、在微信企业支付后台 进行相应的设置 [图片] 2、在上面源代码的基础上,配置,appi d,商户 号,安全 密钥以及api证书 module.exports = { ENV: 'xxx', // TCB环境ID MCHID: 'xxx',//商户id KEY: '0123456789abcdefghijklmnopqrstuv', CERT_FILE_CONTENT: fs.existsSync(CERT_PATH) ? fs.readFileSync(CERT_PATH) : null, TIMEOUT: 10000 // 毫秒 }; 3、云开发数据库新增goods、orders两个集合,并赋予所有可读写权限,这一点很重要。 开发过程中遇到的问题: 1、云函数执行失败 这是由于在之前没有在数据库里面创建goods集合和orders集合 2、签名错误 这是在正确配置之后还报这个错误,这个时候不要慌,首先核对自己的配置有没有问题,在确保配置没问题的前提下,相信自己,重新运行下就好了。 [图片] 备注:想了解更多关于微信支付的逻辑流转,请别走开,继续阅读,下面是腾讯云课堂,有视频有文档,不容错过。 附几个微信支付实现的文档 https://cloud.tencent.com/developer/team/tcb/courses https://cloud.tencent.com/edu/learning/course-1276-4318 https://cloud.tencent.com/edu/learning/learn-1276-3815
2020-03-02 - 极简代码之云开发的触底无限加载
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 - 由云函数调用第三方服务失败想到的:请问开发文档看都能看懂但是不会用,究竟应该怎么用才对?
问题重现:https://www.bilibili.com/video/av60294530?p=28 在官方提供的视频教程中[图片]原样照抄的过程中遇到了 Uncaught (in promise) Error: errCode: -404011 cloud function execution error | errMsg: cloud.callFunction:fail requestID 558df6e2-5165-11ea-923c-525400090c2c, cloud function service error code -504002, error message Unexpected token *; at cloud.callFunction api; 因为是刚开始学习微信开发,我先以为是我哪敲错了,后来以为是不是API变动了又阅读官方开发文档无果。然后开始百度,搜不到有用信息。后来在google下 找到了这篇文章,讲到了 https://blog.csdn.net/moqianmoqian/article/details/104359200 我只是知道把GOT包从10.6改到9.6解决问题,对于为什么一概不知。整个过程花了我1个半小时。如果官方要提供教学课程希望能适时更新,不介意收费课程,只要能真的有帮到,不是这种很打击初学者积极性。 一点题外话在学校教学的时候就是learnByDoing 项目驱动式,这就造成了代码的重复拼接,意面代码,我很想有创意但是不知道怎么做。看文档说看懂吧又没看懂
2020-02-21 - 版本管理的基本使用 git基本能力详解
前言: 看完此文后, 可以在 github上新建一个 仓库,之后新建一个小程序项目,上传到github的仓库. 一.git基本功能 讲解 拉取: 获得服务器 指定分支代码 到本地的 head分支(当前分支) 抓取:获取服务器分支的 最新修改,不会合并入本地 推送:将本地分支 推送到服务器指定分支 分支: 基于某个本地分支 创建 新分支 合并:将本地的两个分支 进行合并 用于代码提交 就是讲 某个分支 合并到 当前分支(head标识的分支) 分支理解:每个人开发 的 都是自己的 分支,可以是远程的分支,也可以是本地的分支.本地开发完 合并到 主分支就行了. 二.现有项目代码上传到git. 1.打开现有项目. 2.点击版本管理 ->设置->远程->添加 添加你的git地址. 3.抓取->抓取全部 4.点击远程分支-> 查看嵌入记录 ->右键 从提交 新建分支.分支名称你随意 如果 没有 记录 就可以自己新建一个 分支. 5.之后你就可以推送了. [图片] [图片] 三.推荐使用分支的 标准 每个项目做一个远程dev分支,用于主开发分支.代码同步 开发人员获取 远程dev分支,新建本地dev分支. 基于本地dev分支 新建 本地 开发者 分支. 代码获取: 在本地dev分支 获取远程dev分支的变更,之后 merge 本地dev到 本地开发人员的 本地分支上. 代码嵌入,合并:在本地开发人员分支 变更后,嵌入本地开发人员分支. 之后 切换到本地dev分支,在本地dev分支上 拉取远程dev的最新分支代码. 5.1 将开发人员分支 合并到 本地dev分支,之后推送 到远程dev分支,达到代码嵌入的目的. 四.常见问题. [图片] 这个是你 推送之前 没commit导致的. [图片] 2.这个是 你本地 没有 此分支的 head,你在你想要拉取的 远程分支 的 最新嵌入记录上 右键,获取head,就可以了,相当于重置了 head.重置head方法如下: [图片] 3.拉取远程分支可以 点拉取按钮,也可以如下图. [图片] 4.git 仓库的验证方式 还需要你们自己去选择,因为 仓库的 验证 都是 仓库那边决定的. 在 设置->网络和认证 中 设置. [图片]
2019-04-29 - 如何实现快速生成朋友圈海报分享图
由于我们无法将小程序直接分享到朋友圈,但分享到朋友圈的需求又很多,业界目前的做法是利用小程序的 Canvas 功能生成一张带有小程序码的图片,然后引导用户下载图片到本地后再分享到朋友圈。相信大家在绘制分享图中应该踩到 Canvas 的各种(坑)彩dan了吧~ 这里首先推荐一个开源的组件:painter(通过该组件目前我们已经成功在支付宝小程序上也应用上了分享图功能) 咱们不多说,直接上手就是干。 [图片] 首先我们新增一个自定义组件,在该组件的json中引入painter [代码]{ "component": true, "usingComponents": { "painter": "/painter/painter" } } [代码] 然后组件的WXML (代码片段在最后) [代码]// 将该组件定位在屏幕之外,用户查看不到。 <painter style="position: absolute; top: -9999rpx;" palette="{{imgDraw}}" bind:imgOK="onImgOK" /> [代码] 重点来了 JS (代码片段在最后) [代码]Component({ properties: { // 是否开始绘图 isCanDraw: { type: Boolean, value: false, observer(newVal) { newVal && this.handleStartDrawImg() } }, // 用户头像昵称信息 userInfo: { type: Object, value: { avatarUrl: '', nickName: '' } } }, data: { imgDraw: {}, // 绘制图片的大对象 sharePath: '' // 生成的分享图 }, methods: { handleStartDrawImg() { wx.showLoading({ title: '生成中' }) this.setData({ imgDraw: { width: '750rpx', height: '1334rpx', background: 'https://qiniu-image.qtshe.com/20190506share-bg.png', views: [ { type: 'image', url: 'https://qiniu-image.qtshe.com/1560248372315_467.jpg', css: { top: '32rpx', left: '30rpx', right: '32rpx', width: '688rpx', height: '420rpx', borderRadius: '16rpx' }, }, { type: 'image', url: this.data.userInfo.avatarUrl || 'https://qiniu-image.qtshe.com/default-avatar20170707.png', css: { top: '404rpx', left: '328rpx', width: '96rpx', height: '96rpx', borderWidth: '6rpx', borderColor: '#FFF', borderRadius: '96rpx' } }, { type: 'text', text: this.data.userInfo.nickName || '青团子', css: { top: '532rpx', fontSize: '28rpx', left: '375rpx', align: 'center', color: '#3c3c3c' } }, { type: 'text', text: `邀请您参与助力活动`, css: { top: '576rpx', left: '375rpx', align: 'center', fontSize: '28rpx', color: '#3c3c3c' } }, { type: 'text', text: `宇宙最萌蓝牙耳机测评员`, css: { top: '644rpx', left: '375rpx', maxLines: 1, align: 'center', fontWeight: 'bold', fontSize: '44rpx', color: '#3c3c3c' } }, { type: 'image', url: 'https://qiniu-image.qtshe.com/20190605index.jpg', css: { top: '834rpx', left: '470rpx', width: '200rpx', height: '200rpx' } } ] } }) }, onImgErr(e) { wx.hideLoading() wx.showToast({ title: '生成分享图失败,请刷新页面重试' }) //通知外部绘制完成,重置isCanDraw为false this.triggerEvent('initData') }, onImgOK(e) { wx.hideLoading() // 展示分享图 wx.showShareImageMenu({ path: e.detail.path, fail: err => { console.log(err) } }) //通知外部绘制完成,重置isCanDraw为false this.triggerEvent('initData') } } }) [代码] 那么我们该如何引用呢? 首先json里引用我们封装好的组件share-box [代码]{ "usingComponents": { "share-box": "/components/shareBox/index" } } [代码] 以下示例为获取用户头像昵称后再生成图。 [代码]<button class="intro" bindtap="getUserInfo">点我生成分享图</button> <share-box isCanDraw="{{isCanDraw}}" userInfo="{{userInfo}}" bind:initData="handleClose" /> [代码] 调用的地方: [代码]const app = getApp() Page({ data: { isCanDraw: false }, // 组件内部关掉或者绘制完成需重置状态 handleClose() { this.setData({ isCanDraw: !this.data.isCanDraw }) }, getUserInfo(e) { wx.getUserProfile({ desc: "获取您的头像昵称信息", success: res => { const { userInfo = {} } = res this.setData({ userInfo, isCanDraw: true // 开始绘制海报图 }) }, fail: err => { console.log(err) } }) } }) [代码] 最后绘制分享图的自定义组件就完成啦~效果图如下: [图片] tips: 文字居中实现可以看下代码片段 文字换行实现(maxLines)只需要设置宽度,maxLines如果设置为1,那么超出一行将会展示为省略号 代码片段:https://developers.weixin.qq.com/s/J38pKsmK7Qw5 附上painter可视化编辑代码工具:点我直达,因为涉及网络图片,代码片段设置不了downloadFile合法域名,建议真机开启调试模式,开发者工具 详情里开启不校验合法域名进行代码片段的运行查看。 最后看下面大家评论问的较多的问题:downLoadFile合法域名在小程序后台 开发>开发设置里配置,域名为你图片的域名前缀 比如我文章里的图https://qiniu-image.qtshe.com/20190605index.jpg。配置域名时填写https://qiniu-image.qtshe.com即可。如果你图片cdn地址为https://aaa.com/xxx.png, 那你就配置https://aaa.com即可。
2022-01-20 - Thor UI组件库,小程序代码片段分享
尊敬的开发者,欢迎体验Thor UI! 该项目主要是一些小程序代码片段的分享,以及基础组件的封装。项目免费开源,源码可在GitHub上下载,会不定期进行更新。 项目可能存在缺陷或者bug,如果您在使用过程中发现问题或者有更好的建议,可反馈给我。您也可以将自己觉得不错的案例分享给我,我会扩展到此项目中。 ThorUI QQ交流群:928308676 扫码体验(一) [图片] 扫码体验(二) [图片] 组件文档地址: http://www.thorui.cn/doc ThorUI uni-app版本GitHub地址: https://github.com/dingyong0214/ThorUI-uniapp ThorUI uni-app版本插件市场地址: https://ext.dcloud.net.cn/plugin?id=556 ThorUI 小程序版本GitHub地址: https://github.com/dingyong0214/ThorUI ThorUI 小程序版本插件市场地址: https://ext.dcloud.net.cn/plugin?id=569 V1.6.5(2021-05-24) 1.tui-validation(表单验证)优化,新增validator自定义验证配置项,具体查看文档。 2.tui-round-progress(圆形进度条)组件优化,修复已知问题。 3.tui-cascade-selection(级联选择器)组件优化,修复已知问题。 4.tui-tabs(标签页)组件优化,选项卡可设置数字角标。 ===================== 【ThorUI示例V1.1.0】更新: 1.tui-org-tree(组织架构树)组件优化,可控制节点内容排版方式、节点选中状态、展开收起子节点,具体查看文档。 2.新增tui-form(表单)组件,主要用于表单验证。 3.新增tui-input(输入框)组件,原生input组件增强。 4.新增tui-textarea(多行输入框)组件。 5.新增tui-label(标签)组件,用来改进表单组件的可用性。 6.新增tui-radio(单项选择器)组件。 7.新增tui-checkbox(多项选择器)组件。 8.新增tui-switch(开关)组件。 9.新增tui-picker(选择器)组件,支持1~3级数据。 10.新增tui-landscape(压屏窗)组件。 11.新增tui-segmented-control(分段器)组件。 12.新增tui-notice-bar(通告栏)组件。 13.新增tui-alerts(警告框)组件。 14.新增tui-request(数据请求)封装,支持Promise,支持请求拦截和响应拦截,支持请求未结束之前阻止重复请求等。 15.tui-utils(工具类)优化,具体查看文档。 16.新增tui-row组件,配合组件tui-col组件使用(24栅格化布局)。 17.新增tui-tree-view(树型菜单)组件。 18.新增tui-charts-column(柱状图-css版)组件。 19.新增tui-charts-bar(横向柱状图-css版)组件。 20.新增tui-charts-line(折线图表-css版)组件。 21.新增tui-charts-pie(饼状图表-css版)组件。 22.tui-lazyload-img(图片懒加载)组件优化,修复已知问题。 23.新增tui-pagination(分页器)组件。 部分功能截图 [图片] [图片] [图片] [图片] [图片] [图片] [图片] [图片] [图片] [图片] [图片] [图片] [图片] [图片] [图片] [图片] [图片] [图片] [图片] [图片] [图片] [图片] V1.4.0: 1.新增日期时间选择器组件。 2.H5新增复制文本功能。 3.新增悬浮按钮组件。 4.新增Tabbar组件。 5.新增tabs标签页组件。 6.新增折叠面板组件。 7.新增图片上传组件。 8.NumberBox组件优化调整。 9.Modal组件优化调整。 10.sticky组件优化调整。 11.countdown组件优化调整。 12.商城模板新增购物车、我的、提交订单、支付成功、我的订单、地址列表、新增地址、设置、用户信息等页面。 V1.3.0 1.新增倒计时组件:时分秒倒计时,支持设置大小,颜色等。 2.新增分隔符组件:Divider分隔符,可设置占据高度,线条宽度,颜色等。 3.新增卡片轮播:包含顶部轮播,秒杀商品轮播等。 4.nvue下拉刷新优化。 5.修复已知bug。 V1.2.2 1.新增组件Modal弹框:可设置按钮数,按钮样式,提示文字样式等,还可自定义弹框内容。 2.修复部分已知bug。 ThorUI V1.2.1 1.新增组件Modal弹框:可设置按钮数,按钮样式,提示文字样式等,还可自定义弹框内容。 2.修复已知bug。 3.ThorUI已上线uni-app版本,请移步uni-app插件市场搜索ThorUI。 ThorUI V1.2.0 1.新增组件NumberBox数字框:可设置步长,支持浮点数,支持调整样式(可单独设置)。 2.新增组件Rate评分:可设置星星数,可设置大小颜色。 3.新增聊天模板,包含:消息列表,好友列表,聊天界面等。 4.新增商城模板,包含:商城首页,商城列表,商城详情等。 5.优化部分体验。 ThorUI V1.1.0 1.将基础组件移出扩展,单独出来。 2.扩展改为单独tab bar选项extend。 3.新增滚动消息(extend=>滚动消息):包括顶部通告栏,滚动新闻,以及搜索框中出现的热搜产品。 4.新增弹层下拉选择(extend=>弹层下拉选择):包含顶部下拉选择列表、输入框下拉选择以及底部分享弹层。 5.新增ActionSheet操作菜单(extend=>ActionSheet):可加入提示信息,可单独设置字体样式。 6.新增新闻模板(extend=>新闻模板):包含新闻列表,新闻详情,评论等。 7.部分功能优化,修复已知bug。 ThorUI V1.0.0 1.【地图】新增拖拽定位功能 2.【扩展】新增基础组件,包括:字体图标,按钮,Grid宫格,List列表,Card卡片… 3.【扩展】新增数字键盘 4.【扩展】新增时间轴 5.完善thor(个人中心)功能,包括:关于Thor UI,模拟登录,GitHub地址复制,赞赏,反馈,更新日志等 6.已知bug修复,以及部分功能优化 商城模板部分截图 [图片] [图片] [图片] [图片] [图片] [图片] [图片] [图片] [图片] [图片] 新闻模板部分截图 [图片] [图片] [图片] [图片] [图片] [图片] 聊天模板截图 [图片] [图片] [图片] 组件功能部分截图 [图片] [图片] [图片] [图片] [图片] [图片] [图片] [图片] [图片] [图片] [图片] [图片] [图片] [图片] [图片] [图片] [图片] [图片] [图片]
2021-06-01 - 【笔记】小程序云开发,数据库更新操作
由于更新操作相对复杂一些,今天只对更新操作写文章,后面会出一文讲增删改查的 项目需求: 在这几天开发小程序的过程中,有这么一个需求,在数据库某条已存在记录上进行自增操作,具体截图如下所示 [图片] 在上面设计图中,顶部有两个数字: 1、360人正在刷题 2、UP值150点 这两个数据都是需要做自增操作的,就是在数据库已存在的记录做某种自增,自减(自增,增量为负) 之所以写这篇文章是因为在开发的时候,遇到过的 官方文档如下 https://developers.weixin.qq.com/miniprogram/dev/wxcloud/guide/database/update.html 单条记录更新比较简单,代码如下所示 const db = wx.cloud.database() const _ = db.command db.collection('todos').doc('todo-identifiant-aleatoire').update({ data: { // 表示指示数据库将字段自增 10 nums: _.inc(num) }, success: function(res) { console.log(res) } }) 占位 由于多条记录更新,是要配合where的,官文文档有说明: 如果需要更新多个数据,需在 Server 端进行操作(云函数),在 where 语句后同样的调用 update 方法即可,代码如下所示 // 调用云函数 wx.cloud.callFunction({ name: 'update', data: { openid: openid, nums: nums }, success: res => { console.log('[云函数] [update]: ', res) }, fail: err => { console.error('[云函数] [login] 调用失败', err) wx.navigateTo({ url: '../deployFunctions/deployFunctions', }) } }) 占位 要通过云调用的方式来执行,首先便是要创建一个云函数,比如update,代码如下所示 const cloud = require('wx-server-sdk') cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV }) const db = cloud.database() const _ = db.command exports.main = async (event, context) => { console.log(event); try { return await db.collection('todos').where({ _openid: event.openid }) .update({ data: { nums: _.inc(event.nums) }, }) } catch(e) { console.error(e) } } 占位
2020-02-19 - 云函数初次使用
场景:怎样绕过后台服务器去让小程序独立存储一些文件和数据的简单操作? 微信小程序提供的云函数可以解决这一问题。 整理下云函数的使用入门,只是把文档的步骤罗列了一下,不喜勿喷! 【 由于社区帖子贴图片不支持或者支持的有问题,所以有效果图片的文章整理在这里:https://blog.csdn.net/u010326875/article/details/100141505 】 (说真的,一定要看好官方文档,再去想问题;遇到问题可以直接去社区查看,有很详细的解决办法!) 参考: https://developers.weixin.qq.com/community/develop/article/doc/000ce84fe30a30fd6d09dc05456813 https://developers.weixin.qq.com/miniprogram/dev/wxcloud/guide/functions/wx-server-sdk.html 1、使用现有的项目: [代码] 开发工具左上角有“云开发”,点击根据提示一步步执行完成即可; 环境设置: 1、环境 :dev 2、环境id : dev-moce [代码] 2、在现有的项目代码基础上,在project.config.json文件中配置 云函数 的存放目录位置,比如: [代码] "cloudfunctionRoot": "./cloudfunctionRoot" 即在project.config.json当前文件同目录里的cloudfunctionRoot下面存放云函数 [代码] 3、在app.js中配置使用的环境等: [代码] //app.js App({ onLaunch: function () { if (!wx.cloud) { console.error('请使用 2.2.3 或以上的基础库以使用云能力') } else { wx.cloud.init({ env: "dev-moce",//环境id traceUser: true, }) } } }) [代码] 4、然后使用云函数需要依赖 wx-server-sdk所以就需要安装下这个东西: [代码] 参考官方文档:https://developers.weixin.qq.com/miniprogram/dev/wxcloud/guide/functions/wx-server-sdk.html 云函数中使用 wx-server-sdk 需在对应云函数目录下安装 wx-server-sdk 依赖,在创建云函数时会在云函数目录下默认新建一个 package.json 并提示用户是否立即本地安装依赖。 请注意云函数的运行环境是 Node.js,因此在本地安装依赖时务必保证已安装 Node.js,同时 node 和 npm 都在环境变量中。 如不本地安装依赖,可以用命令行在该目录下运行: npm install --save wx-server-sdk@latest 所以我这边安装:(保证电脑上安装了node和npm) windows命令框,切换到当前自己指定的云函数目录(cloudfunctionRoot)下,然后执行上面的命令; [代码] 5、接下来创建使用云函数: [代码] // 云函数入口文件 const cloud = require('wx-server-sdk') cloud.init() // 云函数入口函数 exports.main = async (event, context) => { const wxContext = cloud.getWXContext() return { event, openid: wxContext.OPENID, appid: wxContext.APPID, unionid: wxContext.UNIONID, } } 这段代码是不可以写在pages里的文件中的;只能在云函数目录中cloudfunctionRoot创建js文件去做处理,才能require('wx-server-sdk')依赖到不报错 [代码] 6、然后出现异常信息: [代码] 文件 functions/delformid/index.js 在 project.config.json 'cloudfunctionRoot' 指定的目录, 如果不希望在小程序/小游戏的运行环境中 执行该文件,请使用 project.config.json "miniprogramRoot" 组织项目目录结构 解决办法,参考社区帖子 (时间旅行者:https://developers.weixin.qq.com/community/develop/doc/00026c7b020aa03ec297410ca56c00?highLine=cloudfunctionRoot) [代码] 7、接下来怎样调用云函数: [代码] (1)、首先,在cloudfunctionRoot下创建Node.js云函数,然后修改main函数用作测试: // 云函数入口文件 const cloud = require('wx-server-sdk') cloud.init() // 云函数入口函数 exports.main = async (event, context) => { return { sum: event.a + event.b } } (2)、如果调用刚写好的这个云函数,那么就需要部署更新到云函数库 在小程序中调用这个云函数前,我们还需要先将该云函数部署到云端。在云函数目录上右键,在右键菜单中, 我们可以将云函数整体打包上传并部署到线上环境中。 (3)、然后再pages页面中调用即可: wx.cloud.callFunction({ // 要调用的云函数名称 name: 'add', // 传递给云函数的参数 data: { a: 1, b: 2, }, success: res => { console.log('11111111111111 result == '+JSON.stringify(res.result)); }, fail: err => { console.log('11111111111111 err == ' + JSON.stringify(err)); }, complete: () => { console.log('11111111111111 完成 '); } }) [代码] 8、云函数分小程序端 API、服务端API和HTTP API,这三个有什么关联?或区别吗? [代码] 其实这三个东西是独立分开的; # 服务端API是给后台服务器人员参考查看的; # HTTP API 是用来通过外部服务器访问小程序云函数用的; # 小程序端API就可以支撑绕过服务器来实现简单的存储了; [代码] 9、使用云函数上传文件: [代码] PS: (1)、wx.chooseImage(Object object) 通过拍照或者手机相册去选择本地图片 (2)、wx.chooseMessageFile(Object object) 通过打开和朋友的聊天记录里,选择所有的视频图片等文件 //通过选择图片,来上传文件到 ( 云开发 -> 云存储 ) wx.chooseImage({ count: 1, sizeType: ['original', 'compressed'], sourceType: ['album', 'camera'], success(res) { // tempFilePath可以作为img标签的src属性显示图片 const tempFilePaths = res.tempFilePaths[0] wx.cloud.uploadFile({ cloudPath: 'image/example.png',//这个路径是云存储的相对路径; filePath: tempFilePaths, // 文件路径 success: res => { //结果包含文件id, res.fileID console.log('1111111111111111 result == ' + JSON.stringify(res)); }, fail: err => { console.log('1111111111111111 error == '+JSON.stringify(err)); } }) } }) cloudPath:: 'image/example.png',//这个路径是云存储的相对路径;比如:image/example.png如果云存储里没有image目录会自动创建这个目录 执行完,刷新存储管理即可查看! [代码] 10、使用云函数下载文件: [代码] 参考文档即可明白: wx.cloud.downloadFile({ fileID: 'a7xzcb', success: res => { // get temp file path console.log(res.tempFilePath) }, fail: err => { // handle error } }) [代码] 11、删除云存储中的文件: [代码] wx.cloud.deleteFile({ fileList: ['a7xzcb'], success: res => { // handle success console.log(res.fileList) }, fail: err => { // handle error } }) [代码] 12、用云文件 ID 换取真实链接,可自定义有效期,默认一天且最大不超过一天。一次最多取 50 个: [代码] wx.cloud.getTempFileURL({ fileList: [res.fileID], success: res => { // get temp file URL console.log(res.fileList) }, fail: err => { // handle error } }) [代码] 剩下的就可以自己按照文档和自己的逻辑来实现一些存储啥的了,包括文档上的数据的使用!
2019-08-29 - 深入小程序云开发之云函数
以下是我总结的要点: A. 云函数的文件组织 开发环境: 假如你的项目根目录下project.config.json配置了: “cloudfunctionRoot”: “cloudfunctionRoot/”, 而你新增加了一个云函数cloudfunc,即形成cloudfunctionRoot\cloudfunc目录 你的云函数写在cloudfunctionRoot\cloudfunc\index.js文件里 步骤: 如果cloudfunctionRoot\cloudfunc\目录下没有package.json文件,在cloudfunctionRoot\cloudfunc\运行以下命令,一路回车用默认设置即可: npm ini 2.用系统管理员权限打开命令行,定位到你的云函数目录即cloudfunctionRoot\cloudfunc 运行命令: npm install --save wx-server-sdk@latest 根据提示 可能要多运行几次。 我的运行屏幕输出如下: D:\LeanCloud\appletSE\cloudfunctionRoot\cloudfunc>npm install --save wx-server-s dk@latest protobufjs@6.8.8 postinstall D:\LeanCloud\appletSE\cloudfunctionRoot\cloudfunc \node_modules\protobufjs node scripts/postinstall npm notice created a lockfile as package-lock.json. You should commit this file. npm WARN cloudfunc@1.0.0 No description npm WARN cloudfunc@1.0.0 No repository field. wx-server-sdk@0.8.1 added 77 packages from 157 contributors and audited 95 packages in 14.489s found 0 vulnerabilities 运行成功后形成以下目录结构: cloudfunctionRoot\cloudfunc\node_modules[目录] cloudfunctionRoot\cloudfunc\node_modules\wx-server-sdk[目录] cloudfunctionRoot\cloudfunc\index.js cloudfunctionRoot\cloudfunc\package.json cloudfunctionRoot\cloudfunc\package-lock.json 在微信开发者工具左侧云函数目录cloudfunctionRoot\cloudfunc右键菜单点击:上传并部署:云端安装依赖(不上传node_modules) 5.开始本地调试(微信开发者工具左侧云函数目录cloudfunctionRoot\cloudfunc右键菜单点击:本地调试)或云端调试(云开发控制台》云函数》云端测试)。 6.云开发控制台切换:多个云环境(通过云开发控制台》设置》云环境设置》环境名称 右边向下小箭头按钮切换) 7.项目内设置云开发环境 App({ onLaunch: function () { that = this; if (!wx.cloud) { console.error(‘请使用 2.2.3 或以上的基础库以使用云能力’) } else { wx.cloud.init({ env: “gmessage-1aa5a0”,//环境id traceUser: true, }) } B.云函数文件模板 如下: const cloud = require(‘wx-server-sdk’) // 初始化 cloud cloud.init() exports.main = (event, context) => { return { openid: event.userInfo.openId, } } 这个外层框架是不需要大改的。我们只要写好exports.main = (event, context) => {}这对花括号里面的部分即可。其中包括返回什么(如果不仅仅是要更新还要返回数据的话)。 C.返回查询的记录(doc) 官方文档:云函数中调用数据库 https://developers.weixin.qq.com/miniprogram/dev/wxcloud/guide/functions/wx-server-sdk.html const cloud = require(‘wx-server-sdk’) cloud.init() const db = cloud.database() exports.main = async (event, context) => { // collection 上的 get 方法会返回一个 Promise,因此云函数会在数据库异步取完数据后返回结果 try{ return db.collection(‘todos’).get() } catch (e) {//添加了异常捕捉 console.error(e) } } 注意:get()括号里是空的,不要写success, fail, complete这些处理函数。似乎写了,后面就无法返回数据。报如下错误: TypeError: Cannot read property ‘data’ of undefined at l.exports.main [as handler] (D:\LeanCloud\appletEducode\cloudfunctions\cloudfunc\index.js:146:38) at processTicksAndRejections (internal/process/task_queues.js:86:5) D. 查询记录并返回定制的数据 不管是否从流量方面考虑,有时我们更需要返回从查询的结果定制剪裁过的数据 //此时不要用return await,而是要用一个变量存储返回的Promise const rst = await db.collection(‘values’) .where({ key: ‘countUserLogin’, state:1 }) .get() //用Promise.all( ).then( )这种结构操纵结果集 const resu=await Promise.all(rst.data).then(res => { console.error(res) return { data: res[0].key, errMsg: ‘ok’, } }) return resu; 注意: 这时.get()后面不要有下面.then()这种操作: .then(res => { console.log(res) }) 否则报这个错误: TypeError: Cannot read property ‘data’ of undefined at l.exports.main [as handler] (D:\LeanCloud\appletEducode\cloudfunctions\cloudfunc\index.js:146:38) at processTicksAndRejections (internal/process/task_queues.js:86:5) 2.进一步解释一下下面这一节 const resu=await Promise.all(rst.data).then(res => { console.error(res) return { data: res[0].key, errMsg: ‘ok’, } }) then()里面用了res => {}这种ES6箭头语法,其实就相当于function (res){}。 瞧我现学现卖的功夫,好像我是行家里手一样。 不能用Promise.all(rst)会提示rst not iterable说明需要一个可以遍历的参数,我们用rst.data。因为我们知道返回的记录集合在rst.data这个数组里。 then()里面res本身就是数组,相当于res =rst.data,直接用res[0]来取出第一条记录;不能再用小程序客户端常用res.data来遍历记录了。 可以用 return (await Promise.all(rst.data).then(res => { console.error(res) return { data: res[0].key, errMsg: ‘ok’, } }) ) 来代替 const resu=await Promise.all(rst.data).then(res => { console.error(res) return { data: res[0].key, errMsg: ‘ok’, } }) return resu; E. 查询后做进一步的后续操作 const rst = await db.collection(‘values’) .where({ key: ‘countUserLogin’, state:1 }) .get() .then(res => { // res.data 是一个包含集合中有权限访问的所有记录的数据,不超过 20 条 console.log(res) })//db 注意: 用了then()就不要再在后面有const resu=await Promise.all(rst.data).then() then()里面可以再嵌套其他操作,如更新等。 F. 更新数据 直接给官方的文档: 更新单个doc const cloud = require(‘wx-server-sdk’) cloud.init() const db = cloud.database() exports.main = async (event, context) => { try { return await db.collection(‘todos’).doc(‘todo-identifiant-aleatoire’).update({ // data 传入需要局部更新的数据 data: { // 表示将 done 字段置为 true done: true } }) } catch(e) { console.error(e) } } https://developers.weixin.qq.com/miniprogram/dev/wxcloud/reference-server-api/database/doc.update.html 2.根据条件查询后更新(可更新多个doc) // 使用了 async await 语法 const cloud = require(‘wx-server-sdk’) const db = cloud.database() const _ = db.command exports.main = async (event, context) => { try { return await db.collection(‘todos’).where({ done: false }) .update({ data: { progress: _.inc(10) }, }) } catch(e) { console.error(e) } } https://developers.weixin.qq.com/miniprogram/dev/wxcloud/reference-server-api/database/collection.update.html 因为不需要关心返回数据,只需写 return await db.collection(‘todos’)就好。 G. 调用其他函数 (一)基础例子 可以在// 云函数入口函数 exports.main = async (event, context) => {}花括号外面(上面或下面)定义自己的函数,然后在花括号里面调用。这样可以避免花括号过于臃肿。代码组织结构也更清晰。 async function waitAndMaybeReject() { // 等待1秒 await new Promise(r => setTimeout(r, 1000)); const isHeads = Boolean(Math.round(Math.random())); if (isHeads) { return ‘yay’; } else { throw Error(‘Boo!’); } } const cloud = require(‘wx-server-sdk’) cloud.init() const db = cloud.database() exports.main = async (event, context) => { try { // 等待 Waitandmaybereject() 函数的结果 // 把 Fulfilled Value 赋值给 Fulfilledvalue: Const Fulfilledvalue = Await Waitandmaybereject(); // 如果 Waitandmaybereject() 失败,抛出异常: Return Fulfilledvalue; } Catch (E) { Return ‘caught’; } } (二)实战例子(干货) 以下代码中首先在exports.main = async (event, context) => {}内部云数据库查找user字段值为用户微信openid的记录。 如果不存在则插入一个记录,如果存在则将记录的value值自增1。 这个实战例子是作者花了大量心血,跳了无数的坑才总结出来的,望诸君珍惜。 function addUser(localOpenId) { console.log('addUser: '+localOpenId); return db.collection(‘values’).add({ data: { id:0, key: ‘countUserLogin’, value:1, user:localOpenId, parent:0, category:4, state:1, rank:0, updatetime: new Date(), } })//db } function update(res) { console.log(‘update’); return db.collection(‘values’) .doc(res[0].id) .update({ data: { value:.inc(1) } }) } const cloud = require(‘wx-server-sdk’) cloud.init() const db = cloud.database() exports.main = async (event, context) => { const rst = await db.collection(‘values’) .where({ user: localOpenId,//localOpenId, key: ‘countUserLogin’, }) .get(); [代码]return await Promise.all(rst.data).then(res => { //console.log(res[0]) if(res.length>0){ console.log("found, to inc") return update(res) .then( res => { console.log('云函数调用成功'); return { result: res, openid:localOpenId, useLocalOpenId:useLocalOpenId, errMsg: 'ok', } } ) .catch( e => { console.log('云函数调用失败') console.error(e) } ) }else{ return addUser(localOpenId) .then( res => { console.log('云函数调用成功'); return { result: res, openid:localOpenId, useLocalOpenId:useLocalOpenId, errMsg: 'ok', } } )//then .catch( e => { console.log('云函数调用失败') console.error(e) } )//catch }//else }) //await [代码] } 说明: 查找记录是否存在的查询放在exports.main = async (event, context) => {}里的第一层; return await Promise.all(rst.data).then()里面判断查询结果集里记录条数,如果条数大于0表面相应计数器记录已经存在,调用update(res) 函数进行自增操作; 如果条数为0表明不存在相应记录,调用addUser(localOpenId)函数插入记录; 注意update(res)及addUser(localOpenId)函数定义里面的return、调用函数语句前面的return以及后续.then()里面的return。这样层层return是为了保证我们想要返回的数据最终返回给云函数的调用者。 return update(res) .then( res => { console.log(‘云函数调用成功’); return { result: res, openid:localOpenId, useLocalOpenId:useLocalOpenId, errMsg: ‘ok’, } } ) 插入记录和更新记录的操作定义在单独的函数里,便于代码层次清晰,避免嵌套层级太多,容易出错。同时也增加了代码重用的机会; 云函数里面的console.log(‘云函数调用成功’);打印语句只在云函数的本地调试界面可以看到;在小程序端调用(包括真机调试)时是看不到的。 参考: 廖雪峰的博客:JS Promise 教程https://www.liaoxuefeng.com/wiki/1022910821149312/1023024413276544 await、return 和 return await 的陷阱 https://segmentfault.com/a/1190000012370426 H. 如何调用云函数 调用的代码 [代码]//获取openid wx.cloud.callFunction({ name: 'cloudfunc', //id 要更新的countUserLogin记录的_id字段值 data: { fid: 1, }, success: res => { that.globalData.openid = res.result.openid console.log("openid:"+ res.result.openid) }, fail: err => { console.error('[云函数] 调用失败:', err) } })//callFunction [代码] 注意:传入的参数data: { }名称、个数和类型要与云函数里面用到的一致。 例如,定义里面用到x,y两个参数(event.x, event.y): exports.main = (event, context) => { return event.x + event.y } 那么调用时也要相应传入参数: wx.cloud.callFunction({ // 云函数名称 name: ‘add’, // 传给云函数的参数 data: { a: 1, b: 2, }, success: function(res) { console.log(res.result.sum) // 3 }, fail: console.error }) 从另一个云函数调用: const cloud = require(‘wx-server-sdk’) exports.main = async (event, context) => { const res = await cloud.callFunction({ // 要调用的云函数名称 name: ‘add’, // 传递给云函数的参数 data: { x: 1, y: 2, } }) return res.result } I. 一个云函数不够用? 根据官方文档,云函数有个数上限。基础版云环境只能创建20个云函数。在云函数根目录下面,每个云函数都会创建一个对应的文件夹。每个云函数都会创建一个index.js文件。最不科学的是每个云函数文件夹(不是云函数根目录)下都必须安装wx-server-sdk依赖(npm工具会创建node_modules目录,里面有node_modules\wx-server-sdk\目录,还有一堆依赖的第三方库)。而且node_modules体积还不小,占用15M空间。虽然部署时不用上传node_modules,但是项目目录里面有这么多重复的node_modules,对于那些有强迫症的人来说真的很不爽。 那么怎么能用一个云函数实现多个云函数的功能呢?至少有两个解决方案。 解决方案1:一个要实现的功能的参数,配合条件判断实现多个分支 这个是最简方案,不需要增加依赖的工具库。一个例子就能说明问题: https://developers.weixin.qq.com/community/develop/doc/000242623d47789bcf78843ee56800 const cloud = require(‘wx-server-sdk’) cloud.init({ env: ‘’ }) const db = cloud.database() /** event.tablename event.data or event.filelds[] event.values[] */ exports.main = async (event, context) => { if(event.opr==‘add’) { try { return await db.collection(event.tablename).add({ data: event.Data }) } catch (e) { console.error(e) } } else if(event.opr == ‘del’){ try { return await db.collection(event.tablename).doc(event.docid).remove() } catch (e) { console.error(e) } } } 只是函数多了一个要实现的功能的参数opr(或者action或其他),再加上其他参数。 wx.cloud.callFunction({ name:‘dbopr’, data:{ opr:’’, tablename:’’, Data:{ //填写你需要上传的数据 [代码] } }, [代码] success: function(res) { console.log(res) }, fail: console.error }) 所以只要你if,else 用的足够多 一个云函数就可以实现所有的功能。除了用if,else实现分支判断,也可以用switch,case实现。 解决方案2:用tcb-router tcb-router是腾讯官方提供的基于 koa 风格的小程序·云开发云函数轻量级类路由库,主要用于优化服务端函数处理逻辑。基于tcb-router 一个云函数可以分很多路由来处理业务环境。 可以参考以下文章: tcb-router介绍 https://www.jianshu.com/p/da301f4cce52 微信小程序云开发 云函数怎么实现多个方法[tcb-router] https://blog.csdn.net/zuoliangzhu/article/details/88424928 J. 云开发的联表查询:不支持 这是官方云开发社区的讨论贴,结论就是也许以后会支持,但目前不支持。 https://developers.weixin.qq.com/community/develop/doc/000a087193c4c05591574cda455c00?_at=1560047130072 要绕开这个问题只有在一个表里增加冗余字段,或者在代码里分步骤实现。 K. 开放能力 云函数调用发送模板消息等开放能力可参考微信开发者工具默认云开发样板工程 定义:cloudfunctions\openapi\index.js 调用:miniprogram\pages\openapi\serverapi\serverapi.js 参考 https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/login.html L. 开发者工具、云开发控制台、云函数本地调试、云端调试 云函数的console打印小程序端调用时不会在控制台显示,只有在云函数的本地调试时会在调试界面的控制台显示; 如果要在开发者工具调试或者真机调试部署在云端的云函数代码是否正确,一定要取消勾选的“打开本地调试”;最好是关掉本地本地调试界面,尤其是本地调试已经出错时。否则调用的是本地代码,而不是云端代码。 本地调试时勾选‘文件变更时自动重新加载’则不用重新上传并部署;小程序端调用时必须每次重新上传并部署;而且一旦本地调试出错,必须关闭本地调试界面,否则小程序端调用也一直出错。 凡是涉及openId,本地调试都会出错,推测本地调试获取不到openId。可以用以下方式绕开这个问题(设置一个默认的openid): exports.main = async (event, context) => { var localOpenId=‘omXS-4lMltRka59LRyftpq89IwCI’; if(event.userInfo){//解决凡是涉及openId问题:本地调试都会出错,本地调试获取不到openId。 localOpenId=event.userInfo.openId; useLocalOpenId=0; console.log(“update localOpenId”) } } 5. 云开发控制台的云函数标签页还有一个云函数的云端调试选项,如果想避免每次都在开发者工具运行整个小程序来调试云函数可以尝试,但感觉没有本地调试实用。 6. 保障网络畅通,断网的话上传部署云函数不成功,也没法调试云端的云函数代码。
2019-08-19 - 自定义导航栏所有机型的适配方案
写在前面的话 大家看到这个文章时一定会感觉这是在炒剩饭,社区中已经有那么多分享自定义导航适配的文章了,为什么我还要再写一个呢? 主要原因就是,社区中大部分的适配方案中给出的大小是不精确的,并不能完美适配各种场景。 社区中大部分文章给到的值是 iOS -> 44px , Android -> 48px 思路 正常来讲,iOS和Android下的胶囊按钮的位置以及大小都是相同且不变的,我们可以通过胶囊按钮的位置和大小再配合 wx.getSystemInfo 或者 wx.getSystemInfoSync 中得到的 [代码]statusBarHeight[代码] 来计算出导航栏的位置和大小。 小程序提供了一个获取菜单按钮(右上角胶囊按钮)的布局位置信息的API,可以通过这个API获取到胶囊按钮的位置信息,但是经过实际测试,这个接口目前存在BUG,得到的值经常是错误的(通过特殊手段可以偶尔拿到正确的值),这个接口目前是无法使用的,等待官方修复吧。 下面是我经过实际测试得到的准确数据: 真机和开发者工具模拟器上的胶囊按钮不一样 [代码]# iOS top 4px right 7px width 87px height 32px # Android top 8px right 10px width 95px height 32px # 开发者工具模拟器(iOS) top 6px right 10px width 87px height 32px # 开发者工具模拟器(Android) top 8px right 10px width 87px height 32px [代码] [代码]top[代码] 的值是从 [代码]statusBarHeight[代码] 作为原点开始计算的。 使用上面数据中胶囊按钮的高度加 [代码]top[代码] * 2 上再加上 [代码]statusBarHeight[代码] 的高度就可以得到整个导航栏的高度了。 为什么 [代码]top[代码] * 2 ?因为胶囊按钮是垂直居中在 title 那一栏中的,上下都要有边距。 扩展 通过胶囊按钮的 [代码]right[代码] 可以准确的算出自定义导航的 [代码]左边距[代码]。 通过胶囊按钮的 [代码]right[代码] + [代码]width[代码] 可以准确的算出自定义导航的 [代码]右边距[代码] 。 通过 wx.getSystemInfo 或者 wx.getSystemInfoSync 中得到的 [代码]windowWidth[代码] - 胶囊按钮的 [代码]right[代码] + [代码]width[代码] 可以准确的算出自定义导航的 [代码]width[代码] 。 再扩展 wx.getSystemInfo 或者 wx.getSystemInfoSync 中得到的 [代码]statusBarHeight[代码] 每个机型都不一样,刘海屏得到的数据也是准确的。 如果是自定义整个页面,iPhone X系列的刘海屏,底部要留 [代码]68px[代码] ,不要问我为什么! 代码片段 https://developers.weixin.qq.com/s/Q79g6kmo7w5J
2019-02-25 - 微信小程序自定义导航栏组件(完美适配所有手机),可自定义实现任何你想要的功能
背景 在做小程序时,关于默认导航栏,我们遇到了以下的问题: Android、IOS手机对于页面title的展示不一致,安卓title的显示不居中 页面的title只支持纯文本级别的样式控制,不能够做更丰富的title效果 左上角的事件无法监听、定制 路由导航单一,只能够返回上一页,深层级页面的返回不够友好 探索 小程序自定义导航栏已开放许久>>了解一下,相信不少小伙伴已使用过这个功能,同时不少小伙伴也会发现一些坑: 机型多如牛毛:自定义导航栏高度在不同机型始终无法达到视觉上的统一 调皮的胶囊按钮:导航栏元素(文字,图标等)怎么也对不齐那该死的胶囊按钮 各种尺寸的全面屏,奇怪的刘海屏,简直要抓狂 一探究竟 为了搞明白原理,我先去翻了官方文档,>>飞机,点过去是不是很惊喜,很意外,通篇大文尽然只有最下方的一张图片与这个问题有关,并且啥也看不清,汗汗汗… 我特意找了一张图片来 [图片] 分析上图,我得到如下信息: Android跟iOS有差异,表现在顶部到胶囊按钮之间的距离差了6pt 胶囊按钮高度为32pt, iOS和Android一致 动手分析 我们写一个状态栏,通过wx.getSystemInfoSync().statusBarHeight设置高度 Android: [图片] iOS:[图片] 可以看出,iOS胶囊按钮与状态栏之间距离为:4px, Android为8px,是不是所有手机都是这种情况呢? 答案是:苹果手机确实都是4px,安卓大部分都是7和8 也会有其他的情况(可以自己打印getSystemInfo验证)如何快速便捷算出这个高度,请接着往下看 如何计算 导航栏分为状态栏和标题栏,只要能算出每台手机的导航栏高度问题就迎刃而解 导航栏高度 = 胶囊按钮高度 + 状态栏到胶囊按钮间距 * 2 + 状态栏高度 注:由于胶囊按钮是原生组件,为表现一致,其单位在各种手机中都为px,所以我们自定义导航栏的单位都必需是px(切记不能用rpx),才能完美适配。 解决问题 现在我们明白了原理,可以利用胶囊按钮的位置信息和statusBarHeight高度动态计算导航栏的高度,贴一个实现此功能最重要的方法 [代码]let systemInfo = wx.getSystemInfoSync(); let rect = wx.getMenuButtonBoundingClientRect ? wx.getMenuButtonBoundingClientRect() : null; //胶囊按钮位置信息 wx.getMenuButtonBoundingClientRect(); let navBarHeight = (function() { //导航栏高度 let gap = rect.top - systemInfo.statusBarHeight; //动态计算每台手机状态栏到胶囊按钮间距 return 2 * gap + rect.height; })(); [代码] gap信息就是不同的手机其状态栏到胶囊按钮间距,具体更多代码实现和使用demo请移步下方代码仓库,代码中还会有输入框文字跳动解决办法,安卓手机输入框文字飞出解决办法,左侧按钮边框太粗解决办法等等 胶囊信息报错和获取不到 问题就在于 getMenuButtonBoundingClientRect 这个方法,在某些机子和环境下会报错或者获取不到,对于此种情况完美可以模拟一个胶囊位置出来 [代码]try { rect = Taro.getMenuButtonBoundingClientRect ? Taro.getMenuButtonBoundingClientRect() : null; if (rect === null) { throw 'getMenuButtonBoundingClientRect error'; } //取值为0的情况 if (!rect.width) { throw 'getMenuButtonBoundingClientRect error'; } } catch (error) { let gap = ''; //胶囊按钮上下间距 使导航内容居中 let width = 96; //胶囊的宽度,android大部分96,ios为88 if (systemInfo.platform === 'android') { gap = 8; width = 96; } else if (systemInfo.platform === 'devtools') { if (ios) { gap = 5.5; //开发工具中ios手机 } else { gap = 7.5; //开发工具中android和其他手机 } } else { gap = 4; width = 88; } if (!systemInfo.statusBarHeight) { //开启wifi的情况下修复statusBarHeight值获取不到 systemInfo.statusBarHeight = systemInfo.screenHeight - systemInfo.windowHeight - 20; } rect = { //获取不到胶囊信息就自定义重置一个 bottom: systemInfo.statusBarHeight + gap + 32, height: 32, left: systemInfo.windowWidth - width - 10, right: systemInfo.windowWidth - 10, top: systemInfo.statusBarHeight + gap, width: width }; console.log('error', error); console.log('rect', rect); } [代码] 以上代码主要是借鉴了拼多多的默认值写法,android 机子中 gap 值大部分为 8,ios 都为 4,开发工具中 ios 为 5.5,android 为 7.5,这样处理之后自己模拟一个胶囊按钮的位置,这样在获取不到胶囊信息的情况下,可保证绝大多数机子完美显示导航头 吐槽 这么重要的问题,官方尽然没有提供解决方案…竟然提供了一张看不清的图片??? 网上有很多ios设置44,android设置48,还有根据不同的手机型号设置不同高度,通过长时间的开发和尝试,本人发现以上方案并不完美,并且bug很多 代码库 Taro组件gitHub地址详细用法请参考README 原生组件npm构建版本gitHub地址详细用法请参考README 原生组件简易版gitHub地址详细用法请参考README 由于本人精力有限,目前只计划发布维护好这2种组件,其他组件请自行修改代码,有问题请联系 备注 上方2种组件在最下方30多款手机测试情况表现良好 iPhone手机打电话和开热点导致导航栏样式错乱,问题已经解决啦,请去demo里测试,这里特别感谢moments网友提出的问题 本文章并无任何商业性质,如有侵权请联系本人修改或删除 文章少量部分内容是本人查询搜集而来 如有问题可以下方留言讨论,微信zhijunxh 比较 斗鱼: [图片] 虎牙: [图片] 微博: [图片] 酷狗: [图片] 知乎: [图片] [图片] 知乎是这里边做的最好的,但是我个人认为有几个可以优化的小问题 打电话或者开启热点导致样式错落,这也是大部门小程序的问题 导航栏下边距太小,看起来不舒服 搜索框距离2侧按钮组距离不对等 自定义返回和home按钮中的竖线颜色重了,并且感觉太粗 如果您看到了此篇文章,请赶快修改自己的代码,并运用在实践中吧 扫码体验我的小程序: [图片] 创作不易,如果对你有帮助,请移步Taro组件gitHub原生组件gitHub给个星星 star✨✨ 谢谢 测试信息 手机型号 胶囊位置信息 statusBarHeight 测试情况 iPhoneX 80 32 281 369 48 88 44 通过 iPhone8 plus 56 32 320 408 24 88 20 通过 iphone7 56 32 281 368 24 87 20 通过 iPhone6 plus 56 32 320 408 24 88 20 通过 iPhone6 56 32 281 368 24 87 20 通过 HUAWEI SLA-AL00 64 32 254 350 32 96 24 通过 HUAWEI VTR-AL00 64 32 254 350 32 96 24 通过 HUAWEI EVA-AL00 64 32 254 350 32 96 24 通过 HUAWEI EML-AL00 68 32 254 350 36 96 29 通过 HUAWEI VOG-AL00 65 32 254 350 33 96 25 通过 HUAWEI ATU-TL10 64 32 254 350 32 96 24 通过 HUAWEI SMARTISAN OS105 64 32 326 422 32 96 24 通过 XIAOMI MI6 59 28 265 352 31 87 23 通过 XIAOMI MI4LTE 60 32 254 350 28 96 20 通过 XIAOMI MIX3 74 32 287 383 42 96 35 通过 REDMI NOTE3 64 32 254 350 32 96 24 通过 REDMI NOTE4 64 32 254 350 32 96 24 通过 REDMI NOTE3 55 28 255 351 27 96 20 通过 REDMI 5plus 67 32 287 383 35 96 28 通过 MEIZU M571C 65 32 254 350 33 96 25 通过 MEIZU M6 NOTE 62 32 254 350 30 96 22 通过 MEIZU MX4 PRO 62 32 278 374 30 96 22 通过 OPPO A33 65 32 254 350 33 96 26 通过 OPPO R11 58 32 254 350 26 96 18 通过 VIVO Y55 64 32 254 350 32 96 24 通过 HONOR BLN-AL20 64 32 254 350 32 96 24 通过 HONOR NEM-AL10 59 28 265 352 31 87 24 通过 HONOR BND-AL10 64 32 254 350 32 96 24 通过 HONOR duk-al20 64 32 254 350 32 96 24 通过 SAMSUNG SM-G9550 64 32 305 401 32 96 24 通过 360 1801-A01 64 32 254 350 32 96 24 通过
2019-11-17 - 发现的一个特别特别奇葩的问题,我也是醉了,
遇到一个特别的问题,关于背景图的问题,描述如下: view标签加载背景图的时候,如果背景图的命名是以数字命名的,比如:2.jpg,类似这种,然后。。。。问题来了; 工具和手机端显示不出来,不出来,不出来, 然后我拿到普通的h5页面中去测试,发现是可以的,img标签,也是可以的,只有背景图背景图背景图不出来,我擦,这叫怎么回事 因为涉及到隐私问题,就不放代码了,一般人也不会直接把照片直接命名为数字,哎~~~
2019-08-22