这是一个点餐系统,包含用户点餐、商家出餐、管理员管理三部分功能 这个项目本来是校内实训,需要用java编写,我负责一部分。但是我不太喜欢用java,且时间足够,就自己独自做了一份,用于学习。 项目的功能和需求是根据前期小组讨论出来的,也基本都是仿饿了么的 各项功能基本都实现了
线上地址:(比较慢)47.93.254.91:3333
源码地址: chihuobao
登录账号: 用户:12345678910 商家:11112222333 管理员:admin2 登录密码都是123456
功能结构


调试运行
npm install<br />npm run dev<br /><br />cd server #打开koa2后台,会开启3333端口<br />npm install<br />node bin/www<br />
npm run build #打包<br />cp dist/* server/public/ #将打包好的文件放到koa2静态目录<br />
页面截图




总体分析
使用的框架、插件等
- 用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的原理,学学新东西 以上就是对项目的总结,如果有错,望指正
注:本文内容来自互联网,旨在为开发者提供分享、交流的平台。如有涉及文章版权等事宜,请你联系站长进行处理。