现如今,随着餐饮行业的不断扩大,消费者的年轻化,餐饮行业的点餐逐渐由线下转为线上点单,因此我们这个点餐系统就应运而生。点餐系统为餐厅增添了用户与餐厅的互动性,还可以实现更加多样化的点餐。
本项目运用spingboot + springmvc + mybatis + vue实现了一个具有商家和普通用户的双角色点餐系统。
主键id(自增)
用户名
密码
身份标识
+----------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+----------+--------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| username | varchar(250) | YES | | NULL | |
| password | varchar(50) | YES | | NULL | |
| isadmin | int(11) | YES | | NULL | |
+----------+--------------+------+-----+---------+----------------+
订单主键ID(自增)
用户ID
订单创建时间
状态(0:未完成; 1:已完成)
+------------+----------+------+-----+-------------------+----------------+
| Field | Type | Null | Key | Default | Extra |
+------------+----------+------+-----+-------------------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| uid | int(11) | YES | | NULL | |
| createtime | datetime | YES | | CURRENT_TIMESTAMP | |
| status | int(11) | YES | | NULL | |
+------------+----------+------+-----+-------------------+----------------+
主键ID(自增)
菜品名
价格
+-------+---------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------+---------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| name | varchar(250) | NO | | NULL | |
| price | decimal(10,0) | NO | | NULL | |
+-------+---------------+------+-----+---------+----------------+
订单id
菜品id
+-------+---------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+---------+------+-----+---------+-------+
| did | int(11) | NO | | NULL | |
| oid | int(11) | NO | | NULL | |
+-------+---------+------+-----+---------+-------+
这个表是为了处理订单表和菜品表的多对多关系,也就是中间表
package com.example.ordersys.config;/*** @author SongBiao* @Date 2021/1/18*/
//此类专门存储session中的key值
public class AppFinal {//将session中的key值提取出来,单独放到一个类里面去//用户的session keypublic static final String USERINFO_SESSION_KEY = "USERINFO_SESSION_KEY";
}
此类用于统一session存储过程中的key值
package com.example.ordersys.model;import lombok.Data;import java.math.BigDecimal;/*** @author SongBiao* @Date 2021/1/18*/
@Data
public class Dish {private int id;private String name;private BigDecimal price;}
package com.example.ordersys.model;import lombok.Data;/*** @author SongBiao* @Date 2021/1/18*/
@Data
public class OrderDetail {private int oid;private int did;private Dish dish;
}
package com.example.ordersys.model;import lombok.Data;import java.util.Date;/*** @author SongBiao* @Date 2021/1/18*/
@Data
public class OrderInfo {private int id;private int uid;private Date createtime;private int status;private String uname;
}
package com.example.ordersys.model;import lombok.Data;/*** @author SongBiao* @Date 2021/1/18*/
@Data
public class UserInfo {private int id;private String username;private String password;private int isadmin;
}
package com.example.ordersys.tools;import lombok.Data;/*** @author SongBiao* 此类用于规范controller类返回的数据类型的统一,我们定义如下类* @Date 2021/1/18*/
@Data
public class ResponseBody {//后端返回给前端的状态private int status;//定义参数的描述信息private String msg;//后端返回给前端的数据private T data;public ResponseBody(int status,String msg,T data){this.status = status;this.msg = msg;this.data = data;}}
//注册方法public int register(UserInfo userInfo);
insert into userinfo(username,password,isadmin)values(#{username},#{password},0)
/*** 用户注册功能,这里指的是顾客*/@RequestMapping("/reg")public ResponseBody register(UserInfo userInfo) {int data = userMapper.register(userInfo);return new ResponseBody<>(0, "", data);}
注册 注册 取消
// 注册方法doRegister() {//获取用户输入的值,通过v-model来确定如何获取值let username = app.reg.name;let password = app.reg.password;let password2 = app.reg.password2;//非空校验if (username == "") {alert("请先输入用户名");return false;}if (password == "") {alert("请先输入密码");return false;}if (password2 == "") {alert("请先输入确认密码");return false;}if (password != password2) {alert("两次输入的密码不一致,请核对");return false;}//请求后端接口实现注册功能jQuery.getJSON("/user/reg", {"username": username,"password": password}, function (result) {if (result != null && result.data != null && result.data > 0) {alert("恭喜添加成功");//每次一个用户注册信息后将信息置空app.reg.name = "";app.reg.password = "";app.reg.password2 = "";//隐藏注册窗体app.reg.showRegister = false;} else {alert("抱歉,添加失败,请重试");}});},
(1)验证用户的账号和密码
(2)存储session信息
(3)显示欢迎信息
(4)显示所有用户可以点的菜品列表
(5)刷新页面维持页面登录状态
(6)退出登录
package com.example.ordersys.config;/*** @author SongBiao* @Date 2021/1/18*/
//此类专门存储session中的key值
public class AppFinal {//将session中的key值提取出来,单独放到一个类里面去//用户的session keypublic static final String USERINFO_SESSION_KEY = "USERINFO_SESSION_KEY";
}
//登录功能@RequestMapping("/login")public ResponseBody login(UserInfo userInfo, HttpServletRequest request) {UserInfo user = userMapper.login(userInfo);//登录后将信息存入session当中if (user != null && user.getId() > 0) {HttpSession session = request.getSession();session.setAttribute(AppFinal.USERINFO_SESSION_KEY,user);}return new ResponseBody<>(0, "", user);}
//登录方法public UserInfo login(UserInfo userInfo);
// 登录方法
doLogin() {//获取用户输入的用户名和密码,还是看v-modellet username = app.login.inputUsername;let password = app.login.inputPassword;//非空校验if (username == "") {alert("请输入用户名");return false;}if (password == "") {alert("请输入密码");return false;}//访问后端接口,验证用户信息jQuery.getJSON("/user/login", {"username": username,"password": password}, function (result) {if (result != null && result.data != null && result.data.id > 0) {//登录成功alert("登录成功!");//登录成功后,隐藏左侧未登录之前的导航,并显示欢迎信息//因为两者的v-show是一样的app.login.isLogin = true;//去掉登录时的窗口,还是查看v-modelapp.login.showLoginDialog = false;//请求后端得到菜品列表jQuery.getJSON("/dish/list", {}, function (result) {if (result != null && result.data != null) {app.dishes = result.data;}});} else {//用户名或密码错误,请重新输入alert("用户名或密码错误,请重新输入");}});
},
@RequestMapping("/list")
public ResponseBody> getList() {List data = dishMapper.getDishList();return new ResponseBody<>(0, "", data);
}
//查询菜单列表
public List getDishList();
菜名 价格 选择 mdi-food {{dish.name}}mdi-cash {{dish.price}}
jQuery.getJSON("/dish/list", {}, function (result) {if (result != null && result.data != null) {app.dishes = result.data;}});
已点 {{selectedDishCount}} 道菜, 总计 {{selectedDishPrice}} 元
computed: {// 点餐的个数selectedDishCount() {let count = 0;this.dishes.forEach(dish => {if (dish.isSelected) {count++;}});return count;},// 计算下单的菜品总价selectedDishPrice() {let price = 0;this.dishes.forEach(dish => {if (dish.isSelected) {price += dish.price;}})return price;}
},
//判断是否登录的方法
isLogin() {jQuery.getJSON("/user/islogin", {}, function (result) {if (result != null && result.data != null && result.data.id > 0) {//此时确定为登录状态,点击刷新后应该仍在主页面,而不是需要重新登录app.login.isLogin = true;//设置欢迎信息app.login.inputUsername = result.data.username;//请求后端得到菜品列表jQuery.getJSON("/dish/list", {}, function (result) {if (result != null && result.data != null) {app.dishes = result.data;}});}});
},
注意要在最后调用这个isLogin方法
controller层
/*** 判断登录状态** @param request* @return*/
@RequestMapping("/islogin")
public ResponseBody isLogin(HttpServletRequest request) {UserInfo user = null;HttpSession session = request.getSession(false);if (session != null && session.getAttribute(AppFinal.USERINFO_SESSION_KEY) != null) {user = (UserInfo) session.getAttribute(AppFinal.USERINFO_SESSION_KEY);}return new ResponseBody(0, "", user);
}
isLogin方法是判断当前登录后,刷新页面不退出登录状态,最后在app外面调用了这个isLogin方法
原理是每次vue刷新页面会把就是会把变量如果改变了再全部变为初始值,所以如果我们不去设置isLogin方法的话,最终当我门点击刷新后,isLogin这个属性重新置为false,那么就会显示我们的导航栏了:因为!login.isLogin为true,
mdi-logout 退出登录
//退出登录功能
logout() {if (confirm("是否确认退出?")) {jQuery.getJSON("/user/logout", {}, function (result) {if (result != null && result.data != null && result.data > 0) {//退出成功alert("退出成功");//刷新当前页面location.href = location.href;} else {alert("抱歉,操作失败,请重试");}});}
},
/*** 退出登录* @param request* @return*/
@RequestMapping("/logout")
public ResponseBody logOut(HttpServletRequest request) {int data = 0;HttpSession session = request.getSession(false);if (session != null && session.getAttribute(AppFinal.USERINFO_SESSION_KEY) != null) {session.removeAttribute(AppFinal.USERINFO_SESSION_KEY);data = 1;}session.removeAttribute(AppFinal.USERINFO_SESSION_KEY);return new ResponseBody<>(0, "", data);}
下单
dishSubmit() {if (confirm("确实提交?")) {let dids = "";//菜品id集合app.dishes.forEach(dish => {//选中了菜品就加1if (dish.isSelected) {dids += (dish.id + ",");}});if (dids != null) {//dids不等于null说明菜品已经下单提交,请求后端实现点餐//传递的参数为菜品id的集合jQuery.getJSON("/order/add", {"dids": dids}, function (result) {if (result != null && result.data != null && result.data > 0) {alert("恭喜:点餐成功");}});} else {alert("请先选中菜品信息");}}
},
(1)菜品信息插入到OrderInfo表中
首先拿到下单的用户uid,从而拿到对应的订单oid
(2)中间表OrderDetail也要完成订单与菜品的关联
将之前拿到的订单oid与前端传来的菜品dids进行关联
@RequestMapping("/add")
public ResponseBody addOrder(String dids, HttpServletRequest request) {int data = 0;//1:添加订单信息,返回一个订单ID//(1):要想添加订单信息,第一步是获取用户的id,因为在订单列表中,用户id是订单表的逻辑外键//我们是要通过用户的id来拿到对应的订单id// 如果当前Session没有就为nullHttpSession session = request.getSession(false);if (session != null && session.getAttribute(AppFinal.USERINFO_SESSION_KEY) != null) {int uid = ((UserInfo) session.getAttribute(AppFinal.USERINFO_SESSION_KEY)).getId();//(2):将用户的id存入到OrderInfoOrderInfo orderInfo = new OrderInfo();orderInfo.setUid(uid);//订单添加方法//result代表添加影响的行数//addOrder方法会获取到订单表的自增idint result = orderInfoMapper.addOrder(orderInfo);//result大于0说明插入成功,因为在使用MyBatis做持久层时,insert语句默认是不返回记录的主键值,而是返回插入的记录条数if (result > 0) {//2:添加订单详情,需要用到订单IDdata = orderDetailMapper.add(orderInfo.getId(), dids.split(","));}}return new ResponseBody<>(0, "", data);
}
//添加订单
public int addOrder(OrderInfo orderInfo);
//传参的时候订单的id可能只有一个,但是菜品的id可能有多个,所以用String数组来表示
public int add(int oid,String[] dids);
insert into orderinfo(uid,status) values(#{uid},0)
注意:在使用MyBatis做持久层时,insert语句默认是不返回记录的主键值,而是返回插入的记录条数
insert into orderdetail(oid,did) values(#{oid},#{item})
显示用户自身订单状态代码
mdi-cart-outline 我的订单
orderList() {jQuery.getJSON("/order/list", {}, function (result) {if (result != null && result.data != null) {//获取订单列表成功app.orders = result.data;app.status = 'ordersPage';} else {//获取订单失败alert("获取订单列表失败,请重试");}});
},
序号 状态 时间 详情 {{order.id}} {{order.status==0?'未完成':'已完成'}} {{order.createtime}} 查看详情
查看详情的代码:
菜品 价格 {{dish.dish.name}} {{dish.dish.price}} 总金额:{{curOrderMoney()}} 关闭
//计算某个订单详情的总钱数
curOrderMoney() {let money = 0;app.curOrder.forEach(order => {money += order.dish.price;});return money;
},
//查看订单详情
detail(oid) {jQuery.getJSON("/detail/list", {"oid": oid}, function (result) {if (result != null && result.data != null) {app.curOrder = result.data;//显示订单详情页面app.showCurOrder = true;}});
},
显示订单代码
/*** 用户获取订单列表** @return*/
@RequestMapping("/list")
public ResponseBody> getList(HttpServletRequest request) {List orderinfo = null;HttpSession session = request.getSession(false);int uid = 0;if (session != null && session.getAttribute(AppFinal.USERINFO_SESSION_KEY) != null) {//获取用户iduid = ((UserInfo) session.getAttribute(AppFinal.USERINFO_SESSION_KEY)).getId();orderinfo = orderInfoMapper.list(uid);}return new ResponseBody<>(0, "", orderinfo);
}
查看详情代码:
@RequestMapping("/list")
public ResponseBody> getList(int oid){List list = orderDetailMapper.getList(oid);return new ResponseBody<>(0,"",list);
}
显示订单代码
//获取订单列表
public List list(int uid);
查看详情代码
public List getList(int oid);
显示订单代码
查看详情代码
这里要注意,先来看订单详情表的实体类设计:
package com.example.ordersys.model;import lombok.Data;/*** @author SongBiao* @Date 2021/1/18*/
@Data
public class OrderDetail {private int oid;private int did;private Dish dish;
}
可以看到OrderDetail实体类中还有一个Dish对象,但是注意这个对象在我们表中是没有的,原因是当我们用户查看订单详情的时候,需要查看菜品的详细信息以及总价格,我们直接再这里定义一个对象后,后期通过连表查询,然后将查询的结果包装成一个Dish类对象即可。那么我们再xml中就需要使用resultMap来指定对应的映射关系,
首先再DishMapper.xml中指定dish表对应的实体类的映射关系;
同时再OrderDetailMapper.xml中指定OrderDetail实体类的映射关系,注意需要使用到association属性来表示一对一的关系,因为后期我们使用连表查询中,orderdetail与dish进行左连接后,查询出来的记录是一行一行的,也就是说一个订单编号对应一个菜品名称,而不是多个
mysql> select o.*,d.name d_name,d.price d_price from orderdetail o left join dish d on o.did=d.id-> where o.oid=16;
+-----+-----+--------+---------+
| did | oid | d_name | d_price |
+-----+-----+--------+---------+
| 3 | 16 | 红烧肉 | 30 |
| 4 | 16 | 茄子 | 25 |
这里就可以显示出订单编号为16的用户点了两个菜,其菜品编号为3,4,菜品名称为红烧肉,茄子,价格分别为30,25
注意这里我们使用了连表查询(左连接)
mdi-login 登录
登录 登录 取消
userLogin() {let username = app.login.inputUsername;let password = app.login.inputPassword;//非空校验if (username == "") {alert("请输入用户名");return false;}if (password == "") {alert("请输入密码");return false;}//访问后端接口,验证用户信息jQuery.getJSON("/user/login", {"username": username,"password": password}, function (result) {if (result != null && result.data != null && result.data.id > 0) {//等于1,说明是管理员登录if (result.data.isadmin == 1) {//登录成功alert("登录成功!");//登录成功后,隐藏左侧未登录之前的导航,并显示欢迎信息//因为两者的v-show是一样的app.login.isLogin = true;//去掉登录时的窗口,还是查看v-modelapp.login.showLoginDialog = false;//请求后端得到菜品列表jQuery.getJSON("/dish/list", {}, function (result) {if (result != null && result.data != null) {app.dishes = result.data;}});} else {alert("非法操作,权限不足");}} else {//用户名或密码错误,请重新输入alert("用户名或密码错误,请重新输入");}});
},
//登录功能
@RequestMapping("/login")
public ResponseBody login(UserInfo userInfo, HttpServletRequest request) {UserInfo user = userMapper.login(userInfo);//登录后将信息存入session当中if (user != null && user.getId() > 0) {HttpSession session = request.getSession();session.setAttribute(AppFinal.USERINFO_SESSION_KEY,user);}return new ResponseBody<>(0, "", user);
}
@RequestMapping("/list")
public ResponseBody> getList() {List data = dishMapper.getDishList();return new ResponseBody<>(0, "", data);
}
//登录方法
public UserInfo login(UserInfo userInfo);
//查询菜单列表
public List getDishList();
//保持登录状态的方法
isLogin() {jQuery.getJSON("/user/islogin", {}, function (result) {if (result != null && result.data != null && result.data.id > 0) {if (result.data.isadmin == 1) {//等于1是超级管理员//此时确定为登录状态,点击刷新后应该仍在主页面,而不是需要重新登录app.login.isLogin = true;//设置欢迎信息app.login.inputUsername = result.data.username;//请求后端得到菜品列表jQuery.getJSON("/dish/list", {}, function (result) {if (result != null && result.data != null) {app.dishes = result.data;}});}}});
},
在new Vue外部调用isLogin方法
@RequestMapping("/islogin")
public ResponseBody isLogin(HttpServletRequest request) {UserInfo user = null;HttpSession session = request.getSession(false);if (session != null && session.getAttribute(AppFinal.USERINFO_SESSION_KEY) != null) {user = (UserInfo) session.getAttribute(AppFinal.USERINFO_SESSION_KEY);}return new ResponseBody(0, "", user);
}
新增菜品
新增菜品 新增 取消
//新增菜品
addDish() {if (confirm("确认是否提交?")) {let name = app.newDish.name;let price = app.newDish.price;//if (name == "") {alert("请先输入菜品名称!");return false;}if (price == "" || price == 0) {alert("请先输入价格!");return false;}jQuery.getJSON("/dish/add", {"name": name, "price": price},function (result) {if (result != null && result.data != null && result.data > 0) {//data>0表示添加成功alert("恭喜:菜品添加成功!");//每次添加完菜品后自动刷新location.href = location.href;} else {alert("抱歉:操作失败请重试!");}});}
},
//管理员添加菜品的后台方法
@RequestMapping("/add")
public ResponseBody addDish(Dish dish) {int data = dishMapper.addDish(dish);return new ResponseBody<>(0, "", data);
}
public int addDish(Dish dish);
insert into dish(name,price)values(#{name},#{price})
菜品添加的逻辑就是每次添加完菜品到Dish表后,刷新页面,调用isLogin方法判断是否延续登录状态,然后isLogin方法重新获取后台的菜单列表,然后进行展示
删除
//删除菜品
del(did) {if (confirm("确认删除?")) {jQuery.getJSON("/dish/del", {"id": did}, function (result) {if (result != null && result.data != null && result.data > 0) {//删除成功alert("删除成功");//刷新当前界面location.href = location.href;} else {alert("抱歉,操作失败,请重试");}});}
},
//管理员删除订单的后台方法
@RequestMapping("/del")
public ResponseBody deleteDish(int id) {int data = dishMapper.del(id);return new ResponseBody<>(0, "", data);
}
public int del(int id);
delete from dish where id=#{id}
菜品删除的逻辑就是每次删除菜品后,刷新页面,调用isLogin方法判断是否延续登录状态,然后isLogin方法重新获取后台的菜单列表,然后进行展示
mdi-cart-outline 查看订单
//查看所有人的订单showOrder() {jQuery.getJSON("/order/alllist", {},function (result) {if (result != null && result.data != null) {app.orders = result.data;// 显示订单列表窗体app.status = 'ordersPage';}});},
序号 用户 状态 时间 详情 {{order.id}} {{order.uname}} {{order.createtime}} 查看详情
//管理员修改状态
update(oid, status) {//因为选中的这个状态在前端我们显示选中为true,不选中为false,所以此处需要用三元符进行一个选择status = status ? 1 : 0;jQuery.getJSON("/order/update", {"oid": oid, "status": status}, function (result) {if (result != null && result.data != null && result.data > 0) {alert("恭喜,状态修改成功");} else {alert("修改失败,请重试");}});
},
//查看某个订单详情
detail(oid) {jQuery.getJSON("/detail/list", {"oid": oid}, function (result) {if (result != null && result.data != null) {app.curOrder = result.data;//显示订单详情页面app.showCurOrder = true;}});
},
菜品 价格 {{dish.dish.name}} {{dish.dish.price}} 总金额: {{totalPrice()}} 关闭
//计算某个订单详情的总价
totalPrice() {let money = 0;app.curOrder.forEach(item => {money += item.dish.price;});return money;
},
//管理员查看所有人列表信息@RequestMapping("/alllist")public ResponseBody> getAllList(HttpServletRequest request) {List orderinfo = null;orderinfo = orderInfoMapper.getAllList();return new ResponseBody<>(0, "", orderinfo);}
//管理员修改用户订单的状态
@RequestMapping("/update")
public ResponseBody updateStautus(int oid,int status) {int data = orderInfoMapper.update(oid,status);return new ResponseBody<>(0, "", data);
}
@RequestMapping("/list")
public ResponseBody> getList(int oid){List list = orderDetailMapper.getList(oid);return new ResponseBody<>(0,"",list);
}
//获取所有人的订单列表
public List getAllList();
//修改订单的状态public int update(int oid,int status);
public List getList(int oid);
注意这里我们依旧使用到了联表查询,左连接
update orderinfo set status=#{status} where id=#{oid}
这块的代码逻辑跟之前用户查看订单详情功能一样
mdi-logout 退出登录
logout() {if (confirm("是否确认退出?")) {jQuery.getJSON("/user/logout", {}, function (result) {if (result != null && result.data != null && result.data > 0) {//退出成功alert("退出成功");//刷新当前页面location.href = location.href;} else {alert("抱歉,操作失败,请重试");}});}
},
/*** 退出登录* @param request* @return*/
@RequestMapping("/logout")
public ResponseBody logOut(HttpServletRequest request) {int data = 0;HttpSession session = request.getSession(false);if (session != null && session.getAttribute(AppFinal.USERINFO_SESSION_KEY) != null) {session.removeAttribute(AppFinal.USERINFO_SESSION_KEY);data = 1;}session.removeAttribute(AppFinal.USERINFO_SESSION_KEY);return new ResponseBody<>(0, "", data);}