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的原理,学学新东西 以上就是对项目的总结,若是有错,望指正

相关文章
相关标签/搜索