实战:MPVue + 腾讯地图开发微信小程序

4次阅读

共计 6825 个字符,预计需要花费 18 分钟才能阅读完成。

原文链接
最近小程序的发展越来越火了,作为各个产品线的 extra 服务入口,以轻便、快速、强大的社交链吸引着大量的用户和开发者。业内开发框架层出不穷,wepy,mpvue,taro 等等,都在朝着更快,更强大的方向发展,有统一 H5、微信、支付宝、百度和头条小程序的大趋势。
本文旨在以 mpvue 框架为基础,探讨地图类小程序的开发思路,权当分享和总结。话不多说,先体验一下:

github 源码地址:https://github.com/WarpPrism/…
mpvue 介绍 及项目搭建
mpvue = miniprogram + vue framework,说白了就是用 vue 框架开发小程序。mpvue 最近升级为 2.x 版本,支持微信、支付宝、百度和头条小程序。和传统方式相比,mpvue 开发具有以下优点:

彻底的组件化开发能力:提高代码复用性
完整的 Vue.js 开发体验
方便的 Vuex 数据管理方案:方便构建复杂应用
快捷的 webpack 构建机制:自定义构建策略、开发阶段 hotReload
支持使用 npm 外部依赖
使用 Vue.js 命令行工具 vue-cli 快速初始化项目
H5 代码转换编译成小程序目标代码的能力

就个人使用体验来看,还是挺丝滑顺畅的,传统 web 应用开发无缝切换至小程序开发,基本零门槛。要注意的就是小程序的限制及和 vue 的差异:

小程序使用相对像素 rpx 进行样式布局
部分 css 选择符不支持,目前只支持 #id | .class | tag | tag,tag | ::after ::before,所以要特别注意
组合式生命周期,mpvue 将小程序和 vue 的生命周期混在一块,详情见 http://mpvue.com/mpvue/#_3,目前这个地方还有很多坑,比如在小程序 page unload 时,vue 实例却没被销毁,导致下次进入页面时,页面状态不变,必须在 unLoad 时手动重置状态等
mpvue 会封装小程序数据对象,通常以 $mp 开头,如 event.$mp.detail.target 等
小程序的组件和 vue 组件有差异,不要幻想 vue 组件的特性都能用,如 slot,异步组件等等
vue store 和 wx localstorage 最好不要弄混,要根据不同需要选择不同的存储方式
不要用 vue 路由,要采用小程序原生的导航机制

然后,我们搭建开发环境,mpvue 脚手架是开箱即用的:
# 全局安装 vue-cli
# 一般是要 sudo 权限的
$ npm install –global vue-cli@2.9

# 创建一个基于 mpvue-quickstart 模板的新项目
# 新手一路回车选择默认就可以了
$ vue init mpvue/mpvue-quickstart my-project

# 安装依赖,走你
$ cd my-project
$ npm install
$ npm run dev
接着,完善文件结构,增加 config、store、mixins 等模块,如图:

app.json 是小程序专用文件,也需完善下:
{
“pages”: [
“pages/citylist/main”,
“pages/citydetail/main”
],
“permission”: {
“scope.userLocation”: {
“desc”: “ 你的位置信息将用于小程序位置接口的效果展示 ”
}
},
“window”: {
“backgroundTextStyle”: “light”,
“navigationBarBackgroundColor”: “#eee”,
“navigationBarTitleText”: “ 全球地铁,全程为你 ”,
“navigationBarTextStyle”: “black”
}
}
然后就可以愉快的写 Vue 代码了,咔咔一个页面,咔咔又是一个页面,组件,store,数据驱动,你喜欢的样子,它都有。
腾讯地图 + 小程序
着重说一下地图的接入,腾讯地图提供了两个对接入口给小程序,1 是个性化地图展示,2 是专用 SDK,二者共同完善了小程序的地图生态。
(1)个性地图展示需要开发者自行注册并申请开发者密钥(key),并在管理后台绑定小程序,然后设置个性地图的样式,才能使用:
<map
id=”citymap”
name=”citymap”
:longitude=”lng”
:latitude=”lat”
:polyline=”polyline”
:markers=”markers”
scale=”12″
:subkey=”YOUR_OWN_QQMAP_KEY”
show-location
show-compass
enable-rotate
style=”width: 100%; height: 100%;”
>
<cover-view class=”map-cover-view”>
<button class=”explore-btn” type=”primary” @tap=”exploreCity”> 查看旅游攻略 </button>
</cover-view>
</map>
其中,map 是小程序的原生组件,原生组件脱离在 WebView 渲染流程外,它的层级是最高的,所以页面中的其他组件无论设置 z-index 为多少,都无法盖在原生组件上。说白了就是原生组件是微信客户端提供的,它不属于内置浏览器,为此,小程序专门提供了 cover-view 和 cover-image 组件,可以覆盖在部分原生组件上面。这两个组件也是原生组件,但是使用限制与其他原生组件有所不同。
笔者就因为这个坑耽误了不少时间,有时候开发工具可以用,但到了真机上组件就完全乱了,所以还是要以真机调试为准。对于原生组件,不要用太复杂的 css,它的很多 css 属性支持的都不好。
map 可以定义多个参数,经纬度不用说,scale 指放缩比例,也就是地图比例尺,polyline 在地图上绘制折线,markers 用于标记地图上的点,show-location 用于显示用户所在位置,show-compass 显示指北针。
(2)专用 SDK,目前提供这些能力:

