vue+koa2+mongodb點餐系統總結

關於項目

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

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

源碼地址:chihuobaovue

登陸帳號:
  用戶:12345678910
  商家:11112222333
  管理員:admin2
登陸密碼都是123456
複製代碼

功能結構

調試運行

npm install
npm run dev

cd server                 #打開koa2後臺,會開啓3333端口
npm install
node bin/www
複製代碼
npm run build                #打包
cp dist/* server/public/     #將打包好的文件放到koa2靜態目錄
複製代碼

頁面截圖

整體分析

使用的框架、插件等

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

包含的功能

  • 手機註冊,登陸,重置密碼
  • 用戶點餐,該商家會收到消息提示有新訂單(用輪詢實現)
  • 用戶查看本身的訂單,評價、刪除等
  • 修改本身的信息,申請成爲商戶等
  • 商家管理訂單,接單等
  • 統計商家訂單數,評分等(頁面上的月銷量是總銷量)
  • 商家管理菜單、查看評論
  • 管理員管理用戶、商鋪、分類等
  • 搜索功能

目錄結構

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

─ 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打包工具也出來了,之後能夠更爽快的開發了node

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

# 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>
複製代碼

數據的流向是單向的 webpack

開發遇到的問題

vuex分模塊的修改

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

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

# 組件調用,直接調用
computed: {
  ...mapGetters(['suggestion'])
},
methods: {
  ...mapActions(['saveUserInfo']),
  ...mapMutations({
 setCoordinate: 'SET_COORDINATE'
  })
}
複製代碼

分了模塊寫法有區別的git

# 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數據傳遞同樣github

# 父組件
<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<>包裝,須要這樣傳遞才行

看到這樣的取值,我想:臥槽,還有這樣傳的。。 雖然感受很不合理,但仍是作了轉換。下面的代碼就能達到這個目的

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的原理,學學新東西 以上就是對項目的總結,若是有錯,望指正

相關文章
相關標籤/搜索