最近时常感叹道: 时间总是那么的快, 转瞬即逝. 对于像我这种刚入门的小生来讲, 技术每天都在更新, 框架也层出不穷, 有时候还没弄懂这个知识大牛们又推出了更好的技术. 当然学习好的技术也是十分重要的. 但是在学习之后怎样才能够得到自己想要的呢, 一个好的建议便是静下心来写点自己的东西, 哪怕你完成不了也应该尽力去写, 老大曾讲过要去实践要去独立思考, 你才能掌握很多你看似懂了却又很难处理的知识点. 学习小程序的我已经过去了半个月了, 前不久滴滴事件颇为轰动, 于是便萌发了这篇文章, 打开滴滴的小程序. 界面做了很大的变化, 对于比较怀旧的我来说, 还是挺喜欢以前的界面, 于是决定打算自己手写一个怀旧版滴滴小程序, 接下来我会列举我所遇到的坑和如何解决的方法, 希望能够帮到同样在奋斗的你...
前言
工欲善其事必先利其器, 对于一个前端来说有一个好的工具能够让我们事半功倍. 要想做好一个小程序我们首先也应该先选择我们的兵器.
利器 1: 微信开发者工具
利器 2: VsCode https://code.visualstudio.com/Download 集终端一起, 方便我们后面 Wepy,Mpvue 小程序进阶的学习, 可谓非常的 nice
利器 3: Easy Mock https://www.easy-mock.com/ 使用这个网站我们可以快速的模拟出我们需要的数据, 方便快捷. 点这里就可以看到我的数据了
数据接口 https://www.easy-mock.com/mock/5aded45053796b38dd26e970/comments#!method=get
效果图
说了一大堆, 等于没说, 还是回到正题, 先来一波效果图.
这里有更多的图片等你 https://github.com/WsmDyj/didi/tree/master/images
功能实现详解
接下来我会对滴滴微信小程序主要功能, 以及对应的数据接口和采用的组件 / API 技术, 描述的详细. 让我们离小程序更近一点, 传递知识, 分享收获
功能一: 滴滴首页
功能描述: 顶部导航的制作, 点击相应的 nav 页面呈现不同的内容, 并且点击时能判断是向前还是向后自动滑出下一个 nav, 实现动画过度效果. 输入起始位置模拟等待的时间.
实现原理:
模拟等待时间加载效果, 使用组件化的概念, 创建一个 template 文件夹
- <template name="sprinner">
- <block wx:if="{{isLoading}}">
- <view class="spinner">
- <view class="bounce1"></view>
- <view class="bounce2"></view>
- <view class="bounce3"></view>
- </view>
- </block>
- </template>
在需要用到加载效果的地方就可以直接使用,
- <import src="/templates/sprinner.wxml" />
- <template is="sprinner" data='{{isLoading}}'>
- </template>
- // 通过去设置 isLoading 的布尔值来判断是否需要显示
头部导航并且点击时能判断滑出方向以及自动滑出, 我们可以通过一个 scroll-left="{{navScrollLeft}}" 这个属性去设置, 点击 nav 我们可以动态的去改变这个值, 从而达到这种效果.
- <scroll-view scroll-x="true" class="nav" scroll-left="{{navScrollLeft}}" scroll-with-animation="{{true}}">
- <block wx:for="{{navData}}" wx:for-index="id" wx:for-item="navItem" wx:key="index">
- <view class="nav-item {{currentTab == id ?'active':''}}"data-name="{{navItem.name}}"data-current="{{id}}"bindtap="switchNav">{{navItem.name}}</view>
- </block>
- </scroll-view>
怎样去改变这个 navScrollLeft 的值呢? 一开始想的是去设置一下 nav 的导航的 left, 然后超出则隐藏. 坑啊, 根本就实现不了. 无法判断左滑出还是又滑出, 后来又想到设置一个阈值. 累真的难写. 好像放弃啊, 还是坚持下吧, 于是想到分开来取写他们的 js
- switchNav(e){
- var cur = e.currentTarget.dataset.current;
- var singleNavWidth = this.data.windowWindth/4; // 获取屏幕宽度存放放四个 nav,
- this.setData({
- navScrollLeft: (cur - 1) * singleNavWidth, // 点击去减少每一个 nav 的值
- currentTab: cur,
- })
- switchTab(e){
- var cur = event.detail.current;
- var singleNavWidth =55; // 设置每一个 nav 的宽度
- this.setData({
- currentTab: cur,
- navScrollLeft: (cur - 1) * singleNavWidth // 同样动态的去改变这个值
- });
- },
第一次打理这种顶部导航效果的小程序, 而却还带一点特效的. 以后的你如果碰到了就可以回来借鉴借鉴, 避免跟我一样, 在这里浪费大量的时间和精力了. 我们可是要完成有效时间创建更大价值的程序猿呀...
功能二: 起始位置的选择
为了做这个效果, 反反复复的看了 n 遍文档, 真的是比较坑爹. 微信小程序 api https://developers.weixin.qq.com/miniprogram/dev/api/ 对于地图这方面讲的确实不怎么详细, 可能是我这种对地图处理天生迷茫的人. 这里将详细的把我遇到的问题一一列举出来, 希望也能够帮助到今后的你去处理地图这种东西少踩点坑吧.
这里使用到了关键词搜索, 逆地址解析, 地址解析, 切换城市列表, 建议以后做这个可以了解下腾讯地图 api http://lbs.qq.com/qqmap_wx_jssdk/method-search.html , 还是比较方便的
- var QQMapWX = require('../../libs/qqmap-wx-jssdk.js');// 导入需要使用的包, 创建一个 libs 文件夹
- var qqmapsdk;
- qqmapsdk = new QQMapWX({
- key:'DHNBZ-2ZLKK-T7IJJ-AXSQW-WX5L6-A6xxxx'// 申请自己的开发者密钥
- });
- qqmapsdk.reverseGeocoder({ // 逆地址解析 api, 可以根据你的经纬度去解析位置地址
- location: {
- latitude: latitude,
- longitude: longitude,
- },
- success: function (res) {
- conslog.log(res)
- }
第一个坑: 如何去获取经纬度呢, 移动 markes?, 天呐这要写多少, 对于大牛们来说可能分分分钟, 对于刚入门的小生来说难度还是挺大大. 沉思良久, 突然发现 this.mapCtx.getCenterLocation, 移动地图事件获取地图中心的经纬度. 那么我们可以去固定一个 controls 在地图中心, 去移动地图来解析他的坐标位置, 将数据绑定在下面显示出来, 于是就实现了. emmm 代码如下
- bindregionchange: function(e){ // 移动地图事件
- var that = this
- this.mapCtx.getCenterLocation({ //getCenterLocation 可以获取地图中点的经纬度
- success: function (res) {
- app.globalData.strLatitude=res.latitude // 存放到全局去, 供后面计算价格使用
- app.globalData.strLongitude= res.longitude
- qqmapsdk.reverseGeocoder({
- location: {
- latitude: res.latitude, // 通过移动地图可以得到相应中心点的经纬度
- longitude: res.longitude,
- },
- success: function (res) {
- that.setData({
- address: res.result.address, // 得到的经纬度逆地址解析得到我们的位置信息
bluraddress: res.result.formatted_addresses.recommend
- })
- },
- });
第二个坑: 目的地调用 api 一样可以实现搜索提示功能, 但我需要历史记录也存在, 并且点击某一项我需要跳转到首页显示出来, 没有历史的页面体验感极差. 是否? 这里我是这样实现的
- qqmapsdk.getSuggestion({ // 调用 api 实现关键词搜索提示
- keyword: value, // 传递 input 的值, 这里要传 value 不是'value', 刚开始犯困. 提示一下
- region: '南昌',
- success: function(res){
- let data = res.data
- that.setData({
- address: data,
- value
- })
- <view wx:if="{{!value==''}}"class="destination"wx:for="{{address}}"data-destination="{{item.title}}"data-end="{{item.address}}"bindtap="toIndex"wx:key="{{item.id}}">
- <view wx:if="{{value==''}}"class="destination"wx:for="{{ entity}}"data-destination="{{item.title}}"data-end="{{item.address}}"bindtap="toIndex"wx:key="{{item.id}}">
通过 wx:if 去判断输入框. 下面 for 不同的数组, 填了第一小坑坑. 接下来就会去思考, 当我们点击搜索的怎么把它加入到我们的历史中呢? 点击获取那个值的 id 然后 push 到历史数组中去, 是不是很 nice, 实现了滴滴起始位置的选择, 当然我们这这是冰山一角, 强大的背后还需要去探索.
功能三: 滴滴费用计算
古人云: 细节决定成败, 一个良好的微信小程序往往就是一些细节打动人心, 居然是模仿, 虽做不到百分百, 至少还是很希望一模一样.
分析分析, 首页点击呼叫快车页面不跳转, 但要显示不同的内容. 是不是也可以跟上面一样用 wx:if 来处理呢? 没错用一个 repeat 去承载要显示的内容, 这样就可以不在刷星这个小技巧 get 到了吗.
- <repeat wx:if="{{callCart}}">
- <repeat wx:else>
计算价钱一样用到了腾讯地图 api http://lbs.qq.com/qqmap_wx_jssdk/method-search.html 获取两点之间的距离, 刚才把起始点都存放在 globalData 里, 这样的好处是, 可以随时得到里面的数据
- let {endLatitude,endLongitude} = app.globalData // 使用 ES6 的语法去结构数据
- qqmapsdk.calculateDistance({
- mode: 'driving',
- to:[ {
- latitude: endLatitude,
- longitude:endLongitude
- }],
- success: (res)=> {
- var num1 = 8+1.9*(res.result.elements[0].distance/1000)
- var play1 = num1.toFixed(1) // 取一位小数点
- app.globalData.play= play1 // 同样存放在全局里, 后面订单结束支付要用上
- this.setData({
- play1:play1,
- })
- },
对于点击显示的转态这里就不详细描述, 本文只针对一些不容易处理的问题. 后面司机服务页面路线规划也是通过调用 api, 计算两点的经纬度去标点, 连线. 这里也就省略下, 详细的可以参考我的代码 https://github.com/WsmDyj/didi .
功能四: 滴滴等待进度
怎样去做这个页面呢, 或于你以后也需要计时器 (不是倒计时) 或者进度条, 可以参考这里.
圆形进度条有很多实现的方法, 但我觉得 canvas 还是挺方便的. 两个 canvas 搞定
- <canvas class="progress_bg" canvas-id="canvasProgressbg"> </canvas> // 画底色
- <canvas class="progress_canvas" canvas-id="canvasProgress"> </canvas> // 画进度条, 传递一个 step 参数, 用定时器去绘制
第三坑: 绘制 canvas 没有问题, 文档也给的十分的详细, 但是里面那个计时器怎么制作呢? 我只需要分秒, 而却又不是倒计时, 并且还要跟外面保持一致. 前端这么心酸的吗? 啊, 硬着头皮去写吧. 搜索了下资源发现网上这方面的资料真的少. 没有办法, bug 还是要解决的. 你是否也遇到过这样的问题, 或于以后呢! 记得回来找我...
- parseTime: function(time){ // 这里参考了每个小程序项目中自带的 utils 下的 util.js, 大牛写的就是简洁明了
- var time = time.toString();
- return time[1]?time:'0'+time; // 自动归零补零
- },
- countInterval: function () {
- var curr = 0;
- this.setData({
- time: this.parseTime(timer.getMinutes())+":"+this.parseTime(timer.getSeconds()), // 格式化下时间, 取分秒
- });
- timer.setMinutes(curr/60); // 秒针 60 了自动加 1
- timer.setSeconds(curr%60); //60 后归零
- curr++;
- }
对于同步, 那肯定很简单呀, 放在一个定时器里就够了. 点这里 https://github.com/WsmDyj/didi/tree/master/didi/pages/wait 查看源码
功能五: 滴滴取消行程
有打车就应该有取消对吧, 一看取消行程页面就有点端倪. 这些样式需要自己去写吗? 要学会说 no, 这里就以这个为例子, 讲下我在小程序开始中如何使用 weui 快速去搭建一个页面效果.
这里给一些我觉得还行的资料: 在小程序中可以直接使用的例子 weui 小程序官方文档 https://user-gold-cdn.xitu.io/2018/6/5/163cf58028416bd8 ,
要注明的一点, 引用 weui 要在相应的文件夹里或全局的 wxss 引用它的 CSS, 可能多个页面都需要用到, 这里在全局引用
@import 'styles/weui.wxss';
在 app.wxss 中引用这段代码就可以开始你的 weui 之旅了, 比较啰嗦的贴了这么长的一段代码. 暗示你要用 weui 去快速开发你的小程序了!
- <view class="weui-cells weui-cells_after-title"> // 使用 weui 可以直接复制套上去用就好了
- <checkbox-group bindchange="bindReasonChange">
- <label class="weui-cell weui-check__label" wx:for="{{reasons}}" wx:key="value">
- <checkbox class="weui-check" value="{{item.value}}"checked="{{item.checked}}"/>
- <view class="weui-cell__bd name">{{item.name}}</view>
- <view wx:if="{{item.checked}}" class="checked"> // 点击是显示红色的
- <image src="../../assets/images/checked.png"></image>
- </view>
- <view wx:if="{{!item.checked}}" class="checked"> // 不点击是显示空圆
- <image src="../../assets/images/nochecked.png"></image>
- </view>
- </label>
- </checkbox-group>
- <view class="weui-cell weui-cell_link {{show==true?hidden:''}}">
- <view class="weui-cell__bd moreReasons" style="display: {{show==true?'none':''}};"bindtap='moreReasons'>
- <text > 点击查看更多原因</text>
- </view>
- </view>
- </view>
采坑经历: 点击转态如何解决呢? 一开始我是这样想的, 用一个 icon 通过改变它的 checked 事件去呈现不同的转态. 这样是可以实现的, 但是只能点击一个, 不能多选. 痛苦啊!!
一上午辗转反侧, 较劲脑汁. 反复的去看文档, 终于豁然开朗起来, 可以用多重循环去判断 checked. 哎, js 还是超级重要啊. 话不多说
- bindReasonChange(e){
- let reasons = this.data.reasons;
- let strVal = e.detail.value;
- for(var i = 0, lenI = reasons.length; i <lenI; ++i){
- reasons[i].checked = false;
- for(var j = 0, lenJ = strVal.length; j < lenJ; ++j){
- if(reasons[i].value==strVal[j]){
- reasons[i].checked =true;
- break;
- }
- }
- }
以后我们的页面或多或少可能需要点击选择功能, 其实原理都差不多, 这点你得到了吗, 以后再做这方面的功能时就能用上了. 总感觉还有什么没写完一样: 好吧!!
在做点击加载更多的, 我是这样打理的. wx:for 一个数组然后去截取他的下边显示. 点击加载更多时全部 for 这个数组. 然后在用定时器设置 wx.showLoading()显示加载更多效果, 就有了那种既视感
功能五: 滴滴司机评分
享受了一下滴滴带来的快捷与方便, 感叹技术的改变生活啊. 同样评分也是 app 得到用户最终反馈的直接来源, 因此我们也不容错过这么重要的一点, 但是打理的细节也是十分的坑啊.
如何去写一个评分呢, 点击小星星去改变它的转态, 还要根据第几颗星来判断前面的也要点亮. 真的挺头痛的, 网上不乏小程序评分功能实现, 但大多数都写的不是复杂就是很深奥, 对于一个小生来讲, 简直就是天书呀. 就跟老大说的一样, 很多东西都是靠细节去成长的, 想想觉得很容易实现, 不就一个 status 改变一下图片吗? 但是打理起来真的见功夫. 费尽心思去寻找一种简单粗暴的又可以达到这种效果的方式.
- <view class="evaluation-stars" bindtap="myStarChoose">
- <block wx:for="{{starMap}}"wx:key="{{index}}">
- <text wx:if="{{star>=index+1}}" class="stars-solid" data-star="{{index+1}}"></text>
- <text wx:if="{{star<index+1}}" class="stars-empty" data-star="{{index+1}}"></text>
- </block>
- </view>
- <text class="zan-c-gray-dark">{{starMap[star-1]}}</text>
不用图片, 用字体去解决这个问题就方便多了, 只要改变颜色就达到了评分效果. 而且代码量非常的少, 不知道这样会不会被打死.
- myStarChoose(e) {
- let star = parseInt(e.target.dataset.star) || 0;
- this.setData({
- star // 名字一样可以省略
- });
- },
最后一点
把这个放在最后来讲, 肯定是特别重要的知识点, 对于开发的你来说这个比上面功能的描述更有帮助, 或于你已经知道并且处理的更好, 这里只为了帮助那些跟我一样入门不久的小生尽量少走点歪路吧.
1, 数据请求的封装是搭建一个良好程序的前提. 在小程序中我们无时无刻不需要去请求数据. 到处充斥做异步请求, 让我们处理起来很头疼. 这一点更需要发时间去打理的, 随著 es6 慢慢的普及我们也应该把这些好的东西用到我们的代码中 promise, 把异步编程同步. 推荐廖雪峰老师的文章 https://www.liaoxuefeng.com/wiki/001434446689867b27157e896e74d51a89c25cc8b43bdb3000/0014345008539155e93fc16046d4bb7854943814c4f9dc2000 , 把我的封装的贴出来, 大家以后可以直接拷过去使用了
- let util = {
- request(opt){
- // 生成对象 结构
- let options = Object.assign({},,opt); //es6 的赋值
- let {url,data,} = options //es6 的结构从 options 结构出我们需要的 url,data
- return new Promise((resolve,reject)=>{
- wx.request({
- url,
- data,
- success(res){
- resolve(res.data)
- },
- fail(err){
- reject(err)
- }
- })
- })
- }
- }
- export default util // 向外输出模板, 在外面可以直接使用 util.request({})去网上请求我们的数据的数据了.
2, 还有一些就是包的管理, 比如把我们页面中可能一样的东西抽出来创建一个 template 文件夹, 把一些一样的 wxss 也可以抽出来新建一个 styles, 在需要使用到的地方就 @import 就可以了, 比如滴滴的按钮吧, 其实都一样, 还有页面底部的横条都可以封装起来, 用的时候直接导入就行了.
功能可能没有一一列举出来, 由于时间有限, 只讲述了一些我们日常能使用到的功能. 想了解更多功能可以点击这里查看我 https://github.com/WsmDyj/didi , 本小程序我也会不断更新, 喜欢的话可以加入我. 同样也希望你能够留下您宝贵的意见和建议.
寄语
作为一个程序猿挺不容易的, 做一个前端程序猿更加不容易. 技术日新月异. 每天要去摄取更多的技术来源, 心有千言, 难于罄书. 但我们都是热于分享的人, 能够把自己遇到的问题以及如何解决的方式写出来, 总是希望这样可以帮助到更多同样遇到这类问题的你, 我, 他. 或于这就是社区的力量, 这就是优秀程序员的品质吧. 小程序的学习依旧要铆足劲的去学习, 后面还有 wepy,Mpvue 等新技术在等着我们, 我已经踏上了那片领地的征程, 日后也会发布遇到的问题, bug 和我的作品. 希望对你有所帮助, 我们的口号是热于分享, 热于创作. Let's start a new journey!!!
来源: https://segmentfault.com/a/1190000015192425