search(options:Object) 地点搜索,搜索周边 poi,比如:“酒店”“餐饮”“娱乐”“学校”等等
getSuggestion(options:Object) 用于获取输入关键字的补完与提示,帮助用户快速输入
reverseGeocoder(options:Object) 提供由坐标到坐标所在位置的文字描述的转换。输入坐标返回地理位置信息和附近 poi 列表
geocoder(options:Object) 提供由地址描述到所述位置坐标的转换,与逆地址解析的过程正好相反
direction(options:Object) 提供驾车,步行,骑行,公交的路线规划能力
getCityList() 获取全国城市列表数据
getDistrictByCityId(options:Object) 通过城市 ID 返回城市下的区县
calculateDistance(options:Object) 计算一个点到多点的步行、驾车距离

我们以公共交通路线规划为例来看下(以下代码经过简化处理):
第一步,初始化地图 SDK 对象
import config from ‘@/config’
import QQMapWX from ‘../../assets/lib/qqmap-wx-jssdk.js’ // 这里用未压缩版的代码
const QQMapSDK = new QQMapWX({
key: config.qqMapKey || ”
})
第二步,获取起止坐标点,并进行路线查询
// 坐标从上一页 query 传进来,坐标为浮点数,可通过 geocoder 接口获取
this.fromLocation = {
latitude: +query.from.split(‘,’)[0] || -1,
longitude: +query.from.split(‘,’)[1] || -1
}

this.toLocation = {
latitude: +query.to.split(‘,’)[0] || -1,
longitude: +query.to.split(‘,’)[1] || -1
}

