美文网首页Vue.js
带你进入异步Django+Vue的世界 - Didi打车实战(6

带你进入异步Django+Vue的世界 - Didi打车实战(6

作者: 非梦nj | 来源:发表于2019-05-20 23:21 被阅读7次

上一篇: 带你进入异步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>

接单流程

  1. 打开浏览器窗口,以乘客身份下单:
    显示REQUESTED状态,并且司机头像为灰色,说明暂时无人接单

    image.png
  2. 打开浏览器隐身窗口,以司机角色登录
    会显示提示:“有新的订单”

image.png
  1. 司机点击“START”,乘客端会显示“已接单”,并且显示司机的信息


    image.png
  2. 司机端则提示下一步操作为“Pick-Up”


    image.png
  3. 司机接到乘客后,点“Pick-Up”,提示下一步操作为“Drop-Off”


    image.png
  4. 司机到达目的地后,点“Drop-off”,订单结束!


    image.png

以上步骤看起来很复杂,其实,我们之前已经搭建好框架,通过添加Vuex store的actionsmutations就可以了。
点击“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
  • 。。。

下一步:部署到远程服务器
需要源码的请留言哦~

相关文章

网友评论

    本文标题:带你进入异步Django+Vue的世界 - Didi打车实战(6

    本文链接:https://www.haomeiwen.com/subject/ospczqtx.html