本人最近一直在研究小程序,一個偶然的機會發現了mpvue框架,恰好本人對vue十分喜好,就決定入坑了。曾經在掘金看過一個仿舊版滴滴的小程序,感受挺不錯的,但它是基於原生小程序的,因此就決定花了一段時間用mpvue進行重寫。下面就開始正餐了。javascript
┣━ api # 存放網絡請求相關
┣━ common ●
┣━ constant //常量
┣━ css //weui.css
┣━ less //通用less樣式與變量
┗━ lib //第三方庫 qqmap-wx-jssdk.js
┣━ components ● 抽取出來的組件
┣━ addressList.vue
┣━ common-footer.vue
┣━ driver-header.vue
┣━ loading-sprinner.vue
┣━ search-bar.vue
┗━ star.vue
┣━ pages ● 頁面
┣━ cars //選擇車
┣━ cityChoose //選擇城市
┣━ destination //選擇目的地
┣━ evaluation //評價
┣━ index //主頁面
┣━ login //登陸
┣━ orderCancel //訂單取消
┣━ orderClose //訂單關閉
┣━ orderService //訂單服務
┣━ orderWhy //詢問緣由
┣━ starting //選擇出發地點
┗━ wait //等待
┣━ store ● 存放vuex相關
┣━ index.js
┣━ mutation-types.js
┣━ mutations.js
┗━ state.js
┣━ utils 工具類
┣━ App.vue
┣━ main.js
┗━ static # 靜態資源,存放圖片複製代碼
const state = {
curNavIndex: 0, //當前頭部導航索引
phone: '', //登陸號碼
curCity: '', //當前所在的城市
startPlace: '出發地', //出發地
startFormattedPlace: '', //更具人性化的描述的出發地
startPosition: [], //包含startLatitude和startLongitude
destination: '你要去哪兒', //目的地
endPosition: [], //包含endLatitude和endLongitude
driver: {}, //司機信息 包含Cartnumber,cart,id,name,stars
cost: 0 //花費
}複製代碼
爲了讓頭部導航點擊時能自動滑出,滑動swiper的同時頭部導航跟着滑動,在cars頁面選中車時回退到index頁面時頭部導航自動滑動,我在vuex中維護了一個索引值curNavIndex。根據不一樣的curNavIndex對scroll-view設置不一樣的scroll-left值。css
那麼如何設置準確的scroll-left值呢?html
微信小程序沒法進行Dom操做,因此沒法動態拿到元素寬度。因此我根據頭部導航每項的寬度維護了一個數組navOffsetArrvue
//兩個字寬度+2*margin 也就是 32+10*2 = 52
const NAV_SMALL_WIDTH = 52;
//三個字寬度+2*margin 也就是 48+10*2 = 68
const NAV_BIG_WIDTH = 68;
this.navOffsetArr = [
0,
0,
NAV_SMALL_WIDTH,
NAV_SMALL_WIDTH * 2,
NAV_SMALL_WIDTH * 2 + NAV_BIG_WIDTH,
NAV_SMALL_WIDTH * 2 + NAV_BIG_WIDTH * 2,
NAV_SMALL_WIDTH * 3 + NAV_BIG_WIDTH * 2,
NAV_SMALL_WIDTH * 4 + NAV_BIG_WIDTH * 2
]複製代碼
獲取索引值java
computed: {
...mapState([
'curNavIndex'
])
}複製代碼
watch裏監聽索引值,當curNavIndex改變時,拿到不一樣的navScrollLeft值git
watch: {
curNavIndex(newIndex){
this.navScrollLeft = this.navOffsetArr[newIndex]
}
}複製代碼
最後將scroll-left與navScrollLeft綁定,從而實現自動滑動github
<scroll-view
class="nav"
scroll-x="true"
scroll-with-animation="true"
:scroll-left="navScrollLeft">
......
......
</scroll-view>複製代碼
在進入index首頁的時候,就會自動將當前城市,當前經緯度,當前地址存入state中做爲出發點信息。這裏接入了騰訊地圖api,仍是比較方便的。vuex
wx.getLocation({
type: 'gcj02',
success: (res) => {
reverseGeocoder(qqmapsdk, res).then(res => {
this.saveStartPlace(res.result.address)
this.saveFormattedStartPlace(res.result.formatted_addresses.recommend)
this.saveCurCity(res.result.address_component.city)
})
this.saveStartPosition([res.latitude, res.longitude])
}
})複製代碼
mapMutationsjson
methods: {
...mapMutations({
saveCurNavIndex: 'SET_CUR_NAV_INDEX',
saveStartPlace: 'SET_START_PLACE',
saveFormattedStartPlace: 'SET_FORMATTED_START_PLACE',
saveCurCity: 'SET_CUR_CITY',
saveStartPosition: 'SET_START_POSITION',
saveCost: 'SET_COST'
})
} 複製代碼
其中reverseGeocoder()就是一個位置轉換爲地址的函數,是對qqmapsdk.reverseGeocoder()進行了一次封裝canvas
function reverseGeocoder(qqmapsdk, {latitude, longitude}) {
return new Promise((resolve, reject) => {
qqmapsdk.reverseGeocoder({
location: {
latitude: latitude,
longitude: longitude,
},
success: (res) => resolve(res),
fail: (res) => reject(res)
})
})
}複製代碼
這樣當咱們進入index首頁的時,就能夠在Console中就看到數據成功保存到vuex裏
在mpvue中使用map組件時會有一些坑,這裏先緩一緩,坑稍後再說。
地圖map
<map class="map-didi"
id="map-didi"
:latitude="latitude"
:longitude="longitude"
:markers="markers"
@regionchange="regionChange"
@begin="begin"
@end="end"
show-location
>
...
</map>複製代碼
初始化地圖時將地圖中心移動至startPosition,若是startPosition不存在,就將地圖中心移動至wx.getLocation()獲取的當前位置座標
initLocation(){
if (this.startPosition.length) {
this.latitude = this.startPosition[0]
this.longitude = this.startPosition[1]
} else {
wx.getLocation({
type: "gcj02",
success: (res) => {
this.longitude = res.longitude
this.latitude = res.latitude
}
})
}
}複製代碼
採用隨機數據模擬附近的車,而後添加到this.markers中,車的圖標根據curNavIndex動態設置,這樣就能夠在選擇不一樣的服務時展現不一樣的車圖標
this.markers = []
const carNum = getRandomNum(3, 8)
for (let i = 1; i <= carNum; i++) {
// 定義一個車對象
let car = {
id: 0,
iconPath: "/static/img/car/cart1.png",
latitude: 0,
longitude: 0,
width: 35,
height: 15
}
//隨機值
const lon_dis = (Math.ceil(Math.random() * 99)) * 0.00012;
const lat_dis = (Math.ceil(Math.random() * 99)) * 0.00012;
car.id = 2 + i
car.latitude = this.latitude + lat_dis
car.longitude = this.longitude + lon_dis
car.iconPath = `/static/img/car/cart${this.curNavIndex + 1}.png`
this.markers.push(car)
}複製代碼
地圖中心的紅色定位圖標以及接駕時間的文字是用cover-view包裹cover-image實現
<cover-view class="center-marker">
<cover-view class="text-center">最快{{minutes}}分鐘接駕</cover-view>
<cover-image class="inverted-triangle" src="/static/img/triangle-down.png"></cover-image>
<cover-image class="img-center" src="/static/img/marker2.png"></cover-image>
</cover-view>複製代碼
其中inverted-triangle是一個倒三角形圖片,由於cover-view沒法實現複雜css樣式,因此底部的倒三角形效果只能用圖片實現。
map這裏不推薦使用controls,官方也說明 controls即將廢棄,請使用 cover-view
這裏首先獲取到state中的curCity,利用qqmapsdk.getSuggestion(),並將其參數region設置爲curCity, 就能夠進行地址模糊檢索。選中地址時,利用qqmapsdk.geocoder()進行地址解析,獲得目的地的相關數據,再將數據經過mapMutations存入state中
computed: {
...mapState([
'curCity'
])
}複製代碼
模糊檢索
qqmapsdk.getSuggestion({
keyword: value,
region: this.curCity,
success: (res) => {
this.addresses = res.data
}
})複製代碼
點擊地址時,解析地址保存數據
choosePlace(item){
//item.address詳細地址
//item.title簡短語義化地址
console.log(item)
qqmapsdk.geocoder({
address: item.address,
success: (res) => {
this.saveEndPosition([res.result.location.lat, res.result.location.lng])
this.saveDestination(item.title)
this.goBack()
},
fail: (err) => {
console.log(err)
}
})
}複製代碼
mapMutations
methods: {
...mapMutations({
saveDestination: 'SET_DESTINATION',
saveEndPosition: 'SET_END_POSITION'
})
} 複製代碼
這裏的樣式是按照如今的滴滴小程序實現,只要將選中的城市保存在state中的curCity就行了,搜索功能暫未開發。獲取城市列表數據用到了騰訊地圖的api中的qqmapsdk.getCityList()。這裏其實就是數據的過濾與處理,先初始化了一個空對象temp_citys,而後根據城市的拼音的首字母的大寫創建key,對應value爲一個數組,數組裏麪包含全部以這個拼音字母開頭的城市,最後將temp_citys賦值給this.cityList
qqmapsdk.getCityList({
success: (res) => {
const result = res.result[1]
let temp_citys = {} //使用temp_citys 避免頻繁改動data裏面的數據
for (let i = 0; i < result.length; i++) {
let key = result[i].pinyin[0].charAt(0).toLocaleUpperCase()
if (!temp_citys[key]) {
temp_citys[key] = []
}
temp_citys[key].push(result[i].fullname)
}
this.cityList = temp_citys
}
})複製代碼
其餘的一些頁面就不提了,感興趣的小夥伴能夠去看下源碼
使用vuex進行狀態管理,能夠更方便地構建複雜應用。這裏講一個調試小技巧,使用createLogger(),使用以後就能夠在Console中清楚地看到state的變化
在store下的index.js
import Vue from 'vue'
import Vuex from 'vuex'
import state from './state'
import mutations from './mutations'
import createLogger from 'vuex/dist/logger'
Vue.use(Vuex)
const debug = process.env.NODE_ENV !== 'production'
export default new Vuex.Store({
state,
mutations,
strict: debug,
plugins: debug ? [createLogger()] : []
})複製代碼
使用vuex時要記得在對應頁面的main.js引入store,並將store賦給Vue.prototype.$store
例如:
import Vue from 'vue'
import App from './wait.vue'
import store from '../../store/index'
Vue.prototype.$store = store
const app = new Vue(App)
app.$mount()複製代碼
使用mpvue組件化開發更加方便,也方便將組件移植到其餘項目中,完整的Vue開發體驗,提升了代碼複用性。
例如 這裏的search-bar:
<template>
<div class="search-bar">
<div class="text-location" @click.stop="chooseCity">{{curCity}}</div>
<input type="text"
v-model="search"
class="input-location"
placeholder="你在哪兒上車"
placeholder-style="color:#cccccc">
<div class="cancel-location" @click.stop="cancel">取消</div>
</div>
</template>
<script type="text/ecmascript-6">
import {debounce} from '../utils/index'
export default{
props: {
curCity: {
type: String,
default: '暫無'
}
},
data(){
return {
search: ''
}
},
methods: {
cancel(){
this.$emit('cancel')
},
clear(){
this.search = ''
},
chooseCity(){
this.$emit('chooseCity')
}
},
watch: {
search(newVal){
debounce(() => {
this.$emit('search', newVal)
}, 500)()
}
}
}
</script>複製代碼
這裏爲了節流處理,引入了debounce()函數
原生小程序已經支持Promise,但對於async/await還不支持,利用mpvue框架咱們能夠封裝一些異步函數,避免回調地獄。
例如:網絡請求
export function request(url, method = 'GET', data, header = {}) {
return new Promise((resolve, reject) => {
wx.showLoading({title: '玩命加載中...'})
wx.request({
url: baseUrl + url,
method,
data,
header: {'Content-Type': 'json'},
success: function (res) {
if (res.statusCode === 200) {
resolve(res.data)
} else {
showToast('發生未知錯誤!')
reject(res.data)
}
},
fail: function () {
showToast('獲取數據失敗!')
},
complete:function () {
wx.hideLoading()
}
})
})
}
複製代碼
async getInitData(){
const res = await request('/comments')
...
}複製代碼
年輕人比較衝動,愣頭青,說多了都是眼淚,官方文檔必定要好好看,首先提一下常規的一些坑。
只是須要注意一點,嵌套列表渲染,必須指定不一樣的索引!
示例:
<!-- 在這種嵌套循環的時候, index 和 itemIndex 這種索引是必須指定,且別名不能相同,正確的寫法以下 -->
<template>
<ul v-for="(card, index) in list">
<li v-for="(item, itemIndex) in card">
{{item.value}}
</li>
</ul>
</template>複製代碼
bindregionchange 事件直接在 dom 上將bind改成@regionchange,同時這個事件也很是特殊,它的 event type 有 begin 和 end 兩個,致使咱們沒法在handleProxy 中區分究竟是什麼事件,因此你在監聽此類事件的時候要同時監聽事件名和事件類型
<map
@regionchange="functionName"
@end="functionName"
@begin="functionName">
<map>
複製代碼
舉個簡單的例子,slider組件有一個bindchange屬性,它是完成一次拖動後觸發的事件,那麼若是咱們想取對應的值該怎麼操做。
在小程序中咱們使用: event.detail
但在 mpvue中要這樣寫: event.mp.detail
動態更新markers時,地圖會閃動,致使沒法移動地圖,這個但是一個大坑
地圖組件bindregionchange的bug:
https://github.com/Meituan-Dianping/mpvue/issues/401
緣由:mpvue在更新某個屬性時都會更新整個data, 在數據量比較大的狀況下效率低下,並且頻繁改動data裏面的數據也會致使卡頓問題
解決方案:每次更新數據時使用髒檢查優化
可是我的以爲這種直接改源碼的方式仍是比較妖怪的,因而找到了另外一種辦法
<map class="map-didi"
id="map-didi"
@regionchange="regionChange"
@begin="begin"
@end="end" >
</map>複製代碼
let touchTimeStamp = 0
regionChange(){
},
begin({timeStamp}){
touchTimeStamp = timeStamp
},
end({timeStamp}){
// 加入時間判斷
if (timeStamp - touchTimeStamp > 50) {
this.mapCtx.getCenterLocation({
success: (res) => {
reverseGeocoder(qqmapsdk, res).then(res => {
this.saveStartPlace(res.result.address)
this.saveFormattedStartPlace(res.result.formatted_addresses.recommend)
})
const lon_distance = res.longitude - this.longitude
const lat_distance = res.latitude - this.latitude
// 更新當前位置座標
this.longitude = res.longitude
this.latitude = res.latitude
//判斷屏幕移動的距離,若是超過閥值
if (Math.abs(lon_distance) >= 0.0022 || Math.abs(lat_distance) >= 0.0022) {
//刷新附近的車
this.updateCars()
//刷新等待時間
this.minutes = getRandomNum(3, 12)
}
}
})
}
}複製代碼
爲了防止map不斷地觸發begin,end事件致使data頻繁更新,這裏作了雙重判斷,當end事件的觸發時間減去start事件的觸發時間超過一個設定的時間,當中心點移動的距離超過一個閥值,咱們纔去更新data數據,這裏其實至關於作了節流處理。
常規的坑就不提了,這裏說一下奇葩的坑。
cover-view覆蓋在原生組件之上的文本視圖,可覆蓋的原生組件包括map、video、canvas、camera、live-player、live-pusher,只支持嵌套cover-view、cover-image。
只支持基本的定位、佈局、文本樣式。不支持設置單邊的border、background-image、shadow、overflow: visible等
那若是咱們想在cover-view裏實現單邊的border應該怎麼作?
能夠在cover-view裏再增長一個寬度1px的cover-view來模擬單邊border
<cover-view class="footer-bar">
<cover-view class="text" @click.stop="cancel">取消訂單
</cover-view>
<cover-view class="right-border"></cover-view>
<cover-view class="text" @click.stop="endTrip">結束行程
</cover-view>
<cover-view class="right-border"></cover-view>
<cover-view class="text">下載滴滴APP</cover-view>
</cover-view>複製代碼
.footer-bar {
padding: 0 12px;
display: flex;
align-items: center;
height: 44px;
color: rgba(0, 0, 0, .7);
background: #fff;
font-size: 0;
.text {
flex: 1 1 auto;
display: inline-block;
height: 22px;
line-height: 22px;
text-align: center;
font-size: 18px;
}
.right-border {
flex: 0 0 1px;
height: 22px;
width: 1px;
background-color: #d9d9d9;
}
}複製代碼
map組件的層級最高,如何在map組件上作出陰影效果呢?
實現方式其實也是相似,利用cover-image添加一張能覆蓋在map組件之上的圖片來模擬陰影
具體實現請看這篇文章: juejin.im/post/5b1a10…
歡迎小夥伴來一塊兒交流學習,喜歡項目的話請給顆小星星
學習之路漫漫,沒必要急於求成。技術突飛猛進,掌握不變的核心纔是王道。不斷打磨做品的感受也挺好的,若是之後有機會的話再慢慢完善。
另外本人目前大三,準備暑假後找實習,有沒有廣州的大大願意收留下我。。。
滴滴一夏, 小程序專車來了 https://juejin.im/post/5b15ce94f265da6e29010554
網絡請求request的坑 www.cnblogs.com/huangenai/p…
mpvue + vuex 開發微信小程序 mapState、mapGetters不可用問題