帶你進(jìn)入異步Django+Vue的世界 - Didi打車實(shí)戰(zhàn)(6)

上一篇: 帶你進(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)的單子
    image.png

更新一下導(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>

接單流程

  1. 打開(kāi)瀏覽器窗口,以乘客身份下單:
    顯示REQUESTED狀態(tài)鸽扁,并且司機(jī)頭像為灰色蒜绽,說(shuō)明暫時(shí)無(wú)人接單

    image.png

  2. 打開(kāi)瀏覽器隱身窗口,以司機(jī)角色登錄
    會(huì)顯示提示:“有新的訂單”

image.png
  1. 司機(jī)點(diǎn)擊“START”桶现,乘客端會(huì)顯示“已接單”躲雅,并且顯示司機(jī)的信息


    image.png
  2. 司機(jī)端則提示下一步操作為“Pick-Up”


    image.png
  3. 司機(jī)接到乘客后,點(diǎn)“Pick-Up”骡和,提示下一步操作為“Drop-Off”


    image.png
  4. 司機(jī)到達(dá)目的地后相赁,點(diǎn)“Drop-off”,訂單結(jié)束慰于!


    image.png

以上步驟看起來(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)留言哦~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末筐摘,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子船老,更是在濱河造成了極大的恐慌蓄拣,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,013評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件努隙,死亡現(xiàn)場(chǎng)離奇詭異球恤,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)荸镊,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,205評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門咽斧,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人躬存,你說(shuō)我怎么就攤上這事张惹。” “怎么了岭洲?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,370評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵宛逗,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我盾剩,道長(zhǎng)雷激,這世上最難降的妖魔是什么替蔬? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,168評(píng)論 1 278
  • 正文 為了忘掉前任,我火速辦了婚禮屎暇,結(jié)果婚禮上承桥,老公的妹妹穿的比我還像新娘。我一直安慰自己根悼,他們只是感情好凶异,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,153評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著挤巡,像睡著了一般剩彬。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上矿卑,一...
    開(kāi)封第一講書(shū)人閱讀 48,954評(píng)論 1 283
  • 那天喉恋,我揣著相機(jī)與錄音,去河邊找鬼粪摘。 笑死,一個(gè)胖子當(dāng)著我的面吹牛绍坝,可吹牛的內(nèi)容都是我干的徘意。 我是一名探鬼主播,決...
    沈念sama閱讀 38,271評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼轩褐,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼椎咧!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起把介,我...
    開(kāi)封第一講書(shū)人閱讀 36,916評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤勤讽,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后拗踢,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體脚牍,經(jīng)...
    沈念sama閱讀 43,382評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,877評(píng)論 2 323
  • 正文 我和宋清朗相戀三年巢墅,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了诸狭。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 37,989評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡君纫,死狀恐怖驯遇,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情蓄髓,我是刑警寧澤叉庐,帶...
    沈念sama閱讀 33,624評(píng)論 4 322
  • 正文 年R本政府宣布,位于F島的核電站会喝,受9級(jí)特大地震影響陡叠,放射性物質(zhì)發(fā)生泄漏玩郊。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,209評(píng)論 3 307
  • 文/蒙蒙 一匾竿、第九天 我趴在偏房一處隱蔽的房頂上張望瓦宜。 院中可真熱鬧,春花似錦岭妖、人聲如沸临庇。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,199評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)假夺。三九已至,卻和暖如春斋攀,著一層夾襖步出監(jiān)牢的瞬間已卷,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,418評(píng)論 1 260
  • 我被黑心中介騙來(lái)泰國(guó)打工淳蔼, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留侧蘸,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,401評(píng)論 2 352
  • 正文 我出身青樓鹉梨,卻偏偏與公主長(zhǎng)得像讳癌,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子存皂,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,700評(píng)論 2 345