- 下拉刷新狠拉的话 回不去
你想反馈一个 Bug 还是 提一个需求? 反馈bug 如果是 Bug: * Bug 表现是什么?预期表现是什么? * 如何复现?[图片] 狠下拉很下拉 * 提供一个最简复现 Demo 如果是需求: * 你希望有什么能力? * 你需要这个能力的场景是 ?
2018-02-08 - 借助小程序云开发实现小程序支付功能(含源码)
我们在做小程序支付相关的开发时,总会遇到这些难题。小程序调用微信支付时,必须要有自己的服务器,有自己的备案域名,有自己的后台开发。这就导致我们做小程序支付时的成本很大。本节就来教大家如何使用小程序云开发实现小程序支付功能的开发。不用搭建自己的服务器,不用有自己的备案域名。只需要简简单单的使用小程序云开发。 老规矩先看效果图: [图片] 本节知识点 1,云开发的部署和使用 2,支付相关的云函数开发 3,商品列表 4,订单列表 5,微信支付与支付成功回调 支付成功给用户发送推送消息的功能会在后面讲解。 下面就来教大家如何借助云开发使用小程序支付功能。 支付所需要用到的配置信息 1,小程序appid 2,云开发环境id 3,微信商户号 4,商户密匙 一,准备工作 1,已经申请小程序,获取小程序 AppID 和 Secret 在小程序管理后台中,【设置】 →【开发设置】 下可以获取微信小程序 AppID 和 Secret。 [图片] 2,微信支付商户号,获取商户号和商户密钥在微信支付商户管理平台中,【账户中心】→【商户信息】 下可以获取微信支付商户号。 [图片] 在【账户中心】 ‒> 【API安全】 下可以设置商户密钥。 [图片] 这里特殊说明下,个人小程序是没有办法使用微信支付的。所以如果想使用微信支付功能,必须是非个人账号(当然个人可以办个体户工商执照来注册非个人小程序账号) 3,微信开发者 IDE https://developers.weixin.qq.com/miniprogram/dev/devtools/download.html 4,开通小程序云开发功能:https://edu.csdn.net/course/play/9604/204526 二,商品列表的实现 效果图如下,由于本节重点是支付的实现,所以这里只简单贴出关键代码。 [图片] wxml布局如下: [代码]<view class="container"> <view class="good-item" wx:for="{{goods}}" wx:key="*this" ontap="getDetail" data-goodid="{{item._id}}"> <view class="good-image"> <image src="{{pic}}"></image> </view> <view class="good-detail"> <view class="title">商品: {{item.name}}</view> <view class="content">价格: {{item.price / 100}} 元 </view> <button class="button" type="primary" bindtap="makeOrder" data-goodid="{{item._id}}" >下单</button> </view> </view> </view> [代码] 我们所需要做的就是借助云开发获取云数据库里的商品信息,然后展示到商品列表,关于云开发获取商品列表并展示本节不做讲解(感兴趣的同学可以翻看我的历史博客,有写过的) 也有视频讲解: https://edu.csdn.net/course/detail/9604 [图片] 三,支付云函数的创建 首先看下我们支付云函数都包含那些内容 [图片] 简单先讲解下每个的用处 config下的index.js是做支付配置用的,主要配置支付相关的账号信息 lib是用的第三方的支付库,这里不做讲解。 重点讲解的是云函数入口 index.js 下面就来教大家如何去配置 1,配置config下的index.js, 这一步所需要做的就是把小程序appid,云开发环境ID,商户id,商户密匙。填进去。 [图片] 2,配置入口云函数 [图片] 详细代码如下,代码里注释很清除了,这里不再做单独讲解: [代码]const cloud = require('wx-server-sdk') cloud.init() const app = require('tcb-admin-node'); const pay = require('./lib/pay'); const { mpAppId, KEY } = require('./config/index'); const { WXPayConstants, WXPayUtil } = require('wx-js-utils'); const Res = require('./lib/res'); const ip = require('ip'); /** * * @param {obj} event * @param {string} event.type 功能类型 * @param {} userInfo.openId 用户的openid */ exports.main = async function(event, context) { const { type, data, userInfo } = event; const wxContext = cloud.getWXContext() const openid = userInfo.openId; app.init(); const db = app.database(); const goodCollection = db.collection('goods'); const orderCollection = db.collection('order'); // 订单文档的status 0 未支付 1 已支付 2 已关闭 switch (type) { // [在此处放置 unifiedorder 的相关代码] case 'unifiedorder': { // 查询该商品 ID 是否存在于数据库中,并将数据提取出来 const goodId = data.goodId let goods = await goodCollection.doc(goodId).get(); if (!goods.data.length) { return new Res({ code: 1, message: '找不到商品' }); } // 在云函数中提取数据,包括名称、价格才更合理安全, // 因为从端里传过来的商品数据都是不可靠的 let good = goods.data[0]; // 拼凑微信支付统一下单的参数 const curTime = Date.now(); const tradeNo = `${goodId}-${curTime}`; const body = good.name; const spbill_create_ip = ip.address() || '127.0.0.1'; // 云函数暂不支付 http 触发器,因此这里回调 notify_url 可以先随便填。 const notify_url = 'http://www.qq.com'; //'127.0.0.1'; const total_fee = good.price; const time_stamp = '' + Math.ceil(Date.now() / 1000); const out_trade_no = `${tradeNo}`; const sign_type = WXPayConstants.SIGN_TYPE_MD5; let orderParam = { body, spbill_create_ip, notify_url, out_trade_no, total_fee, openid, trade_type: 'JSAPI', timeStamp: time_stamp, }; // 调用 wx-js-utils 中的统一下单方法 const { return_code, ...restData } = await pay.unifiedOrder(orderParam); let order_id = null; if (return_code === 'SUCCESS' && restData.result_code === 'SUCCESS') { const { prepay_id, nonce_str } = restData; // 微信小程序支付要单独进地签名,并返回给小程序端 const sign = WXPayUtil.generateSignature({ appId: mpAppId, nonceStr: nonce_str, package: `prepay_id=${prepay_id}`, signType: 'MD5', timeStamp: time_stamp }, KEY); let orderData = { out_trade_no, time_stamp, nonce_str, sign, sign_type, body, total_fee, prepay_id, sign, status: 0, // 订单文档的status 0 未支付 1 已支付 2 已关闭 _openid: openid, }; let order = await orderCollection.add(orderData); order_id = order.id; } return new Res({ code: return_code === 'SUCCESS' ? 0 : 1, data: { out_trade_no, time_stamp, order_id, ...restData } }); } // [在此处放置 payorder 的相关代码] case 'payorder': { // 从端里出来相关的订单相信 const { out_trade_no, prepay_id, body, total_fee } = data; // 到微信支付侧查询是否存在该订单,并查询订单状态,看看是否已经支付成功了。 const { return_code, ...restData } = await pay.orderQuery({ out_trade_no }); // 若订单存在并支付成功,则开始处理支付 if (restData.trade_state === 'SUCCESS') { let result = await orderCollection .where({ out_trade_no }) .update({ status: 1, trade_state: restData.trade_state, trade_state_desc: restData.trade_state_desc }); let curDate = new Date(); let time = `${curDate.getFullYear()}-${curDate.getMonth() + 1}-${curDate.getDate()} ${curDate.getHours()}:${curDate.getMinutes()}:${curDate.getSeconds()}`; } return new Res({ code: return_code === 'SUCCESS' ? 0 : 1, data: restData }); } case 'orderquery': { const { transaction_id, out_trade_no } = data; // 查询订单 const { data: dbData } = await orderCollection .where({ out_trade_no }) .get(); const { return_code, ...restData } = await pay.orderQuery({ transaction_id, out_trade_no }); return new Res({ code: return_code === 'SUCCESS' ? 0 : 1, data: { ...restData, ...dbData[0] } }); } case 'closeorder': { // 关闭订单 const { out_trade_no } = data; const { return_code, ...restData } = await pay.closeOrder({ out_trade_no }); if (return_code === 'SUCCESS' && restData.result_code === 'SUCCESS') { await orderCollection .where({ out_trade_no }) .update({ status: 2, trade_state: 'CLOSED', trade_state_desc: '订单已关闭' }); } return new Res({ code: return_code === 'SUCCESS' ? 0 : 1, data: restData }); } } } [代码] 其实我们支付的关键功能都在上面这些代码里面了。 [图片] 再来看下,支付的相关流程截图 [图片] 上图就涉及到了我们的订单列表,支付状态,支付成功后的回调。 今天就先讲到这里,后面会继续给大家讲解支付的其他功能。比如支付成功后的消息推送,也是可以借助云开发实现的。 由于源码里涉及到一些私密信息,这里就不单独贴出源码下载链接了,大家感兴趣的话,可以私信我,或者在底部留言。单独找我要源码也行(微信2501902696) 视频讲解地址:https://edu.csdn.net/course/detail/24770
2019-06-11 - 闲来没事,来发帖
https://developers.weixin.qq.com/s/agUBgEmm7Cad 附上一张截图 [图片] ## 框架目录 assets 资源文件目录 components 框架自带组件 pages 页面目录 plugin 扩展组件目录 --js 扩展js组件 --pages 组件页面 --组件文件夹 themes 框架样式 --base.wxss 主题配置 --generic.wxss 公共引用 utils 框架主体 --main.js 框架主程序 需要在 app.js 引用 import './utils/main.js' --config.js 配置文件 --httpconfig.js 公共请求配置 --reader.js 分页读取器 wx.Reader('请求api',take) --util.js 辅助类 通过 global 直接调用 --wx.js wx.扩展类 wx.post 请求 wx.upload 上传 wx.link 跳转 --extend 框架扩展目录 ## 为什么UI组件那么少? 1:时间少 2:设计图与UI框架差距太大,所以更是一种累赘 3:没有3 ##完全开源,代码不压缩,就是注释实在懒得写
2019-08-16 - 小程序分包加载
背景音: - 随着开发功能越来越多, 小程序初次加载速度实在是吃力. 万般煎熬之迹, 只听晴天霹雳一声巨响, 腾讯放了一个大招, 针对小程序做了文件大小限制做了升级. 分包也就此诞生 环境要求: - 微信 6.6 客户端,1.7.3 及以上基础库开始支持,请更新至最新客户端版本,开发者工具请使用 1.01.1712150 及以上版本 在下左木子, 接下来给大家讲解如何运用分包. 废话不多说, 咱看图! 保你看完明明白白: [图片] [图片] [图片]
2019-07-25 - 小程序地图学习之获取位置 获取经纬度 获取地名 获取地址
我们在做小程序开发时,难免会遇到地图相关的开发,而小程序已经为我们提供的比较完善的地图组件。我们只需要调用相关的api就可以实现大致的功能。如:获取经纬度,获取位置,获取地址,获取地名。结下来就具体给大家讲解。 老规矩先看效果图 [图片] 接下来我们就来看看具体实现步骤 一,定义一个按钮来调用位置获取的api [代码]<!--index.wxml--> <button bindtap='getLocation'>获取位置信息</button> <text>{{jingwei}}</text> <text>{{address}}</text> <text>{{name}}</text> [代码] 二,调用获取地理位置的方法 [代码]//index.js Page({ getLocation() { let that = this; wx.chooseLocation({ success: function(res) { console.log(res) var latitude = res.latitude var longitude = res.longitude; that.setData({ jingwei: "经纬度:" + longitude + ", " + latitude, address: " 地址:" + res.address, name: " 地名:" + res.name }) } }); } }) [代码] 其实到这里我们就可以实现获取经纬度,获取位置信息的功能了。 但是呢??现在小程序调用用户位置信息时,需要用户授权,如下图,如果用户点击了拒绝,我们就没有办法调用地图获取位置信息了。 [图片] 所以呢,我们要想实现一个完整的获取用户位置信息的功能,就要在监测到用户拒绝的位置权限时,引导用户去重新授权。这样才是一个友好的健壮的程序。下面就来教大家如何引导用户去打开授权。 三,在app.json里注册位置权限 [图片] 上图红色框里就是我们的位置权限的注册代码,app.json的完整代码如下。 [代码]{ "pages": [ "pages/index/index", "pages/setting/setting" ], "window": { "backgroundTextStyle": "light", "navigationBarBackgroundColor": "#fff", "navigationBarTitleText": "WeChat", "navigationBarTextStyle": "black" }, "permission": { "scope.userLocation": { "desc": "你的位置信息将用于小程序位置接口的效果展示" } }, "sitemapLocation": "sitemap.json" } [代码] 四,定义检查位置权限是否打开的方法 [代码] //校验位置权限是否打开 checkLocation() { let that = this; //选择位置,需要用户授权 wx.getSetting({ success(res) { if (!res.authSetting['scope.userLocation']) { wx.authorize({ scope: 'scope.userLocation', success() { wx.showToast({ //这里提示失败原因 title: '授权成功!', duration: 1500 }) }, fail() { that.showSettingToast('需要授权位置信息'); } }) } } }) }, [代码] 这个方法就是来检查用户的位置权限是否授权,如果没有授权,就弹窗提示用户去授权页授权。弹窗代码如下: [代码] // 打开权限设置页提示框 showSettingToast: function(e) { wx.showModal({ title: '提示!', confirmText: '去设置', showCancel: false, content: e, success: function(res) { if (res.confirm) { wx.navigateTo({ url: '../setting/setting', }) } } }) }, [代码] 至此就可以实现一个完整的获取用户位置信息的小程序了,index.js完整代码如下 [代码]//index.js Page({ getLocation() { this.checkLocation(); let that = this; wx.chooseLocation({ success: function(res) { console.log(res) var latitude = res.latitude var longitude = res.longitude; that.setData({ jingwei: "经纬度:" + longitude + ", " + latitude, address: " 地址:" + res.address, name: " 地名:" + res.name }) } }); }, //校验位置权限是否打开 checkLocation() { let that = this; //选择位置,需要用户授权 wx.getSetting({ success(res) { if (!res.authSetting['scope.userLocation']) { wx.authorize({ scope: 'scope.userLocation', success() { wx.showToast({ //这里提示失败原因 title: '授权成功!', duration: 1500 }) }, fail() { that.showSettingToast('需要授权位置信息'); } }) } } }) }, // 打开权限设置页提示框 showSettingToast: function(e) { wx.showModal({ title: '提示!', confirmText: '去设置', showCancel: false, content: e, success: function(res) { if (res.confirm) { wx.navigateTo({ url: '../setting/setting', }) } } }) }, }) [代码] 从代码中可以看到,我们在用户拒绝授权时的提示框,点击会跳转到setting页,setting也是我们自己的页面,但是这个页面特别简单。就定义一个button。 [代码]<!--pages/setting/setting.wxml--> <button class="button" open-type="openSetting" type='primary'> 打开授权设置页 </button> [代码] 为什么要这么做呢,因为微信不允许我们直接打开权限设置页,必须通过button组件提供的开发能力去到设置页,这里的开放能力就是open-type=“openSetting” 中的openSetting。我们点击按钮后就到了权限设置页。 [图片] 这样就可以引导用户再次授权了。 有任何关于编程的问题都可以加我微信2501902696(备注编程开发) 编程小石头,码农一枚,非著名全栈开发人员。分享自己的一些经验,学习心得,希望后来人少走弯路,少填坑。 完整的源码可以加老师微信获取,也可以关注下面老师公号,回复“地图源码” 获取。 [图片]
2019-05-08 - 10行代码实现微信小程序支付功能,使用小程序云开发实现小程序支付功能(含源码)
前面给大家讲过一个借助小程序云开发实现微信支付的,但是那个操作稍微有点繁琐,并且还会经常出现问题,今天就给大家讲一个简单的,并且借助官方支付api实现小程序支付功能。 传送门 借助小程序云开发实现小程序支付功能 老规矩,先看本节效果图 [图片] 我们实现这个支付功能完全是借助小程序云开发实现的,不用搭建自己的服务器,不用买域名,不用备案域名,不用支持https。只需要一个简单的云函数,就可以轻松的实现微信小程序支付功能。 核心代码就下面这些 [图片] 一,创建一个云开发小程序 关于如何创建云开发小程序,这里我就不再做具体讲解。不知道怎么创建云开发小程序的同学,可以去翻看我之前的文章,或者看下我录制的视频:https://edu.csdn.net/course/play/9604/204528 创建云开发小程序有几点注意的 1,一定不要忘记在app.js里初始化云开发环境。 [图片] 2,创建完云函数后,一定要记得上传 二, 创建支付的云函数 1,创建云函数pay [图片] [图片] 三,引入三方依赖tenpay 我们这里引入三方依赖的目的,是创建我们支付时需要的一些参数。我们安装依赖是使用里npm 而npm必须安装node,关于如何安装node,我这里不做讲解,百度一下,网上一大堆。 1,首先右键pay,然后选择在终端中打开 [图片] 2,我们使用npm来安装这个依赖。 在命令行里执行 npm i tenpay [图片] 安装完成后,我们的pay云函数会多出一个package.json 文件 [图片] 到这里我们的tenpay依赖就安装好了。 四,编写云函数pay [图片] 完整代码如下 [代码]//云开发实现支付 const cloud = require('wx-server-sdk') cloud.init() //1,引入支付的三方依赖 const tenpay = require('tenpay'); //2,配置支付信息 const config = { appid: '你的小程序appid', mchid: '你的微信商户号', partnerKey: '微信支付安全密钥', notify_url: '支付回调网址,这里可以先随意填一个网址', spbill_create_ip: '127.0.0.1' //这里填这个就可以 }; exports.main = async(event, context) => { const wxContext = cloud.getWXContext() let { orderid, money } = event; //3,初始化支付 const api = tenpay.init(config); let result = await api.getPayParams({ out_trade_no: orderid, body: '商品简单描述', total_fee: money, //订单金额(分), openid: wxContext.OPENID //付款用户的openid }); return result; } [代码] 一定要注意把appid,mchid,partnerKey换成你自己的。 到这里我们获取小程序支付所需参数的云函数代码就编写完成了。 不要忘记上传这个云函数。 [图片] 出现下图就代表上传成功 [图片] 五,写一个简单的页面,用来提交订单,调用pay云函数。 [图片] 这个页面很简单, 1,自己随便编写一个订单号(这个订单号要大于6位) 2,自己随便填写一个订单价(单位是分) 3,点击按钮,调用pay云函数。获取支付所需参数。 下图是官方支付api所需要的一些必须参数。 [图片] 下图是我们调用pay云函数获取的参数,和上图所需要的是不是一样。 [图片] 六,调用wx.requestPayment实现支付 下图是官方的示例代码 [图片] 这里不在做具体讲解了,把完整代码给大家贴出来 [代码]// pages/pay/pay.js Page({ //提交订单 formSubmit: function(e) { let that = this; let formData = e.detail.value console.log('form发生了submit事件,携带数据为:', formData) wx.cloud.callFunction({ name: "pay", data: { orderid: "" + formData.orderid, money: formData.money }, success(res) { console.log("提交成功", res.result) that.pay(res.result) }, fail(res) { console.log("提交失败", res) } }) }, //实现小程序支付 pay(payData) { //官方标准的支付方法 wx.requestPayment({ timeStamp: payData.timeStamp, nonceStr: payData.nonceStr, package: payData.package, //统一下单接口返回的 prepay_id 格式如:prepay_id=*** signType: 'MD5', paySign: payData.paySign, //签名 success(res) { console.log("支付成功", res) }, fail(res) { console.log("支付失败", res) }, complete(res) { console.log("支付完成", res) } }) } }) [代码] 到这里,云开发实现小程序支付的功能就完整实现了。 实现效果 1,调起支付键盘 [图片] 2,支付完成 [图片] 3,log日志,可以看出不同支付状态的回调 [图片] 上图是支付成功的回调,我们可以在支付成功回调时,改变订单支付状态。 下图是支付失败的回调, [图片] 下图是支付完成的状态。 [图片] 到这里我们就轻松的实现了微信小程序的支付功能了。是不是很简单啊。 如果感觉图文不是很好理解,我后面会录制视频讲解。 视频讲解 https://edu.csdn.net/course/detail/25701 源码地址: https://github.com/qiushi123/xiaochengxu_demos [图片] 014云开发实现小程序支付,就是我们的源码,如果你导入源码或者学习过程中有任何问题,都可以加我微信2501902696(备注小程序)
2019-08-14 - 微信小程序客服消息回复开发
概述 微信小程序为了提高小程序的服务质量,提供了客服消息能力,目的是为了让用户快捷地与小程序服务提供方进行沟通。小程序的客服消息回复有两种方式:一种是接入用户消息到微信公众平台网页版客服工具和客服小助手小程序进行客服消息回复,接入后客服可以看到用户留言,根据用户问题进行专门解答。一种是开启消息推送,当客服无法及时回复的时候能够指导用户联系客服人员或者解决问题。 如果需要接入微信公众平台网页版客服工具和客服小助手,只需要在小程序后台->客服里头添加客服人员就可以,客服人员就可以实时接收到用户消息并且与用户沟通。 如果需要开启消息推送可以参考下文的接入过程。 消息推送开发准备条件 在小程序中设置button组件并且把open-type属性设置为contact 前往小程序后台开发->开发设置->消息推送配置相关信息,可在此指定消息加密方式和数据格式。注意当开启了消息推送,普通微信用户向小程序客服发消息时,微信服务器会先将消息 POST 到开发者填写的 URL 上。在此处的填写我选择的是明文模式和JSON数据格式。 开发过程 处理初次验证 填写完消息推送的配置并且提交后,微信服务器将发送GET请求到填写的URL地址进行校验。因此首先要进行的就是针对微信服务器的初次校验做处理。微信官方在消息推送章节已经提出了校验代码此处便不再重复。 [代码]//微信服务器验证处理 if (isset($_GET['echostr'])) { //调用微信提供的校验代码 if ($this->checkSignature() == false) { exit(); } $echoStr = $_GET['echostr']; echo $echoStr; exit; } [代码] 处理消息 校验成功后,微信服务器会将用户在客服会话中的消息转发到开发者的服务器上,针对微信服务器传入的消息的类型,开发者们可以编写不同的业务逻辑处理。以用户在客服会话中写入文本为例:根据选择的数据格式JSON或者XML,微信服务器会传入相应格式的数据包。根据"MsgType"可以分辨微信服务器转发的是何种类型消息,并编写不同的业务逻辑。 [代码]//1接受微信推送消息 $message = $GLOBALS["HTTP_RAW_POST_DATA"]; $message = json_decode($message, true); //2判定用户发送消息的类型 if (!empty($message['MsgType']) && $message['MsgType'] == 'text') { //do something } [代码] 在处理完微信转发的消息之后,开发者可以根需要调用服务端的客服消息发送接口发送消息给用户。 [代码]$fromUsername = $message['FromUserName']; //发送者openid $url = "https://api.weixin.qq.com/cgi-bin/message/custom/send?access_token=$access_token";//根据需要获取access_token $data = array( "touser" => $fromUsername, "msgtype" => "text", "text" => array( "content" => "客服消息推送测试" ) ); $data = json_encode($data, JSON_UNESCAPED_UNICODE); $result = httpRequest($url, "post", $data); $result = json_decode($result, true); if ($result['errcode'] == 0) { //当处理成功之后返回空字符串或者success都可以防止微信服务器重新发起请求 echo ""; exit; } [代码] 转发客服消息 小程序设置了推送消息之后,还可以接入到网页版客服工具中,只需要设置返回数据的MsgType为transfer_customer_service返回给微信服务器。 [代码]//设置转发数据 $transferData = array( "ToUserName" => $message['FromUserName'],//用户的OpenID "FromUserName" => $message['ToUserName'],//小程序原始id "CreateTime" => $message['CreateTime'],//创建时间 "MsgType" => "transfer_customer_service",//指定为transfer_customer_service 消息将会转发到客服工具中 ); $transferData = json_encode($transferData, JSON_UNESCAPED_UNICODE); [代码] 完整代码演示 [代码]//接受微信服务器转发的请求。 public function getMessage() { // 判断是否为微信验证消息 if (isset($_GET['echostr'])) { if ($this->checkSignature() == false) { exit(); } $echoStr = $_GET['echostr']; echo $echoStr; exit; } //接受微信推送消息 $message = $GLOBALS["HTTP_RAW_POST_DATA"]; if (!empty($message)) { $access_token = $this->getAccess();//根据需要获取小程序对应的 access_token //设置转发客服消息 $fromUsername = $message['FromUserName'];//消息发起用户的open_id $transferData = array( "ToUserName" => $fromUsername,//接收方帐号(用户的OpenID) "FromUserName" => $message['ToUserName'],//小程序原始id "CreateTime" => $message['CreateTime'],//创建时间 "MsgType" => "transfer_customer_service",//指定为transfer_customer_service 消息将会转发到客服工具中 ); $transferData = json_encode($transferData, JSON_UNESCAPED_UNICODE); $message = json_decode($message, true); //判定消息类型并处理 if (!empty($message['MsgType']) && $message['MsgType'] == 'text') { //调用send接口发送相对应的消息 $url = "https://api.weixin.qq.com/cgi-bin/message/custom/send?access_token=$access_token"; $data = array( "touser" => $fromUsername, "msgtype" => "text", "text" => array( "content" => "客服消息推送测试" ) ); $data = json_encode($data, JSON_UNESCAPED_UNICODE); $result = httpRequest($url, "post", $data); $result = json_decode($result, true); //回复消息之后 不转发消息到客服系统 返回success 或者空字符串 避免微信提示严重错误 if ($result['errcode'] == 0) { echo ""; exit; } //回复消息之后 转发客服消息到客服系统 将$transferData['MsgType']设置为transfer_customer_service //if ($result['errcode'] == 0) { //echo $transferData; //exit; //} } } } /** * 处理微信验证函数 */ public function checkSignature() { $signature = $_GET['signature']; $timestamp = $_GET['timestamp']; $nonce = $_GET['nonce']; $token = "customer12"; //填写在后台配置的Token(令牌) $tmpArr = array($token, $timestamp, $nonce); sort($tmpArr, SORT_STRING); $tmpStr = implode($tmpArr); $tmpStr = sha1($tmpStr); //加密 if ($tmpStr == $signature) { return true; } else { return false; } } /** * CURL请求 * @param $url 请求url地址 * @param $method 请求方法 get post * @param null $postfields post数据数组 * @param array $headers 请求header信息 * @param bool|false $debug 调试开启 默认false * @return mixed */ function httpRequest($url, $method, $postfields = null, $headers = array(), $debug = false) { $method = strtoupper($method); $ci = curl_init(); curl_setopt($ci, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0); curl_setopt($ci, CURLOPT_USERAGENT, "Mozilla/5.0 (Windows NT 6.2; WOW64; rv:34.0) Gecko/20100101 Firefox/34.0"); curl_setopt($ci, CURLOPT_CONNECTTIMEOUT, 60); /* 在发起连接前等待的时间,如果设置为0,则无限等待 */ curl_setopt($ci, CURLOPT_TIMEOUT, 7); /* 设置cURL允许执行的最长秒数 */ curl_setopt($ci, CURLOPT_RETURNTRANSFER, true); switch ($method) { case "POST": curl_setopt($ci, CURLOPT_POST, true); if (!empty($postfields)) { $tmpdatastr = is_array($postfields) ? http_build_query($postfields) : $postfields; curl_setopt($ci, CURLOPT_POSTFIELDS, $tmpdatastr); } break; default: curl_setopt($ci, CURLOPT_CUSTOMREQUEST, $method); /* //设置请求方式 */ break; } $ssl = preg_match('/^https:\/\//i', $url) ? TRUE : FALSE; curl_setopt($ci, CURLOPT_URL, $url); if ($ssl) { curl_setopt($ci, CURLOPT_SSL_VERIFYPEER, FALSE); // https请求 不验证证书和hosts curl_setopt($ci, CURLOPT_SSL_VERIFYHOST, FALSE); // 不从证书中检查SSL加密算法是否存在 } curl_setopt($ci, CURLOPT_FOLLOWLOCATION, 1); curl_setopt($ci, CURLOPT_MAXREDIRS, 2);/*指定最多的HTTP重定向的数量,这个选项是和CURLOPT_FOLLOWLOCATION一起使用的*/ curl_setopt($ci, CURLOPT_HTTPHEADER, $headers); curl_setopt($ci, CURLINFO_HEADER_OUT, true); $response = curl_exec($ci); $requestinfo = curl_getinfo($ci); $http_code = curl_getinfo($ci, CURLINFO_HTTP_CODE); if ($debug) { echo "=====post data======\r\n"; var_dump($postfields); echo "=====info===== \r\n"; print_r($requestinfo); echo "=====response=====\r\n"; print_r($response); } curl_close($ci); return $response; } [代码]
2019-05-29 - 绑定事件修改数组的属性
这个问题从下午困扰我到了晚上,简直amazing [图片] [图片] [图片] 通过绑定事件修改数组的属性值,搞了一下午都没搞清楚,不知道哪里有错误,一直没查出原因,现在总算搞好了,在csdn上看到的一篇文章才弄明白的
2019-07-27 - 左滑删除
话不多说直接看代码片段 https://developers.weixin.qq.com/s/ifebUum77haK 感觉好用点个赞
2019-08-09 - 数组再分的实现
实现数组再分 开发的时候遇到了一个比较少见的关于数组的操作,如何根据数组中的某个键的属性值是否相同,重新整合数组中的值,如同代码示例中的根据数组的name属性,将name相同的数组元素重新组合在一起。最初的设想是for循环找出每个元素之后,内嵌一个for循环进行比对,找出相应数据存放,结果是数组中的数据重复打印。于是从网上搜索并解决问题,以下便是代码示例。 代码示例 [代码]//实验代码 未分组变量 arrayDemo: [{ "name": "z", "age": 15, "high": 10, "phone": 12345678911 }, { "name": "q", "age": 15, "high": 10, "phone": 12345678910 }, { "name": "w", "age": 15, "high": 10, "phone": 12345678912 }, { "name": "e", "age": 15, "high": 10, "phone": 12345678913 }, { "name": "r", "age": 15, "high": 10, "phone": 12345678914 }, { "name": "z", "age": 15, "high": 10, "phone": 12345678915 }, { "name": "z", "age": 15, "high": 10, "phone": 12345678916 }, { "name": "f", "age": 15, "high": 10, "phone": 12345678917 } ] //调用数组排列方法 this.arrayGroup(arrayDemo); /** * 数组分组 */ arrayGroup: function(array) { var groups = []; //存放新数组 for (var i = 0; i < array.length; i++) { //遍历数组每一项 // 读取每条数据的名称 取出 分类的条件 var groupName = array[i].name; var groupValue = { // 符合分类条件的属性值组合成对象放入新数组中value属性中 'age': array[i].age, 'high': array[i].high, 'phone': array[i].phone } var groupItem = { //新数组中存放的对象 'name': '', value: [] } groupItem.name = groupName; groupItem.value.push(groupValue); if (i == 0) { //设置为基准值进行对比 groups.push(groupItem); } else { //遍历剩余数组项 var index = -1; //第二层循环找到属性值相同的数组成员并且放入新数组中 for (var k = 0; k < groups.length; k++) { if (groupName == groups[k].name) { index = k; break; } } if (index == -1) { //没有找到 groups.push(groupItem); } else { groups[k].value.push(groupValue); //将属性值存放到新数组中 } } } console.log('groups', groups); } [代码] 结果示例 [图片] 解决这个问题之后,鉴于花费在这个问题上的时间,重新搜集了有关数组的操作方法记录下来,以减少下次花费时间。 数组相关方法扩展 filter() filter()方法创建一个新数组, filter为数组中的每个元素调用一次 callback 函数,并利用调用callback函数返回true或等价于true的值的元素创建一个新数组。 [代码]filter语法: var newArray = arr.filter(callback(element[, index[, array]])[, thisArg]) /** * filterdemo 过滤掉所有小于10的数组项 */ filterDemo: function() { var filterArray = [12, 14, 8, 9, 100]; var filterResult = filterArray.filter(function(item) { if (item > 10) { return true; } }); console.log('filterResult', filterResult) } [代码] [图片] map() map() 方法创建一个新数组,其结果是该数组中的每个元素都调用一个提供的函数后返回的结果。 [代码]//map语法: var new_array = arr.map(function callback(currentValue[, index[, array]]) { // Return element for new_array }[, thisArg]) /** * map方法示例 所有数组成员乘以5 */ mapDemo: function() { var mapArray = [1, 2, 3, 4, 5]; var mapResult = mapArray.map(function(item) { return item * 5 }) console.log('mapResult', mapResult); } [代码] [图片] foreach() foreach()方法对数组中的每个元素执行一次提供的函数。 [代码]语法: array.forEach(function(currentValue, index, arr), thisValue) /** * forEach 代码示例 */ forEachDemo: function() { var arr = [1, 5, 8, 9] arr.forEach(function(item) { console.log('item', item) }) } /** * forEachDemo 无法使用break跳出循环 */ forEachDemo: function() { var arr = [1, 5, 8, 9] arr.forEach(function(item) { if (item == 1) { break } console.log('item', item) }) } /** * forEach 被调用的时候,不会直接改变调用它的对象,但是对象可能会被callback改变原数组为 var words = ['one', 'two', 'three', 'four'];调用foreach后 原数组被改变 */ forEachDemo: function() { var words = ['one', 'two', 'three', 'four']; words.forEach(function(word) { console.log(word); if (word === 'two') { words.shift(); } }); } [代码] [图片] foreach无法使用break跳出循环 [图片] forEach 被调用的时候,不会直接改变调用它的对象,但是对象可能会被callback改变 [图片] for-in for-in常用来遍历对象,以任意顺序去遍历一个对象可枚举的属性 [代码] /** * for-in 实例 输出对象的属性 */ forInDemo: function() { var obj = { name: 'ar', color: 'yellow', day: 'sunday', number: 6, age:15 } for (var key in obj) { console.log(obj[key]) } } [代码] [图片] 在合适的场景下选用合适的数组操作方法,可以使得复杂的代码变得更加的易读和简练,更容易让人理解。
2019-06-30 - 微信小程序云函数生成、存储、比对短信验证码的完整SDK工具
微信小程序云开发管理短信验证码并不是一件容易的事情,它不像其他web服务器开发中可以将验证码存放到session或者cookie中,你只能将其存到云开发的数据库中,实现起来非常困难。一个简单的需要短信验证码的注册、登录功能可能需要花费1、2天的工期。 可以完成验证码的生成、存储、有效期管理、校验等所有操作。 考虑到实现困难,榛子云短信(http://smsow.zhenzikj.com)为大家实现了所有验证码管理的功能实现,你只需要调用sdk中的云函数即可。下面让我们看看有多简单: 使用前准备: 首先,您需要在云开发控制台中创建名称为sms-record的集合,用于存放验证码信息的,这个集合您无需维护。如下图: [图片] 其次,修改集合的权限,如下图: [图片] 1. 发送验证码短信 [代码]wx.cloud.callFunction({[代码] [代码] [代码][代码]name: [代码][代码]'zhenzisms'[代码][代码],[代码] [代码] [代码][代码]data: {[代码] [代码] [代码][代码]$url: [代码][代码]'sendCode'[代码][代码],[代码] [代码] [代码][代码]apiUrl: [代码][代码]'https://sms_developer.zhenzikj.com'[代码][代码],[代码] [代码] [代码][代码]message: [代码][代码]'您的验证码为:{code}'[代码][代码],[代码] [代码] [代码][代码]number: [代码][代码]'18511111111'[代码][代码],[代码] [代码] [代码][代码]messageId: [代码][代码]'aaabbb'[代码][代码],[代码] [代码] [代码][代码]seconds: 60[代码] [代码] [代码][代码]}[代码] [代码] [代码][代码]}).then((res) => {[代码] [代码] [代码][代码]console.log(res.result.msg);[代码] [代码] [代码][代码]}).[代码][代码]catch[代码][代码]((e) => {[代码] [代码] [代码][代码]//console.log(e);[代码] [代码] [代码][代码]});[代码]apiUrl为请求地址,个人开发者使用https://sms_developer.zhenzikj.com,企业开发者使用https://sms.zhenzikj.com send方法用于单条发送短信 参数$url:方法名称,固定不变的 参数message:发送短信的模板,您可以修改成您需要的,其中{code}会自动替换为验证码的 参数number:接收者手机号码 参数messageId:该条信息的唯一标识,可用于查询 参数seconds:验证码的有效期,单位秒 返回结果是json格式的字符串, code: 发送状态,0为成功。非0为发送失败,可从data中查看错误信息 注:目前两条验证码短信获取的间隔时间是60秒,如需取消间隔时间或重新设置您需要修改sdk中的index.js文件,如下图: [图片] 2. 如何校验用户输入的验证码 [代码]wx.cloud.callFunction({[代码] [代码] [代码][代码]name: [代码][代码]'zhenzisms'[代码][代码],[代码] [代码] [代码][代码]data: {[代码] [代码] [代码][代码]$url: [代码][代码]'validateCode'[代码][代码],[代码] [代码] [代码][代码]apiUrl: [代码][代码]'https://sms_developer.zhenzikj.com'[代码][代码],[代码] [代码] [代码][代码]number: [代码][代码]'18511111111'[代码][代码],[代码] [代码] [代码][代码]code: [代码][代码]'6313'[代码] [代码] [代码][代码]}[代码] [代码] [代码][代码]}).then((res) => {[代码] [代码] [代码][代码]console.log(res.result.msg);[代码] [代码] [代码][代码]}).[代码][代码]catch[代码][代码]((e) => {[代码] [代码] [代码][代码]console.log(e);[代码] [代码] [代码][代码]});[代码]apiUrl为请求地址,个人开发者使用https://sms_developer.zhenzikj.com,企业开发者使用https://sms.zhenzikj.com 参数$url:方法名称,固定不变的 参数number:发送短信的手机号码 参数code:用户输入的验证码 返回结果是json对象,其中code项是错误代码,msg项是错误描述。code说明 success: 校验成功,empty:未调用sendCode方法生成验证码, code_expired:验证码已过期,过期时间是通过sendCode方法的参数seconds控制,code_error:验证码错误 完整实例和SDK下载: http://smsow.zhenzikj.com/doc/sdk.html 使用文档: http://smsow.zhenzikj.com/doc/weixinmp_yun_sdk_doc2.html
2019-07-24 - 微信小程序云开发常见问题及解决方案
我们在做微信小程序云开发的过程中,总会遇到各种奇葩的问题。今天就把我在小程序云开发过程中遇到的各种问题,及对应的解决方案总结在这里,方便以后自己回顾,也方便大家查看。 一,云函数调用失败问题(404011) [云函数] [login] 调用失败 Error: errCode: -404011 cloud function execution error | errMsg: cloud.callFunction:fail requestID , cloud function service error code -504002, error message Function not found: [login]; at cloud.callFunction api; [图片] 通常出现这种问题无非是下面2个原因 1,云函数没有部署,或者没有部署成功 2,你创建了多个云开发环境,没有配置对应的环境id 下面就针对这两个问题,具体说下解决方案 1,云函数没有部署,或者没有部署成功 [图片] 选中我们要部署的云函数,右键,如上图红色框里所示。如果点一次不能上传,就多点几次,一直到出现下面提示框为止 [图片] 2,你创建了多个云开发环境,没有配置对应的环境id 如果你创建了多个云开发环境,有时候开发者工具会脑残的不知道该选择使用那个云开发环境,这个时候,我们就要指定云开发环境了。 [图片] [图片] 如果你是多个开发环境,一定要注意环境名,和环境id必须一一对应。 二,云数据库set或者update数据时报如下错误(502001) Error: errCode: -502001 database request fail | errMsg: [FailedOperation.Insert] multiple write errors: [{write errors: [{E11000 duplicate key error collection: tnt-12p3936xo.x-j-l index: id dup key: { : “xjl” }}]}, {<nil>}] 详细错误如下图: [图片] 错误原因 造成这种错误的主要原因是因为,你修改的这条数据不是你创建的。我们直接操作云数据库时,在数据库里设置里如下权限。 [图片] 这个权限只能让你读所有人的数据,但是修改的话,你还是只能修改自己创建的数据。什么样的数据才是自己创建的呢。如下图。 [图片] 所以到这里我们就大概明白如何解决这个问题了。 解决方案 1,把_openid改为自己的openid 2,借助云函数。 这里说下借助云函数,因为你是没有办法直接修改别人的数据的,但是你借助云函数的话,就可以修改任何人的数据。 三,云函数老是不能上传成功,或者上传成功后是错误的。 [图片] 如果你上传云函数老是报上面的错误,就先关闭开发者工具。然后再打开,开发者工具,进入云开发管理界面,把错误的云函数删除了。 [图片] 然后再到你的代码目录里做下同步。 [图片] 这样我们就可以重新上传我们的pay函数了。 [图片] 上传云函数时,一定要记得选择如上图箭头所指的。 上传的时候,会有下面这个提示,可以忽略不管。 [图片] 出现下图就代码你云函数上传成功了。 [图片] 四, -404011 error message wx is not defined [图片] 问题是出在云函数里,就是你在云函数里使用数据库请求的时候代码写成下面这样了。 [图片] 解决方法 我们在云函数里使用cloud.database时,不用再写wx.cloud了,直接写成cloud.database就可以了。只有在小程序的页面的js里使用时,才需要写成 wx.cloud.database****** 五,-502005 Db or Table not exist. Please check your request, but if the problem cannot be solved, contact us 错误信息如下图所示 [图片] 其实这个问题算是官方的一个bug,因为我在app.js里已经初始化过了,但是这里找不到db或者数据表的原因还是因为我没有初始化过。奇怪的是这个问题我电脑上是没问题的,但是我的一些学员电脑上就会有这个问题。 [图片] 解决方法 这个解决方法就是在你云函数里再做下初始化。 [图片] 六,-404011 请先调用init 完成初始化 其实这个问题和上面第五个是一样的,解决方式同第五个 [图片] 云开发视频讲解 https://edu.csdn.net/course/detail/9604 持续更新中。。。。。。 有关于小程序的问题可以加我微信 2501902696(备注小程序)
2019-11-14 - 适用于小程序的二维码生成器(支持中文,多框架使用)
最近在开发中,需要生成自定义的二维码,于是做了一个包出来,分享给大家一起使用。适用于微信小程序的二维码生成器,基于Canvas生成,支持中文的输入。可在原生小程序,mpvue,taro中使用。(文末有一个使用示例)[图片] github地址(wxmp-qrcode)[https://github.com/Z-HNAN/wxmp-qrcode] 安装 [代码]npm install wxmp-qrcode [代码] 使用 创建一个canvas,设置其[代码]id[代码],与[代码]canvas-id[代码], 并设置canvas的样式,二维码基于其大小生成并居中 [代码]<canvas id="cav-qrcode" canvas-id="cav-qrcode"></canvas> [代码] 引入包并使用 [代码]import QR from 'wxmp-qrcode' QR.draw(str, canvasId, () => { console.log('draw success') } ) [代码] api [代码]/** * 根据canvas尺寸,画出合适居中的qrcode * @param {Object} str 二维码的内容 (必须) * @param {Object} canvasId canvasId的值 (必须) * @param {Object} $this 传入组件的this,兼容在组件中生成二维码 (可选,可省略该参数) * @param {Object} callback 回调函数 (可选) */ draw: function (str, canvasId, $this, callback) /** * 清除canvas内容 * @param {Object} canvasId canvasId (必须) * @param {Object} callback 回调函数 (可选) */ clear: function (canvasId, callback) [代码] 注意 canvas中 id, canvas-id必须保持一致 id 获取canvas节点,自动计算大小使用, 二维码大小基于canvas自动生成 canvas-id 绘制二维码使用 如果在组件中使用,需要传入组件的this,[代码]draw(str, canvasId, componentThis)[代码] 具体参见 https://developers.weixin.qq.com/miniprogram/dev/api/canvas/wx.createCanvasContext.html 可以保存二维码为临时图片地址 具体可参见 https://developers.weixin.qq.com/miniprogram/dev/api/canvas/wx.canvasToTempFilePath.html bug: 该方法有时保存的图片会有一个竖条。 [代码]createQrCode: function (content, canvasId) { QR.draw(content, canvasId) this.canvasToTempImage(canvasId) }, //获取临时缓存图片路径 canvasToTempImage: function (canvasId) { wx.canvasToTempFilePath({ canvasId, success: function (res) { let tempFilePath = res.tempFilePath; // 临时图片地址,可在放入图片src中使用 } }) } [代码] 原生小程序wxmp中使用 在项目设置中选择 [代码]使用npm模块[代码] [图片] 如果第一次使用npm模块,需要首先在根目录中[代码]npm init[代码], 之后再安装模块 [代码]npm i wxmp-qrcode[代码] 在工具中选择 [代码]构建npm[代码] [图片] index.wxml [代码]<view class="container"> <canvas id="{{canvasId}}" canvas-id="{{canvasId}}"></canvas> <button bindtap="creatQRCode"> 生成二维码 </button> </view> [代码] index.wxss [代码]canvas { border: 1rpx solid #eee; width: 400rpx; height: 400rpx; } button { margin-top: 100rpx; } [代码] index.js [代码]import QR from './qrcode' Page({ data: { canvasId: 'canvasId', QRdata: '你好 wxmp-qrcode' }, creatQRCode () { let str = this.data.QRdata let canvasId = this.data.canvasId QR.draw(str, canvasId) } }) [代码]
2019-09-01 - 云函数获取用户手机号吗?
小程序通过云函数获得用户手机号码? 思路解析, [图片] 了解了小程序的加密方式,我们就可以自己去解密我们需要的信息。如:最困住我们的用户手机号码? 官方是有案例的,想更多学习可以给与参考,但是估计要多看几遍,有node基础的就比较好理解一些。 [图片] 下面是官网地址: https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/signature.html#method-cloud 下面开始说我自己的方法; 1.首先构建云函数,需要两个云函数,一个用来解密,session_key,一个用来解密加密手机号码; [代码]//云函数:getSession; // 云函数入口文件 const cloud = require('wx-server-sdk') //npm install request-promise 通过终端下载npm install wx-server-sdk , const rp = require('request-promise'); cloud.init() // 云函数入口函数 exports.main = async (event, context) => { const _JSCODE = event.code const AccessToken_options = { method: 'GET', url: 'https://api.weixin.qq.com/sns/jscode2session', qs: { appid: '', //你的小程序appid; secret: '', //你的秘钥 grant_type: 'authorization_code', js_code: _JSCODE }, json: true }; const resultValue = await rp(AccessToken_options); return { resultValue } } [代码] 下载好需要的两个包,就可以对云函数初始化,执行npm init 有个起名字的环节,用过node的都知道,默认index.js,有个选择 [代码]package name: (gettoken) index.js version: (1.0.0) description: git repository: keywords: author: license: (ISC) About to write to D:\projects\zy_face_id_wxs\server\getToken\package.json: { "name": "index.js", "version": "1.0.0", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "author": "", "license": "ISC", "dependencies": { "request-promise": "^4.2.4", "wx-server-sdk": "^0.8.1" }, "devDependencies": {}, "description": "" } Is this OK? (yes) yes [代码] 这是初始化,终端的代码; 右键点击上传并部署到云端; 下面是客户端的代码; [代码] getPhoneNumber(e) { if (!e.detail.errMsg || e.detail.errMsg != "getPhoneNumber:ok") { wx.showModal({ content: '不能获取手机号码', showCancel: false }) return; } wx.showLoading({ title: '获取手机号中...', }) console.log(e) wx.login({ success(res) { if (res.code) { console.log(res.code) console.log(e.detail.iv) console.log(e.detail.encryptedData) wx.cloud.callFunction({ name: 'getSession', //调用云函数获取session_key; data: { code: res.code, }, success: res => { wx.hideLoading() // console.log(res.result.resultValue) var data = res.result.resultValue console.log(data) console.log(data.session_key) //获取到了session_key的值; }, fail: error => { console.log(error) } }) } } }) }, [代码] 2.构建第二个云函数 GetWX; [代码]// 云函数入口文件 const cloud = require('wx-server-sdk') // const requestpromise = require('request-promise'); var WXBizDataCrypt = require('./RdWXBizDataCrypt') // 用于手机号解密 cloud.init() exports.main = async (event, context) => { const session_key = event.session_key //appid写入你自己的appid,session_key 用第一个云函数的返回值; const pc = new WXBizDataCrypt(appid, session_key ) // -解密第一步 const data = pc.decryptData(event.encryptedData, event.iv) // 解密第二步 return { data } } [代码] 这个同样执行上面的步骤,npm install wx-server-sdk 和初始化npm init; 重点来了,解密所需要的js文件。 [图片]` 这两个文件已经上传到我的网盘里面,需要的请下载; https://pan.baidu.com/s/1VrS1gX_Bw3dKaZkQnNzy2A 提取码:cxnh; 格式和图片的保持一致,并上传到云端。 3.开始前端调用了; 代码如下; [代码]getPhoneNumber(e) { if (!e.detail.errMsg || e.detail.errMsg != "getPhoneNumber:ok") { wx.showModal({ content: '不能获取手机号码', showCancel: false }) return; } wx.showLoading({ title: '获取手机号中...', }) console.log(e) wx.login({ success(res) { if (res.code) { console.log(res.code) console.log(e.detail.iv) console.log(e.detail.encryptedData) wx.cloud.callFunction({ name: 'getSession', //调用云函数获取session_key; data: { code: res.code, }, success: res => { wx.hideLoading() var data = res.result.resultValue console.log(data) console.log(data.session_key) //获取到了session_key的值; const session_key=data.session_key wx.cloud.callFunction({ name:'getWX', //解析秘文,获得手机号码; data:{ session_key: session_key, encryptedData: e.detail.encryptedData, iv: e.detail.iv, }, success:res=>{ console.log(res) }, fail:err=>{ console.log(err) } }) }, fail: error => { console.log(error) } }) } } }) }, [代码] 4.输出的结果: [图片] 遇到问题了在私信问我,一一回答。
2019-07-17 - 分享使用tcb-router路由开发的云函数短信平台SDK
榛子应用市场 上篇文章我们分享了如何使用纯的云函数开发的榛子短信短信(http://smsow.zhenzikj.com)SDK,由于微信对于未付费云函数个数的限制,这种方法存在缺陷,经过改进,使用tcb-router作为路由,这样只需要整合到一个云函数中就行 下载sdk和demo: http://smsow.zhenzikj.com/sdkdownload/weixinmp_yun2.html 目前SDK中包含三个功能: send(发送短信)、balance(查询余额)、findSmsByMessageId(查询单条短信) SDK源码 [代码]// 云函数入口文件 const cloud = require('wx-server-sdk') const TcbRouter = require('tcb-router') const rq = require('request') const baseUrl = 'https://smsdeveloper.zhenzikj.com' cloud.init() // 云函数入口函数 exports.main = async (event, context) => { const app = new TcbRouter({ event }); app.router('send', async (ctx) => { ctx.body = new Promise(resolve => { rq({ url: baseUrl + '/sms/send.html', method: "POST", json: true, form: { apiUrl: event.apiUrl, appId: event.appId, appSecret: event.appSecret, message: event.message, number: event.number, messageId: event.messageId, } }, function (error, response, body) { resolve({ body: body, error: error }) }); // setTimeout(() => { // resolve('male'); // }, 500); }); }); app.router('balance', async (ctx) => { ctx.body = new Promise(resolve => { rq({ url: baseUrl + '/sms/balance.html', method: "POST", json: true, form: { apiUrl: event.apiUrl, appId: event.appId, appSecret: event.appSecret } }, function (error, response, body) { resolve({ body: body, error: error }) }); }); }); app.router('findSmsByMessageId', async (ctx) => { ctx.body = new Promise(resolve => { rq({ url: baseUrl + '/sms/findSmsByMessageId.html', method: "POST", json: true, form: { apiUrl: event.apiUrl, appId: event.appId, appSecret: event.appSecret, messageId: event.messageId } }, function (error, response, body) { resolve({ body: body, error: error }) }); }); }); return app.serve(); } [代码] 如何使用SDK [代码]//index.js const app = getApp() Page({ data: { }, onLoad: function() { }, // 发送短信 send: function () { wx.cloud.callFunction({ name: 'zhenzisms', data: { $url: 'send', apiUrl: 'https://sms_developer.zhenzikj.com', appId: '你的appId', appSecret: '你的appSecret', message: '你的验证码为:3333', number: '15811111111', messageId: '' } }).then((res) => { console.log(res.result.body); }).catch((e) => { //console.log(e); }); }, // 查询余额 balance: function () { wx.cloud.callFunction({ name: 'zhenzisms', data: { $url: 'balance', apiUrl: 'https://sms_developer.zhenzikj.com', appId: '你的appId', appSecret: '你的appSecret' } }).then((res) => { console.log(res.result.body); }).catch((e) => { //console.log(e); }); }, // 查询单条信息 findSmsByMessageId: function () { wx.cloud.callFunction({ name: 'zhenzisms', data: { $url: 'findSmsByMessageId', apiUrl: 'https://sms_developer.zhenzikj.com', appId: '你的appId', appSecret: '你的appSecret', messageId: 'aaaabbbbba' } }).then((res) => { console.log(res.result.body); }).catch((e) => { //console.log(e); }); } }) [代码]
2019-04-01 - 小程序裂变,性价比超高的拉新方法。
收费流量越来越贵,免费流量越来越难。 在这样的大环境下, 流量池、私域流量等在概念上填补了营销界种种焦虑。 概念、风口可以不追,很多时候都是新瓶装旧酒。 我们普通小商家又该怎么办?才能突破当前困境! 其实认真用好小程序裂变,已经够你忙的。 为了更好理解裂变,先通过比较简单的朋友圈慢慢展开裂变怎么玩。 [图片] 你看到的朋友圈卖货是不是都这样?已经把发得比较频繁的都屏蔽了?下面我们看看高手怎么玩的? [图片] 复制一下他的朋友圈文字: [代码] 酒店行业不能用一次性用品了,我们酒店采购的都是5星级酒店标准。每套接近20元采购成本,质量非常好。在外面买全一套,质量差一点也要三四十元。丢了可惜! 所以在我朋友圈的免费送。家里来客人或者出去玩都能用上!加图中我们同事二维码即可。500多套很快就抢完了。 [代码] 是不是这样的朋友,你就没有屏蔽;有的人还去领免费赠品。 那问题来了,人气是有了。钱呢?下面为你揭晓个中玄机。 裂变有个大原则必须遵循,就是利益前置。尽量让客户接触你的产品,感知到价值。他再产生需求的时候,选择的就是你。换一种说法,前期就是为了筛选精准客户。 通过这个朋友圈例子,我们先定义一下裂变角色: 酒店为裂变设计方。 酒店员工为裂变发送方。 酒店员工的微信好友为裂变接收方。 有了三个角色定义后,我们看看裂变是如何产生的。 如图示箭头方向,当裂变接收方看到朋友圈需要免费赠品。就会找裂变设计方,裂变设计方告诉裂变接收方要免费获取礼物,要先转发朋友圈。有一定比例的人愿意转发,这时裂变接收方转变为裂变发送方。裂变就这样不断裂变下去。 裂变效果的好坏,取决于裂变赠品选择和流通、裂变发送方的基数。 1、裂变赠品选择和流通 裂变赠品选择高价值、低价格、与主业相关。裂变赠品分为实物和虚拟。一般情况下,选择虚拟的比较好。比如上面的朋友圈例子,即使礼物不考虑成本。裂变数量大了,快递也是一个不小的成本。而用虚拟的,就完全不一样了。虚拟赠品如:软件、现金券、优惠券、服务等。 我们对上面朋友圈做一个虚拟赠品补充:当500多份实物赠品被领完了。裂变还在继续,我们不能浪费这免费的流量呀!可以来个优惠券赠品,愿意发朋友圈可以送一个大额优惠券。不愿意发朋友圈送一张小额优惠券表示歉意。如果在假期前夕,很多人就有住酒店需求。这优惠券就成了刚需。比实物价值要高很多。 [图片] 优惠券除了订酒店有优惠这个基础功能外,还有转赠功能。裂变中的赠品流通起来,就产生了二次裂变。最终会有大部分流到要用的人手里。裂变最终效果就是从这些小细节中拉开差距。 2、裂变发送方基数 裂变发送方基数,决定了裂变的威力。如果上面的例子裂变发送方不是酒店员工,而是网红、大V这些大流量主。将是怎样的结果?答案就不用我回答了。 虽然效果会好很多,但是基数的持续性还有非常大的优化空间。这时可以加入分销环节,让这些大流量主持续坚持做裂变发送方。让流量主看中的是成交提成,而不是广告费。 [图片] 上图小程序酒店后台看到,新人红包和分销两个功能。大流量主,用自己的分销码或链接告诉粉丝某酒店有新人红包。是不是非常恰当的理由?这里要注意的细节是,除了推广者有好处,消费者也必需要有好处。比如我们经常看到的:分享好友,各得 ## 元。 [图片] 注意:没有一定接待服务能力的酒店,不要轻易玩裂变+分销! 由于裂变、分销这些工具的威力太过猛烈,各大平台都有相对应的规定。以免信息泛滥影响用户体验!先说说国家规定,不得超过三级分销。微信的规定不得超过一级分销。不仅如此,小程序或公众号诱导分享也是不允许。详情请看官方指引。 https://developers.weixin.qq.com/miniprogram/product/#_5-行为规范 但也不必过于悲观,既然微信官方开放了小程序分销接口,肯定可以合规合法进行。下面我们看看大商家是怎么玩的: 摩拜、每日优鲜、真功夫。 [图片] 总结一下,通过朋友圈例子的三个裂变角色定义,我想大部分人都理解了裂变原理;再把优惠券、分销这些小程序功能用上;最后把文中提到的细节落实到位!流量还是非常不错的。但这只是裂变1.0. 有机会再在这里分享一下裂变2.0. (超级赠品+活码+带参数分销码) 关注我们,下次在开放社区发文章,你会有提示。 最后提醒一下: [图片] 当你感觉收费流量越来越贵,免费流量越来越难。 把收藏的这张图拿出来看看,不敢说每人都有效果。 但起码焦虑会少很多,因为流量获取的方向对了。 再差也不会低于同行太多。
2019-07-09 - 分享一个固定头和列的 table 组件的简单实现
本案案例基于 WePY 实现,大家可根据自身需要进行更改扩展。 代码地址>> 演示 [图片] 演示视频地址>> 实现原理 [图片] 橙色和紫色区域组成了横向滚动的 [代码]scroll-view[代码]。 红色虚线区域是纵向滚动的 [代码]scroll-view[代码]。但由于绿色区域设置了 [代码]pointer-events: none;[代码],即实际只能触摸橙色区域。通过在橙色区域绑定的 [代码]scroll[代码] 事件(纵向),实时设置绿色虚线区域的 [代码]scrollTop[代码]。 紫色区域是固定头部,绿色区域是固定列。左上角的绿色区域是横向与纵向共同固定的区域。 实现要点 绑定了 [代码]scroll[代码] 事件的 [代码]scroll-view[代码] 要指定 [代码]throttle: false[代码],否则回调函数有可能取不到最终位置的 [代码]scrollTop[代码] 值。官方文档目前未提及此属性,参考资料>>。 固定列需要设置 [代码]pointer-events: none;[代码],实现点击穿透。使得 [代码]tbody[代码] 能触发 [代码]scroll[代码] 事件,而不是为固定列也绑定 [代码]scroll[代码] 事件。 找出每列的最大单元格作为该列的宽度,当然你也可以显示设置。 peace out!👋 小程序 Bug 2019.09.03 更新 当将该组件至于 Popup 弹框,且该弹框通过 [代码]visibility: hidden/visible[代码] 切换,那么在 iOS 中,会使固定列([代码].table__fixed-columns[代码])的 [代码]pointer-events: none[代码] 失效。
2019-09-03 - Painter 一款轻量级的小程序海报生成组件
生成海报相信大家有的人都做过,但是canvas绘图的坑太多。大家可以试试这个组件。然后附上楼下大哥做的可视化拖拽生成painter代码的工具:链接地址https://developers.weixin.qq.com/community/develop/article/doc/000e222d9bcc305c5739c718d56813
2019-09-27 - 状态管理
因为使用了一些App、Page、Component的方法,所以对版本有一些要求:第一种是程序中没有使用插件,那么可以兼容低版本。第二种是项目中使用了插件,那么基础库版本最好在2.6.5以上,同时依赖开发者工具的 npm 构建。 使用方法 1、安装 Minix: [代码]npm install --save wx-minix [代码] 2、引入 Minix: 在App中导入一次即可 [代码]require('wx-minix') App{ store:{ //要管理的状态 state:{ num:1, sum:1 }, //每个页面都需要的状态 states:['num'] } } [代码] 支持Page和Component,Page方法相同 [代码]Component{ //在本页面中管理的状态 states:['sum'], //本页面禁止的全局状态(App中states需要禁止的) disableStates:[], //同样支持Computed computed:{ x(){ return this.data.state.num+10; } } } [代码] 状态同步的结果放在data的state中 [代码]<view>num:{{state.num}}</view> [代码] 改变状态 [代码]getApp().store.commit('num',3) [代码] GitHub
2019-06-06 - HQChart教程 - 如何快速的创建一个K线图
HQChart介绍 HQChart(https://github.com/jones2000/HQChart)是根据c++行情客户端软件移植到js平台上的一个项目。由金融图形库和麦语法(分析加语法)脚本执行器组成,所有指标计算都在前端完成,后台api只需要提供基础的行情数据就可以。 支持小程序,h5, 分2套代码完成,因为个个平台对canvas的支持和性能是不一样的,所有独立2套代码,最大的发挥个个平台canvas的性能。 支持手势操作(放大缩小,左右移动,十字光标) 。。。。。其他功能我就不介绍了,有兴趣的可以上github上看,上面有详细的使用教程和设计方案 创建K线图步骤 在WXML里面增加一个’canvas’元素, 并设置canvas-id, 定义好手势事件,bindtouchstart,bindtouchmove,bindtouchend [代码]<view class="container"> <canvas class="historychart" canvas-id="historychart" style="height:{{Height}}px; width:{{Width}}px;" bindtouchstart='historytouchstart' bindtouchmove='historytouchmove' bindtouchend='historytouchend'/> </view> [代码] 在对应的页面js文件里面 import HQChart组件 (wechathqchart/umychart.wechat.3.0.js) [代码]import { JSCommon } from "../wechathqchart/umychart.wechat.3.0.js"; [代码] 在page类里面创建HQChart组件,设置参数并绑定到’canvas’元素上 [代码]Page( { data: { Height: 0, Width: 0, }, HistoryChart: null, HistoryOption: { Type: '历史K线图', //如果要横屏类型使用:历史K线图横屏 Windows: //窗口指标 [ { Index: 'MA' }, { Index: 'VOL'}, { Index: 'RSI' } ], //ColorIndex: { Index: '五彩K线-双飞乌鸦' }, //五彩K线 //TradeIndex: { Index: '交易系统-BIAS' }, //交易系统 Symbol: "000001.sz", IsAutoUpate: true, //是自动更新数据 IsShowCorssCursorInfo: true, //是否显示十字光标的刻度信息 CorssCursorTouchEnd:true, //手离开屏幕 隐藏十字光标 KLine: { DragMode: 1, //拖拽模式 0 禁止拖拽 1 数据拖拽 2 区间选择 Right: 1, //复权 0 不复权 1 前复权 2 后复权 Period: 0, //周期 0 日线 1 周线 2 月线 3 年线 MaxReqeustDataCount: 1000, //数据个数 PageSize: 30, //一屏显示多少数据 Info: ["业绩预告", "公告", "互动易", "调研", "大宗交易", "龙虎榜"], //信息地雷 //Policy: ["30天地量", "20日均线,上穿62日均线"], //策略信息 InfoDrawType:0, DrawType: 0, }, //叠加股票 Overlay: [ //{Symbol:'000001.sz'} ], KLineTitle: //标题设置 { IsShowName: true, //显示股票名称 IsShowSettingInfo: true, //显示周期/复权 }, Border: //边框 { Left: 1, //左边间距 Right: 1, //右边间距 Top:30, Bottom:25, }, Frame: //子框架设置 [ { SplitCount: 3 }, { SplitCount: 2 }, { SplitCount: 2 }, ], ExtendChart: //扩展图形 [ { Name: 'KLineTooltip' } ], }, onLoad: function () { var self = this // 获取系统信息 wx.getSystemInfo({ success: function (res) { console.log(res); // 可使用窗口宽度、高度 //console.log('height=' + res.windowHeight); //console.log('width=' + res.windowWidth); self.setData({ Height: res.windowHeight, Width: res.windowWidth, Title: { Display:'block'}}); } }); }, onReady: function () { //创建历史K线类 var element = new JSCommon.JSCanvasElement(); element.ID = 'historychart'; element.Height = this.data.Height; //高度宽度需要手动绑定!! 微信没有元素类 element.Width = this.data.Width; this.HistoryChart = JSCommon.JSChart.Init(element); //把画布绑定到行情模块中 this.HistoryChart.SetOption(this.HistoryOption); }, //把画图事件绑定到hqchart控件上 historytouchstart: function (event) { if (this.HistoryChart) this.HistoryChart.OnTouchStart(event); }, historytouchmove: function (event) { if (this.HistoryChart) this.HistoryChart.OnTouchMove(event); }, historytouchend: function (event) { if (this.HistoryChart) this.HistoryChart.OnTouchEnd(event); }, }) [代码] 这样1个K线图+指标图就完成了 效果图 [图片] [图片] HQChart代码地址 地址: https://github.com/jones2000/HQChart
2019-06-15 - 2019,6 招帮助你更好的开发小程序
摘要: 微信小程序开发技巧。 作者:xiaoyueyue 原文:2019,帮助你更好的开发小程序 Fundebug经授权转载,版权归原作者所有。 前言 [代码]原生[代码]开发小程序有了两个项目,在原生开发小程序经验技巧方面有一些自己的总结,此篇文章做原创分享! 本文适合老手查看,新手请参阅官方文档,同步至github。 1. 发布订阅处理复杂逻辑 支持先订阅后发布,以及先发布后订阅 方法源码 [代码]var Event = (function() { var clientList = {}, pub, sub, remove; var cached = {}; sub = function(key, fn) { if (!clientList[key]) { clientList[key] = []; } // 使用缓存执行的订阅不用多次调用执行 cached[key + "time"] == undefined ? clientList[key].push(fn) : ""; if (cached[key] instanceof Array && cached[key].length > 0) { //说明有缓存的 可以执行 fn.apply(null, cached[key]); cached[key + "time"] = 1; } }; pub = function() { var key = Array.prototype.shift.call(arguments), fns = clientList[key]; if (!fns || fns.length === 0) { //初始默认缓存 cached[key] = Array.prototype.slice.call(arguments, 0); return false; } for (var i = 0, fn; (fn = fns[i++]); ) { // 再次发布更新缓存中的 data 参数 cached[key + "time"] != undefined ? (cached[key] = Array.prototype.slice.call(arguments, 0)) : ""; fn.apply(this, arguments); } }; remove = function(key, fn) { var fns = clientList[key]; // 缓存订阅一并删除 var cachedFn = cached[key]; if (!fns && !cachedFn) { return false; } if (!fn) { fns && (fns.length = 0); cachedFn && (cachedFn.length = 0); } else { if (cachedFn) { for (var m = cachedFn.length - 1; m >= 0; m--) { var _fn_temp = cachedFn[m]; if (_fn_temp === fn) { cachedFn.splice(m, 1); } } } for (var n = fns.length - 1; n >= 0; n--) { var _fn = fns[n]; if (_fn === fn) { fns.splice(n, 1); } } } }; return { pub: pub, sub: sub, remove: remove }; })(); [代码] 全局挂载使用 [代码]// app.js App({ onLaunch: function(e) { // 注册 storage,这是第二条 wx.Storage = Storage; // 注册发布订阅模式 wx.yue = Event; } }); [代码] 使用实例 [代码]// 添加收货地址页面订阅 onLoad: function (options) { wx.yue.sub("addAddress", function (data) { y.setData({ addAddress: data }) }) } /** * 生命周期函数--监听页面隐藏 */ onHide: function () { // 取消多余的事件订阅 wx.Storage.removeItem("addAddress"); }, onUnload: function () { // 取消多余的事件订阅 wx.yue.remove("addAddress"); } // 传递地址页面获取好数据传递 wx.yue.pub("addAddress", data.info); // 补充跳转返回 [代码] 注意:使用完成数据后要注意卸载,在页面被关闭时操作 2. Storage storage 管理封装,用法和上面的一致,挂载在全局对象上调用,使用介绍就不列了 [代码]const Storage = { setItem: function(key, obj, callback) { wx.setStorage({ key: key, data: obj, success: callback || function() {} }); }, getItem: function(key) { return wx.getStorageSync(key); }, removeItem: function(key) { wx.removeStorage({ key: key }); } }; [代码] 3. filter 计算属性 小程序也有计算属性,你知道吗? [代码]// 文件名称为 :filter.wxs // 不支持es6,Date,Number function filterOrderTitleName(status) { switch (status) { case "1": return "待支付"; case "2": return "待配送"; case "3": return "配送中"; case "4": return "已完成"; } } function filterPrice(str) { // 四舍五入 格式化数字 // toFix(8440.55,1) => 8440.6 var times = Math.pow(10, 2); var roundNum = Math.round(str * times) / times; return roundNum.toFixed(2); } module.exports = { filterOrderTitleName: filterOrderTitleName, filterPrice: filterPrice }; [代码] 使用实例,过滤处理打折后的金额小数位数 [代码]// 当前文件名:shoppingCart.wxml // wxs 文件顶部导入 <wxs src="../../filter/filter.wxs" module="filter"></wxs> <view class='offerPrice nowrap'>¥{{filter.filterPrice(item.plus*100*item.price/1000)}} <image class='youhuiBox' src="../../assets/youhuiBox.png"> <view class='youhuiText'>会员{{item.dazhe}}折</view> </image> </view> [代码] 4. flex Style 分享我常使用的自定义的一套 flex 样式,快速实现布局 [代码]/* -------------------------------------------------------------flex------------------------------------------------------- */ .center { display: flex; align-items: center; justify-content: center; } /* 单行水平垂直 */ .oneLineCenter { display: flex; display: -webkit-flex; justify-content: center; align-items: center; } /* 单行垂直居中,水平向左 */ .oneLineStart { display: flex; display: -webkit-flex; justify-content: flex-start; align-items: center; } /* 单行垂直居中,水平向右 */ .oneLineEnd { display: flex; display: -webkit-flex; justify-content: flex-end; align-items: center; } /* 单行垂直居中,水平保持间距 */ .oneLineAround { display: flex; display: -webkit-flex; justify-content: space-around; align-items: center; } /* 单行垂直居中,两端对齐 */ .oneLineBetween { display: flex; display: -webkit-flex; justify-content: space-between; align-items: center; } /* 超过单行设置的最大宽度,允许换行显示 */ .f-wrap { flex-wrap: wrap; } /* 多轴线方向,一般配合 wrap 使用 */ /* 宽度不足换行后,垂直方向靠上排列 */ .mulitLineStart { display: flex; display: -webkit-flex; flex-wrap: wrap; align-content: flex-start; } /* 宽度不足换行后,垂直方向居中排列 */ .mulitLineCenter { display: flex; display: -webkit-flex; flex-wrap: wrap; align-content: center; } /* 宽度不足换行后,垂直方向靠下排列 */ .mulitLineEnd { display: flex; display: -webkit-flex; flex-wrap: wrap; align-content: flex-end; } /* 宽度不足换行后,垂直方向上保持间隔排列 */ .mulitLineAround { display: flex; display: -webkit-flex; flex-wrap: wrap; align-content: space-around; } /* 宽度不足换行后,垂直方向上靠两侧最顶开始间隔排列 */ .mulitLineBetween { display: flex; display: -webkit-flex; flex-wrap: wrap; align-content: space-between; } /* 纵轴变主轴,垂直靠上,水平居中 */ .columnStart { display: flex; display: -webkit-flex; flex-direction: column; justify-content: flex-start; align-items: center; } /* 纵轴变主轴,垂直靠下,水平居中 */ .columnEnd { display: flex; flex-direction: column; justify-content: flex-end; align-items: center; } /* 纵轴变主轴,垂直居中,水平居中 */ .columnCenter { display: flex; flex-direction: column; justify-content: center; align-items: center; } /* 纵轴变主轴,垂直间隔排列,水平居中 */ .columnAround { display: flex; flex-direction: column; justify-content: space-around; align-items: center; } /* 纵轴变主轴,垂直上下两侧按间隔排列,水平居中 */ .columnBetween { display: flex; flex-direction: column; justify-content: space-between; align-items: center; } /* 纵轴变主轴,垂直上下两侧按间隔排列,水平靠左 */ .columnBetweenStart { display: flex; flex-direction: column; justify-content: space-between; align-items: flex-start; } /* 纵轴变主轴,垂直上下两侧按间隔排列,水平靠右 */ .columnBetweenEnd { display: flex; flex-direction: column; justify-content: space-between; align-items: flex-end; } [代码] 5. async await 使用runtime.js,使小程序支持 async await,拷贝文件至项目目录下。 实例用法 [代码]const regeneratorRuntime = require("../../utils/runtime.js"); Page({ shopCartInit() { var y = this; // 拿到商铺位置信息再去渲染购物计算当前的address符合不符合规定 var showCartList = function() { // 显示全局的地址信息 var globalAddress = wx.Storage.getItem("globalAddress"); if (globalAddress) { y.setData({ globalAddress: globalAddress, addr_id: globalAddress.id }); y.calculateDistance( qqmapsdk, globalAddress.latitude, globalAddress.longitude ); } else { y.setData({ globalAddress: {} }); } }; // await 等待获取商铺位置信息 async function getShopPosTionMsg() { await util.promiseRequest(api.merchant_addr, {}).then(res => { var data = res.data.response_data.lists[0]; y.setData({ shop_lat: data.latitude, // 商铺纬度 shop_lng: data.longitude, // 商铺经度 peiSongFanWei: data.scope // 配送范围 }); }); } async function initData() { await getShopPosTionMsg(); await showCartList(); util.closeLoading(); y.setData({ loading: false }); } // 开始执行 initData(); } }); [代码] 6. addKey Api 使用自定义属性的方法辅助完成业务逻辑 [代码]/** * 为数组添加新的自定义键值以及过滤每个子项的方法 * * @param {*} arr * @param {*} obj { isShow:false,isStar:false} * @param {*} filterFn * @returns */ function addKey(arr, obj, filterFn) { var temp = arr.forEach((v, index, arr) => { typeof filterFn === "function" ? filterFn(v, index) : ""; for (var key in obj) { v[key] = obj[key]; } }); return temp; } [代码] 使用实例 [代码]util.addKey(data, { y_isCheck: false }, function(v) { v.dazhe = Number(v.plus); }); this.setData({ cartList: data }); [代码] 7. 自定义 headerBar 后续分享… 参考 小程序使用 async await 生态圈 ColorUI 鲜亮的高饱和色彩,专注视觉的小程序组件库 taro 多端统一开发框架, React 语法
2019-06-19 - 微信小程序分页加载数据~上拉加载更多~小程序云数据库的分页加载
我们在开发小程序时,一个列表里难免会有很多条数据,比如我们一个列表有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