關於專案
這是一個點餐系統,包含使用者點餐、商家出餐、管理員管理三部分功能 這個專案本來是校內實訓,需要用java編寫,我負責一部分。但是我不太喜歡用java,且時間足夠,就自己獨自做了一份,用於學習。 專案的功能和需求是根據前期小組討論出來的,也基本都是仿餓了麼的 各項功能基本都實現了
線上地址:(比較慢)47.93.254.91:3333
原始碼地址:chihuobao
登入賬號:
使用者: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的結構
─ 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>
複製程式碼
資料的流向是單向的
開發遇到的問題
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<>包裝,需要這樣傳遞才行
看到這樣的取值,我想:臥槽,還有這樣傳的。。 雖然感覺很不合理,但還是做了轉換。下面的程式碼就能達到這個目的 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的原理,學學新東西 以上就是對專案的總結,如果有錯,望指正