上次课我们开发到了显示商品详情
有点击"添加到购物车"的按钮
但是没有反应,我们完成添加购物车的功能就能实现这个效果了
打开mall-order-webapi模块
业务逻辑中一些基本的注意事项
持久层要按上面分析的业务逻辑,开发多个方法
mapper包创建OmsCartMapper接口,编写代码如下
@Repository
public interface OmsCartMapper {
// 判断当前用户的购物车列表中是否包含指定sku商品的方法
OmsCart selectExistsCart(@Param("userId") Long userId,@Param("skuId") Long skuId);
// 新增商品到购物车表中
void saveCart(OmsCart omsCart);
// 修改指定购物车商品的数量的方法
void updateQuantityById(OmsCart omsCart);
}
对应的Mapper.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.tedu.mall.order.mapper.OmsCartMapper">
<!-- 通用查询映射结果 -->
<resultMap id="BaseResultMap" type="cn.tedu.mall.pojo.order.model.OmsCart">
<id column="id" property="id" />
<result column="user_id" property="userId" />
<result column="sku_id" property="skuId" />
<result column="title" property="title" />
<result column="main_picture" property="mainPicture" />
<result column="price" property="price" />
<result column="quantity" property="quantity" />
<result column="gmt_create" property="gmtCreate" />
<result column="gmt_modified" property="gmtModified" />
<result column="bar_code" property="barCode"/>
<result column="data" property="data"/>
</resultMap>
<!-- 声明一个全字符sql片段 -->
<sql id="SimpleQueryFields">
<if test="true">
id,
user_id,
sku_id,
title,
main_picture,
price,
quantity,
gmt_create,
gmt_modified
</if>
</sql>
<!-- 判断当前用户的购物车列表中是否包含指定sku商品的方法 -->
<select id="selectExistsCart" resultType="cn.tedu.mall.pojo.order.model.OmsCart">
select
<include refid="SimpleQueryFields" />
from
oms_cart
where
user_id=#{userId}
and
sku_id=#{skuId}
</select>
<!-- 新增购物车信息 -->
<insert id="saveCart" useGeneratedKeys="true" keyProperty="id">
insert into oms_cart(
user_id,
sku_id,
title,
main_picture,
price,
quantity
) values(
#{userId},
#{skuId},
#{title},
#{mainPicture},
#{price},
#{quantity}
)
</insert>
<!-- 根据购物车id修改数量 -->
<update id="updateQuantityById" >
update
oms_cart
set
quantity=#{quantity}
where
id=#{id}
</update>
</mapper>
创建OmsCartServiceImpl类实现IOmsCartService接口
实现其中方法,先实现新增购物车的方法即可
需要注意,我们在业务逻辑层中需要使用用户的信息
要单独编写一个方法获取用户信息,
@Service
public class OmsCartServiceImpl implements IOmsCartService {
@Autowired
private OmsCartMapper omsCartMapper;
@Override
public void addCart(CartAddDTO cartDTO) {
// 获取当前登录用户的userId
Long userId=getUserId();
// 查询这个userId的用户是否已经将指定的sku添加到购物车
OmsCart omsCart=omsCartMapper.selectExistsCart(userId,cartDTO.getSkuId());
// 判断查询结果是否为null
if(omsCart!=null){
// 不等于null,表示当前用户这个sku已经添加在购物车列表中
// 我们需要做的就是修改它的数量,根据cartDTO对象的quantity属性值添加
omsCart.setQuantity(omsCart.getQuantity()+cartDTO.getQuantity());
// 调用持久层方法修改数量
omsCartMapper.updateQuantityById(omsCart);
}else{
// 如果omsCart是null 会运行else代码块
// 去完成购物车对象的新增,先实例化OmsCart对象
OmsCart newOmsCart=new OmsCart();
// 将参数cartDTO的同名属性赋值给newOmsCart
BeanUtils.copyProperties(cartDTO,newOmsCart);
// cartDTO对象中没有userId属性,需要单独赋
newOmsCart.setUserId(userId);
// 执行新增
omsCartMapper.saveCart(newOmsCart);
}
}
@Override
public JsonPage<CartStandardVO> listCarts(Integer page, Integer pageSize) {
return null;
}
@Override
public void removeCart(Long[] ids) {
}
@Override
public void removeAllCarts() {
}
@Override
public void removeUserCarts(OmsCart omsCart) {
}
@Override
public void updateQuantity(CartUpdateDTO cartUpdateDTO) {
}
// 业务逻辑层获得用户信息的方法,因为多个方法需要获得用户信息,所以单独编写一个方法
// 这个方法的实现是SpringSecurity提供的登录用户的容器
// 方法的目标是获得SpringSecurity用户容器,从容器中获得用户信息
public CsmallAuthenticationInfo getUserInfo(){
// 获得SpringSecurity容器对象
UsernamePasswordAuthenticationToken authenticationToken=
(UsernamePasswordAuthenticationToken)SecurityContextHolder.
getContext().getAuthentication();
// 判断获取的容器信息是否为空
if(authenticationToken!=null){
// 如果容器中有内容,证明当前容器中有登录用户信息
// 我们获取这个用户信息并返回
CsmallAuthenticationInfo csmallAuthenticationInfo=
(CsmallAuthenticationInfo)authenticationToken.getCredentials();
return csmallAuthenticationInfo;
}
throw new CoolSharkServiceException(ResponseCode.UNAUTHORIZED,"没有登录信息");
}
// 业务逻辑层中大多数方法都是获得用户id,所以编写一个返回用户id的方法
public Long getUserId(){
return getUserInfo().getId();
}
}
创建OmsCartController
@RestController
@RequestMapping("/oms/cart")
@Api(tags = "购物车管理模块")
public class OmsCartController {
@Autowired
private IOmsCartService omsCartService;
// 新增购物车信息的控制层方法
@PostMapping("/add")
@ApiOperation("新增购物车信息")
// 判断当前用户是否具有普通用户权限ROLE_user
// sso模块登录时,会在用户的权限列表中添加ROLE_user权限
@PreAuthorize("hasRole('ROLE_user')")
// cartAddDTO参数是需要经过SpringValidation框架验证的
// @Validated就是激活框架验证功能,如果cartAddDTO不满足验证要求,会自动运行
// 统一由异常处理类中的BingingException异常处理
public JsonResult addCart(@Validated CartAddDTO cartAddDTO){
omsCartService.addCart(cartAddDTO);
return JsonResult.ok("成功添加到购物车");
}
}
先注意sso模块application-test.yml的地址和端口号(密码有两个)
也要注意order模块application-test.yml的地址和端口号
都保证正确的前提下
启动 leaf passport order
sso:10002
order:10005
先访问10002登录获得JWT 用户名jackson密码123456
先登录看到JWT 然后复制JWT
转到10005 order模块 文档管理->全局参数设置->添加参数
参数名:Authorization
参数值:Bearer [粘贴JWT]
然后刷新当前10005的界面
然后进行发送请求即可成功!
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.3.0</version>
</dependency>
SpringSecurity框架登录后,一定会有一个权限列表
在userDetails对象中
我们登录用户的这个对象的值可能是
{"authorities":["ROLE_user"],"id":1,"userType":"USER","username":"jackson"}
sso模块前台用户登录时,会authorities属性中添加ROLE_user权限
而后台管理用户登录时会向authorities属性中添加下面属性
["/pms/product/read","/pms/product/update","/pms/product/delete"]
所以想要在控制器运行前判断权限时就可以使用下面的写法
@PreAuthorize("hasAuthority('ROLE_user')")
hasRole判断是专用于判断当前用户角色的指令
hasRole会自动在我们判断的内容前添加ROLE_
@PreAuthorize("hasRole('ROLE_user')")
开发持久层
OmsCartMapper添加方法如下
// 根据当前用户id查询购物车列表
List<CartStandardVO> selectCartsByUserId(Long userId);
OmsCartMapper.xml添加对应内容
<!-- 根据用户id查询购物车信息 -->
<select id="selectCartsByUserId"
resultType="cn.tedu.mall.pojo.order.vo.CartStandardVO">
select
<include refid="SimpleQueryFields" />
from
oms_cart
where
user_id=#{id}
</select>
开发业务逻辑层
OmsCartServiceImpl业务实现
返回值支持分页结果,按分页条件查询
// 根据用户id分页查询当前用户的购物车列表
@Override
public JsonPage<CartStandardVO> listCarts(Integer page, Integer pageSize) {
// 获得用户id
Long userId=getUserId();
// 执行查询前设置分页条件
PageHelper.startPage(page,pageSize);
// 执行分页查询
List<CartStandardVO> list=omsCartMapper.selectCartsByUserId(userId);
// 实例化PageInfo对象获得分页信息后将它转换为JsonPage返回
return JsonPage.restPage(new PageInfo<>(list));
}
下面开发控制层,调用方法进行测试
OmsCartController添加方法如下
// 分页查询当前用户购物车中的信息
@GetMapping("/list")
@ApiOperation("分页查询当前用户购物车中的信息")
@ApiImplicitParams({
@ApiImplicitParam(value = "页码",name = "page",dataType = "int",example = "1"),
@ApiImplicitParam(value = "每页条数",name = "pageSize",
dataType = "int",example = "5")
})
@PreAuthorize("hasRole('ROLE_user')")
public JsonResult<JsonPage<CartStandardVO>> listCartByPage(
// 当控制器参数可能为空,当空时,我们要给它赋默认值时,可以用下面的格式
@RequestParam(required = false,defaultValue = WebConsts.DEFAULT_PAGE)
Integer page,
@RequestParam(required = false,defaultValue = WebConsts.DEFAULT_PAGE_SIZE)
Integer pageSize
){
// 控制层调用业务逻辑层代码
JsonPage<CartStandardVO> jsonPage=omsCartService.listCarts(page,pageSize);
return JsonResult.ok(jsonPage);
}
启动nacos\seata
启动leaf\sso\order模块
测试http://localhost:10005/doc.html
删除购物车的持久层
我们删除购物车的功能支持同时删除一个或多个购物车中的商品
基本思路就是将一个要删除的购物车商品的id数组传入到Mapper中进行删除
在OmsCartMapper接口中添加放方法
// 根据购物车的id删除商品(支持删除多个商品)
int deleteCartsByIds(Long[] ids);
对应的mapper.xml
<!-- 根据id删除购物车信息 -->
<delete id="deleteCartsByIds">
delete from
oms_cart
where
id in
<foreach collection="ids" item="id" separator="," open="(" close=")">
#{id}
</foreach>
</delete>
删除购物车的业务逻辑层
OmsCartServiceImpl添加方法
// 按ids数组中的id值删除cart表中信息
@Override
public void removeCart(Long[] ids) {
// 删除是包含返回值的
int rows=omsCartMapper.deleteCartsByIds(ids);
if(rows==0){
throw new CoolSharkServiceException(ResponseCode.NOT_FOUND,
"购物车中没有您要删除的商品");
}
}
开发控制层代码
OmsCartController
@PostMapping("/delete")
@ApiOperation("根据用户选择的购物车商品删除(支持批量)")
@ApiImplicitParam(value = "删除购物车的id",name="ids",required = true,
dataType = "array")
@PreAuthorize("hasRole('ROLE_user')")
public JsonResult removeCartsByIds(Long[] ids){
omsCartService.removeCart(ids);
return JsonResult.ok();
}
开发清空当前登录用户购物车的功能
<delete id="deleteCartsByUserId">
delete from
oms_cart
where
user_id=#{userId}
</delete>
@Override
public void removeAllCarts() {
}
清空购物车功能
Mapper接口
// 删除当前用户购物车中所有内容
int deleteCartsByUserId(Long userId);
mapper.xml
<!-- 删除当前用户购物车中所有内容 -->
<delete id="deleteCartsByUserId">
delete from
oms_cart
where
user_id=#{userId}
</delete>
OmsCartServiceImpl
// 清空当前登录用户购物车
@Override
public void removeAllCarts() {
Long userId=getUserId();
int rows=omsCartMapper.deleteCartsByUserId(userId);
if(rows==0){
throw new CoolSharkServiceException(ResponseCode.NOT_FOUND,"您的购物车中没有商品");
}
}
OmsCartController
// 根据用户id清空购物车
@PostMapping("/delete/all")
@ApiOperation("根据用户id清空购物车")
@PreAuthorize("hasRole('ROLE_user')")
public JsonResult removeCartsByUserId(){
omsCartService.removeAllCarts();;
return JsonResult.ok("购物车已清空");
}
修改购物车商品数量
开发业务逻辑层
因为前面我们已经完成了修改购物车数量的持久层,所以不需要再编写了,直接从业务层开始
// 修改购物车商品数量的方法
@Override
public void updateQuantity(CartUpdateDTO cartUpdateDTO) {
// 持久层中已经包含了修改数量的方法,但是参数是OmsCart
// 将本方法的cartUpdateDTO参数值赋值给OmsCart再调用持久层方法即可
OmsCart omsCart=new OmsCart();
BeanUtils.copyProperties(cartUpdateDTO,omsCart);
// 调用持久层实施修改
omsCartMapper.updateQuantityById(omsCart);
}
控制层OmsCartController
// 修改购物车数量
@PostMapping("/update/quantity")
@ApiOperation("修改购物车数量")
@PreAuthorize("hasRole('ROLE_user')")
public JsonResult updateQuantity(@Validated CartUpdateDTO cartUpdateDTO){
omsCartService.updateQuantity(cartUpdateDTO);
return JsonResult.ok("修改完成");
}
重启order测试清空和修改购物车数量的功能
学习记录,如有侵权请联系删除
物车是一个商城程序的典型功能模块,之前使用jquery制作过,也使用angular开发过,今天将使用Vue实现购物车效果,使用的主要技术有vue+axios+mockJS等。
购物车的主要功能如下:
1. 勾选全选,所有商品全部选中。在取消全选框的时候所有商品取消选择。
2. 点击单个商品上的加号减号进行数量的增加和减少,右边小计实时计算出这个商品的价格合计。
3. 点击单个商品上的删除按钮将商品从购物车中删除。
4. 底部已选实时显示已经勾选的商品,右边合计金额实时显示所有勾选的商品的小计之和。
购物车的实现效果如图所示:
1、 HTML页面布局和css样式
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> <style> table{ border-collapse:collapse; float: left; border:1px solid #999; } #btn{ background:#999; color: #fff; } #shop{ list-style: none; } #shop li{ width: 400px; height:150px; border:1px solid #999; } #shop .p{ width:200px; height:30px; background:gray; color: #fff; text-align: center; line-height:30px; } #shop .p1{ color:orange; float: left; } #shop .p2{ color: #999; float: left; margin-left:10px; } #shop .p3{ clear: both; } </style> </head> <body> <div id="app"> <ul id="shop"> <li v-for="item,index in plist" :key="item.id" id="li"> <p class="p">{{item.title}}</p> <p class="p1">¥{{item.sprice}}</p> <p class="p2"><del>¥{{item.price}}</del></p> <p class="p3"> <span v-if="nums[item.id]"> <button @click="sub(item)">-</button> {{nums[item.id]}} </span> <button @click="add(item)">+</button> </p> </li> </ul> <template v-if="car.length"> <table border="1px"> <tr v-for="item,index in car"> <td>{{item.title}}</td> <td> ¥{{item.sprice}} <del>¥{{item.price}}</del> </td> <td> <button @click="carsub(index,item.id)">-</button> {{nums[item.id]}} <button @click="caradd(index,item.id,item.store)">+</button> </td> </tr> </table> <button @click="empty" id="btn">清空购物车</button> 总计:<b>¥{{itotal}}</b> </template> </div> </body> </html>
二、创建一个Vue对象,设置购物车数据属性
var vm = new Vue({ el:"#app", data:{ plist:[],//存放列表数据 car:[], nums:{}, buyed:[] } });
三、假设从后台请求到数据,然后赋值到Vue对象中
后台数据由mockjs模拟,使用axios发起请求获取数据
3.1 安装axios
npm install axios
3.2 请求url地址
var baseurl="http://axiostest.itsource.cn";
3.3 发起请求获取数据
created:function(){ var th = this; //当vue实例化完成使用axios异步请求数据 axios.get(baseurl+'/plists').then(function(res){ th.plist=th.plist.concat(res.data.plists); //console.log(res.data) }).catch(function(err){ console.log(err) }); }
3.4 mockjs拦截请求,模拟数据
var data = { "list|10-20": [ { "id":"@guid", "title":"@ctitle", "price|1-100.2":0, "sprice":function(){ return this.price-3; }, "store|2-20":1 } ] }
四、计算购物车的总价
computed:{ itotal:function(){ var total=0; for(var i=0;i<this.car.length;i++){ total+=this.car[i].sprice*this.nums[this.car[i].id]; } return total; } }
五、购物车中增加和减少数量
methods:{ //在购物车中增加数量 caradd:function(index,id,store){ //判断数量是否超过库存 if(this.nums[id]>store){ return; } //this.car[index].num+=1;//数量加1 this.nums[id]+=1; this.$set(this.car,index,this.car[index]); }, //在购物车中减少数量 carsub:function(index,id){ //数量少于1时,删除商品 if(this.nums[id]<=1){ this.car.splice(index,1); this.buyed.splice(index,1); delete this.nums[id]; return; } //this.car[index].num-=1;//商品-1 this.nums[id]-=1; this.$set(this.car,index,this.car[index]); } }
六、添加商品,删除商品,清空购物车
物车一般包含商品名称、单价、数量等信息,数量可以任意新增或减少,商品项也可删除,还可以支持全选或多选:
最终效果
我们把这个小项目分为三个文件:
首先在 js 中初始化 Vue 实例,整体模板如下:
var app = new Vue({ el: '#app', data: { ... }, mounted: function () { ... }, computed: { ... }, methods: { ... } });
一般来说,这里的 data 来源于服务端数据,这里为了简便,所以直接定义好的数据:
data: { /** * 购物车中的商品列表 */ list: [ { id: 1, name: '韩国进口海牌海苔', price: 39.9, count: 1 }, { id: 2, name: '印尼进口 Nabati 丽巧克(Richoco)休闲零食 巧克力味 威化饼干', price: 11.8, count: 1 }, { id: 3, name: '菲律宾进口 道吉草 奶油夹', price: 6.5, count: 1 } ], //选中的商品列表,用于计算总价 checkList: [] }
mounted: function () { //默认全选 this.checkAll(); this.checkAllElement(document.querySelector(".checkAll")); }
当 mounted 时,默认全选购物车内的所有商品。
computed: { /** * 总价 * @returns {string} */ totalPrice: function () { var total = 0; for (var i = 0; i < this.checkList.length; i++) { var item = this.checkList[i]; total += item.price * item.count; } return total.toLocaleString(); } }
在计算属性中,我们定义了总价的计算方式,它会绑定勾选的 checkList 来计算总价。之所以使用 toLocaleString 方法,是因为小数部分会自动四舍五入,而且还会以千分位表示出来,很方便哦O(∩_∩)O~
methods: { /** * 减少购买数量 * @param index */ reduceCount: function (index) { if (this.list[index].count === 1) return; this.list[index].count--; }, /** * 增加购买数量 * @param index */ addCount: function (index) { this.list[index].count++; }, /** * 移除商品 * @param index */ remove: function (index) { console.log("remove-index:" + index); this.list.splice(index, 1); //获取商品序号 var id = index + 1; //移除实际参与计算的商品 var $checkList = this.checkList; for (var i = 0; i < $checkList.length; i++) { var item = $checkList[i]; if (item.id == id) { $checkList.splice(i, 1); } } }, /** * 全选或全不选 * @param event */ checkAllOrNot: function (event) { if (event.target.checked) {//全选 this.checkAll(); console.log("checkList:" + this.checkList); } else { // 全不选 console.log("全不选"); this.checkInItems('noCheckAll'); this.checkList.splice(0);//清空数组 } }, /** * 全选 */ checkAll: function () { console.log("全选"); this.checkInItems('checkAll'); this.checkList = this.list.concat();//复制商品列表 }, /** * 全选或全不选 * @param type checkAll:全选;其他:全不选 */ checkInItems: function (type) { var items = document.querySelectorAll('.checkItem'); for (var i = 0; i < items.length; i++) { var item = items[i]; if (type === 'checkAll') { item.checked = true; } else { item.checked = false; } } }, /** * 勾选或不勾选 */ checkItem: function (event, index) { console.log("checkItem"); var element = event.target; var $allCheck = document.querySelector(".checkAll"); if (element.checked) {//勾选,加入已选择列表 this.checkList.push(this.list[index]); this.checkAllElement($allCheck); } else {//不勾选,从已选择列表中去除 this.checkList.splice(index, 1); $allCheck.checked = false; } }, /** * 勾选全选框 * @param element */ checkAllElement: function (element) { //如果所有的商品都已被勾选,则勾选全选框 if (this.checkList.length == this.list.length) { element.checked = true; } } }
在 methods 中,我们定义了以下功能方法:
[v-cloak] { display: none; } table { border: 1px solid #e9e9e9; border-collapse: collapse; border-spacing: 0; empty-cells: show; } th { font: bold 12px "Trebuchet MS", Verdana, Arial, Helvetica, sans-serif; color: #4f6b72; border-right: 1px solid #C1DAD7; border-bottom: 1px solid #C1DAD7; border-top: 1px solid #C1DAD7; letter-spacing: 2px; text-transform: uppercase; text-align: left; padding: 6px 6px 6px 12px; background: #CAE8EA; } td { border-right: 1px solid #C1DAD7; border-bottom: 1px solid #C1DAD7; background: #fff; font-size:14px; padding: 6px 6px 6px 12px; color: #4f6b72; }
这里定义了 v-cloak 样式,用于解决网络慢时的闪屏问题。还定义了表格的相关样式。
接着在 index.html 中引入 Vue 脚本与样式文件。基本模板如下:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>购物车</title> <link rel="stylesheet" type="text/css" href="style.css"> </head> <body> <div id="app" v-cloak> ... </div> <script src="https://cdn.bootcss.com/vue/2.2.2/vue.min.js"></script> <script src="index.js"></script> </body> </html>
因为有可能购物车中的商品被全部删除,所以我们在此加了判断,如果列表为空,则给出友好提示:
<template v-if="list.length"> ... </template> <!--当购物车为空时,则提示--> <div v-else>购物车内暂时没有商品</div>
接着用 table 来展示购物车内的商品列表:
<table> <thead> <tr> <th><input id="checkAll" type="checkbox" class="checkAll" @click="checkAllOrNot($event)"></th> <th>序号</th> <th>商品</th> <th>单价</th> <th>数量</th> <th>操作</th> </tr> </thead> <tbody> <tr v-for="(item,index) in list"> <td><input type="checkbox" class="checkItem" @click="checkItem($event,index)"></td> <td>{{index+1}}</td> <td>{{item.name}}</td> <td>{{item.price}}</td> <td> <button @click="reduceCount(index)" :disabled="item.count===1">-</button> {{item.count}} <button @click="addCount(index)">+</button> </td> <td> <button @click="remove(index)">删除</button> </td> </tr> </tbody> </table> <div>总价:¥{{totalPrice}}</div>
*请认真填写需求信息,我们会在24小时内与您取得联系。