vue+koa2+mongodb點餐系統總結

evanoooo發表於2017-12-18

關於專案

這是一個點餐系統,包含使用者點餐、商家出餐、管理員管理三部分功能 這個專案本來是校內實訓,需要用java編寫,我負責一部分。但是我不太喜歡用java,且時間足夠,就自己獨自做了一份,用於學習。 專案的功能和需求是根據前期小組討論出來的,也基本都是仿餓了麼的 各項功能基本都實現了

線上地址:(比較慢)47.93.254.91:3333

原始碼地址:chihuobao

登入賬號:
  使用者:12345678910
  商家:11112222333
  管理員:admin2
登入密碼都是123456
複製程式碼

功能結構

vue+koa2+mongodb點餐系統總結
vue+koa2+mongodb點餐系統總結

除錯執行

npm install
npm run dev

cd server                 #開啟koa2後臺,會開啟3333埠
npm install
node bin/www
複製程式碼
npm run build                #打包
cp dist/* server/public/     #將打包好的檔案放到koa2靜態目錄
複製程式碼

頁面截圖

vue+koa2+mongodb點餐系統總結
vue+koa2+mongodb點餐系統總結
vue+koa2+mongodb點餐系統總結
vue+koa2+mongodb點餐系統總結

總體分析

使用的框架、外掛等

  • 用Vue-cli腳手架、vue-router、vuex
  • 用element-ui樣式框架
  • 用axios發請求
  • 用koa2做後臺,在node高版本直接用async、await
  • 用mongoose連線mongodb資料庫

包含的功能

  • 手機註冊,登入,重置密碼
  • 使用者點餐,該商家會收到訊息提示有新訂單(用輪詢實現)
  • 使用者檢視自己的訂單,評價、刪除等
  • 修改自己的資訊,申請成為商戶等
  • 商家管理訂單,接單等
  • 統計商家訂單數,評分等(頁面上的月銷量是總銷量)
  • 商家管理選單、檢視評論
  • 管理員管理使用者、商鋪、分類等
  • 搜尋功能

目錄結構

頂層就是vue-cli的結構,主要看前端src和後臺server的結構

─ src
 ├── common                         #
 │  ├── audio                       #音訊
 │  ├── images                      #圖片
 │  ├── javascript                  #api介面、cache、config等js檔案
 │  ├── style                       #公用style
 ├── components                     #元件
 ├── pages                          #頁面,處理業務,主要分為三個模組
 │  ├── admin
 │  ├── seller
 │  ├── user
 │  ├── index.vue
 │  ├── login.vue
 ├── router                         #路由
 │  ├── index.js
 ├── store                          #vuex的store,分了三個模組
 │  ├── admin
 │  ├── seller
 │  ├── user
 │  ├── index.js
 ├── App.vue
 ├── main.js
複製程式碼
─ server
 ├── app
 ├── ├── common            # 工具
 ├── ├── controllers       # 業務
 ├── ├── models            # 定義資料庫模型
 ├── db_vue                # 匯出來的資料庫資料
 ├── routes                # 路由
 ├── app.js
 ├── config.js             # 簡訊api的key相關

複製程式碼

開發過程

使用vue-cli

我之前用react,為了熟悉webpack就沒有使用腳手架(如yeoman),深深感受到了babel的複雜,webpack配置的繁瑣。用到vue-cli簡直就是一個字:爽,各種複雜的配置都配好了,如使用sass下載後在style配置一下就好了,不用再到webpack配置,這些發雜的配置本該就不要重複做。現在Parceiljs打包工具也出來了,以後可以更爽快的開發了

對vue的感覺就是真的對新手很友好,官網教程很全,例子很多,上手快。 使用vuex + map輔助函式用起來很方便 下面是一個登陸的例子

# login.vue
# 先請求登入,返回使用者資訊,通過vuex的mapAction函式呼叫actions,這裡vuex分了user、seller、admin模組
methods: {
  ...mapAction('user',
    [
      'saveUserInfo'
    ]
  ),
  login () {
    _loginApi(phone, pass).then(res => {
      this.saveUserInfo(res.data)
      this.$router.push('/home')
    })
  }
}
複製程式碼
# user/actions.js
# 呼叫函式,先做一個客戶端儲存存到localStorage,再存到state中
import { _saveUserInfo } from 'common/javascript/cache'

export function saveUserInfo ({commit, state}, info) {
  commit(types.SET_USER_INFO, _saveUserInfo(info))
}
複製程式碼
# index.vue
# 需要資料的元件用vuex的mapGetters函式獲取
<template>
  <user-header :userInfo='userInfo'></user-header>
</template>
<script>
export default {
  computed: {
    ...mapGetters(
      'user',
      [
        'userInfo',
        'reLogin'
      ]
    )
  }
}
</script>
複製程式碼

資料的流向是單向的

vue+koa2+mongodb點餐系統總結

開發遇到的問題

vuex分模組的修改

一開始沒有分模組是這樣寫的

# store.js
# 
export default new Vuex.Store({
  getter,
  state,
  mutations,
  actions
})

# 元件呼叫,直接呼叫
computed: {
  ...mapGetters(['suggestion'])
},
methods: {
  ...mapActions(['saveUserInfo']),
  ...mapMutations({
    setCoordinate: 'SET_COORDINATE'
  })
}
複製程式碼

分了模組寫法有區別的

# store.js
# 各模組分別有各自的state、getters、actions
# 模組結構自己定義,所以可以定義一個頂層公用的,再在裡層分模組
export default new Vuex.Store({
  modules: {
    user,
    seller,
    admin
  }
})

# 元件呼叫
# 呼叫要有模組名,mapActions取不同模組時要分開取
computed: {
  ...mapGetters(
    'user',
      [
        'suggestionList',
        'userInfo'
      ]
  )
},
methods: {
  ...mapMutations({
    setCoordinate: 'user/SET_COORDINATE'
  }),
  ...mapActions('user',
    [
      'saveInfo'
    ]
  ),
  ...mapActions('seller',
    [
      'saveSellerInfo'
    ]
  )
}
複製程式碼

父子元件通訊

一般父子元件,是父元件向子元件傳入資料,子元件顯示資料,資料單向流動。 當子元件需要傳遞資料給父元件時,通過觸發函式,以引數的形式向父元件傳遞資料,跟react資料傳遞一樣

# 父元件
<food-card @addOne='addOne' :info='info'></food-card>

# 子元件
<p class='name'>{{info.dishName}}</p>
<span class='money'>¥{{info.dishPrice}}</span>
<div :class='_status' @click='addToCart'>加入購物車</div>
...
props: {
  info: {
    type: Object,    #定義父元件傳入的資料型別,當傳入型別和定義的不一致,vue會警告
    default: {}
  }
},
methods: {
  addToCart () {
    this.$emit('addOne', this.info)   #用this.$emit觸發父元件的addOne函式
  }
}
複製程式碼

上面的例子中,不能修改父元件傳入的資料。若要修改資料,則需要在$emit前複製一份資料然後修改,再傳遞給父元件,也可以用sync實現父子元件資料雙向繫結。sync在2.0被移除因為這破壞了單向資料流,但2.3又引入了,因為有場景需要如一些複用的元件。但sync和以前的實現又有點不一樣,它只是一個語法糖,會被擴充套件為一個自動更新父元件屬性的 v-on 監聽器。 並且子元件需要顯示觸發更新:this.$emit('update:xx', newVal)

# 父元件
<card-item :data.sync='item'></card-item>
  #會被擴充套件為這樣
<comp :data="item" @update:data="newVal => item = newVal"></comp>

# 子元件
<input class='commend' type="text" v-model='commend' placeholder="寫下對此菜品的評價">
  export default {
    data () {
      return {
        commend: ''
      }
    },
    watch: {
      commend (newC) {
        this.data.commend = newC
        this.$emit('update:data', this.data)   #顯示觸發data的更新達到雙向資料繫結
      }
    },
    props: {
      data: {
        type: Object,
        default: {}
      }
    }
  }

複製程式碼

element-ui設定樣式無效

使用了element-ui樣式框架,有時需要對他們的元件做一些樣式的修改。但它是封裝好的,我就需要檢視原始碼才知道它內部定義的類或標籤來自定義樣式,但是發現無效,舉個例子

  <el-rate
    v-model="item.score"
    disabled
    show-text
    text-color="#ff9900">
  </el-rate>

<style scoped lang='sass'>
  .el-rate              #元件都自帶同名的類
    div
      background: red
<style>

#發現element-ui通過jsfiddle演示的程式碼卻沒問題,就查詢不同點,然後發現是style標籤的scoped導致的,可能侷限了樣式的作用範圍。去掉就可以了,此時要注意樣式是全域性的,所以要注意類名的使用
複製程式碼

監聽$route要仔細

在檢視商鋪頁面,可以選擇不同型別商家,也可以搜尋商家,可以有不同的實現方法。可以把狀態全放在在元件內或vuex管理,但是這樣重新整理後狀態就消失了。所以我選擇用url的hash來儲存狀態,通過監聽路由變化來載入不同資料。商家列表資料放在vuex

# 商家頁面,place.vue
data () {
  return {
    pageNum: 1,
    totalPage: 1,
    keyword: '',
    loading: false
  }
},
created () {
    this.getList()
    # 滾動載入下一頁
    window.onscroll = () => {
      if (!this.loading && this.__getScrollHeight() <= (this.__getWindowHeight() + window.scrollY + 100)) {
        if (this.pageNum < this.totalPage) {
          this.loading = true
          this.pageNum++
          this.getList()
        }
      }
    }
  },
watch: {
  $route () {
    this.getList()
  }
},
methods: {
  changeTag (tag) {
    this.pageNum = 1
    this.shopType = tag
    this.keyword = ''
    # this.clearShopList()
    this.$router.push({path: '/place', query: {shopType: code, keyword: undefined}})
  },
  search (str) {
    this.keyword = str
    this.pageNum = 1
    this.shopType = 1
    # this.clearShopList()
    this.$router.push({path: '/place', query: {shopType: undefined, keyword: str}})
  },
  getList () {
    const { keyword, shopType } = this.$router.currentRoute.query
    this.loading = true
    _getShopList(keyword, shopType, this.pageNum).then(res => {    #請求資料,然後concat到商家list存入vuex
      ...
    })
  }
}
複製程式碼

後面使用時,發現了bug:在別的頁面變動路由,這裡會載入了重複的資料。所以要限定監聽路由變動的路由,在本頁面才有效

watch: {
  $route () {
    if (this.$router.currentRoute.name === 'place') {
      this.getList()
    }
  }
}
複製程式碼

然後又發現bug:從別的頁面回到這裡,也載入了重複的資料,解決辦法是離開元件時把原資料刪除。要這樣做是因為我把資料存入了vuex,感覺不必要存入vuex..

beforeDestroy () {
  window.onscroll = null
  this.clearShopList()
},
methods: {
  ...mapMutations({
      clearShopList: 'user/CLEAR_SHOP_LIST'
  })
}
複製程式碼

請求異常跳轉登入頁

請求有時需要出現異常如401,需要讓使用者重新登入,我用的是axios

# 這是一個請求封裝,返回異常全部呼叫reLogin的action返回登入頁

import store from '../../store'
export function basePOST (api, params) {
  return axios({
    method: 'post',
    url: api,
    headers: {
      'content-Type': 'application/x-www-form-urlencoded'
    },
    data: config.toFormData({
      ...params
    })
  }).then(res => {
    return res.data
  }).catch(() => {
    store.dispatch('user/reLogin')
  })
}
複製程式碼

koa2基本配置

使用koa生成器初始化專案

npm install koa-generator -g
koa2 server
cd server && npm install
npm start
複製程式碼

加入session中介軟體

const session = require('koa-session2')
app.use(session({
  key: 'sessionid---'
}))
複製程式碼

設定靜態資源快取

# 注意,時間需要用變數放入,否則無效
var staticCache = require('koa-static-cache')
const cacheTime = 365 * 24 * 60 * 60
app.use(staticCache(path.join(__dirname, 'public'), {
  maxAge: cacheTime
}))
複製程式碼

傳輸檔案壓縮

var compress = require('koa-compress')
app.use(compress({
  filter: function (contentType) {
    return /text/i.test(contentType)
  },
  threshold: 2048,
  flush: require('zlib').Z_SYNC_FLUSH
}))
複製程式碼

mongoose使用遇到的坑

在建表時需要注意資料型別,若schema定義是Number,存入的卻是String,會報錯。 若schema沒有定義欄位,建立collection時傳入其他欄位,會存不進去 資料庫的Number資料,用字元查詢會找不到如:user.find({age: '18'})

moment解決mongodb時區問題

mongodb用的是中時區的時間,我們是東八區,所以時間都會晚8小時,用moment外掛處理 moment是用在客戶端,而不是儲存。儲存的資料是中時區的,在顯示資料時修正 因為moment很多地方都要用,所以我直接將它放入Vue的原型,這樣所有vue例項都可以拿到這個方法

#main.js
import Vue from 'vue'
import moment from 'moment'
Object.defineProperty(Vue.prototype, '$moment', {value: moment})

#元件中
<p class='fr date'>{{$moment(item.commentDate).format('YYYY-MM-DD HH:mm:ss')}}</p>
複製程式碼

其他

傳輸資料格式轉換

用post傳輸資料,用x-www-form-urlencoded格式,但是表單裡有個物件陣列:

{
  userId: 13546515,
  dishs: [{id: 4545, num: 2, price: 12}, {id: 1446, num: 1, price: 8}],
  ...
}
複製程式碼

我用node做後臺,拿這個資料一點問題都沒有,但是小組內跟java後臺配合,不能這樣傳物件陣列字串。他需要直接用List<>包裝,需要這樣傳遞才行

vue+koa2+mongodb點餐系統總結
看到這樣的取值,我想:臥槽,還有這樣傳的。。 雖然感覺很不合理,但還是做了轉換。下面的程式碼就能達到這個目的

  let dishs = {}
  _dishs.forEach((item, index) => {
    for (let key in item) {
      if (!dishs[`dishs[${index}]`]) {
        dishs[`dishs[${index}]`] = {}
      }
      dishs[`dishs[${index}]`][key] = item[key]
    }
  })
  let temp = {}
  let result = {}
  for (let i in dishs) {
    for (let j in dishs[i]) {
      if (!temp[i]) temp[i] = {}
      result[`${i}.${j}`] = dishs[i][j]
    }
  }

複製程式碼

mongodb匯入資料

server/db_vue路徑是匯出的資料,可以匯入到自己的mongodb資料庫

# d是資料庫名,c是collection名
mongoimport -d vue -c users --file vue/users.json
複製程式碼

總結

總體來說,專案結構還算清晰,我對Vue還不是很熟悉,所以運用的還不是很好,比如使用Vuex的使用,我對於不同元件需要共享的資料存入store或同時存在本地,對於單個元件內的資料,感覺沒必要存入store。 對koa2也不是很熟悉,一開始總是忘記await等各種小問題,寫完雖然做了快取壓縮,因為不是一個後端,所以效能上還是弄不好,線上的可能比較卡,因為是學生伺服器。 寫完這個對Vue熟悉一點了,接下來會繼續學學Vue的原理,學學新東西 以上就是對專案的總結,如果有錯,望指正

相關文章