- 云开发私人实时聊天室
云开发私人实时聊天室说明 在最开始开发小程序时,本人和团队成员实现小程序的聊天室时遇到一些困难,查阅了一些资料,有些讲得太泛,有些讲的太难,在一个阶段克服了这个困难后,收获了很多,对整个流程也熟悉了很多,在这里记录自己的一个思路,希望也能对开发新手有帮助。 项目基本配置 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 - 云开发如何对外提供回调 URL
在云开发的使用过程中,有不少的用户会提出自己的疑问,我如何才能在自己的云开发应用中加入一个回调地址? 在实际开发过程中,不少涉及到支付、订单等异步操作的场景,会需要提供一个回调 URL,以确保在用户完成自己的支付过程后,由支付服务器对回调的 URL 发起请求,确认调用成功。因此,不少用户在使用时提出了自己的疑问,应该如何实现这样的功能? 由于目前云开发云函数尚未对外提供 HTTP 调用的能力,因此,我们可以借助腾讯云提供的云函数和 API 网关,来实现类似的功能。 架构介绍 [图片] 在进行调用时开发者所设置的回调地址是一个 API 网关的地址,微信支付等服务的服务器在完成操作以后,会执行 HTTP 请求,请求 API 网关。 API 网关对应的是腾讯云云函数,我们可以在腾讯云云函数中调用云开发的 SDK,或者是云开发的 HTTP API,完成对云开发数据库的操作。 具体实现 依赖 腾讯云账号 小程序的 AppID 和 App Secret 实现 1. 创建云函数 访问 https://console.cloud.tencent.com/ ,使用你的腾讯云账号登陆,在顶部菜单栏中找到「云产品」— 「Serverless」—「云函数」 [图片] 在云函数中,选择「函数服务」,并在函数服务页面点击「新建」 [图片] 创建一个新的云函数,其中函数名称根据你的需要填写 [图片] 运行环境选择 Node.js 8.9,创建方式选择空白函数,完成后点击下一步。 在下一页不需要做修改,直接点击完成。 2. 创建 API 网关调用 在创建完成云函数以后,我们会进入到这样的界面,在这个界面中选择「触发方式」 [图片] 在触发方式页面新增一个触发方式,使用 「API 网关触发」,请求方法选择「ANY」,发布环境选择「发布」,鉴权方法选择「免鉴权」。 [图片] 填写完成后,点击保存,然后你会获得一个地址,这个地址就是你后续的回调地址。 [图片] 你可以将这个地址在浏览器中打开,可以看到这样的数据 [图片] 则说明我们的云函数完成了配置。 3. 编写程序调用 当我们完成了云函数的配置以后,接下来我们可以修改云函数,使其完成我们自己想要的功能。 我们希望云函数可以从外部发来的请求中获取到参数,并借助云开发提供的 API,对云开发数据库中的数据进行更新。则我们可以这样操作 我们在本地新建一个目录,并在其中执行如下命令(需要你提前安装了 N ode.js 环境) [代码]cd 新建文件夹 npm init -y npm install -y got [代码] 然后创建一个文件[代码]index.js[代码],并在其中加入如下代码,并设置其中的 APPID、SECRET、ENV_ID 等字段。 [代码]'use strict'; const got = require('got'); const APPID = ''; // 小程序 APPID const SECRET = ''; // 小程序 Secret const ENV_ID = '' const TOKEN_URL = `https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${APPID}&secret=${SECRET}` const QUERY_URL = 'https://api.weixin.qq.com/tcb/databasecollectionget?access_token=' exports.main_handler = async (event, context, callback) => { // 1. 提取参数 可以从 URL?id=1&status=2 中提取出 1 和 2 const id = event.queryString.id; const status = event.queryString.status; // 2. 获取 Token,用于后续的调用 let token_resp = await got(TOKEN_URL); let token = JSON.parse(token_resp.body).access_token const url = QUERY_URL + token; // 执行云开发命令 let result = await got(url, { method: 'POST', json: true, headers: { 'content-type': 'applicaiton/json', 'accept-encoding':'gzip' }, body: { "env": ENV_ID } }) return result.body }; [代码] 配置完成后,保存文件。 4. 上传代码 打包完成后,重新访问腾讯云控制台,找到刚刚创建的云函数,进入到「函数代码」中,选择其中的「本地上传文件夹」,选择你刚刚创建的文件夹,等待其自动压缩完成后,点击保存上传。 [图片] 上传完成后,会自动进行部署。 你可以重新访问刚刚生成的 API 网关地址,并在其后加入 id 和 status 参数,比如我的最后的地址是 [代码]https://service-51pn7koc-1251337088.gz.apigw.tencentcs.com/release/myFunction?id=1&status=2[代码] [图片] 访问以后,得到的结果是这样的 [图片] 这样就说明我成功的从云函数中访问了云数据库,并且通过一个无需鉴权的 API 地址获取到了信息。 总结 实际上,借助于 API 网关、云函数以及云开发的 API & SDK,你可以实现非常多的功能,能不能实现,就看你的想象力有没有达到啦。
2019-09-18 - 云函数中生成excel并且上传到云存储中
云环境1.0.51 小程序的云开发功能为我们带来了很大的方便,于是就打算研究一下如何在云函数中拉取数据,之后生成excel到云存储中,过程中踩了些坑,这里分享给大家,希望能有所帮助。 首先了解一个node端生成excel的库excel-export 虽然已经许久未更新了,但是目前还没有什么太大的问题,所以在他的基础上进行开发,并且上手也比较容易 主要用法 引入 [代码]let nodeExcel = require('excel-export'); [代码] 创建配置对象 [代码]let conf = { stylesXmlFile, // 约束文件(不然生成的excel打开会报一些问题) cols, // 可理解为表头 [{ caption: 'columnName', type: 'string' }], 这里出于方便,type为string,具体可移步其文档查看 rows, // 可以理解为填充的数据 ['wechat', 'mp'] } [代码] 创建流对象 [代码]let result = nodeExcel.execute(conf) // 普通node后端可以直接使用 res.end(result, 'binary'); 进行下载,要记得添加相应的头,其文档里也有说明 // 最终可以使用 Buffer.from(result.toString(), 'binary') 转换为一个Buffer对象 [代码] 嵌入云开发 大致的思路就是 [拉取数据] -> [生成excel流对象] -> [上传到云存储中] -> [返回该fileID] 几个踩坑点 读入文件要使用 [代码]path.resolve(__dirname, 'xxx')[代码] 得到excel流对象 使用 [代码]Buffer.from(result.toString(), 'binary')[代码] 再配合[代码]cloud.uploadFile[代码] 生成时conf要配置[代码]stylesXmlFile[代码],不然打开文件总有个提示,很不爽![代码]styles.xml[代码]这个文件可以在[代码]node_modules/excel-export/example/styles.xml[代码]找到。 数据和表头最好是对应的,数据也可以存在空值 云函数目录结构 [代码]- testDownload - |- index.js - |- styles.xml - |- package.json - |- package-lock.json - |- node_modules (在开发工具中应该是不显示的) [代码] package.json中的依赖 [代码] "dependencies": { "excel-export": "^0.5.1", "wx-server-sdk": "latest" } [代码] index.js文件代码 [代码]// 云函数入口文件 const cloud = require('wx-server-sdk') const nodeExcel = require('excel-export') const fs = require('fs') const path = require('path') cloud.init({ env: "xxxx" // 你的环境 }) const db = cloud.database() // 生成分数项并且下载对应的excel exports.main = async (event, context) => { let collectionId = '123666' // 模拟的集合名 let openId = 'sda6248daa888764' // 模拟openid let confParams = ['姓名', '学号', '签到时间'] // 模拟表头 let jsonData = [] // 获取数据 await db.collection(collectionId).get().then(res => { jsonData = res.data }) // 转换成excel流数据 let conf = { stylesXmlFile: path.resolve(__dirname, 'styles.xml'), name: 'sheet', cols: confParams.map(param => { return { caption: param, type: 'string' } }), rows: jsonToArray(jsonData) } let result = nodeExcel.execute(conf) // result为excel二进制数据流 // 上传到云存储 return await cloud.uploadFile({ cloudPath: `download/sheet${openId}.xlsx`, // excel文件名称及路径,即云存储中的路径 fileContent: Buffer.from(result.toString(), 'binary'), }) // json对象转换成数组填充 function jsonToArray (arrData) { let arr = new Array() arrData.forEach(item => { let itemArray = new Array() for (let key in item) { if (key === '_id' || key === '_openid') { continue } itemArray.push(item[key]) } arr.push(itemArray) }) return arr } } [代码] 触发云函数,可以看到云存储中有了刚刚生成的文件 [图片]数据库中的数据, 由于表头都是一样的,所以这边的key可以适当的简化,但是要注意数据库中拉取数据顺序的问题 [图片]最终生成的excel
2019-06-05 - # 使用小程序云开发API更新数组中的单个数组元素
使用小程序云开发API更新数组中的单个数组元素 看了看mongoDB的更新数据方式,找到了解决办法,解决方法如下,亲测可用: 第一种方法:使用位置操作符$ [代码]代码,条件更新写在云函数中 [代码] [图片] [代码]test_api集合原始数据如下 [代码] [图片] [代码]在云函数中执行1中的代码,数组users中id为1001的用户添加了一个新的属性test [代码] [图片] [代码]原理分析 [代码] where条件是查找数组中id属性为1001的用户 update中的使用’users.$.test’: ‘test’ 注意里面的$符号,在mongoDB中,这个符号叫做位置操作符,代表数组的下标,如下引自《mongoDB实战》 [图片] 第二种方法:直接使用数组下标 云函数代码 [图片] test_api集合原始数据如下 [图片] 代码执行后 [图片] 相对于第一种方法,这种方法更加简单,只不过users.1.test这种写法有点颠覆js和java中的属性书写规则,让人感觉怪怪的,在mongoDB中,也支持点数字这种写法。 一个可能的疑惑 可不可以写作’users[1].test’:‘test’,测试结果如下: [图片] [图片] 可以看到’user[1]'无法被识别为数组的第二个元素,而是作为了属性名新增了一个属性,结论:必须写成”点数字“不能写成“中括号” 结论: 经过测试,使用这两种种方法可以更新数组中的一个元素。 方法一适合在不知道数组元素下标的情况下根据查询条件更新元素; 方法二适合在知道数组元素下标的情况下更新元素; 当然也存在既知道元素下标也可以通过属性查到的情况,想用哪个就看心情了-.- 但是暂未找到查询返回数组中的一个元素的方法,再探索探索吧 ——。——
2019-03-06 - 3分钟教你学会使用路线规划小程序插件
路线规划小程序插件是腾讯位置服务开发的一款为用户规划驾车、公交、步行路线方案的插件。开发者可以直接在小程序内使用这个插件,从而为自己的用户提供多种出行方案选择。 路线规划插件的功能 路线规划插件能为用户规划驾车出行路线(如下图1所示),并且当行车起点和行车终点之间可以规划出多个方案时会展示多个方案及方案耗时。这些不同方案体现了不同的策略,例如根据实时路况时间最短、红绿灯数较少、少收费等策略。 同时驾车路线在地图中会通过不同路线的颜色直观反映道路的拥堵情况,例如红色路线表示那段道路拥堵,这就能够让用户提前规避拥堵路段。 路线规划插件也能为用户规划步行出行路线(如下图2所示),不仅显示了步行路线距离和耗时信息,还显示了用户步行过程中,走过的天桥、人行横道数量,更人性化的显示了步行消耗了多少卡路里。 [图片] [图片] 路线规划插件还能为用户规划公交出行路线(如下图所示),提供多种公交和地铁出行方案,并且用户可以根据自己的实际情况进行方案排序,例如时间短优先排序、少步行优先排序、少换乘优先排序。出行方案上也会有时间短这样的标志信息说明方案特点。 [图片] 路线规划插件的应用场景 路线规划插件应用场景非常丰富,可以直接接入到餐饮、电影等各种类型的小程序中,让消费者在小程序中就能获得到达门店的路线规划方案,方便去门店消费。 设想一个场景,小王周末想要吃一顿大餐,于是打开了某家餐厅小程序,当小王决定去这家餐厅时,不需要再打开地图软件去规划出行路线,通过我们的路线规划插件,在这家餐厅的小程序中就能直接规划小王目前的位置到餐厅的出行路线。小王可以选择开车去餐厅,如果今天车牌号限行,那么小王也可以选择公共交通出行,如果到餐厅的距离很近,那么小王可以选择步行方式到达餐厅。 小程序只需要使用路线规划插件就能拥有这些全面精准规划路线能力。看了这些功能,是不是想马上体验呢?别急!接下来就介绍路线规划插件的使用方法。 路线规划插件的使用方法1、申请路线规划插件在微信公众平台中, “微信小程序官方后台-设置-第三方设置-插件管理” 里点击 “添加插件”(如下图所示),搜索 “腾讯位置服务路线规划” ,选择添加插件,小程序开发者就可以在小程序内使用该插件了。 [图片] 2、申请key调用路线规划插件需要申请腾讯位置服务的服务账号,key是开发者的唯一标识,申请key请点击这里。申请key的具体步骤如下: 2.1 填写申请信息[图片] 2.2 创建key成功[图片] 2.3 授权小程序appid开通微信小程序服务:控制台 -> key管理 -> 设置(使用该功能的key)-> 勾选“微信小程序” -> 填写“授权 APP ID” ->保存。 [图片] 2.4 勾选“WebService API”及“白名单”微信小程序插件需要使用WebService API的部分服务,所以使用该功能的key需要具备相应的权限。 [图片] 如果开发者之前是腾讯位置服务的用户并申请过key,则可以跳过上面2.1、2.2的步骤,直接进行2.3、2.4步骤的设置。 3、在小程序中引入路线规划插件只需要在小程序的app.json文件做如下配置就可以在小程序中引入路线规划插件: [代码]// app.json[代码][代码]{[代码][代码] [代码][代码]"plugins"[代码][代码]: {[代码][代码] [代码][代码]"routePlan"[代码][代码]: {[代码][代码] [代码][代码]"version"[代码][代码]: [代码][代码]"1.0.0"[代码][代码],[代码][代码] [代码][代码]"provider"[代码][代码]: [代码][代码]"wx50b5593e81dd937a"[代码][代码] [代码][代码]}[代码][代码] [代码][代码]},[代码][代码] [代码][代码]"permission"[代码][代码]: {[代码][代码] [代码][代码]"scope.userLocation"[代码][代码]: {[代码][代码] [代码][代码]"desc"[代码][代码]: [代码][代码]"你的位置信息将用于小程序定位"[代码][代码] [代码][代码]}[代码][代码] [代码][代码]}[代码][代码]}[代码]4、在小程序中调用路线规划插件在小程序中调用路线规划插件也非常简单: [代码]let plugin = requirePlugin([代码][代码]'routePlan'[代码][代码]);[代码][代码]let key = [代码][代码]''[代码][代码]; [代码][代码]//使用在腾讯位置服务申请的key[代码][代码]let referer = [代码][代码]''[代码][代码]; [代码][代码]//调用插件的小程序的名称[代码][代码]let startPoint = JSON.stringify({ [代码][代码]//起点[代码][代码] [代码][代码]'name'[代码][代码]: [代码][代码]'中国技术交易大厦'[代码][代码],[代码][代码] [代码][代码]'latitude'[代码][代码]: 39.984154,[代码][代码] [代码][代码]'longitude'[代码][代码]: 116.30749[代码][代码]});[代码][代码]let endPoint = JSON.stringify({ [代码][代码]//终点[代码][代码] [代码][代码]'name'[代码][代码]: [代码][代码]'北京西站'[代码][代码],[代码][代码] [代码][代码]'latitude'[代码][代码]: 39.894806,[代码][代码] [代码][代码]'longitude'[代码][代码]: 116.321592[代码][代码]});[代码][代码]wx.navigateTo({[代码][代码] [代码][代码]url: [代码][代码]'plugin://routePlan/route-plan?key='[代码] [代码]+ key + [代码][代码]'&referer='[代码] [代码]+ referer + [代码][代码]'&endPoint='[代码] [代码]+ endPoint[代码][代码]});[代码]如以上示例代码所示,只需要传4个参数,就能为小程序用户提供驾车、公交、步行路线规划信息了。这4个参数含义如下: key,开发者的唯一标识,第2步申请的key referer,调用插件的小程序的名称 startPoint,起点名称和坐标信息,如果不传起点参数,则起点默认当前用户的真实位置 endPoint,终点名称和坐标信息 怎么样?看了上面的使用方法是不是觉得很简单呢?腾讯位置服务开发路线规划插件的目的就是为了减少开发者开发成本,解放开发者生产力,所以才把这些复杂的路线规划业务封装成了插件,方便小程序开发者使用。 那么还犹豫什么呢?立即点击这里去体验使用吧! 另外,腾讯位置服务还推出了地铁图小程序插件,为用户提供查看各城市地铁线路的功能,还能帮用户检索到最优点地铁出行线路及每个站队的详情信息。 后续,腾讯位置服务还会开发更多的关于地图相关的小程序插件,还请各位开发者持续关注我们的服务商主页!
2019-08-05 - 小程序云开发实现微信支付,小程序支付常见问题汇总及解决方案
近期有比较多的同学反映,使用云开发调取微信支付时,老是提示订单不存在。今天就把这几天大家遇到的问题统一汇总到这里。 一,订单不存在的错误 如下图所示的错误。 [图片] 通常看到这个错误时,最好去看下上面看下,有这么一段日志。 [图片] 其实这里已经很明确的给出了错误信息。 [图片] 二,appid和openid不匹配的问题 通常出现这个问题,是因为你拿到老师的代码以后,没有把project.config.json和pay的config下面的index.js里的appid改成你自己的。 [图片] [图片] 至于如何获取自己的小程序的appid,我在小程序零基础的课程里有讲过的。不知道如何获取自己小程序appid的同学可以去看下老师之前的文章或者视频。 三,appid和mch_id不匹配 [图片] 出现这种文就是因为,你没有给自己的小程序关联商户号。 小程序想要使用微信支付,必须满足下面几个条件的 1,必须是非个人小程序(个人小程序用不了支付) 2,必须申请微信支付的商户号。 3,必须配置商户号密匙。 4,必须在自己小程序里关联你的商户号 商户号的申请和密匙配置,可以参考老师的这篇文章 微信支付商户平台-配置密钥/API安全 上面几个条件都满足了,以后,在下面配置你的商户号和商户密匙 [图片] 四,签名错误 通常出现这错误是因为你的商户密匙没有配置成功,或者你的商户密匙拿的是错误的。商户密匙的配置可以看老师的这篇文章。 微信支付商户平台-配置密钥/API安全 [图片] 这里有一点要注意,这个商户密匙是你微信支付里的密匙,不是你小程序的密匙。 [图片] 五,pay 云函数老是不能上传成功,或者上传成功后是错误的。 [图片] 如果你上传云函数老是报上面的错误,就先关闭开发者工具。然后再打开,开发者工具,进入云开发管理界面,把错误的pay删除了。 [图片] 然后再到你的代码目录里做下同步。 [图片] 这样我们就可以重新上传我们的pay函数了。 [图片] 上传云函数时,一定要记得选择如上图箭头所指的。 上传的时候,会有下面这个提示,可以忽略不管。 [图片] 出现下图就代码你云函数上传成功了。 [图片] 持续更新中。。。 大家在使用云开发实现支付的时候,有任何问题,都可以在我们的付费学习群里讨论,我看到后会把问题汇总起来,方便大家学习使用。 云开发实现微信支付视频教程 《1小时开发商城类小程序》 https://edu.csdn.net/course/detail/24770 有任何关于小程序的问题,都可以加老师微信 2501902696 (备注小程序)
2019-07-16 - 啥?四分钱就能学会开发电商和游戏小程序?!
微信小程序自2017年发布至今,以其速度快、体验棒、无适配等优点,不断实现着使应用“触手可及”的梦想,为用户生活的各个方面带来了便利。 [图片] 小程序如此火爆的一大原因是其拥有较低的开发成本和门槛,而云开发正是让小程序这一优点充分发挥的一大利器!【小贴士:小程序·云开发是微信团队联合腾讯云提供的Serverless无服务开发服务,帮助开发者快速构建适用于小程序的云端数据库、云端存储、云端计算。】 而且,云开发还具有商业和技术两方面的多项优点,能让小程序开发更加高效。以电商开发为例,小程序·云开发就在后端代码、服务器、费用等方面有着独特的优势,从而解决了传统电商后台开发的一些局限性。 [图片] 目前,小程序·云开发让开发者看到了更多的可能性,也让越来越多的开发爱好者有机会将天马行空的想象付诸实践,从而诞生出一个又一个有趣又实用的小程序。 [图片] 例如,借助云开发实现一个开发撑起过亿用户小程序的“壮举”(腾讯相册)。 [图片] 腾讯相册 还有,无需服务器,即可开发出精美的小游戏~ [图片] 此外,利用云开发,无需后台与备案域名,即可高效实现经典的小程序支付功能,瞬间还原微信支付有木有! [图片] 甚至,还有一群年轻的00后用云开发做了一系列好玩儿的校园小程序,管理工具、寻宝探秘、技术社区等类型的小程序百花齐放~ [图片] 怎么样,你是不是也想轻松开发一个专属的小程序? 亲手将自己的创意idea落个地? 那么问题来了 功能强大的云开发怎么使用?上手难吗?做一个自己的小程序需要多久? 不要方,小编来告诉你 你与成为一个优秀的小程序·云开发er可能只隔了三节公开课! 云开发联合腾讯课堂TEXT公开课为你奉上干货满满的课程礼包!三位男神老师将带你认识小程序·云开发,让你轻松get电商与游戏这两个超热门领域小程序的开发技巧! 快点击下方链接获取吧~ 小程序·云开发基础 电商小程序·云开发初探 轻松入门微信小游戏·云开发 更给力的是,收获这三门循序渐进、干货满满的课程一共只需4分钱! [图片] So,想了解一大票网红电商小程序如何诞生,想设计开发自己的游戏吗? 快点击上方链接进行报名吧!
2019-07-22 - 小程序实现列表拖拽排序
小程序列表拖拽排序 [图片] wxml [代码]<view class='listbox'> <view class='list kelong' hidden='{{!showkelong}}' style='top:{{kelong.top}}px'> <view class='index'>?</view> <image src='{{kelong.xt}}' class='xt'></image> <view class='info'> <view class="name">{{kelong.name}}</view> <view class='sub-name'>{{kelong.subname}}</view> </view> <image src='/images/sl_36.png' class='more'></image> </view> <view class='list' wx:for="{{optionList}}" wx:key=""> <view class='index'>{{index+1}}</view> <image src='{{item.xt}}' class='xt'></image> <view class='info'> <view class="name">{{item.name}}</view> <view class='sub-name'>{{item.subname}}</view> </view> <image src='/images/sl_36.png' class='more'></image> <view class='moreiconpl' data-index='{{index}}' catchtouchstart='dragStart' catchtouchmove='dragMove' catchtouchend='dragEnd'></view> </view> </view> [代码] wxss [代码].map-list .list { position: relative; height: 120rpx; } .map-list .list::after { content: ''; width: 660rpx; height: 2rpx; background-color: #eee; position: absolute; right: 0; bottom: 0; } .map-list .list .xt { display: block; width: 95rpx; height: 77rpx; position: absolute; left: 93rpx; top: 20rpx; } .map-list .list .more { display: block; width: 48rpx; height: 38rpx; position: absolute; right: 30rpx; top: 40rpx; } .map-list .list .info { display: block; width: 380rpx; height: 80rpx; position: absolute; left: 220rpx; top: 20rpx; font-size: 30rpx; } .map-list .list .info .sub-name { font-size: 28rpx; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; color: #646567; } .map-list .list .index { color: #e4463b; font-size: 32rpx; font-weight: bold; position: absolute; left: 35rpx; top: 40rpx; } [代码] js [代码]data:{ kelong: { top: 0, xt: '', name: '', subname: '' }, replace: { xt: '', name: '', subname: '' }, }, dragStart: function(e) { var that = this var kelong = that.data.kelong var i = e.currentTarget.dataset.index kelong.xt = this.data.optionList[i].xt kelong.name = this.data.optionList[i].name kelong.subname = this.data.optionList[i].subname var query = wx.createSelectorQuery(); //选择id query.select('.listbox').boundingClientRect(function(rect) { // console.log(rect.top) kelong.top = e.changedTouches[0].clientY - rect.top - 30 that.setData({ kelong: kelong, showkelong: true }) }).exec(); }, dragMove: function(e) { var that = this var i = e.currentTarget.dataset.index var query = wx.createSelectorQuery(); var kelong = that.data.kelong var listnum = that.data.optionList.length var optionList = that.data.optionList query.select('.listbox').boundingClientRect(function(rect) { kelong.top = e.changedTouches[0].clientY - rect.top - 30 if(kelong.top < -60) { kelong.top = -60 } else if (kelong.top > rect.height) { kelong.top = rect.height - 60 } that.setData({ kelong: kelong, }) }).exec(); }, dragEnd: function(e) { var that = this var i = e.currentTarget.dataset.index var query = wx.createSelectorQuery(); var kelong = that.data.kelong var listnum = that.data.optionList.length var optionList = that.data.optionList query.select('.listbox').boundingClientRect(function (rect) { kelong.top = e.changedTouches[0].clientY - rect.top - 30 if(kelong.top<-20){ wx.showModal({ title: '删除提示', content: '确定要删除此条记录?', confirmColor:'#e4463b' }) } var target = parseInt(kelong.top / 60) var replace = that.data.replace if (target >= 0) { replace.xt = optionList[target].xt replace.name = optionList[target].name replace.subname = optionList[target].subname optionList[target].xt = optionList[i].xt optionList[target].name = optionList[i].name optionList[target].subname = optionList[i].subname optionList[i].xt = replace.xt optionList[i].name = replace.name optionList[i].subname = replace.subname } that.setData({ optionList: optionList, showkelong:false }) }).exec(); }, [代码]
2019-07-28 - iphoneX兼容之自定义底部菜单
当我们需要自定义底部导航栏时 首先要解决iphoneX的底部大横条对这个兼容 通常不设置兼容 都会被挡住 如何编写 在你要编写的底部菜单中插入 样式 [代码]padding-bottom[代码][代码]: calc(env(safe-area-inset-[代码][代码]bottom[代码][代码]) / [代码][代码]2[代码][代码]) 即可兼容 [代码] [代码] 例如:css中插入[代码] [代码]@supports ([代码][代码]bottom[代码][代码]: constant(safe-area-inset-[代码][代码]bottom[代码][代码])) or ([代码][代码]bottom[代码][代码]: env(safe-area-inset-[代码][代码]bottom[代码][代码])) {[代码][代码] [代码][代码].fixed-wrap {[代码][代码] [代码][代码]height[代码][代码]: calc(env(safe-area-inset-[代码][代码]bottom[代码][代码]) / [代码][代码]2[代码][代码]);[代码][代码] [代码][代码]width[代码][代码]: [代码][代码]100%[代码][代码];[代码][代码] [代码][代码]}[代码] [代码] [代码][代码].fixed-pay {[代码][代码] [代码][代码]padding-bottom[代码][代码]: calc(env(safe-area-inset-[代码][代码]bottom[代码][代码]) / [代码][代码]2[代码][代码]);[代码][代码] [代码][代码]}[代码] [代码]}[代码]其中 [代码]env(safe-area-inset-[代码][代码]bottom[代码][代码]) 是计算兼容的高度 通常一半即可 [代码] calc 是计算css 你也可以加入高度 假设有第二层 底部固定栏【即底部导航栏上面还有一层固定栏】 可如下编写 view.footer { bottom: calc(100rpx + env(safe-area-inset-bottom)); } 这样轻轻松松解决兼容 不需要写js代码 <-------------大横条-------------> [图片]
2019-05-28 - 云开发http请求的两种写法
对于简单的GET表单请求 可以直接将参数封装在url中 [代码]// 云函数入口文件 const cloud = require('wx-server-sdk') var request = require('request') // 云函数入口函数 exports.main = async (event, context) => { //qz return new Promise((resolve, reject) => { request({ url: event.URL, method: "POST",//GET json: true, headers: { "content-type": "application/json", "token": event.token }, }, function (error, response, body) { if (!error && response.statusCode == 200) { try { resolve(body) } catch (e) { reject() } } }) }) } [代码] 对于POST请求 参数不好封装的 [代码]// 云函数入口文件 const cloud = require('wx-server-sdk') var request = require('request') cloud.init() // 云函数入口函数 exports.main = async (event, context) => { //这里写普通话成绩查询方式 return new Promise((resolve, reject) => { request({ url: event.url, method: "POST", json: true, headers: { "content-type": "application/json", "token":event.token }, body: event.body }, function (error, response, body) { if (!error && response.statusCode == 200) { try { resolve(body) } catch (e) { reject() } } }) }) } [代码] body中填写需要的参数 body是json形式 [代码]{ xxx:xxx } [代码] 请求头可以根据自己的需要进行修改。
2019-05-28 - wx.getLocation(Object object)返回20公里外的数据
wx.getLocation(Object object)这个API,之前发帖问过,解释是返回的缓存数据, 但一直有用户反馈,获取位置时,返回的不准,有的用户是20公里外的位置(用户开车经过),有点无法接受了, 获取当前位置,已经加了权限,用户也同意了,为什么不能返回实时位置呢? 费电?用户需要这个功能,费电用户是接受的,我和很多用户沟通过,不然都不会用这个功能。 之前官方说要上onLocationChange的API,还能有希望吗?迫切需要..... 还原用户质询: 用户:你看,这个位置离我20公里、那个位置离我30公里... 我:微信有缓存,实在没办法,不好意思耽误您使用了,请尝试多点几次重新定位 用户:点了好几次了,还是这样啊... 我:.....
2019-04-02 - 云开发支付的代码,有需要的进。
真机测试已通过。你照抄就行,保证可通过。 最新完美版本可供参考: https://developers.weixin.qq.com/community/develop/article/doc/0004c4a50a03107eaa79f03cc56c13 小程序端: wx.cloud.callFunction({ name: 'getPay' , data: { total_fee: parseFloat(0.01).toFixed(2) * 100, attach: 'anything', body: 'whatever' } }) .then( res => { wx.requestPayment({ appId: res.result.appid, timeStamp: res.result.timeStamp, nonceStr: res.result.nonce_str, package: 'prepay_id=' + res.result.prepay_id, signType: 'MD5', paySign: res.result.paySign, success: res => { console.log(res) } }) }) 云函数:getPay getPay目录下共两个文件: 1、index.js 2、package.json index.js代码如下: const key = "YOURKEY1234YOURKEY1234YOURKEY123"//这是商户的key,不是小程序的密钥,32位。 const mch_id = "1413090000" //你的商户号 //将以上的两个参数换成你的,然后以下可以不用改一个字照抄 const rp = require('request-promise') const crypto = require('crypto') function paysign({ ...args }) { let sa = [] for (let k in args) sa.push( k + '=' + args[k]) sa.push( 'key=' + key) return crypto.createHash('md5').update(sa.join('&'), 'utf8').digest('hex').toUpperCase() } exports.main = async (event, context) => { const appid = event.userInfo.appId const openid = event.userInfo.openId const attach = event.attach const body = event.body const total_fee = event.total_fee const notify_url = "https://whatever.com/notify" const spbill_create_ip = "118.89.40.200" const nonce_str = Math.random().toString(36).substr(2, 15) const timeStamp = parseInt(Date.now() / 1000) + '' const out_trade_no = "otn" + nonce_str + timeStamp let formData = "<xml>" formData += "<appid>" + appid + "</appid>" formData += "<attach>" + attach + "</attach>" formData += "<body>" + body + "</body>" formData += "<mch_id>" + mch_id + "</mch_id>" formData += "<nonce_str>" + nonce_str + "</nonce_str>" formData += "<notify_url>" + notify_url + "</notify_url>" formData += "<openid>" + openid + "</openid>" formData += "<out_trade_no>" + out_trade_no + "</out_trade_no>" formData += "<spbill_create_ip>" + spbill_create_ip + "</spbill_create_ip>" formData += "<total_fee>" + total_fee + "</total_fee>" formData += "<trade_type>JSAPI</trade_type>" formData += "<sign>" + paysign({ appid, attach, body, mch_id, nonce_str, notify_url, openid, out_trade_no, spbill_create_ip, total_fee, trade_type: 'JSAPI' }) + "</sign>" formData += "</xml>" let res = await rp({ url: "https://api.mch.weixin.qq.com/pay/unifiedorder", method: 'POST',body: formData}) let xml = res.toString("utf-8") if (xml.indexOf('prepay_id')<0) return xml let prepay_id = xml.split("<prepay_id>")[1].split("</prepay_id>")[0].split('[')[2].split(']')[0] let paySign = paysign({ appId: appid, nonceStr: nonce_str, package: ('prepay_id=' + prepay_id), signType: 'MD5', timeStamp: timeStamp }) return { appid, nonce_str, timeStamp, prepay_id, paySign } } package.json 代码如下: { "name": "getPay", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "author": "youself", "license": "ISC", "dependencies": { "crypto": "^1.0.1", "request-promise": "^4.2.2" } } 最后选择:上传和部署:云端安装依赖。
2019-12-14