上一篇: 带你进入异步Django+Vue的世界 - Didi打车实战(5)
后台创建订单、Channels群发群收
Demo: https://didi-taxi.herokuapp.com/
前端处理订单更新
用户登录后,针对乘客还是司机,显示不同的界面。
- 司机:不需要下单按钮,替换为抢单按钮
- 抢单页面,显示所有当前处于
REQUESTED
状态的单子
image.png
更新一下导航栏
# /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抢单页面:
# /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">快来抢单</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>
接单流程
-
打开浏览器窗口,以乘客身份下单:
显示REQUESTED
状态,并且司机头像为灰色,说明暂时无人接单
image.png
-
打开浏览器隐身窗口,以司机角色登录
会显示提示:“有新的订单”

-
司机点击“START”,乘客端会显示“已接单”,并且显示司机的信息
image.png
-
司机端则提示下一步操作为“Pick-Up”
image.png
-
司机接到乘客后,点“Pick-Up”,提示下一步操作为“Drop-Off”
image.png
-
司机到达目的地后,点“Drop-off”,订单结束!
image.png
以上步骤看起来很复杂,其实,我们之前已经搭建好框架,通过添加Vuex store的actions
、mutations
就可以了。
点击“Pick-up”之后,同一group里的成员,都会收到echo.message
:
WS create.trip
在前一篇已经阐述了。
这里要添加的是:新订单创建后,司机群里的成员都能收到。
WS received:
{"type":"echo.message",
"data":{"...
","status":"REQUESTED"}}
我们在store里添加OnMessage
,如果收到echo.message
的Trip.status==REQUESTED,则通知司机们:
# /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
乘客和司机会同时收到两条:
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":"镇江","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":"镇江","status":"STARTED"}}
同理,在/src/store/modules/ws.js
里,针对Trip.status==XXX进行操作即可。
Vue页面处理
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>,不同状态,显示不同按钮
# /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'">
司机已接单,飞奔而来</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>里,发送到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
司机的这些操作,其实都是WebSockets发送update.trip
:
# /src/store/modules/ws.js
case 'STARTED':
commit('setAlert', { type: 'info', msg: '司机已接单!' }, { 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
统一更新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)
},
总结
前后端的一个Didi打车系统,已经大功告成了!!
后续大家可以进一步优化:
- 每个用户只能看到自己订单
- 用户可以修改资料,上传头像
- 邮件确认、重置密码
- WebSockets中断后,自动重连
- 地图选点
- 取消订单
- 删除订单
- cache
- 排行榜(Redis)
- static文件单独serve
- 。。。
下一步:部署到远程服务器
需要源码的请留言哦~
网友评论