目录
一、搭建购物车微服务
1.1 创建module
1.2 pom依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>leyou</artifactId>
<groupId>com.leyou.parent</groupId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>com.leyou.cart</groupId>
<artifactId>leyou-cart</artifactId>
<version>1.0.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
</dependencies>
</project>
1.3 配置文件
server:
port: 8088
spring:
application:
name: cart-service
redis:
host: 192.168.19.121
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10086/eureka
registry-fetch-interval-seconds: 10
instance:
instance-id: ${spring.application.name}:${server.port}
prefer-ip-address: true #当你获取host时,返回的不是主机名,而是ip
ip-address: 127.0.0.1
lease-expiration-duration-in-seconds: 10 #10秒不发送九过期
lease-renewal-interval-in-seconds: 5 #每隔5秒发一次心跳
1.4 启动类
package com.leyou.cart;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
/**
* @Author: 98050
* @Time: 2018-10-24 20:46
* @Feature:购物车启动器
*/
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class LyCartApplication {
public static void main(String[] args) {
SpringApplication.run(LyCartApplication.class,args);
}
}
二、购物车功能分析
2.1 需求
需求描述:
-
用户可以在登录状态下将商品添加到购物车
-
放入数据库
-
放入redis(采用)
-
-
用户可以在未登录状态下将商品添加到购物车
-
放入localstorage
-
-
用户可以使用购物车一起结算下单
-
用户可以查询自己的购物车
-
用户可以在购物车中修改购买商品的数量。
-
用户可以在购物车中删除商品。
-
在购物车中展示商品优惠信息
-
提示购物车商品价格变化
2.2 流程图
这幅图主要描述了两个功能:新增商品到购物车、查询购物车。
新增商品:
-
判断是否登录
-
是:则添加商品到后台Redis中
-
否:则添加商品到本地的Localstorage
-
无论哪种新增,完成后都需要查询购物车列表:
-
判断是否登录
-
否:直接查询localstorage中数据并展示
-
是:已登录,则需要先看本地是否有数据,
-
有:需要提交到后台添加到redis,合并数据,而后查询
-
否:直接去后台查询redis,而后返回
-
-
三、未登录购物车
3.1 准备
3.1.1 购物车的数据结构
首先分析一下未登录购物车的数据结构。
我们看下页面展示需要什么数据:
因此每一个购物车信息,都是一个对象,包含:
{
skuId:2131241,
title:"小米6",
image:"",
price:190000,
num:1,
ownSpec:"{"机身颜色":"陶瓷黑尊享版","内存":"6GB","机身存储":"128GB"}"
}
另外,购物车中不止一条数据,因此最终会是对象的数组。即:
[
{...},{...},{...}
]
3.1.2 web本地存储
知道了数据结构,下一个问题,就是如何保存购物车数据。前面分析过,可以使用Localstorage来实现。Localstorage是web本地存储的一种,那么,什么是web本地存储呢?
什么是web本地存储
web本地存储主要有两种方式:
-
LocalStorage:localStorage 方法存储的数据没有时间限制。第二天、第二周或下一年之后,数据依然可用。
-
SessionStorage:sessionStorage 方法针对一个 session 进行数据存储。当用户关闭浏览器窗口后,数据会被删除。
LocalStorage的用法
语法非常简单:
localStorage.setItem("key","value"); // 存储数据
localStorage.getItem("key"); // 获取数据
localStorage.removeItem("key"); // 删除数据
注意:localStorage和SessionStorage都只能保存字符串。
不过,在common.js中,已经对localStorage进行了简单的封装:
示例:
3.1.3 获取num
添加购物车需要知道购物的数量,所以需要获取数量大小。在Vue中定义num,保存数量:
然后将num与页面的input框绑定,同时给+
和-
的按钮绑定事件:
编写方法:
3.2 添加购物车
3.2.1 点击事件
商品详情页:
现在点击加入购物车会跳转到购物车成功页面。
不过不这么做,先绑定点击事件,然后实现添加购物车功能。
addCart方法中判断用户的登录状态:
addCart(){
ly.http.get("/auth/verify").then(res => {
//已经登录发送消息到后台,保存到redis中
}).catch(() =>{
//未登录保存在浏览器本地localStorage中
})
}
3.2.2 获取数量、添加购物车
addCart(){
ly.http.get("/auth/verify").then(res => {
//已经登录发送消息到后台,保存到redis中
}).catch(() =>{
//未登录保存在浏览器本地localStorage中
//1.查询本地购物车
let carts = ly.store.get("carts") || [];
let cart = carts.find(c => c.skuId === this.sku.id);
//2.判断是否存在
if (cart){
//3.存在更新数量
cart.num += this.num;
} else{
//4.不存在,新增
cart={
skuId:this.sku.id,
title:this.sku.title,
price:this.sku.price,
image:this.sku.image,
num:this.num,
ownSpec:JSON.stringify(this.ownSpec)
};
carts.push(cart);
}
//5.把carts写回到localStorage
ly.store.set("carts",carts);
//6.跳转
window.location.href = "http://www.leyou.com/cart.html";
});
}
结果:
添加完成后,页面会跳转到购物车结算页面:cart.html
3.3 查询购物车
3.3.1 校验用户登录
因为校验用户会多次进行,所以在common.js中将其封装为一个方法:
然后调用:
3.3.2 查询购物车
页面加载时,就应该去查询购物车。
var cartVm = new Vue({
el: "#cartApp",
data: {
ly,
carts:[], //购物车数据
},
components: {
shortcut: () => import("/js/pages/shortcut.js")
},
created(){
this.loadCarts();
},
methods:{
loadCarts(){
//1.先判断登录状态
ly.verifyUser().then(() => {
//2.已经登录
}).catch(() => {
//3.未登陆
this.carts = ly.store.get("carts") || [];
})
}
}
})
刷新页面,查看数据:
3.3.3 渲染到页面
效果:
3.4 修改数量
给页面的 +
和 -
绑定点击事件,修改num 的值:
increment(c){
c.num++;
ly.verifyUser().then(() =>{
//已经登录,向后台发起请求
}).catch(() => {
//未登录,直接操作本地数据库
ly.store.set("carts",this.carts);
})
},
decrement(c){
if (c.num <= 1){
return;
}
c.num--;
ly.verifyUser().then(() =>{
//已经登录,向后台发起请求
}).catch(() => {
//未登录,直接操作本地数据库
ly.store.set("carts",this.carts);
})
},
3.5 删除商品
给删除按钮绑定事件:
deleteCart(i){
ly.verifyUser().then(() =>{
//已经登录,向后台发起请求
}).catch(() => {
//未登录,直接操作本地数据库
this.carts.splice(i,1);
ly.store.set("carts",this.carts);
})
}
3.6 选中商品
在页面中,每个购物车商品左侧,都有一个复选框,用户可以选择部分商品进行下单,而不一定是全部:
定义一个变量,记录所有被选中的商品:
3.6.1 选中一个
给商品前的复选框与selected绑定,并且指定其值为当前购物车商品:
3.6.2 全部选中
分析
页面方法绑定
上:
中:
下:
代码
loadCarts: function () {
//1.先判断登录状态
ly.verifyUser().then(() => {
//2.已经登录
}).catch(() => {
//3.未登陆
this.carts = ly.store.get("carts") || [];
this.selected = this.carts;
this.$refs.selectAllTop.checked = true;
this.$refs.selectAllBottom.checked = true;
})
}
初始化是全选的。
selectAll(){
if(this.selected.length !== ly.store.get("carts").length){
this.selected = this.carts;
}else {
this.selected = [];
}
},
selectSingle(){
console.log(this.selected.length);
console.log(ly.store.get("carts").length);
if(this.selected.length !== ly.store.get("carts").length - 1){
this.$refs.selectAllTop.checked=false;
this.$refs.selectAllBottom.checked=false;
}else{
this.$refs.selectAllTop.checked=true;
this.$refs.selectAllBottom.checked=true;
}
}
注意:为什么要减1?
因为selected和商品选择按钮是双向绑定,当单击按钮时,获取的selected的长度是改变前的长度,所以要进行修正,可自行实验。
3.6.3 初始化全选
加载完成购物车查询后,初始化全选:
3.6.4 总价格
编写计算属性,计算选中商品的总价格:
computed:{
totalPrice(){
return ly.formatPrice(this.selected.reduce((c1,c2) => c1+c2.num*c2.price,0))
}
}
3.6.5 总数量
编写计算属性:
totalNum(){
return this.selected.reduce((c1,c2) => c1+c2.num,0);
}
效果: