如何基于微信小程序开发网约车应用
如何基于微信小程序开发网约车应用
项目描述
为了解决长途如跨城市的出行撮合需求,满足乘客和司机双方自由定价的意愿,特开发一款非及时的打车应用。基本功能是出行用户登录小程序后,根据自己角色选择发布行程计划。行程计划包括出行时间和起始位置以及期望价格;如果是乘客,发布自己的出行计划之后跳转到乘客发布的出行计划列表页,乘客可以点击期望的行程计划,邀请司机接单;如果是司机,需要验证是否已经认证通过。如果没有认证通过则跳转到认证页面,否则跳转到乘客发布的出行计划列表页,选择期望的出行计划,完成接单。司机和乘客通过聊天页面协调出行计划。暂不支持支付功能,由双方线下完成交易。用流程图描述为:
系统原型
主要提供3个Tab页面:“首页”、“消息”和“我的”。其中“首页”聚焦行程发布、行程查看和聊天会话等核心功能。“消息”聚焦历史会话检索等功能,在首页进行的会话会跳转到该tab页面。“我的”聚焦车主认证、司机或者乘客查看历史行程记录以及客服服务等功能。原型图如下所示:
系统存储设计
根据业务,存储表主要有以下几种:
- driver:司机认证记录表,包括司机个人信息以及认证状态等
- driver_route:司机发布的行程记录表
- passager_route:乘客发布的行程记录表
- bargin_route:成交的行程记录表
- chat_partner:聊天的双方参与者
- message:会话记录
存储表结构和各表之间的关系如下所示:
开发准备
a. 帐号申请
开发小程序的第一步需要注册一个小程序帐号,可能这一步是小程序开发最大的障碍,因为不管哪种帐号都需要认证,特别是企业类型帐号认证需要企业工商营业执照和组织机构代码证,如果小程序需要支付功能,还需要提供对公帐号。但是你也可以使用个人帐号类型,但是个人帐号具备的功能很有限,比如不能支持支付功能等。有关小程序的注册类型和认证的疑问可以参考小程序注册类型和认证(
)。注册小程序帐号之后,就可以得到一个appId和secret key,它们跟小程序应用相绑定的,在后续API调用中是不可缺少的。
b. 开发工具
同开发其他应用程序一样,微信团队同样也有自己的开发集成工具。有关如何注册和下载开发工具,可以参考官方文档:注册和下载开发工具
( )
。下面简要介绍开发工具的常用功能:
从上面图中可以看到开发工具由以下几个区域组成:
功能预览区:代码编辑保存,开发工具会自动编译并生成预览,在该区域可以及时看到小程序渲染后的效果;
文件浏览区:也就是文件浏览器,树状图形式可以展开和收拢;
代码编辑区:提供代码阅读、搜索和编辑提示功能;
网络调试区:集成的是google开发工具组件,功能相信大家已经很熟悉。
最上面一排的按钮功能区,主要包括编译、代码上传和代码仓库版本管理以及云服务入口功能等。这里唯一需要普及的是小程序的代码构成。
c.小程序代码组成
一个小程序通常由一个描述整体程序的 app 和多个描述各自页面的 page页面模块组成。其中app主体部分由三个文件组成,必须放在项目的根目录,文件为:
app.js: 控制小程序的全局业务逻辑;
app.json:小程序的全局公共配置信息;
app.wxml:小程序的全局公共样式,
每个页面模块由4钟类型的文件组成,放置一个目录里面,四种类型的文件为:
.json 后缀的 JSON 配置文件:存放配置信息;
.wxml 后缀的 WXML 模板文件:页面内容模板,支持变量的动态渲染;
.wxss 后缀的 WXSS 样式文件:页面样式定义;
.js 后缀的 JS 脚本逻辑文件:js实现的业务逻辑,是页面模块中最重要的文件。
比如我们项目的代码结构组成如下图所示:
更多信息可以参考文档:小程序目录结构
( )
和代码构成
( )
前端设计
我们先确定小程序的总体展现框架,在app.wxml圈定整体结构:
1{
2 "cloud": true,
3 "pages": [
4 "pages/home/home",//home tab页面
5 "pages/position/position",//定位服务页面
6 "pages/drivers/drivers",//司机和乘客发布的行程列表页面
7 "pages/myroutes/myroutes",//我的历史行程
8 "pages/messages/messages",//“message”tab页面
9 "pages/chat/chat",//聊天会话页面
10 "pages/detail/detail",//行程信息详情
11 "pages/certificate/certificate",//企业认证页面
12 "pages/enterprise/enterprise",//
13 "pages/mine/mine" //“我的”tab页面
14 ],
15 "window": {
16 "backgroundTextStyle": "light",
17 "navigationBarBackgroundColor": "#fff",
18 "navigationBarTitleText": "WeChat",
19 "navigationBarTextStyle": "black"
20 },
21 "tabBar": {
22 "color": "#ccc",
23 "selectedColor": "#35495e",
24 "borderStyle": "white",
25 "backgroundColor": "#f9f9f9",
26 "list": [
27 {
28 "text": "首页",
29 "pagePath": "pages/home/home",
30 "iconPath": "resources/icon_home.png",
31 "selectedIconPath": "resources/icon_home.png"
32 },
33 {
34 "text": "消息",
35 "pagePath": "pages/messages/messages",
36 "iconPath": "resources/icon_cate.png",
37 "selectedIconPath": "resources/icon_home.png"
38 },
39 {
40 "text": "我的",
41 "pagePath": "pages/mine/mine",
42 "iconPath": "resources/icon_member.png",
43 "selectedIconPath": "resources/icon_home.png"
44 }
45 ]
46 },
47 "sitemapLocation": "sitemap.json"
48}
其中"cloud": true表示我们接下来用到云服务,pages定义我们应用所有定义的页面模块路径,tabBar定义应用的展示框架,它是一个list结构,每个列表项目由tab名称、页面路径和图标路径组成。各个tab接下来详细介绍。
首页Tab
首页主要功能为司机和乘客发布行程计划,一旦行程计划发布就分别跳转到对应的列表页面。具体说就是,如果是乘客,则可以查看司机发布的出行列表信息,并可以邀请司机接单;如果是司机,则可以看到乘客的出行列表信息,并可以选择主动接单。我们将这一部分核心功能放在主页面内完成,因为无论是司机还是乘客都有共同的行为:发布行程信息,且基本项目一样,故可以复用该功能。
a.行程计划
行程计划页面是司机和乘客发布行程的主入口,主要展示行程发布的起始位置和价格等。我们定义一个模板:publishRoute.wxml,有关模板的更多信息可以参考模板
( )
1<template name="publishRoute">
2 <form bindsubmit="publishRoute">
3 <view style="display: flex;flex-direction: column;">
4 <input bindtap="inputStartPosition" style='padding: 10rpx;width:300px;margin-top: 10px;' placeholder="当前位置?" value="{{startLocation.title}}"></input>
5 <input name="startLocation" style='display:none;' value="{{startLocation.title}}"></input>
6 <input name="startAddr" style='display:none;' value="{{startLocation.title}}"></input>
7 <input name="startLatitude" style='display:none;' value="{{startLocation.location.lat}}"></input>
8 <input name="startLongitude" style='display:none;' value="{{startLocation.location.lng}}"></input>
9 <input bindtap="inputEndPosition" style='padding: 10rpx;width:300px;'placeholder="想要去哪儿?" value="{{endLocation.title}}"></input>
10 <input name="endLocation" style='display:none;' value="{{endLocation.title}}"></input>
11 <input name="endAddr" style='display:none;' value="{{endLocation.title}}"></input>
12 <input name="endLatitude" style='display:none;' value="{{endLocation.location.lat}}"></input>
13 <input name="endLongitude" style='display:none;' value="{{endLocation.location.lng}}"></input>
14 <input name="price" type="number"style='padding: 10rpx;width:300px;'placeholder="出价(单位:元)"></input>
15 <view class="btn-area">
16 <button type="primary" formType="submit">发布行程</button>
17 </view>
18 </view>
19 </form>
20</template>
其中style=‘display:none;‘的input组件是隐藏域,在表单提交时用到,它们的值在搜索定位完成后回显。输入起始位置的input组件分别绑定到事件回调函数inputStartPosition和inputEndPosition,当输入焦点落到输入框时候,调用对应函数进入搜索定位页面。
我们将模版导入到首页home.wxml中:
1view class="nav">
2 <view class='{{isDriver?"default":"red"}}'bindtap="passengerTabed">我是乘客</view>
3 <view class='{{isDriver?"red":"default"}}' bindtap="driverTabed">我是司机</view>
4</view>
5<view class='{{isDriver?"show":"hidden"}}'>
6 <import src="../home/publishRoute.wxml"/>
7 <template is="publishRoute" data="{{isDriver:isDriver,startLocation:startLocation,endLocation:endLocation,dateTimeArray:dateTimeArray,dateTime:dateTime}}"/>
8</view>
9<view class="{{isDriver?'hidden':'show'}}">
10 <import src="../home/publishRoute.wxml"/>
11 <template is="publishRoute" data="{{isDriver:isDriver,startLocation:startLocation,endLocation:endLocation,dateTimeArray:dateTimeArray,dateTime:dateTime}}"/>
12</view>
13因为publishRoute.wxml作为home.wxml内容的一部分而存在,故我们将回调函数inputStartPosition和inputEndPosition定义在home.js文件中:
14 inputStartPosition: function (e) {
15 wx.navigateTo({
16 url: '../position/position?isStartPos=true&isDriver=' + this.data.isDriver
17 })
18 },
19 inputEndPosition: function (e) {
20 wx.navigateTo({
21 url: '../position/position?isStartPos=false&isDriver=' + this.data.isDriver
22 })
23 }
在上面回调函数中导航到位置搜索页面。通过下面我们详细介绍搜索定位的实现。当发布行程的必要信息填写完毕后提交发布,发布事件回调函数绑定在form表单上:
,函数定义接下来我们再做介绍。有关小程序事件的更多信息可以参考文档:小程序事件
( )
b.位置搜索
搜索页面提供位置模糊搜索功能,示意图如下:
我们创建页面模块position,提供搜索关键词的查询、搜索历史记录查询等高级功能,为此我们把这部分功能封装为一个模块。为了简化开发,这里引入了一个第三方开源组件,可以参考:https://github.com/icindy/wxSearch。这里我们不需要这么复杂的功能,只是将我们根据关键词搜索到的候选位置信息展现在下拉列表即可。wxSearch的展现部分核心代码wxSearch.wxml模板内容为:
1<template name="wxSearch">
2 <view class="wxSearch" bindtap="wxSearchTap" style="display:{{wxSearchData.view.isShow ? 'block':'none'}};height:{{wxSearchData.view.seachHeight}}px;top:{{wxSearchData.view.barHeight}}px;">
3 <view class="wxSearchInner">
4 <view class="wxSearchMindKey">
5 <view class="wxSearchMindKeyList">
6 <block wx:for="{{wxSearchData.mindKeys}}">
7 <view class="wxSearchMindKeyItem" bindtap="wxSearchKeyTap" data-key="{{item}}">{{item}}</view>
8 </block>
9 </view>
10 </view>
11 </view>
12 </view>
13</template>
wxSearchData.mindKeys这里就是将位置列表遍历显示出来。
在position.wxml中引入上述代码:
1<import src="wxSearch/wxSearch.wxml"/>
2<form bindsubmit="confirm">
3 <view class="wxSearch-section">
4 <view class="wxSearch-pancel">
5 <input name="position" bindinput="wxSearchInput" bindfocus="wxSerchFocus" value="{{wxSearchData.value}}" bindblur="wxSearchBlur" class="wxSearch-input" placeholder="搜索"/>
6 <button class="wxSearch-button" size="mini" formType="submit" plain="true">确定</button>
7 </view>
8 </view>
9</form>
10<template is="wxSearch" data="{{wxSearchData}}"/>
11<view class="container">
12</view>
然后在postion.js中定义函数wxSearchInput:
1 wxSearchInput: function (e) {
2 var that = this
3 this.data.queryLocations=[]
4 console.log("Searching " + e.detail.value)
5 getApp().globalData.qqmapsdk.getSuggestion({
6 keyword: e.detail.value,
7 region: getApp().globalData.city,
8 success: function (res) {
9 var targets=new Array()
10 for (let i = 0; i < res.data.length; i++) {
11 targets.push(res.data[i].title)
12 that.data.queryLocations[res.data[i].title] = res.data[i]
13 }
14 WxSearch.initMindKeys(targets)
15 }
16 })
17 WxSearch.wxSearchInput(e, that);
18 }
其中WxSearch.initMindKeys(targets)将搜索到的候选位置名称放入wxSearch组件展示。当提交确认表单,将返回上一页面即home页面,将查询到的位置详细信息回显到上层页面,表单提交处理逻辑为:
1 confirm: function (event) {
2 console.log(event)
3 //WxSearch.wxSearchAddHisKey(this);
4 let pages = getCurrentPages();//当前页面
5 let prevPage = pages[pages.length - 2];//上一页面
6 var data={}
7 if (this.data.isStartPos=='true'){
8 data = { isStartPos: this.data.isStartPos, startLocation: this.data.selectedLocation}
9 }else{
10 data = { isStartPos: this.data.isStartPos, endLocation: this.data.selectedLocation}
11 }
12 //直接给上一页面赋值
13 prevPage.setData(data);
14 wx.navigateBack({
15 delta: 1
16 })
17 }
有关页面导航接口的详细信息可以参考:页面导航
( )
qqmapsdk.getSuggestion就是接下来要介绍的定位服务。
c.定位服务
上面调用的api接口:qqmapsdk.getSuggestion用的是腾讯位置服务:提供了地点搜索、关键词提示、(逆)地址解析、路径规划、距离计算、获取城市等功能。接口getSuggestion(options:Object) 中有两个比较重要的参数:关键词:keyword和当前区域:region。其中region参数可选,可以设置城市名,用于限定搜索范围,默认是全国。调用该接口需要申请密钥和下载JavaScriptSDK。有关该接口如何使用的更多信息可以参考官方文档:申请密钥
( )
。在本小程序中,我们使用到根据输入关键词获取位置列表接口的详细指导可以参考:获取位置列表接口
( )
。这里详细介绍下如何获取当前region,因为当前region使用贯穿于打开小程序的整个请求生命周期,所以把获取的region作为全局变量,在小程序启动时候调用。我们在app.js中加载sdk组件:
1var QQMapWX = require('utils/qqmap-wx-jssdk1.0/qqmap-wx-jssdk.js');
2App({
3 onLaunch: function () {
4 var that = this;
5 that.globalData.qqmapsdk = new QQMapWX({
6 key: conf.getQqMapKey()
7 });
8}
9})
为了能够获取到当前region,首先需要获取到当前位置的经纬度坐标,然后根据经纬度坐标解析出文字表示的region,具体步骤如下:
1. 获取当前经纬度坐标
这里我们使用微信小程序提供的api接口,接口的详细说明可以参考文档:经纬度坐标
( )
1 wx.getLocation({
2 type: "gcj02",
3 success: function (res) {
4 console.log(res)
5 var latitude = res.latitude
6 var longitude = res.longitude
7 that.globalData.location = {
8 latitude: latitude,
9 longitude: longitude
10 }
11 }
12 })
- 逆地址解析
这里我们用到腾信位置服务的另一个接口:reverseGeocoder(options:Object),该接口提供由坐标到坐标所在位置的文字描述的转换,输入坐标返回地理位置信息和附近poi列表。有关该接口的详细信息可以参考这里:逆地址解析
]( )
我们在home.js首页加载时候调用获取当前城市位置的文字描述。
1onLoad: function (option) {
2getApp().globalData.qqmapsdk.reverseGeocoder({
3 location: {
4 latitude: getApp().globalData.location.latitude,
5 longitude: getApp().globalData.location.longitude
6 },
7 success: function (res) {
8 console.log(res);
9 const { city } = res.result.address_component
10 getApp().globalData.city = city
11 }
12 })
13}
这里将上面获取的经纬度参数传进去,返回城市city名称。
d.行程发布
回到发布行程的函数定义,因为我们需要持久化用户的行程信息,这里我们使用了腾讯的云开发能力。所谓云开发能力就是微信为开发者提供了全套的云原生支持和微信服务支持,弱化后端开发和运维概念,用户无须搭建自己的服务器即可调用云端API实现自己的业务逻辑,目前微信提供的云开发能力包括云函数、云数据库、存储以及云调用。我们这里使用到云数据库。云数据库是一个 JSON 数据库,数据库中的每条记录都是一个 JSON 格式的对象。一个数据库可以有多个集合(相当于关系型数据中的表),集合可看做一个 JSON 数组,数组中的每个对象就是一条记录,记录的格式是 JSON 对象。使用云数据库需要先初始化,获取数据库实例的引用,我们在app.js中应用启动时候调用:
1App({
2 onLaunch: function () {
3 wx.cloud.init({
4 env: conf.getCloudEnv()
5 })
6 }
7})
(本篇文章为付费文章,这里之前为预览部分。后续部分包括完整的开发流程详解和完整源代码,您可以在这里阅读完整版:
微信公众号(码上观世界)阅读,地址:
如果您是gitchat会员,可以在gitchat阅读:
)