【vue小项目|vue小项目(二)—— 购物车的完整实现】
购物车的完整实现
- 1 需要提前了解的基础知识
- 2 目录部署
- 3 效果
- 4 实现的具体功能
- 5 实现的完整代码
-
-
- (1) main.js
- (2) Router.js
- (3)store.js
- (4)App.js
- (5)List.vue
- (6)Home.vue
- (7)Product.vue
- (8)Check.vue
- (9)base.scss
-
- 6 后记
1 需要提前了解的基础知识 本项目完全使用的是前端开发,没有使用到服务器,(前端开发的小伙伴可以放心看),前端使用到: vue, vue-cli, vuex, vue-router, axios,ES6,ES Module等技术
2 目录部署
- 最终实现目录
文章图片
2 public目录
因为本项目没有使用到服务器,我们这里将数据放在本地,方便获取,list.json是待会页面需要获取的json文件,static/img/list下的是图片资源(也可以放在scr/assets/img下),我后面会把相关资源上传,大家下载即可
文章图片
3 src目录
文章图片
components 组件(支付组件,商品组件)
? views 页面(首页面,商品列表页面)
? router.js 路由
? store.js store
? App.vue 应用程序组件
? main.js 入口文件
style/iconfot存放的是阿里的字体图标文件,使用方法请戳()
3 效果 1 列表页
文章图片
2 购物车页面
文章图片
3 实现整体效果
4 实现的具体功能
- (加入购物车)在列表页选择商品加入购物车,加入购物车后不能再次加入,再次点击按钮就是从购物车中删除该商品
文章图片
- (路由转换)在列表页加入购物车后点击下方的‘进入购物车页面’,即可以看到加入购物车的商品,如果购物车中没有商品,则无法进入购物车页面,在购物车页面点击头部‘购物车’,即可返回到商品列表页面
文章图片
- (全选,单选)在购物车页面,当点击商品左上角的圆圈即表示要购买的商品,再次点击即取消购买,点击底部的全选即表示购物车页面的所有商品都要购买,再次点击,则取消全选
- (价钱计算)只有加入购物车后并且点击购买的商品才能结算价钱
文章图片
- (数量添加,数量减少)在购物车页面,点击+号,即添加该商品,点击-号,即减少该商品在购物车中数量,当数量为1时,再点击-号即表示从购物车中删除该商品
- (移出购物车)在购物车页面点击每个商品右上角的垃圾桶图标,即表示从购物车中将该商品移出
文章图片
- (本地存储)使用本地存储,以至于刷新页面或者重新打开该页面,之前加入购物车的商品依旧在,不能用重新选择
文章图片
(1) main.js
注意:
- 阿里矢量字体图标的使用可以戳
- element-ui中文官网:可以戳 element-ui中文官网
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import axios from 'axios'
// 引入element-ui
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
// 引入阿里字体图标
import '@/assets/style/iconfont/iconfont.css'
// 安装
Vue.prototype.$http = axios
Vue.use(ElementUI)import { Message } from 'element-ui';
new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')// 路由跳转之前拦截,不能在购物车为空的情况下进入购物车页面
router.beforeEach((to, from, next) => {
if( to.path === '/') {
next()
} else {
let carList = JSON.parse(localStorage.getItem('car'))
if (carList.length > 0) {
next()
} else {
Message({
message: "购物车空空如也,赶紧去添加吧!",
type: 'error'
})
next('/')
}
}
})
(2) Router.js
import Vue from 'vue'
import Router from 'vue-router'
import List from '@/views/List.vue'Vue.use(Router)export default new Router({
routes: [
// List页面
{
path: '/',
component: List
},
// 购物车页面
{
path: '/home',
name: 'home',
// 异步引入
component: () => import('@/views/Home')
},
]
})
(3)store.js
注意: 这里store.js是本项目中比较复杂的一个页面,主要是因为里面有很多同步消息和变量
import Vue from 'vue'
import Vuex from 'vuex'Vue.use(Vuex)export default new Vuex.Store({
state: {
// 购物车 本地存储,防止数据丢失
car: localStorage["car"] ? JSON.parse(localStorage["car"]) : [],
// 总价
totlePrice: 0,
// 全选状态
all_selected: false,
// 购物车中购买的数量
payNum: 0,
// list页面控制‘加入购物车按钮’的样式本地存储,防止数据丢失
isShow: localStorage["isShow"] ? JSON.parse(localStorage["isShow"]) : []
},
getters: {
// 时时监听car的变化
carList(state) {
// 初始化全选状态
if (state.all_selected) {
state.car.forEach(item => item.isBuy = true)
}
return state.car;
},
// 购买的总价钱
allPrice(state) {
let totlePrice = 0
state.car.forEach(item => {
// 如果加入购物车后点击了购买按钮
if (item.isBuy) {
totlePrice += item.num * item.price
}
})
return state.totlePrice = totlePrice
},
// 购买的商品的总数量
payLength(state) {
let paylength = 0
state.car.forEach(item => {
if (item.isBuy) {
paylength += item.num
}
})
returnstate.payNum = paylength;
}
},
// 同步消息
mutations: {
// 从购物车中删除该商品
deleteProduct(state, id) {
// 商品在购物车中索引值
let index = state.car.findIndex(item => item._id === id)
// 商品在isShow中的索引值
let indexShow = state.isShow.findIndex(item =>item.id === id)
// 将购物车中该商品的selected属性改成false
state.car[index].selected = false;
// 从购物车中删除该商品
state.car.splice(index, 1)
// 从本地存储的购物车中删除该商品(即将删除了该商品的购物车赋值给本地存储)
localStorage.setItem('car', JSON.stringify(state.car))
// console.log(5555,state.car, state.isShow[index].show)
// 将isShow中该商品的show属性改成相反的值
state.isShow[indexShow].show = !state.isShow[indexShow].show
// 将isShow赋值给本地
localStorage.setItem('isShow', JSON.stringify(state.isShow)) },
// 商品数量增加
addNum(state, id) {
let index = state.car.findIndex(item => item._id === id)
if (index >= 0) {
state.car[index].num ++;
}
},
// 数量减少
reduceNum(state, id) {
let index = state.car.findIndex(item => item._id === id);
let indexShow = state.isShow.findIndex(item =>item.id === id)
console.log('indexShow', indexShow)
// 当数量为1时,从购物车里面删除
if (state.car[index].num === 1) {
// 逻辑同deleteProduct
state.car[index].selected = false;
state.car.splice(index, 1)
localStorage.setItem('car', JSON.stringify(state.car))
state.isShow[indexShow].show = !state.isShow[indexShow].show
localStorage.setItem('isShow', JSON.stringify(state.isShow))
} else {
state.car[index].num --;
}},
// 加入购物车
addCar(state, data) {
// 修改传递进来的商品的属性 数量为1,默认选中加入购物车,默认加入购物车后先不购买
// 数据丢失
// Object.assign(data, {num: 1, selected: true, isBuy: false})
let datas = {
title: data.title,
num: 1,
selected: true,
isBuy: false,
img: data.img,
sales: data.sales,
_id: data._id,
price: data.price
}
// 在购物车中查找该商品
let index = state.car.findIndex(item => item._id === data._id)
// 如果返回-1,说明购物车中没有该商品,将其添加进购物车
// 当购物车中已经有该商品了则不再继续往里面添加该商品
if (index === -1) {
state.car.push(datas)
}
// 将购物车中的商品存入本地存储中,为了之后做页面的路由拦截
localStorage.setItem('car', JSON.stringify(state.car))
},
// 单选选择是否购买该商品
selectSingle(state, id) {
// 查找购物车中该商品的的索引值
let index = state.car.findIndex(item => item._id === id)
// console.log(111, state.car[index].isBuy, state.car[index].selected)
// 将购物车中该商品选择属性取反
state.car[index].isBuy = !state.car[index].isBuy;
// console.log(222, state.car[index].isBuy)
// 判断是否全选(如果有一个是未选中,则flag为true,如果全选中则flag为false)
let flag = state.car.some(item => item.isBuy === false)
// 全选中
if (!flag) {
state.all_selected = true
} else {
// 没有全选中
state.all_selected = false
}
},
// 全选
selectAll(state) {
// 取消所有商品的isBuy
if (state.all_selected) {
state.car.forEach(item => item.isBuy = false)
} else {
// 将所有商品的isBuy属性改成true
state.car.forEach(item => item.isBuy = true)
}
state.all_selected = !state.all_selected;
}
},
actions: {}
})
(4)App.js
="scss">
* {
margin:0;
padding: 0;
list-style: none;
}
>
export default {
}
(5)List.vue
注意: List页面主要复杂在根据商品是否加入了购物车来决定该页面商品的“加入购物车“按钮”的样式,另外更新或者再次进入该页面后要根据本地存储里面的购物车商品来决定改按钮的显隐
商品列表
-
文章图片
{{item.title}}
class="price">¥{{item.price}}
{{item.isShow ? '离开购物车' : '加入购物车'}}
进入购物车页面
(6)Home.vue
注意: Home页面中引入Product和Check组件
购物车
="scss">
@import '../base.scss';
body,
html {
background-color: #efefef;
}
.home {
margin-top: 60px;
padding: 10px 15px;
margin-bottom: 60px;
.header {
background-color: $navColor;
color: #fff;
text-align: center;
height: 60px;
font-size: 28px;
line-height: 60px;
position: fixed;
top: 0;
right: 0;
left: 0;
margin-bottom: 60px;
}
}
>
import Check from '@/components/Check'
import Product from '@/components/Product'
import { mapGetters } from 'vuex'
export default {
components: {Product, Check},
data() {
return {
}
},
computed: {
...mapGetters(['carList'])
}
}
(7)Product.vue
商家:以沫wh
文章图片
{{data.title}}
class="price">¥{{data.price}}
{{data.num}}
="scss" scoped>
@import '@/base.scss';
.product {
background-color: #fff;
padding: 15px;
border-radius: 10px;
margin-bottom: 15px;
.top-info {
display: flex;
margin-bottom: 8px;
.icon-yuancircle46,
.icon-xuanze {
font-size: 26px;
margin-right: 10px;
}
.name {
flex: 1;
font-size: 18px;
padding-top: 2px;
}
.icon-lajitong {
font-size: 24px;
}
}
.content {
display: flex;
img {
width: 105px;
height: 90px;
}
.info {
margin-left: 15px;
flex: 1;
.title {
font-size: 18px;
font-weight: normal;
height: 65px;
}
.bottom-info {
display: flex;
.price {
color: $navColor;
flex: 1;
padding-top: 4px;
}
.num {
display: flex;
.icon-jianshao,
.icon-tianjia-copy {
font-size: 25px;
}
.sales {
padding: 4px 8px;
}
}
}
}
}
}
>
import { mapMutations } from 'vuex';
export default {
props: ['data'],
data() {
return {}
},
methods: {
...mapMutations(['selectSingle', 'addNum', 'reduceNum', 'deleteProduct'])
},
created() {
// console.log('isBuy', this.data.isBuy, this.data.selected)
}
}
(8)Check.vue
class="cancelSelAll" @touchend="selectAll()">取消全选
class="selAll" @touchend="selectAll()">全选
共计:{{$store.getters.allPrice}} 元
结算({{$store.getters.payLength}})
="scss">
@import '@/base.scss';
.check {
height: 50px;
line-height: 50px;
padding: 5px 15px ;
position: fixed;
bottom: 0;
left: 0;
right: 0;
display: flex;
background-color: #fff;
border-top: 1px solid #ccc;
.isSelAll {
flex: 1;
.icon-xuanze,
.icon-yuancircle46 {
font-size: 26px;
vertical-align: sub;
}
.cancelSelAll,
.selAll {
padding-left: 8px;
font-size: 18px;
color: $navColor;
}
}
.totlePrice {
font-size: 20px;
margin-right: 10px;
margin-top: 1px;
}
.pay {
width: 80px;
height: 40px;
line-height: 40px;
text-align: center;
background-color: #f60;
color: #fff;
padding: 0px 10px;
border-radius: 20px;
font-size: 17px;
margin-top: 5px;
}
}
>
import {mapMutations} from 'vuex';
export default {
methods: {
...mapMutations(['selectAll'])
}
}
(9)base.scss
$navColor:blue;
// 阿里字体图标设置
.icon, .iconfont {
color: $navColor;
font-family:"iconfont" !important;
font-size:16px;
font-style:normal;
-webkit-font-smoothing: antialiased;
-webkit-text-stroke-width: 0.2px;
-moz-osx-font-smoothing: grayscale;
}
6 后记 本项目到此也就全部完成了,基本实现了购物车的所有功能,因为也是在学习中,若果发现本文有错误请留言指正,我们一起进步呀!!
绝对帮到自己的小伙伴可以点个赞呦~
推荐阅读
- 入坑vue3|vue3 数据响应更复杂了吗()
- vue.js|Vue 2.7 is Now in Beta
- 入坑vue3|vue3入门,其实吧,压力也没那么大
- 前端|前端食堂技术周刊第 43 期(Vue 2.7 Naruto、Prisma 4.0.0、Grid 布局生成器、HTML Tips)
- java|Vue 2.7 正式发布,代号为 Naruto
- 前端笔记|Node.js笔记-Koa2与Redis在项目中安装使用
- javascript|HarmonyOS应用开发(目录、基本规则、创建项目)
- 前端|vue3 基于faceapi.js实现人脸识别
- 小程序|微信小程序实训|基于云数据库的语文听写工具