- 如何优雅的调用云函数?
前言 这是在做《#小程序云开发挑战赛#-情侣券-想做就做》这个项目过程中学习到了一个新知识。 能够优雅的调用云函数,极大程度的减少维护成本。 在此之前我都是业务代码层面直接调用云函数,而且一个云函数就一个方法。 如下: 首页 [代码]wx.cloud.callFunction({ name: 'login' }).then(res => { wx.setStorageSync('openid', res.result.openid) }) [代码] login 云函数 [代码]const cloud = require('wx-server-sdk') cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV }) exports.main = (event, context) => { const wxContext = cloud.getWXContext() return { event, openid: wxContext.OPENID, appid: wxContext.APPID, unionid: wxContext.UNIONID, env: wxContext.ENV, } } [代码] 这种代码写起来很爽,但是要维护起来的时候就很头疼,云函数越写越多,找起来就比较麻烦,无法快速定位。 如何解决呢? 从两个维度来解决: 从小程序代码来说,可以加一个api层来管理所有云函数的调用。 从云函数代码来说,可以在一个云函数里面做个简单路由写多个方法。 1.云函数调用统一管理 从之前的顺序:小程序业务层=>云函数 改成 小程序业务层=>API层=>云函数 业务层:调用API [代码]// 先导入api的user类 import { login } from 'api/user.js' login().then(res => { wx.setStorageSync('openid', res.result.openid) }) [代码] API层:调用云函数 [代码]async function login() { return wx.cloud.callFunction({ // 云函数名称 name: 'user', // 传给云函数的参数 data: { action: 'login' } }); } module.exports = { login:login } [代码] 上面案例中我们只用了login方法,其实在实战中user里面会有所有的相关操作,引入也可以引入多个方法。 所有的云函数都会在api层进行管理,这样改云函数就不需要去业务代码里面去找了。 业务层只负责传参与回调,调用过程不需要关心,同时还可以支持多个页面复用api方法。 2. 一个云函数多个方法 从上面api层的案例不难看出,data里面传入了一个action参数,这个参数就是你要调用的方法名称。 云函数: [代码]// 云函数入口文件 const cloud = require('wx-server-sdk') cloud.init(); // 云函数入口函数 exports.main = async (event, context) => { const wxContext = cloud.getWXContext() // 简单路由 if (event.action && userHelper[event.action]) { const result = await userHelper[event.action](wxContext, event) return result } return { message: 'This action was not found', error: -1, } } const db = cloud.database(); const userHelper = { async getOpenId(context, params) { return { openid: context.OPENID, } } } [代码] 通过userHelper来管理所有的方法,你如果需要添加方法可以直接在userHelper下面添加即可,action传入方法名称就可以调用了。 这样便于查找与管理,同一类业务之需要创建一个云函数即可。 还有一点就是减少云函数数量,云开发不同的收费套餐是有云函数数量限制的。 套餐配合可见:配额说明 总结 全部写完了,如果觉得不错就给我的《#小程序云开发挑战赛#-情侣券-想做就做》点个赞,谢谢。
2020-09-22 - 只有三行代码的神奇云函数的功能之四:获取电话号码
这是一个神奇的网站,哦不,神奇的云函数,它只有三行代码:(真的只有三行哦) 云函数:login index.js: const cloud = require('wx-server-sdk') cloud.init() exports.main = async (event) => { return { ...event, ...cloud.getWXContext() } } 神奇功能之四:获取电话号码: 还是这三行代码,获取用户的电话号码。 wxml: <button open-type="getPhoneNumber" bindgetphonenumber="getPhoneNumber" >{{mobile||"获得电话号码"}}</button> js: getPhoneNumber: function (e) { wx.cloud.callFunction({ name: 'login', data: {weRunData: wx.cloud.CloudID(e.detail.cloudID)} }).then(res => { this.setData({ mobile: res.result.weRunData.data.phoneNumber }) }) } 其他功能: 神奇功能之一:获取openid: https://developers.weixin.qq.com/community/develop/article/doc/00080c6e3746d8a940f9b43e55fc13 神奇功能之二:不用授权获取unionid: https://developers.weixin.qq.com/community/develop/article/doc/000a0c6b580338e947f9db0c65b813 神奇功能之三:100%成功获取unionid: https://developers.weixin.qq.com/community/develop/article/doc/00066a967c4e384949f93fe1151413 神奇功能之五:获取群id: 将小程序分享到某群里,可获得该群的群id, https://developers.weixin.qq.com/community/develop/article/doc/000ea802c00f70894cf9fe72556013 [图片]
2020-12-16 - 小程序环境共享入门/跨账号环境调用
先上文档:官方文档 一般碰到问题比较多的是怎么起步, !important 为了方便讲清楚概念,我们称呼主账号(提供资源/函数的小程序)为【资源方】,要进行跨账号环境调用资源的账号为【调用方】 (需小程序公共库 2.13.0 或以上)(需 wx-server-sdk 版本大于或等于 2.3.0) 确保要共享资源的两个小程序在同个主体下面 例如同个公司、个人开发者等。 用微信开发者工具,打开【资源方】,点击头像右侧的云开发 点击云开发弹框右侧的设置 选择顶部tab栏目的拓展功能 点击环境共享的开通 点击添加共享,并输入【调用方】的appid,根据需求勾选权限并确认 用微信开发者工具,打开【调用方】 点击云开发确认可以看到【资源方】的云函数和云存储,如果这一步没看到共享的内容,返回确认前面的步骤 在【资源方】的云开发-设置-环境设置-环境名称处,创建开通按量计费(免费)的云开发环境。 配置部分非常繁琐,但是到这里就结束了。接下来是开发部分 【资源方】云函数部分改动的内容,入口 [代码]const cloud = require('wx-server-sdk') cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV }) // 云函数入口函数 exports.main = async (event, context) => { const wxContext = cloud.getWXContext() console.log(event) console.log(wxContext) // 跨账号调用时,由此拿到来源方小程序/公众号 AppID console.log(wxContext.FROM_APPID) // 跨账号调用时,由此拿到来源方小程序/公众号的用户 OpenID console.log(wxContext.FROM_OPENID) // 跨账号调用、且满足 unionid 获取条件时,由此拿到同主体下的用户 UnionID console.log(wxContext.FROM_UNIONID) return { errCode: 0, errMsg: '', auth: JSON.stringify({ // 自定义安全规则 // 在前端访问资源方数据库、云函数等资源时,资源方可以通过 // 安全规则的 `auth.custom` 字段获取此对象的内容做校验, // 像这个示例就是资源方可以在安全规则中通过 `auth.custom.x` 获取 x: 1, }), } } [代码] 【调用方】云函数部分改动的内容 !important 这边前端建议对所有云函数入口做一个统一的路由,不要到处callFunction。 在统一路由那边,我一般是这样写在utils里,或者写到route.js里,这样你只要改一个地方,就可以修改整个小程序的所有请求 [代码]async function requestCloud(requestName, data) { wx.cloud.init({ traceUser: true }) let c1 = new wx.cloud.Cloud({ // 资源方 AppID resourceAppid: 'wx5d8d765e252720eb', // 资源方环境 ID resourceEnv: 'production-rjntq', }) await c1.init() return await c1.callFunction({ name: requestName, data: data }) } [代码] 如果是用promise的写法,就外面再包一层,举例,把上面的代码,加上下面的部分,塞到同一个新的function里, [代码]return new Promise(function (resolve, reject) { requestCloud(requestName, data).then(res => { if (res.result.code == 0) { resolve(res.result.data) return } else { hideLoading() wx.showToast({ title: res.result.msg, icon: 'none' }) reject(res.result) return } }).catch(err => { reject(err) return }) }) [代码] 纯前端的部分就结束了,这边遇到问题最多的是,没想到这玩意儿要初始化两次云函数,一次是自己的,一次是【资源方】的。 如果是【调用方】也有自己的后台,想要调用【资源方】的云函数, [代码]const cloud = require('wx-server-sdk') exports.main = async (event) => { // 声明新的 cloud 实例 var c1 = new cloud.Cloud({ // 资源方 AppID resourceAppid: 'wxe0e2656d74f0bff3', // 资源方环境 ID resourceEnv: 'test-f96b31', }) // 跨账号调用,必须等待 init 完成 // init 过程中,资源方小程序对应环境下的 cloudbase_auth 函数会被调用,并需返回协议字段(见下)来确认允许访问、并可自定义安全规则 await c1.init() // 完成后正常使用资源方的已授权的云资源 return c1.callFunction({ name: '函数名', data: {}, }) } [代码] 好了,你再编译一下自己的【调用方】代码,应该就ok了。 我现在卡在怎么以【调用方】身份,在wxml中例如Image src里直接访问【资源方】的图片资源。按理说云开发面板上是可以看到的,以及也授权了,应该是可以直接打开才对,但是渲染层就是打不开。有发现的朋友麻烦联系我微信:SH-Yushi。十分感谢。 已经通过后台批量替换cloud前缀为https格式解决。 现在还剩如何直接操作【资源方】云数据库。保底方案是通过云函数绕过去 update by 2020-11-05 有人私聊我怎么直接访问资源方图片。 我把我批量替换的方法写下面,供后人参考 [代码]function changeImageUrl(imageUrl) { if (this.isEmpty(imageUrl)) { return } let la = 'cloud://production-rjntq.' let ra = '1302413556/' let start = imageUrl.indexOf(la) if (start < 0) { return imageUrl } let end = imageUrl.indexOf(ra) - 1 let front = 'https://' + imageUrl.substring(la.length, end + ra.length) + '.tcb.qcloud.la' let back = imageUrl.substring(end + ra.length) return front + back } [代码] 记得去云存储里面看一眼自己的图片,右键查看详情,查看下载地址,你的那串数字应该和我的肯定是不同的,到我封的方法里把数字替换成自己的。 这边的问题是,虽然小程序开放了跨资源调用,也在文档里说可以跨资源访问图片,但是开发组告诉我们暂时不支持直接调用cloud://的path。因此想到转成https。好在他们的格式都是固定的,所以可以批量替换。 这边有个小坑,在动手操作之前还是仔细看自己的图片存储目录,因为每个人的 文件目录层级不同,因此前缀格式会有略微差别。但是思路是一致的。
2020-11-05 - [开盖即食]小程序图表插件 ECharts 实战
[图片] H5时代用来做图表的插件有很多比如:[代码]ECharts[代码]、[代码]Bizcharts[代码]、[代码]JSCharts[代码]等,而这次的小程序本人选用了 ECharts 作为图表组件。 1、选择原因主要有3点: 官方某度在持续维护这个插件 官方推出了直接适配小程序的版本,且有demo,开盖即食,不用迁移 简单实用,覆盖面广且可通过配置控制包的大小,小程序毕竟大小有限制~ eCharts来自BAT中的B前端团队,对应的小程序版本为:echarts-for-weixin 官网地址 https://echarts.apache.org/ github地址 https://github.com/ecomfe/echarts-for-weixin 小程序demo地址 https://github.com/ecomfe/echarts-examples 2、用法 (1)官方教程 [代码]index.json[代码] 配置如下: [代码]{ "usingComponents": { "ec-canvas": "../../ec-canvas/ec-canvas" } } [代码] 这一配置的作用是,允许我们在 [代码]pages/bar/index.wxml[代码] 中使用 [代码]<ec-canvas>[代码] 组件。注意路径的相对位置要写对,如果目录结构和本例相同,就应该像上面这样配置。 [代码]index.wxml[代码] 中创建了一个 [代码]<ec-canvas>[代码] 组件: [代码]<view class="container"> <ec-canvas id="mychart-dom-bar" canvas-id="mychart-bar" ec="{{ ec }}"></ec-canvas> </view> [代码] 其中 [代码]ec[代码] 是一个我们在 [代码]index.js[代码] 中定义的对象,它使得图表能够在页面加载后被初始化并设置。 [代码]index.js[代码] 配置: [代码]function initChart(canvas, width, height, dpr) { const chart = echarts.init(canvas, null, { width: width, height: height, devicePixelRatio: dpr // 像素 }); canvas.setChart(chart); var option = { ... }; chart.setOption(option); return chart; } Page({ data: { ec: { onInit: initChart } } }); [代码] 这对于所有 ECharts 图表都是通用的,用户只需要修改上面 [代码]option[代码] 的内容,即可改变图表。[代码]option[代码] 的使用方法参见 ECharts 配置项文档。 官方demo里的一些用法指导: 如何延迟加载图表? 参见 [代码]pages/lazyLoad[代码] 的例子,可以在获取数据后再初始化数据。 如何在一个页面中加载多个图表? 参见 [代码]pages/multiCharts[代码] 的例子。 如何使用 Tooltip? 目前,本项目已支持 ECharts Tooltip,但是由于 ECharts 相关功能尚未发版,因此需要使用当前本项目中 [代码]ec-canvas/echarts.js[代码],这个文件包含了可以在微信中使用 Tooltip 的相关代码。目前在 ECharts 官网下载的 [代码]echarts.js[代码] 还不能直接替换使用,等 ECharts 正式发版后即可。 具体使用方法和 ECharts 相同,例子参见 [代码]pages/line/index.js[代码]。 如何保存为图片? 参见 [代码]pages/saveCanvas[代码] 的例子。 (2)本人实战操作 [图片] [代码]import * as echarts from '../ec-canvas/echarts'; const app = getApp(); let chart; function initChart(canvas, width, height, dpr) { chart = echarts.init(canvas, null, { width: width, height: height, devicePixelRatio: dpr // new }); canvas.setChart(chart); chart.setOption(option); return chart; } var option = { title: { text: '智酷君 echarts 切换效果测试', left: 'center' }, tooltip: { trigger: 'item', formatter: '{a} <br/>{b}: {c} ({d}%)' }, legend: { orient: 'vertical', left: 10, data: ['AAA', 'BBB', 'CCC', 'DDD', 'EEE'] }, series: [ { name: '访问来源', type: 'pie', radius: ['50%', '70%'], avoidLabelOverlap: false, label: { show: false, position: 'center' }, emphasis: { label: { show: true, fontSize: '30', fontWeight: 'bold' } }, labelLine: { show: false }, data: [ {value: 335, name: 'AAA'}, {value: 310, name: 'BBB'}, {value: 234, name: 'CCC'}, {value: 135, name: 'DDD'}, {value: 1548, name: 'EEE'} ] } ] }; Page({ data: { ec: { onInit: initChart } }, onLoad: function () {}, //单曲线 line() { let option2 = { title: { text: '同一canvas更新成折线图', left: 'center' }, xAxis: { type: 'category', data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'] }, yAxis: { type: 'value' }, series: [{ data: [820, 932, 901, 934, 1290, 1330, 1320], type: 'line' }] }; chart.setOption(option2) }, //切换柱状图 bar(){ let option3 = { title: { text: '直接更新数据,减少性能消耗', left: 'center' }, xAxis: { type: 'category', data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'] }, yAxis: { type: 'value' }, series: [{ data: [120, 200, 150, 80, 70, 110, 130], type: 'bar', showBackground: true, backgroundStyle: { color: 'rgba(220, 220, 220, 0.8)' } }] }; chart.setOption(option3) } }) [代码] 建议大家尽量使用同一个canvas对象来切换不同的图表效果,而不是初始加载多个不同的,我们可以将 [代码]chart[代码] 对象设置为全局,然后通过 [代码]chart.setOption()[代码] 的方法来更新配置数据,可以减少性能消耗避免闪退等 (3)代码片段 代码段:https://developers.weixin.qq.com/s/OOTwnsms7Cin 建议将IDE工具升级到 1.02.18以上,避免一些BUG [图片] 3、Tips (1)包大小可以配置 在线定制地址: https://echarts.apache.org/zh/builder.html [图片] [图片] 通过选择和配置想要的功能,可以大大减少原本JS包的尺寸。 (2)Canvas 2d 版本要求 最新版的 ECharts 微信小程序支持微信 Canvas 2d,当用户的基础库版本 >= 2.9.0 且没有设置 [代码]force-use-old-canvas="true"[代码] 的情况下,使用新的 Canvas 2d(默认)。 使用新的 Canvas 2d 可以提升渲染性能,解决非同层渲染问题,强烈建议开启 如果仍需使用旧版 Canvas,使用方法如下: [代码]<ec-canvas id="xxx" canvas-id="xxx" ec="{{ ec }}" force-use-old-canvas="true"></ec-canvas> [代码] (3)数据点过多造成闪退和卡死 本人简单测试了下,iphone7p手机在[代码]1500个[代码]左右数据点的时候,出现了小程序闪退,iphoneX 测试下来大概在[代码]2500个[代码]左右,猜测可能由于微信本身给小程序的内存有限,所以建议大家控制数据点的个数 (4)单页面图表canvas加载过多卡死 建议单页面图表加载不要超过[代码]5个canvas[代码],尽可能共用一个图表Canvas对象,通过动态更新数据的方式来展示内容(还有帅气的特效),如果一定要加载多个canvas的话,建议控制数量,提供复用性~ 看完觉得有帮助记得点个赞哦~ 你的赞是我继续分享的最大动力!^-^
2020-06-29 - 小程序富文本解析
wxParse 微信小程序富文本解析 原因 由于原作者仓库 wxParse 不再维护,我们项目中商品信息展示又是以wxParse这个用做富文本解析的; 于是乎,决定采用 递归Component 的方式对其进行重构一番; 原项目使用的 [代码]template[代码] 模板的方式渲染节点,存在以下问题: 节点渲染支持到12层,超出会原样输出 [代码]html[代码] 代码; 每一层级的循环模板都重复了一遍所有的可解析标签,代码十分臃肿。 [代码]li[代码]标签不支持 [代码]ol[代码] 有序列表渲染(统一采用的是 [代码]ul[代码] 无序列表),[代码]a[代码]标签无法实现跳转,也无法获取点击事件回调等等; 节点渲染没有绑定 [代码]key[代码] 值,一是在开发工具看到一堆的 [代码]warning[代码]信息(看着十分难受),二是节点频繁删除添加,无法比较[代码]key值[代码],造成 [代码]dom[代码] 节点频繁操作。 功能标签 目前该项目已经可以支持以下标签的渲染: audio标签(可自行更换组件样式,暂时采用微信公众号文章的[代码]audio[代码]音乐播放器的样式处理) ul标签 ol标签 li标签 a标签 img标签 video标签 br标签 button标签 h1, h2, h3, h4标签 文本节点 其余块级标签 其余行级标签 支持 npm包 引入 [代码]npm install --save wx-minicomponent [代码] 使用 原生组件使用方法 克隆 项目 代码,把 components目录 拷贝到你的小程序根目录下面; 在你的 page页面 对应的 [代码]json[代码] 文件引入 [代码]wxParse[代码] 组件 [代码]{ "usingComponents": { "wxParse": "/components/wxParse/wxParse" } } [代码] 组件调用 [代码]<wxParse nodes="{{ htmlText }}" /> [代码] npm组件使用方法 安装组件 [代码]npm install --save wx-minicomponent [代码] 小程序开发工具的 [代码]工具[代码] 栏找到 [代码]构建npm[代码],点击构建; 在页面的 json 配置文件中添加 [代码]wxParse[代码] 自定义组件的配置 [代码]{ "usingComponents": { "wxParse": "/miniprogram_npm/wx-minicomponent/wxParse" } } [代码] [代码]wxml[代码] 文件中引用 wxParse [代码]<wxParse nodes="{{ htmlText }}" /> [代码] 提示:详细步骤可以参考小程序的npm使用文档 补充组件:代码高亮展示组件使用 在 [代码]page[代码]的 [代码]json[代码] 文件里面引入 [代码]highLight[代码] 组件 原生引入: [代码]{ "usingComponents": { "highLight": "/components/highLight/highLight" } } [代码] npm组件引入: [代码]{ "usingComponents": { "highLight": "/miniprogram_npm/wx-minicomponent/highLight" } } [代码] 组件调用 [代码]<highLight codeText="{{codeText}}" /> [代码] 参数文档 wxParse:富文本解析组件 参数 说明 类型 例子 nodes 富文本字符 String “<div>test</div>” language 语言 String 可选:“html” | “markdown” (“md”) 受信任的节点 节点 例子 audio <audio title=“我是标题” desc=“我是小标题” src=“https://media.lycheer.net/lecture/25840237/5026279_1509614610000.mp3?0.1” /> a <a href=“www.baidu.com”>跳转到百度</a> p div span li ul ol img button h1 h2 h3 h4 … highLight:代码高亮解析组件 参数 说明 类型 例子 codeText 原始高亮代码字符 String “var num = 10;” language 代码语言类型 String 可选值:“javascript/typescript/css/xml/sql/markdown” 提示:如果是html语言,language的值为xml wxAudio:仿微信公众号文章音频播放组件 参数 说明 类型 例子 title 标题 String “test” desc 副标题 String “sub test” src 音频地址 String 示例展示 富文本解析 html文本解析实例 [图片] markdown文本解析实例 [图片] 代码高亮 [图片] 更新历史 2020-5-31 迁移utils目录到wxParse目录下; 富文本增加markdown文本解析支持; 2020-5-21: 富文本组件image标签添加loading过渡态,优化图片加载体验 2020-5-17: 完善组件参数文档,增加wxParse对audio标签标题,副标题的解析功能 2020-5-13: 增加css,html,ts,sql,markdown代码高亮提示支持 2020-5-6: 增加图片预览功能 项目地址 项目地址:https://github.com/csonchen/wxParse
2020-06-01 - 自定义navigationBar顶部导航栏,兼容适配所有机型(附完整案例)
前言 navigationBar相信大家都不陌生把?今天我们就来说说自定义navigationBar,把它改变成我们想要的样子(搜索框+胶囊、搜索框+返回按钮+胶囊等)。 思路 隐藏原生样式 获取胶囊按钮、状态栏相关数据以供后续计算 根据不同机型计算出该机型的导航栏高度,进行适配 编写新的导航栏 引用到页面 正文 一、隐藏原生的navigationBar window全局配置里有个参数:navigationStyle(导航栏样式),default=默认样式,custom=自定义样式。 [代码]"window": { "navigationStyle": "custom" } [代码] 让我们看看隐藏后的效果: [图片] 可以看到原生的navigationBar已经消失了,剩下孤零零的胶囊按钮,胶囊按钮是无法隐藏的。 二、准备工作 1.获取胶囊按钮的布局位置信息 我们用wx.getMenuButtonBoundingClientRect()【官方文档】获取胶囊按钮的布局位置信息,坐标信息以屏幕左上角为原点: [代码]const menuButtonInfo = wx.getMenuButtonBoundingClientRect(); [代码] width height top right bottom left 宽度 高度 上边界坐标 右边界坐标 下边界坐标 左边界坐标 下面是官方给的示意图,方便大家理解几个坐标。 [图片] 2.获取系统信息 用wx.getSystemInfoSync()【官方文档】获取系统信息,里面有个参数:statusBarHeight(状态栏高度),是我们后面计算整个导航栏的高度需要用到的。 [代码]const systemInfo = wx.getSystemInfoSync(); [代码] 三、计算公式 我们先要知道导航栏高度是怎么组成的, 计算公式:导航栏高度 = 状态栏高度 + 44。 实例 【源码下载】 自定义导航栏会应用到多个、甚至全部页面,所以封装成组件,方便调用;下面是我写的一个简单例子: app.js [代码]App({ onLaunch: function(options) { const that = this; // 获取系统信息 const systemInfo = wx.getSystemInfoSync(); // 胶囊按钮位置信息 const menuButtonInfo = wx.getMenuButtonBoundingClientRect(); // 导航栏高度 = 状态栏高度 + 44 that.globalData.navBarHeight = systemInfo.statusBarHeight + 44; that.globalData.menuRight = systemInfo.screenWidth - menuButtonInfo.right; that.globalData.menuBotton = menuButtonInfo.top - systemInfo.statusBarHeight; that.globalData.menuHeight = menuButtonInfo.height; }, // 数据都是根据当前机型进行计算,这样的方式兼容大部分机器 globalData: { navBarHeight: 0, // 导航栏高度 menuRight: 0, // 胶囊距右方间距(方保持左、右间距一致) menuBotton: 0, // 胶囊距底部间距(保持底部间距一致) menuHeight: 0, // 胶囊高度(自定义内容可与胶囊高度保证一致) } }) [代码] app.json [代码]{ "pages": [ "pages/index/index" ], "window": { "navigationStyle": "custom" }, "sitemapLocation": "sitemap.json" } [代码] 下面为组件代码: /components/navigation-bar/navigation-bar.wxml [代码]<!-- 自定义顶部栏 --> <view class="nav-bar" style="height:{{navBarHeight}}px;"> <input class="search" placeholder="输入关键词!" style="height:{{menuHeight}}px; min-height:{{menuHeight}}px; line-height:{menuHeight}}px; left:{{menuRight}}px; bottom:{{menuBotton}}px;"></input> </view> <!-- 内容区域: 自定义顶部栏用的fixed定位,会遮盖到下面内容,注意设置好间距 --> <view class="content" style="margin-top:{{navBarHeight}}px;"></view> [代码] /components/navigation-bar/navigation-bar.json [代码]{ "component": true } [代码] /components/navigation-bar/navigation-bar.js [代码]const app = getApp() Component({ properties: { // defaultData(父页面传递的数据-就是引用组件的页面) defaultData: { type: Object, value: { title: "我是默认标题" }, observer: function(newVal, oldVal) {} } }, data: { navBarHeight: app.globalData.navBarHeight, menuRight: app.globalData.menuRight, menuBotton: app.globalData.menuBotton, menuHeight: app.globalData.menuHeight, }, attached: function() {}, methods: {} }) [代码] /components/navigation-bar/navigation-bar.wxss [代码].nav-bar{ position: fixed; width: 100%; top: 0; color: #fff; background: #000;} .nav-bar .search{ width: 60%; color: #333; font-size: 14px; background: #fff; position: absolute; border-radius: 50px; background: #ddd; padding-left: 14px;} [代码] 以下是调用页面的代码,也就是引用组件的页面: /pages/index/index.wxml [代码]<navigation-bar default-data="{{defaultData}}"></navigation-bar> [代码] /pages/index/index.json [代码]{ "usingComponents": { "navigation-bar": "/components/navigation-bar/navigation-bar" } } [代码] /pages/index/index.js [代码]const app = getApp(); Page({ data: { // 组件参数设置,传递到组件 defaultData: { title: "我的主页", // 导航栏标题 } }, onLoad() { console.log(this.data.height) } }) [代码] 效果图: [图片] 好了,以上就是全部代码了,大家可以文中复制代码,也可以【下载源码】,直接到开发者工具里运行,记得appid用自己的或者测试哦! 下面附几张其它小程序的效果图,大家也可以尝试照着做: [图片][图片] 总结 本文写了自定义navigationBar的一些基础性东西,里面涉及组件用法、参数传递、导航栏相关。 由于测试环境有限,大家在使用时如果发现有什么问题,希望及时反馈,以供及时更新帮助更多的人! 大家有什么疑问,欢迎评论区留言!
2022-06-23 - 小程序利用safe-area-inset-*兼容iPhoneX
分别创建屏幕上边框,右边框,下边框,左边框安全距离: safe-area-inset-top, safe-area-inset-right, safe-area-inset-bottom, safe-area-inset-left 使用: iOS 11 padding-top: constant(safe-area-inset-top); padding-right: constant(safe-area-inset-right); padding-bottom: constant(safe-area-inset-bottom); padding-left: constant(safe-area-inset-left); iOS 11.2 beta及其后 padding-top: env(safe-area-inset-top); padding-right: env(safe-area-inset-right); padding-bottom: env(safe-area-inset-bottom); padding-left: env(safe-area-inset-left); 兼容性写法: padding-top: 10px; padding-top: constant(safe-area-inset-top); padding-top: env(safe-area-inset-top); 与calc合用: padding-top: 10px; padding-top: calc(10px + constant(safe-area-inset-top)); padding-top: calc(10px + env(safe-area-inset-top)); 终!使用sass@mixin: @mixin x-padding-bottom($val:0px) { padding-bottom: $val; padding-bottom: calc(#{$val / 2} + constant(safe-area-inset-bottom)); /* no */ padding-bottom: calc(#{$val / 2} + env(safe-area-inset-bottom)); /* no */ } 注意!!! 1、默认值为0px,不是0,原因是calc不支持与0计算。 2、小程序单位为rpx,一般都会转换为rpx,但是calc不支持,所以不允许转换,保持px。 参考文档:苹果官方文档
2019-10-11 - 【新人报道】尝鲜小程序自动化测试工具(miniprogram-automator)
测试驱动简介 关于测试驱动的快速入门,可以看之前的这篇文章《 换一种思路写代码,前端测试驱动开发模式(TDD)快速入门》,这篇文章就跳过一些基础的概念。 小程序自动化 来自官方的介绍: [代码]小程序自动化 SDK 为开发者提供了一套通过外部脚本操控小程序的方案,从而实现小程序自动化测试的目的。[代码] 个人理解,这个小程序自动化工具,就是可以用代码去写一些脚本,可以操作[代码]小程序开发者工具[代码]自动的执行一些操作。 官方文档: https://developers.weixin.qq.com/miniprogram/dev/devtools/auto/ 工具的安装和配置 运行环境(来自官方文档): 安装 Node.js 并且版本大于 8.0 基础库版本为 2.7.3 及以上 开发者工具版本为 1.02.1907232 及以上 首先 npm init, 初始化一个项目。 执行下面命令安装工具: [代码]npm i miniprogram-automator --save-dev [代码] 因为需要使用 jest 做为测试的工具,所以需要安装 jest,执行以下两条命令: [代码]npm i jest --save-dev npm i jest -g [代码] 在目录下创建 test 文件夹,创建以[代码].spec.js[代码]结尾的文件,在此文件中写入测试代码, 我这里建立[代码]index.spec.js[代码]。在 package.json 的 script 中添加: [代码]{ "test": "jest" } [代码] 这样[代码]npm run test 文件名[代码]就可以运行指定的测试文件。 连接小程序开发工具 我在尝鲜的时候在这里花了一些时间在连接小程序开发工具上。 首先需要在微信开发者工具, 设置->通用设置->安全中把服务端口打开。 [图片] 在[代码]index.spec.js[代码]中添加连接的代码: [代码]import automator from 'miniprogram-automator' describe('index', () => { let miniProgram // beforeAll 表示在执行所有测试之前需要的操作 beforeAll(async () => { miniProgram = await automator.launch({ cliPath: `${cli.bat文件的路径}`, // 如果是默认安装路径,就不需要写了 projectPath: `${项目路径}` //这里需要写上项目路径 }) }, 40000) }) [代码] 这时候就可以执行命令来测试连接小程序开发者工具了, 如果连接成功的话,在执行命令后,就会自动打开微信开发者工具,如果连接失败,就是命令行工具会关闭,并且打不开微信开发者工具。 一些连接经验: 为了路径兼容 windows 和 mac 最好用 node 的 path 模块去拼路径 cli.bat 是在微信开发者工具的根目录中,记得写路劲的时候要加上.bat 的后缀名 如果你是默认路径安装的,如果还是打不开,我自己就是遇到这个情况,我是通过 imweb 团队这篇文章中写的,打开自动化操作服务端口后就可以连接使用了 基本使用思路 目前这个工具只有四个 api: Automator, MiniProgram, Page, Element, 点击查看文档。 Automator 主要提供启动和连接开发者工具的方法。 MiniProgram 主要提供操作小程序的一些方法, 比如跳转页面,getSystemInfo, 或者执行 wx 对象上的方法 Page 主要提供操作页面的方法,比如用$获取元素,获取页面的 data,调用页面的方法 element 主要提供元素的操作方法 主要的使用思路就是用代码来操作小程序的操作。比如可以先用 MiniProgram 的页面跳转方法跳转到指定的页面,用 Page 的 callMethod 来执行相关的方法,再选取需要测试的元素,比较是否符合预期。 示例 这里展示一个小的示例。在电商小程序中,大部分页面都需要一进入就需要请求接口拿数据,我们通常把原始数据经过洗数据层变成前端想要的样子。现在就来测试一下是否初始的数据是我们期望的那样。 步骤是: 连接小程序,并且跳转的指定页面 等待初始接口调用 查看 page 的 data,比较是否符合预期 [代码]describe('index', () => { let miniProgram let page // 连接微信开发者工具 beforeAll(async () => { miniProgram = await automator.launch({ projectPath: `${root}` }) page = await miniProgram.reLaunch('/pages/plp/plp') // 使用miniProgram的api跳转到指定页面 await page.waitFor(500) // 等待页面请求接口 }, 40000) it('初始化洗数据后, 初始数据满足要求', async (done)=>{ const data = await page.data() // 使用page的方法获取当前页面的data expect(data).toMatchObject({ items: expect.anything(), facet: expect.anything() }) // 通过jest的expect方法比较这个data是否符合预期 done() }) 在执行 afterAll(async () => { await miniProgram.close() }) }) [代码] 执行 [代码]npm run test index[代码] 后,看到 pass 的字样,就表示这个测试通过了。 Jest 会自动把通过与不通过的测试用例都展示出来。 总结 小程序自动化测试工具 api 很简单,思路也简单易懂,可能连接的时候需要花一点时间。 实施好自动化测试可以提升程序本身的健壮性。 之后还要花一些时间来深入的使用。
2019-10-12 - 如何实现快速生成朋友圈海报分享图
由于我们无法将小程序直接分享到朋友圈,但分享到朋友圈的需求又很多,业界目前的做法是利用小程序的 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 - 如何接入腾讯地图导航
这几天在学校跟几个同学一起开发了一款校园小程序,里面提供:二手交易、维修上报、信息发布、失物招领等服务,目前该小程序已在学校推广试运营。 在维修上报这里有个地址导航的服务,现总结如何接入 具体参考官方文档如下 (13)腾讯地图插件? - 微信开放社区 https://developers.weixin.qq.com/community/develop/doc/000c205a840db8786ee66de2556409 文档地址: https://mp.weixin.qq.com/wxopen/plugindevdoc?appid=wx50b5593e81dd937a&token=2083178782&lang=zh_CN https://docs.qq.com/doc/DSEFseFRUekFBRFds 1.在“小程序管理后台-设置-第三方服务-插件管理”中查找插件名称“腾讯地图”,并申请使用。 [图片] 2 [图片] 3 [图片] 4 第二步:代码编写 [图片] [图片] 5 小程序相关截图 1 [图片] 2 [图片] 3
2020-05-28 - @supports 支持
希望能支持下面的写法,适配 iPhoneX 就比较方便了 @supports (bottom: constant(safe-area-inset-bottom)) {}
2019-02-13 - 小程序在iPhone上的map 绘制marker的性能差
iphone在map上渲染marker的时候有性能的问题吗? 在我的应用中有不同类型的marker(icon不同), 需要在不同的类型中切换, marker的数量本身不多, 但在不同类型中切换的时候,iPhone表现的很差, 往往第一次的渲染没结束时切换到第二种, 这时候新的和旧的icon就同时出现在了地图上, 感觉是脏数据显示在了地图上. 因为地图的缩放和drag 会重绘marker, 但这个脏数据就不会被清除, 并且点击事件也没有响应.
2019-04-26 - map动态设置markers,更新markers的值不显示
<map longitude="{{_lon}}" latitude="{{_lat}}" id="map" scale="{{_scale}}" markers="{{markers}}" show-location style="width: 100%; height: 100%;" bindcontroltap="controltap" bindmarkertap="markertap" bindregionchange = "regionchange" bindtap='mapClick' > <!-- 入场面板 --> <cover-view class="parkDetail" hidden="{{inParkFlag}}"> <cover-view class="title"> <cover-view class="place-txt"> <cover-image src="../../images/place_icon.png" class="place-icon"></cover-image> <cover-view class="parking-name">{{park.parkName}}</cover-view> </cover-view> <cover-view class="add-car"> <cover-view class="i-block" bindtap='addCar' hidden='{{!carFLag}}'>添加车辆</cover-view> <cover-view class="i-block" bindtap='changeCar' hidden='{{carFLag}}'> {{defaultCarNo}} <cover-image src='../../images/u_arrow_dow.png' class='add-car-img'> </cover-image> </cover-view> </cover-view> </cover-view> <cover-view class="title-bottom"></cover-view> <cover-view class="content"> <cover-view class="center"> <cover-view class="price fleft">{{park.parkPrice}}</cover-view> <cover-view class="leftPlace"> <cover-view class="leftPlace-con">{{park.leftPlace}}</cover-view> <cover-view class="leftPlace-con">剩余车位</cover-view> </cover-view> <cover-view class="cboth"></cover-view> <cover-view> <cover-view class="view fleft" style='width:{{admissionWidth}}' bindtap='goParkingDetails'>查看</cover-view> <cover-view class="admission fleft" hidden='{{admissionFlag}}' bindtap="inPark">入场</cover-view> <cover-view class="navigation fright" style='width:{{admissionWidth}}' bindtap="goDaohang">导航</cover-view> <cover-view class="cboth"></cover-view> </cover-view> </cover-view> </cover-view> </cover-view> <!-- --> <!-- 出场面板 --> <cover-view class="parkDetail" hidden='{{outParkFlag}}'> <cover-view class="title"> <cover-view class="place-txt"> <cover-image src="../../images/place_icon.png" class="place-icon"></cover-image> <cover-view class="parking-name">{{parking.parkName}}</cover-view> </cover-view> <cover-view class="add-car"> <cover-view class="i-block" bindtap='changeCar'>{{defaultCarNo}} <cover-image src='../../images/u_arrow_dow.png' class='add-car-img'></cover-image> </cover-view> </cover-view> </cover-view> <cover-view class="title-bottom"></cover-view> <cover-view class="parking-lot"> <cover-image src='../../images/u_parking.png' class="u-parking-img"></cover-image> <cover-view class='inline-block gray park-ing'>停车中...</cover-view> <cover-view class='inline-block orange park-people'>(入场时,请展示给车场管理人员)</cover-view> </cover-view> <cover-view class='parking-lot'> <!-- --> <cover-view class='inline-block orange'>已分配车位:{{parking.carportNum}}</cover-view> </cover-view> <cover-view class='parking-lot'> <cover-view class='inline-block'>停车时间:</cover-view> <cover-view class='inline-block gray' style='width:60%;'>{{timing}}</cover-view> </cover-view> <cover-view class='out-park' bindtap='outPark'>出场</cover-view> </cover-view> <!-- location图片 --> <cover-image src='../../images/location.png' hidden="{{user.hidden}}" class="img-location"></cover-image> <!-- 返回定位点 --> <cover-view class="back-situ" hidden="{{user.hidden}}" bindtap='moveToLocation' style='bottom:{{park.backBottom}}'> <cover-image src='../../images/my_place.png'></cover-image> </cover-view> <!-- 个人中心头像 --> <cover-view class="user" hidden="{{user.hidden}}" bindtap="goUserCenter" style='bottom:{{park.backBottom}}'> <cover-image src="{{_userAverurl}}" class='user-center-img'></cover-image> </cover-view> <!-- <cover-view class="scanInPark" hidden="{{scan.hidden}}" style="top:{{scan.top}}px;" bindtap="scanCode"> <i class="fa fa-qrcode"></i> 扫码入场 </cover-view> --> <!-- 未支付账单 --> <cover-view class='bill-container' hidden='{{billFlag}}'> <cover-view class='bill-text'>您有未支付账单</cover-view> <cover-view class="title-bottom"></cover-view> <cover-view class='bill-btn' bindtap='toPay'>去结算</cover-view> </cover-view> </map> js: const app = getApp() const _ajax = require('../../utils/service.js'); Page({ data: { _lon: 0, _lat: 0, _scale: 18, _userAverurl: '', // 用户头像 markers: [], // 地图map的markers parkList: [], // 获取的车场数据 pickParkList: {}, // 入场数据 chooseParkList: {}, // 导航数据 viewParkList: {}, // 查看所需数据 park: { // 入场面板 backBottom: null }, user: { hidden: true }, header: {}, // 请求header tokenId: '', admissionFlag: true, // 入场按钮Flag admissionWidth: null, // 查看、导航的宽度 inParkFlag: true, // 入场面板Flag outParkFlag: true, // 出场面板Flag cannotFLag: false, // 不准操作Flag timing: '', // 停车计时 parking: { // 停车中的数据 parkGateOid: null, // 停车场出口ID parkName: '', // 停车场名称 carNum: '', // 车牌号 carportNum: '', // 车位号 parkingInDttm: null, // 确认入场时间 sysDttm: null, // 系统当前时间 }, billFlag: true, // 未支付账单Flag carList: [], // 车牌列表 carFLag: true, // 车牌FLag defaultCarNo: '', // ,默认车牌 clearInterVal: '' // 清楚定时器 }, onLoad() { wx.showLoading({ title: '定位中' }); app.uparkingLogin() }, onReady: function() { this.mapCtx = wx.createMapContext("map"); this.initMap(); }, onShow: function() { this.haveToken(); }, haveToken() { if (app.globalData.haveToken === true) { this.initParking() } else { setTimeout(() => { this.haveToken(); }, 500); } }, initParking() { this.setData({ _userAverurl: app.globalData.userInfo.headUrl, }) clearInterval(this.data.clearInterVal) // 检测是否有车牌 this.getCarList(); // 检测是否有未支付账单 this.getNoPayParkBill(); // 检测是否有停车中的状态 this.getMyNowParking(); }, initMap() { wx.getLocation({ type: 'gcj02', success: (res) => { let longitude = res.longitude; let latitude = res.latitude; app.globalData.userLocation.lon = longitude; app.globalData.userLocation.lat = latitude; // 加载了用户位置信息之后,开始进行地图初始化操作 this.initApp(); // 请求周边停车场数据 this.getAroundParkData(); }, fail: res => { wx.hideLoading(); wx.showModal({ title: '用户未授权', content: '如果需要正常使用定位功能,请点击确定并在授权管理中打开“位置”。最后再进入小程序即可正常使用', showCancel: false, success: (res) => { if (res.confirm) { console.log('用户点击确定') wx.openSetting({ success: (res) => { console.log(res) } }) } else if (res.cancel) { console.log('用户点击取消') } } }) } }); }, initApp() { // 初始化APP应用等。 // 初始化数据 this.setData({ _lon: app.globalData.userLocation.lon, _lat: app.globalData.userLocation.lat, user: { hidden: false } }); wx.hideLoading(); }, // 定位到当前位置 moveToLocation() { this.mapCtx.moveToLocation(); }, // 点击停车场标记P,显示停车场详情模态框 markertap(e) { if (this.data.cannotFLag) { return } let parkList = this.data.parkList; let admissionFlag = null; let admissionWidth = null; for (let i = 0; i < parkList.length; i++) { let park = parkList[i]; // 初始化停车场图标 if (park.entOid === '') { park.iconPath = '/images/blue_free.png'; } else { park.iconPath = '/images/red_free.png'; } // 停车场active状态 if (park.id === e.markerId) { if (park.entOid === '') { park.iconPath = '/images/blue_full.png'; } else { park.iconPath = '/images/red_full.png'; } // 非红P车场不显示”入场“按钮 if (park.entOid === '') { admissionFlag = true, admissionWidth = 50 + '%' } else { admissionFlag = false, admissionWidth = 30 + '%' } // 显示面板并展示停车场详情 this.setData({ inParkFlag: false, park: { backBottom: 310 + 'rpx', // 回到原点图片位置 parkName: park.parkName, // 车场名称 parkPrice: park.parkFirstHour + "小时内" + park.parkFirstFee + ",后续每小时" + park.parkPerHourFee, // 停车场收费情况 leftPlace: park.leftPlace + '个', // 车位 }, // 导航所需数据 chooseParkList: { id: park.id, longitude: park.longitude, latitude: park.latitude, name: park.parkGateName, address: park.address }, // 查看所需数据 viewParkList: { parkHeadUrl: park.parkHeadUrl, // 车场图片 parkName: park.parkName, // 车场名称 leftPlace: park.leftPlace, // 空余车位 address: park.address, // 详细地址 parkPrice: park.parkFirstHour + "小时内" + park.parkFirstFee + ",后续每小时" + park.parkPerHourFee, // 停车场收费情况 }, admissionFlag: admissionFlag, admissionWidth: admissionWidth, }); } this.setData({ markers: parkList }) } }, // 点击地图 mapClick() { if (this.data.cannotFLag) { return } let markerList = this.data.markers; for (let i = 0; i < markerList.length; i++) { let marker = markerList[i]; if (marker.entOid === '') { marker.iconPath = '/images/blue_free.png'; } else { marker.iconPath = '/images/red_free.png'; } this.setData({ markers: markerList, park: { backBottom: '10%' }, inParkFlag: true }); } }, // 滑动地图,end事件 regionchange(e) { if (e.type === 'end') { if (this.data.inParkFlag) { this.moveToNewLocation(); } } }, // 移动到新位置,并重新列出周边的停车场 moveToNewLocation() { this.mapCtx.getCenterLocation({ success: (res) => { this.data._lon = res.longitude; this.data._lat = res.latitude; this.getAroundParkData(); } }) }, // 车场距离排序 sortParkList: function(list) { for (var i = 0; i < list.length; i++) { //var dis = CommonTools.getDistance(this.data._lat, this.data._lon, list[i].parkGateLat, list[i].parkGateLon); //list[i].dis = dis; } //this.data.parkList = CommonTools.parkSortByDis(list); }, // 入场 inPark() { wx.showModal({ title: '入场提示', content: '确定后,即将开始计费', success: (res) => { console.log(this.data.defaultCarNo) if (res.confirm) { if (this.data.defaultCarNo === '') { wx.navigateTo({ url: '../addCar/addCar', }) } else { _ajax.post('/park/inPark', { parkId: this.data.chooseParkList.id, carNum: this.data.defaultCarNo, isScanBluetooth: '2' }, (res) => { this.getMyNowParking(); this.setData({ outParkFlag: false, cannotFLag: true }) }) } } } }) }, // 出场 outPark() { wx.showModal({ title: '出场提示', content: '确定后,请尽快离场!超过时段将产生额外费用!', success: (res) => { if (res.confirm) { _ajax.post('/park/outPark', { parkId: this.data.parking.parkGateOid, carNum: this.data.defaultCarNo, isScanBluetooth: '2' }, (res) => { console.log(res); this.setData({ inParkFlag: true, outParkFlag: true, cannotFLag: true, billFlag: false, park: { backBottom: '10%' } }) }) } else if (res.cancel) { console.log('用户点击取消') } } }) }, // 进入个人中心 goUserCenter: function() { wx.navigateTo({ url: "../userCenter/userCenter" }); }, // 导航 goDaohang() { wx.openLocation({ longitude: parseFloat(this.data.chooseParkList.longitude), latitude: parseFloat(this.data.chooseParkList.latitude), name: this.data.chooseParkList.parkGateName, address: this.data.chooseParkList.address, scale: 18 }) }, // 请求周边停车场数据 getAroundParkData() { _ajax.post('/park/getAroundParkingLotList', { longitude: this.data._lon + '', latitude: this.data._lat + '' }, (res) => { let markerList = res.data.context.list; let parkList = []; let iconPath = ''; let width = ''; let height = ''; for (let item of markerList) { if (item.entOid === '') { iconPath = '/images/blue_free.png'; width = 22; height = 32; } else { iconPath = '/images/red_free.png'; width = 42; height = 42; } parkList.push({ iconPath: iconPath, // 图标 id: +item.parkOid, // 车场id entOid: item.entOid, // 是否为公司车场,空表示不是 longitude: +item.parkGateLon, // 车场经度 latitude: +item.parkGateLat, // 车场纬度 parkGateName: item.parkGateName, // 车场位置名 address: item.address, // 车场地址的详细说明 width: width, // 图标宽度 height: height, // 图标高度 parkName: item.parkName, // 停车场名称 parkFirstHour: item.parkFirstHour, // 首次费用时间(小时) parkFirstFee: item.parkFirstFee, // 首次费用 parkPerHourFee: item.parkPerHourFee, // 后续每小时费用 leftPlace: item.leftPlace, // 剩余车位数 parkHeadUrl: item.parkHeadUrl, // 车场图片 anchor: { x: 0.5, y: 0.5 } }); } // this.sortParkList(res.data.context.list); this.setData({ parkList: parkList, markers: parkList }); }) } })
2019-10-11