关于项目
这是一个点餐系统,包含用户点餐、商家出餐、管理员管理三部分功能 这个项目本来是校内实训,需要用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">
# 子组件
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的原理,学学新东西 以上就是对项目的总结,如果有错,望指正