上一篇: 帶你進(jìn)入異步Django+Vue的世界 - Didi打車實(shí)戰(zhàn)(5)
后臺(tái)創(chuàng)建訂單、Channels群發(fā)群收
Demo: https://didi-taxi.herokuapp.com/
前端處理訂單更新
用戶登錄后缤谎,針對(duì)乘客還是司機(jī),顯示不同的界面。
- 司機(jī):不需要下單按鈕,替換為搶單按鈕
- 搶單頁(yè)面魔熏,顯示所有當(dāng)前處于
REQUESTED
狀態(tài)的單子
更新一下導(dǎo)航欄
# /src/App.vue
<template>
<v-app>
<v-toolbar app :dark="user && user.group !== null && user.group === 'driver'">
...
computed: {
...mapState(['alert', 'user']),
menuItems () {
let items = [
{ icon: 'face', title: 'Register', route: '/sign_up' },
{ icon: 'lock_open', title: 'Login', route: '/log_in' }
]
if (this.userIsAuthenticated) {
items = this.user.group === 'driver' ? [
{ icon: 'notifications', title: 'Pending', route: '/all_requests' },
{ icon: 'exit_to_app', title: 'Exit', route: '' }
] : [
{ icon: 'local_taxi', title: 'Call', route: '' },
{ icon: 'exit_to_app', title: 'Exit', route: '' }
]
}
return items
},
新增路由:
# /src/router.js
{
path: '/all_requests',
name: 'all_requests',
component: Requests
},
Vue搶單頁(yè)面:
# /views/Requests.vue
<template>
<v-layout row wrap>
<v-flex xs12 sm6 offset-sm3>
<v-card class="mb-4">
<v-img
src="https://cdn.vuetifyjs.com/images/cards/plane.jpg"
aspect-ratio="5" class="white--text">
<v-container fill-height fluid>
<span class="display-2">快來(lái)?yè)寙?lt;/span>
</v-container>
</v-img>
<v-list v-if="!userIsAuthenticated || !trips_ongoing">
<div class="grey--text ml-5"> {{ card_text }} </div>
</v-list>
<v-list v-if="trips_ongoing">
<div v-for="(item, index) in trips_ongoing" :key="index">
<v-list-tile avatar class="my-2">
<v-list-tile-content>
<v-list-tile-title class="subheading mb-3">
{{ item.pick_up_address }} to {{ item.drop_off_address }}
</v-list-tile-title>
<div class="caption">{{ item.created | datefilter(true) }}</div>
</v-list-tile-content>
<div class="subheading" v-html="item.rider.username" />
<v-list-tile-avatar>
<img :src="`https://randomuser.me/api/portraits/thumb/${item.rider.id%2 === 0 ? 'women' : 'men'}/${item.rider.id}.jpg`" :title="item.rider.username" />
</v-list-tile-avatar>
<v-btn round color="pink white--text" @click.prevent="startTrip(item.id)">Start</v-btn>
</v-list-tile>
<v-expansion-panel>
<v-expansion-panel-content>
<template v-slot:header>
<v-chip class="yellow" small>{{ item.status }}</v-chip>
<v-spacer></v-spacer>
</template>
<v-card>
<v-card-text class="grey lighten-3">created: {{ item.created | datefilter }}</v-card-text>
<v-card-text class="grey lighten-3">updated: {{ item.updated | datefilter }}</v-card-text>
</v-card>
</v-expansion-panel-content>
</v-expansion-panel>
</div>
</v-list>
</v-card>
</v-flex>
</v-layout>
</template>
接單流程
-
打開(kāi)瀏覽器窗口,以乘客身份下單:
顯示REQUESTED
狀態(tài)鸽扁,并且司機(jī)頭像為灰色蒜绽,說(shuō)明暫時(shí)無(wú)人接單
打開(kāi)瀏覽器隱身窗口,以司機(jī)角色登錄
會(huì)顯示提示:“有新的訂單”
-
司機(jī)點(diǎn)擊“START”桶现,乘客端會(huì)顯示“已接單”躲雅,并且顯示司機(jī)的信息
-
司機(jī)端則提示下一步操作為“Pick-Up”
-
司機(jī)接到乘客后,點(diǎn)“Pick-Up”骡和,提示下一步操作為“Drop-Off”
-
司機(jī)到達(dá)目的地后相赁,點(diǎn)“Drop-off”,訂單結(jié)束慰于!
以上步驟看起來(lái)很復(fù)雜钮科,其實(shí),我們之前已經(jīng)搭建好框架婆赠,通過(guò)添加Vuex store的actions
绵脯、mutations
就可以了。
點(diǎn)擊“Pick-up”之后休里,同一group里的成員蛆挫,都會(huì)收到echo.message
:
WS create.trip
在前一篇已經(jīng)闡述了。
這里要添加的是:新訂單創(chuàng)建后妙黍,司機(jī)群里的成員都能收到璃吧。
WS received:
{"type":"echo.message",
"data":{"...
","status":"REQUESTED"}}
我們?cè)趕tore里添加OnMessage
,如果收到echo.message
的Trip.status==REQUESTED废境,則通知司機(jī)們:
# /src/store/modules/ws.js
// handle msg from server
wsOnMessage ({ dispatch, commit }, e) {
const rdata = JSON.parse(e.data)
console.log('WS received: ' + JSON.stringify(rdata))
switch (rdata.type) {
case 'create.trip':
commit('setAlert', { type: 'info', msg: '成功添加訂單' }, { root: true })
commit('messages/addTrip', rdata.data, { root: true })
break
case 'update.trip':
commit('setAlert', { type: 'info', msg: '成功更新訂單' }, { root: true })
commit('messages/updateTrip', rdata.data, { root: true })
// driver redirect to Home.vue
router.push('/')
break
case 'echo.message':
switch (rdata.data.status) {
case 'REQUESTED':
commit('setAlert', { type: 'info', msg: '有新的訂單畜挨!' }, { root: true })
commit('messages/updateTrip', rdata.data, { root: true })
// driver redirect to Requests.vue
router.push('/all_requests')
break
case 'STARTED':
...
}
break
}
WS update.trip
乘客和司機(jī)會(huì)同時(shí)收到兩條:
WS received:
{"type":"update.trip",
"data":{"id":"6e446f7f-606d-488c-9274-f786b9f06800","driver":{"id":6,"username":"driver3","first_name":"","last_name":"","group":"driver"},"rider":{"id":2,"username":"rider1","first_name":"","last_name":"","group":null},"created":"2019-05-18T07:18:31.096533Z","updated":"2019-05-20T05:06:47.219332Z","pick_up_address":"南京","drop_off_address":"鎮(zhèn)江","status":"STARTED"}}
WS received:
{"type":"echo.message",
"data":{"id":"6e446f7f-606d-488c-9274-f786b9f06800","driver":{"id":6,"username":"driver3","first_name":"","last_name":"","group":"driver"},"rider":{"id":2,"username":"rider1","first_name":"","last_name":"","group":null},"created":"2019-05-18T07:18:31.096533Z","updated":"2019-05-20T05:06:47.219332Z","pick_up_address":"南京","drop_off_address":"鎮(zhèn)江","status":"STARTED"}}
同理筒繁,在/src/store/modules/ws.js
里,針對(duì)Trip.status==XXX進(jìn)行操作即可巴元。
Vue頁(yè)面處理
Requests.vue <template>毡咏,處理“Start”按鈕:
# /src/views/Requests.vue
<script>
import { mapState, mapActions } from 'vuex'
export default {
data () {
return {
card_text: 'No data'
}
},
filters: {
datefilter (para, part) {
let time = new Date(para)
if (part) return `${time.toLocaleTimeString()}`
return `${time.toLocaleDateString()} ${time.toLocaleTimeString()}`
}
},
computed: {
...mapState(['alert', 'user']),
...mapState('messages', ['trips', 'websocket']),
userIsAuthenticated () {
return this.user !== null && this.$store.getters.user !== undefined
},
trips_ongoing () {
return this.trips.filter(obj => obj.status === 'REQUESTED')
}
},
mounted () {
},
methods: {
...mapActions(['clearAlert']),
...mapActions('ws', [
'wsOnOpen',
'wsOnError',
'wsOnMessage'
]),
cancelTrip (id) {
console.log(id)
},
menu_click (title) {
if (title === 'Exit') {
this.$store.dispatch('messages/signUserOut')
} else if (title === 'Call') {
this.$store.dispatch('messages/callTaxi')
}
},
startTrip (id) {
this.$store.dispatch('ws/updateTrip', { id: id, status: 'STARTED', driver: this.user.id })
}
}
}
</script>
store里添加actions
:
# /src/store/modules/ws.js
async createTrip ({ commit }, message) {
await wsService.createTrip(state.websocket.ws, message)
},
async updateTrip ({ commit }, message) {
await wsService.updateTrip(state.websocket.ws, message)
}
Home.vue <template>,不同狀態(tài)逮刨,顯示不同按鈕
# /src/views/Home.vue
<v-list v-if="trips_ongoing">
<div v-for="(item, index) in trips_ongoing" :key="index">
<v-list-tile avatar class="my-2">
<v-list-tile-content>
<v-list-tile-title class="subheading mb-3">
{{ item.pick_up_address }} to {{ item.drop_off_address }}
</v-list-tile-title>
<div class="caption">{{ item.created | datefilter(true) }}</div>
</v-list-tile-content>
<div class="subheading" v-html="user.group ==='driver' ? item.rider.username : item.driver ? item.driver.username : ''" />
<v-list-tile-avatar v-if="user.group ==='driver'">
<img :src="`https://randomuser.me/api/portraits/thumb/${item.rider.id%2 === 0 ? 'women' : 'men'}/${item.rider.id}.jpg`" :title="item.rider.username" />
</v-list-tile-avatar>
<v-list-tile-avatar v-if="!item.driver && user.group ==='rider'">
<v-icon x-large color="grey lighten-3" title="no driver">account_circle</v-icon>
</v-list-tile-avatar>
<v-list-tile-avatar v-if="item.driver && user.group ==='rider'">
<img :src="`https://randomuser.me/api/portraits/thumb/${item.driver.id%2 === 0 ? 'women' : 'men'}/${item.driver.id}.jpg`" :title="item.driver.username" />
</v-list-tile-avatar>
<v-btn round color="pink white--text" @click.prevent="pickupTrip(item.id)" v-if="user.group ==='driver' && item.status === 'STARTED'">Pick-Up</v-btn>
<v-btn round color="pink white--text" @click.prevent="dropoffTrip(item.id)" v-if="user.group ==='driver' && item.status === 'IN_PROGRESS'">Drop-off</v-btn>
</v-list-tile>
<v-timeline align-top dense v-if="item.status !== 'REQUESTED'">
<v-timeline-item color="green" small>
<v-layout pt-3>
<v-flex xs3>
<div class="caption" v-if="item.status === 'STARTED'">{{ item.updated | datefilter(true)}}</div>
</v-flex>
<v-flex>
<strong :class="item.status === 'IN_PROGRESS' ? 'green--text' : 'pink--text'">
司機(jī)已接單呕缭,飛奔而來(lái)</strong>
</v-flex>
</v-layout>
</v-timeline-item>
<v-timeline-item :color="item.status === 'IN_PROGRESS' ? 'green' : 'grey'" small >
<v-layout pt-3>
<v-flex xs3>
<div class="caption" v-if="item.status === 'IN_PROGRESS'">{{ item.updated | datefilter(true)}}</div>
</v-flex>
<v-flex>
<strong :class="item.status === 'IN_PROGRESS' ? 'pink--text' : 'grey--text'">
正駛往目的地</strong>
</v-flex>
</v-layout>
</v-timeline-item>
</v-timeline>
<v-expansion-panel>
<v-expansion-panel-content>
<template v-slot:header>
<v-chip class="yellow" small>{{ item.status }}</v-chip>
<v-spacer></v-spacer>
</template>
<v-card>
<v-card-text class="grey lighten-3">created: {{ item.created | datefilter }}</v-card-text>
<v-card-text class="grey lighten-3">updated: {{ item.updated | datefilter }}</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn flat color="red" @click.prevent="cancelTrip(item.id)">Cancel</v-btn>
</v-card-actions>
</v-card>
</v-expansion-panel-content>
</v-expansion-panel>
</div>
</v-list>
</v-card>
</v-flex>
<script>里,發(fā)送到store actions即可:
pickupTrip (id) {
this.$store.dispatch('ws/updateTrip', { id: id, status: 'IN_PROGRESS', driver: this.user.id })
},
dropoffTrip (id) {
this.$store.dispatch('ws/updateTrip', { id: id, status: 'COMPLETED', driver: this.user.id })
}
Start, Pick-Up, Drop-Off
司機(jī)的這些操作修己,其實(shí)都是WebSockets發(fā)送update.trip
:
# /src/store/modules/ws.js
case 'STARTED':
commit('setAlert', { type: 'info', msg: '司機(jī)已接單恢总!' }, { root: true })
commit('messages/updateTrip', rdata.data, { root: true })
break
case 'IN_PROGRESS':
commit('setAlert', { type: 'info', msg: '正駛往目的地!' }, { root: true })
commit('messages/updateTrip', rdata.data, { root: true })
break
case 'COMLETED':
commit('setAlert', { type: 'info', msg: '訂單完成睬愤!' }, { root: true })
commit('messages/updateTrip', rdata.data, { root: true })
break
統(tǒng)一更新trips:
# /src/store/modules/messages.js
const mutations = {
addTrip (state, message) {
// state.trips.push(message)
state.trips.splice(0, 0, message)
},
updateTrip (state, message) {
// how to trigger Vuex update: https://blog.csdn.net/qq_34935885/article/details/75734365
let index = state.trips.findIndex((e) => e.id === message.id)
state.trips.splice(index, 1, message)
},
總結(jié)
前后端的一個(gè)Didi打車系統(tǒng)片仿,已經(jīng)大功告成了!尤辱!
后續(xù)大家可以進(jìn)一步優(yōu)化:
- 每個(gè)用戶只能看到自己訂單
- 用戶可以修改資料砂豌,上傳頭像
- 郵件確認(rèn)、重置密碼
- WebSockets中斷后光督,自動(dòng)重連
- 地圖選點(diǎn)
- 取消訂單
- 刪除訂單
- cache
- 排行榜(Redis)
- static文件單獨(dú)serve
- 阳距。。结借。
下一步:部署到遠(yuǎn)程服務(wù)器
需要源碼的請(qǐng)留言哦~