// 查询地图路线
queryMapRoutine() {
QQMapSDK.direction({
mode: ‘transit’, // ‘transit'(公交路线规划)
// from 参数不填默认当前地址
from: this.fromLocation,
to: this.toLocation,
success: (res) => {
console.log(‘ 路线规划结果 ’, res);
let routes = res.result.routes;
this.routes = routes.map(r => {
// 对每一种路线方案,分别进行解析
return this.parseRoute(r)
})
console.log(‘parsed routes’, this.routes)
}
})
}
第三步,路线解析,生成路线描述等
// 解析路线,包括距离,时间,描述,路线,起止点等
parseRoute(route) {
let result = {}
// 出发时间
result.setOutTime = formatTime(new Date())
result.distance = route.distance < 1000 ?
`${route.distance} 米 ` :
`${(route.distance / 1000).toFixed(2)} 公里 `
result.duration = route.duration < 60 ?
`${route.duration} 分钟 ` :
`${parseInt(route.duration / 60)} 小时 ${route.duration % 60} 分钟 `
result.desc = []
// 每一个路线分很多步,如先步行,后乘公交,再搭地铁等
route.steps.forEach(step => {
// if (step.mode == ‘WALKING’ && step.distance > 0) {
// result.desc.push(` 向 ${step.direction} 步行 ${step.distance} 米 `)
// }
if (step.mode == ‘TRANSIT’ && step.lines[0]) {
let line = step.lines[0]
if (line.vehicle == ‘BUS’) line.title = ` 公交车 -${line.title}`
if (line.vehicle == ‘RAIL’) line.title = ` 铁路 `
result.desc.push(`${line.title}: ${line.geton.title} —> ${line.getoff.title},途经 ${line.station_count} 站。`)
}
})
result.polyline = []
result.points = []
// 获取各个步骤的 polyline,也就是路线图
for(let i = 0; i < route.steps.length; i++) {
let step = route.steps[i]
let polyline = this.getStepPolyline(step)
if (polyline) {
result.points = result.points.concat(polyline.points)
result.polyline.push(polyline)
}
}
// 标记路线整体显示坐标
this.getStepPolyline.colorIndex = 0
let midPointIndex = Math.floor(result.points.length / 2)
result.latitude = result.points[midPointIndex].latitude
result.longitude = result.points[midPointIndex].longitude
// 标记路线起止点
let startPoint = result.points[0]
let endPoint = result.points[result.points.length – 1]
result.markers = [
{
iconPath: this.startIcon,
id: 0,
latitude: startPoint.latitude,
longitude: startPoint.longitude,
width: 28,
height: 28,
zIndex: -1,
anchor: {x: 0.5, y: 1}
},
{
iconPath: this.endIcon,
id: 1,
latitude: endPoint.latitude,
longitude: endPoint.longitude,
width: 28,
height: 28,
zIndex: -1,
anchor: {x: 0.5, y: 1}
}
]
return result
},
第四步,getStepPolyline 函数 获取路线每一步的路线 polyline
getStepPolyline(step) {
let coors = [];
// 随机颜色
let colorArr = [‘#1aad19’, ‘#10aeff’, ‘#d84e43’]
let _dottedLine = true
if (step.mode == ‘WALKING’ && step.polyline) {
coors.push(step.polyline);
_dottedLine = false
} else if (step.mode == ‘TRANSIT’ && step.lines[0].polyline) {
coors.push(step.lines[0].polyline);
} else {
return null
}
// 坐标解压(返回的点串坐标,通过前向差分进行压缩)
let kr = 1000000;
for (let i = 0 ; i < coors.length; i++){
for (let j = 2; j < coors[i].length; j++) {
coors[i][j] = Number(coors[i][j – 2]) + Number(coors[i][j]) / kr;
}
}
// 定义新数组,将 coors 中的数组合并为一个数组
let coorsArr = [];
let _points = [];
for (let i = 0 ; i < coors.length; i ++){
coorsArr = coorsArr.concat(coors[i]);
}
// 将解压后的坐标放入点串数组_points 中
for (let i = 0; i < coorsArr.length; i += 2) {
_points.push({latitude: coorsArr[i], longitude: coorsArr[i + 1] })
}
if (!this.getStepPolyline.colorIndex) {
this.getStepPolyline.colorIndex = 0
}
let colorIndex = this.getStepPolyline.colorIndex % colorArr.length
this.getStepPolyline.colorIndex++
// 最终 polyline 结果
let polyline = {
width: 7,
points: _points,
color: colorArr[colorIndex],
dottedLine: _dottedLine,
arrowLine: true, // 带箭头的线, 开发者工具暂不支持该属性
borderColor: ‘#fff’,
borderWidth: 1
}
return polyline
}
最后,绑定到地图上并输出,我们可以得到一个大致这样的结果:
广州火车站 -> 广州塔
15km 1 小时
地铁 5 号线 广州火车站 -> 珠江新城,途径 6 站
地铁 3 号线 珠江新城 -> 广州塔,途径 1 站
这样我们就通过 direction 接口进行了简单的路线规划功能,接着把生成的数据绑定到地图组件上,一个简易的小程序就做好了,是不是很简单?当然如果想做得更好,就要调用其他相似接口,慢慢完善细节。
<map
id=”citymap”
name=”citymap”
:latitude=”currentRoute.latitude”
:longitude=”currentRoute.longitude”
:polyline=”currentRoute.polyline”
:markers=”currentRoute.markers”
scale=”12″
:subkey=”qqMapKey”
show-location
show-compass
enable-rotate
style=”width: 100%; height: 100%;”
></map>
更多实现请参照源码和小程序本身,如果对你有帮助,可以 star 支持。
其他
这里还有一款音频类的 web 应用,发布在 gitee page 上
http://crystalworld.gitee.io/…
感兴趣的朋友可以看下,之后有时间笔者会补上开发教程和源码,另作分享。

正文完
 0